FAIStimulus::IsExpired() not working?

I’m trying to retrieve an actor that has been sensed by an AIPerceptionComponen that has not expired yet. The AIPerceptionComponent is part of the AIController and the max age of the AI Sight Config has been set to 2 seconds for testing. The following code is run every 0.5 seconds:

TArray<AActor*> Actors;
AISensingComponent->GetPerceivedActors(UAISenseConfig_Sight::StaticClass(), Actors);

if (Actors.Num() < 1)
	return;

const FActorPerceptionInfo* ActorInfo = AISensingComponent->GetActorInfo(*Actors[0]);

if (ActorInfo->LastSensedStimuli[0].IsExpired())
	return;

GEngine->AddOnScreenDebugMessage(-1, 0.5f, FColor::Red, "I still know where you are!");
GEngine->AddOnScreenDebugMessage(-1, 0.5f, FColor::Red, FString::FromInt(ActorInfo->LastSensedStimuli[0].GetAge()));

However, the two messages are always shown on the screen once an Actor has been seen. The age also seems to go from 0 to 1, to -2147483648 where the last number seems to me some sort of indication that the stimuli has expired. This gives me the idea that IsExpired() might possibly be not working, any ideas?

Have you tried to use a gameplay debugger, and what is its visual output?

/** this means the stimulus was originally created with a "time limit" and this time has passed. 
	 *	Expiration also results in calling MarkNoLongerSensed */
	uint32 bExpired:1;	

What a result of the MarkNoLongerSensed()? Result = WasSuccessfullySensed()

Good idea, the gameplay debugger shows this:

How would I get the output of marknolongersensed, add a breakpoint there?

Try to debug at Stimulus.MarkExpired

 bool UAIPerceptionComponent::AgeStimuli(const float ConstPerceptionAgingRate)
    {
    	bool bExpiredStimuli = false;
    
    	for (TActorPerceptionContainer::TIterator It(PerceptualData); It; ++It)
    	{
    		FActorPerceptionInfo& ActorPerceptionInfo = It->Value;
    
    		for (FAIStimulus& Stimulus : ActorPerceptionInfo.LastSensedStimuli)
    		{
    			// Age the stimulus. If it is active but has just expired, mark it as such
    			if (Stimulus.AgeStimulus(ConstPerceptionAgingRate) == false 
    				&& (Stimulus.IsActive() || Stimulus.WantsToNotifyOnlyOnPerceptionChange())
    				&& Stimulus.IsExpired() == false)
    			{
    				AActor* TargetActor = ActorPerceptionInfo.Target.Get();
    				if (TargetActor)
    				{
    					Stimulus.MarkExpired(); // try to debug there
    					RegisterStimulus(TargetActor, Stimulus);
    					bExpiredStimuli = true;
    				}
    			}
    		}
    	}
    
    	return bExpiredStimuli;
    }

Stimulus.MarkExpired() gets called when that green sphere disappears.

I noticed that the MarkExpired call takes place only when the target is out of lose range (pink).

float GetAge() const { return Strength > 0 ? Age : NeverHappenedAge; }

In my case at expired point:
Age = 2.09
ExpirationAge = 2.0
Strength = 1.0
bExpired = 1

I also get it round that point, but for some reason IsExpired() still returns false and therefore those messages still get printed.

Target is invalid after it was expired and bExpired = 0. Use FAIStimulus::IsActive() for validation of the LastSensedStimuli.

But IsActive() is already set to false when the AI loses sight not when the age is greater than it’s experiation age.

Hmm, okey. Try to use

bool IsExpired = AgeStimulus(0.f) == false;

/** @return false when this stimulus is no longer valid, when it is Expired */
	FORCEINLINE bool AgeStimulus(float ConstPerceptionAgingRate) 
	{ 
		Age += ConstPerceptionAgingRate; 
		return Age < ExpirationAge;
	}

well the problem with that is:

const FActorPerceptionInfo* ActorInfo = AISensingComponent->GetActorInfo(*Actors[0]);

so ActorInfo->LastSensedStimuli[0] is constant and therefore you can’t apply AgeStimulus on it.

Copy FAIStimulus to a local variable by value.

Well that works, but is that really the way to go?

Due to the lack of methods for accessing to ExpirationAge this should work. Elapsed time is added each time in AgeStimuli. It works.

When time will expire then Age == FLT_MAX and ExpiredAge == FLT_MAX too.

I think I’ll use that then, that seems like the cleanest, thanks! Still does IsExpired() then work as intended or not?

void UAIPerceptionComponent::HandleExpiredStimulus(FAIStimulus& StimulusStore)
{
ensure(StimulusStore.IsExpired() == true);
StimulusStore = FAIStimulus();
}

And

// default constructor
	FAIStimulus()
		: ... bExpired(false)
	{}

At HandleExpiredStimulus bExpired was reseted to 0. Maybe it’s a bug. But the more it seems that this behavior is not provided. LastSensedStimuli[0] zero should be replaced with the ID of the sense FAIStimulus::Type. LastSensedStimuli can be rewritten, I think you can not rely on the data that is written in it, or they will have to check every tick.

if (PerceptualInfo->LastSensedStimuli.Num() <= SourcedStimulus->Stimulus.Type)
		{
			const int32 NumberToAdd = SourcedStimulus->Stimulus.Type - PerceptualInfo->LastSensedStimuli.Num() + 1;
			for (int32 Index = 0; Index < NumberToAdd; ++Index)
			{
				PerceptualInfo->LastSensedStimuli.Add(FAIStimulus());
			}
		}

		check(SourcedStimulus->Stimulus.Type.IsValid());

		FAIStimulus& StimulusStore = PerceptualInfo->LastSensedStimuli[SourcedStimulus->Stimulus.Type];

I guess I’ll go with this then, thanks!
if (ActorInfo->LastSensedStimuli[SightConfig->GetSenseID()].GetAge() == FLT_MAX)

I’m seeing this in 4.11 as well. I’m kind of lost on what the right answer was here though. My workaround so far is to check perception on my target every once in awhile because I’m not getting notified about expired Sight stimuli.

After digging into the source code, I have found that IsExpired() is actually set to true as soon as Age reaches MaxAge. However, the callback (OnPerceptionUpdated) does not get called at that time. So you have to handle expiration elsewhere. Luckily, there is one in AIPerceptionComponent!

Please take a look at my answer in this post:

Hope it helps!