Is it possible to end a SetTimer from within the lambda function?

I cant really make this work. Is it even possible to end a SetTimer from within the lambda function?

    FTimerHandle RoundTimer;
        int32 counter = 0;
    GetWorld()->GetTimerManager().SetTimer(RoundTimer, [this, &RoundTimer, &counter]()
        {
          counter++;
          if(counter>20)
          GetWorld()->GetTimerManager().ClearTimer(RoundTimer);
        }, 0.2, false);

Hi SophiaWolfie,
I’ve never tried to end a timer from a lambda but I can’t think of any reason why not. But shouldn’t you be setting the last parameter to True to loop?

1 Like

sorry yes. thats a typo, it should be true.
In all my tests i cant make it stop from within. It seems the variable is lost somewhere, and the settimer just keeps going in an infinite loop.

So im having to create a separate function everytime i need to use SetTimer, and call it again recursively.
There is no way i can pass the FTimerHandle to it so far…

I just had a test and no it’s not a good way to do it, multiple instances affect it too, it needs to be a hooked function:

void AMyActor::OnTimeElapsed() {
	counter++;
	if(counter>20) {
		GetWorld()->GetTimerManager().ClearTimer(RoundTimer);
		UE_LOG(LogTemp,Display,TEXT("Timer finished 20 loops."));
	} else {
		UE_LOG(LogTemp,Display,TEXT("Timer:%d"),counter);
	}
}

void AMyActor::Test() {

// Works	GetWorld()->GetTimerManager().SetTimer(RoundTimer,this,&AMyActor::OnTimeElapsed,0.2,true);
/* 
	// RoundTimer2 is null in the lambda and counter2 is 130 when ">20" reached (multiple instances of this running)
	FTimerHandle	RoundTimer2;
    int32			counter2=0;

	GetWorld()->GetTimerManager().SetTimer(RoundTimer2,[this,&RoundTimer2,&counter2](){
		counter2++;
		if(counter2>20){
			GetWorld()->GetTimerManager().ClearTimer(RoundTimer2);
			UE_LOG(LogTemp,Display,TEXT("Timer finished 20 loops."));
		} else {
			UE_LOG(LogTemp,Display,TEXT("Timer:%d"),counter2);
		}
	},0.2,true);
*/

1 Like

I tried it. It doesnt work.

It seems counter2 becomes empty. I think this is because the function where i called this from eventually ends.

You shouldn’t ever lambda-capture local variables by reference. Local variables are de-allocated by the time function is done executing. Then your counter++ statement is modifying something in memory which may or may not be used by another part of code. This is undefined behavior.

FTimerHandle is a struct that can be copied around safely. You can pass this one by value and it should work when using ClearTimer.

The only way such a counter could work, is if you make it global or static. But then it’s gonna be shared accross all function calls.

If you want a counter that is local to each function call, while still working as a counter within the lambda, you need to provide a a bigger global (or static) data structure to hold the counters of the (potential) multiple functions calls. For example you can use a TMap to map timer handles to counters :

TMap<FTimerHandle, int32> Counters;
void AMyActor::Foo()
{
    FTimerHandle Handle;
    GetWorld()->GetTimerManager().SetTimer(Handle, [this, Handle]()
    {
        int32& Counter = Counters.FindRef(Handle);
        Counter++;
        if (Counter > 20)
        {
            Counters.Remove(Handle);
            GetWorld()->GetTimerManager().ClearTimer(Handle);
        }
    }, 0.2, true);
    Counters.Add(Handle, 0);
}
1 Like

That doesnt work either, because not even this works:

	FTimerHandle Timer;
	GetWorld()->GetTimerManager().SetTimer(Timer, [this, &Timer, B]()
		{
			GEngine->AddOnScreenDebugMessage(-1, 20.f, FColor::Purple, "------------ ");
		GetWorld()->GetTimerManager().ClearTimer(Timer);
		}, 0.2, true);

Basically it doesnt seem to be possible to stop the timer from the inside of the function.
In your solution you passed the Handle without referencing, that wont compile because of the GetWorld()->GetTimerManager().ClearTimer(Timer)

Hmm the Handle might be captured before being set.
Looks like we need a custom index, this is gonna get messier.

The following should work I tested them.
To illustrate what I mentioned earlier I made both cases.

The simple case is when you don’t need multiple timers running concurrently. In this case you can just use static Handle and static Counter, it behaves just like global variables / single global timer :

void AMyActor::Foo()
{
    static FTimerHandle Handle;
    static int32 Counter;
    Counter = 0;
    GetWorld()->GetTimerManager().SetTimer(Handle, [this]() {
        Counter++;
        UE_LOG(LogTemp, Log, TEXT("Counter: %i"), Counter);
        if (Counter > 20)
            GetWorld()->GetTimerManager().ClearTimer(Handle);
    }, 0.2f, true);
}

Second case is more difficult, but this does the trick :

void AMyActor::Foo()
{
    static int32 GlobalIndex = 0;
    static TMap<int32, FTimerHandle> Handles;
    static TMap<int32, int32> Counters;

    int32 Index = ++GlobalIndex;
    FTimerHandle Handle;
    GetWorld()->GetTimerManager().SetTimer(Handle, [this, Index]() {
        int32& Counter = *Counters.Find(Index);
        Counter++;
        UE_LOG(LogTemp, Log, TEXT("Counter: %i"), Counter);
        if (Counter > 20)
        {
            Counters.Remove(Index);
            FTimerHandle Handle = Handles.FindAndRemoveChecked(Index);
            GetWorld()->GetTimerManager().ClearTimer(Handle);
        }
    }, 0.2f, true);
    Handles.Add(Index, Handle);
    Counters.Add(Index, 0);
}

promote the FTimerHandle to the class variable