AIPerceptionStimuliSourceComponent Cook Indeterminism

Hi we are running into an issue with AIPerceptionStimuliSourceComponent where it is causing our cooks to become indeterministic

A cook with Diff only shows:

-[1] /script/aimodule/aiperceptionstimulisourcecomponent Default__Ooze3_Duplicate_C/AIPerceptionStimuliSource Super: NULL, Template: /Game/DesignAssets/Population/NPC/Monster/Aberration/Ooze/Ooze3.default__ooze3_c/aiperceptionstimulisource, Flags: 262185, Size: 17, FilterFlags: 0

+[1] /script/aimodule/aiperceptionstimulisourcecomponent Default__Ooze3_Duplicate_C/AIPerceptionStimuliSource Super: NULL, Template: /Game/DesignAssets/Population/NPC/Monster/Aberration/Ooze/Ooze3.default__ooze3_c/aiperceptionstimulisource, Flags: 262185, Size: 10, FilterFlags: 0

My hunch here is that is related to the RemoveAndSwap here, but this also out of my realm of expertise and im not sure how to debug this. Ive also attached some additional relevant logs to the ticket that may help

Steps to Reproduce
Add AIPerceptionStimuliSourceComponent to a blueprint. Do an initial cook with -cookincremental, then do another cook -cookincremental and -diffonly

The export is possibly becoming smaller (17 bytes down to 10 bytes) because in the second cook it has the same value for one of its properties as its archetype does, and delta serialization causes the serialization of the field to be skipped.

I agree the RemoveAllSwap is suspicious and is the first thing to test. If that’s the problem, then the repro case is more complicated than just creating a blueprint (needs a blueprint hierarchy, plus some missing Senses arguments), so I’d like to have you try out a fix rather than trying to recreate it:

Can you try a code change:

Add a NormalizeSenses function. Move the code in OnRegister that calls RemoveAllSwap into NormalizeSenses. Modify NormalizeSenses to also call NormalizeSenses on the archetype object. Also call NormalizeSenses from PreSave:

Engine\Source\Runtime\AIModule\Classes\Perception\AIPerceptionStimuliSourceComponent.h

class UAIPerceptionStimuliSourceComponent : public UActorComponent
{
...
public:
 
	AIMODULE_API UAIPerceptionStimuliSourceComponent(const FObjectInitializer& ObjectInitializer);
 
// NewLines
	AIMODULE_API virtual void PreSave(FObjectPreSaveContext SaveContext) override;
	AIMODULE_API void NormalizeSenses();
// EndNewLines
...
}

Engine\Source\Runtime\AIModule\Private\Perception\AIPerceptionStimuliSourceComponent.cpp

...
#include "Perception/AIPerceptionStimuliSourceComponent.h"
#include "Perception/AIPerceptionSystem.h"
// NewLines
#include "UObject/ObjectSaveContext.h"
// EndNewLines
#include "VisualLogger/VisualLogger.h"
...
void UAIPerceptionStimuliSourceComponent::OnRegister()
{
	Super::OnRegister();
 
#if WITH_EDITOR
	// when in the editor world we don't remove the null entries
	// since those can get changed to something else by the user
	if (!GIsEditor || GIsPlayInEditorWorld)
#endif // WITH_EDITOR
	{
// ModifiedLines
		NormalizeSenses();
// EndModifiedLines
	}
 
	if (bAutoRegisterAsSource)
	{
		RegisterWithPerceptionSystem();
	}
}
...
// NewLines
void UAIPerceptionStimuliSourceComponent::PreSave(FObjectPreSaveContext SaveContext)
{
	Super::PreSave(SaveContext);
	NormalizeSenses();
}
 
void UAIPerceptionStimuliSourceComponent::NormalizeSenses()
{
#if WITH_EDITOR
	UAIPerceptionStimuliSourceComponent* Archetype = Cast<UAIPerceptionStimuliSourceComponent>(GetArchetype());
	if (Archetype)
	{
		Archetype->NormalizeSenses();
	}
#endif
	RegisterAsSourceForSenses.RemoveAllSwap([](const TSubclassOf<UAISense>& SenseClass) {
		return SenseClass == nullptr;
		});
}
// EndNewLines

If that fixes the issue, I will submit it for 5.8 (and will let you know of any further changes from code review).

If not, then I’ll look for another issue.