Latent Timer
Latent timer
Why “reinventing the wheel” while we have TimerManager
Well, TimerManager
has many problems when it comes to gameplay:
SetTimerForNextTick
actually called this tick rather than the next tickCallback order is not guaranteed to be the same as the timer set order
Can’t specify
tick group
when setting timerTimerManager::Tick
itself is hardcoded in some late time of a frameOnly support delay in time seconds, do you want to delay in frames?
Don’t support loop count
FTimerDta
is still quite bloated, with size optimization done onFTimerUnifiedDelegate
Could we solve all the problems?
Well, well, solving 100% of them is hard, but 90% is piece of cake with our great savior — FLatentActionManager
FLatentActionManager
is a simple but powerful tool to tick
any instance of FPendingLatentAction
every frame.
Every LatentAction
is bound to a UObject
, it ticks in the tick group
of the bound object ! Or “by the end of frame” if tick disabled for the bound object where is just near and ahead of TimerManager::Tick
.
FPendingLatentAction
could be derived to do anything you want. Eg: FDelayUntilNextTickAction
, FDelayAction
they are the heroes behind the beloved delay node in blueprint.
The way I solve it with Rem::Latent::FTimerLatentAction_Delay
Providing
Rem::Latent::SetTimerForThisTick
andRem::Latent::SetTimerForNextTick
for maximum explicitly and flexibility when expressingdelay a tick
Latent Action
is processed in the order they get bound to theUObject
The tick group of a
Latent Action
could be controlled by specifying a ticking object within the target tick group. And it support specifying tick dependency with no efforts!Support delay in frames! Which doesn’t likely to exist in
TimerManager
. Two helper struct:FTimerParameterHelper_Time
,FTimerParameterHelper_Frame
, one API:Rem::Latent::SetTimer
Support specific loop count :
FTimerParameterHelper_Time::LoopCount
,FTimerParameterHelper_Frame::LoopCount
Support counting from next frame: see
FTimerParameterHelper_Time::bSkipCountingThisFrame
My
FTimerLatentAction_Delay
only has size of 40 bytes to get all the jobs done, whileFTimerDta
has the size of 128, 3.2x bigger!A
fire and forget
alternative for pausing timer for one frame pause:Rem::Latent::SetTimerPausedOneFrame
Familiar APIs:
Rem::Latent::PauseTimer
,Rem::Latent::UnpauseTimer
,Rem::Latent::SetTimerPaused
(did we met before?),Rem::Latent::StopTimer
,Rem::Latent::FindTimerAction
Re-triggerable is natively supported:
Rem::Latent::ResetTimerDelay
, support both delay in time and in frameCall count compensation and opting out it with
FTimerParameterHelper_Time::bMaxOncePerFrame
(Same as what’s inTimerManager
)27 bits wasted for now, they are the hope for the future!
Limitations
Rem::Latent::FTimerHandle
is 32-bit, becauseFLatentActionManager::AddNewAction
only acceptsint32
, while it wasuint64
inTimerManager
Infinite loop map happen if
Rem::Latent::SetTimerForThisTick
is called on the same object withinFLatentActionManager::ProcessLatentActions
, useRem::Latent::SetTimerForNextTick
instead in the caseRem::Latent::SetTimerForThisTick
will not get called in the relevanttick group
, if the bound object is already ticked this frame, considerRem::Latent::SetTimerForNextTick
instead in the caseTimeToDelay
,LoopCount
,InitialDelay
are all 4 bytes only for simplicity, might consider extended to those 27 spared bits in the futureRequires tick enabled on the bound object to “set tick group” for our timer latent action
Sample code
1
2
3
4
5
void UYourObject::DoJob()
{
auto TimerHandle = Rem::Latent::SetTimerForThisTick(*this,
FTimerDelegate::CreateUObject(this, &ThisClass::Callback));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void UYourObject::TryDoJobUntilSucceed()
{
bool bWantToRetry{true};
ON_SCOPE_EXIT
{
if (bWantToRetry)
{
Rem::Latent::SetTimerForNextTick(*this, FTimerDelegate::CreateWeakLambda(this,
[this]
{
TryDoJobUntilSucceed();
}));
}
};
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void UYourObject::RetriggerableJob()
{
// ...
if (!TimerHandle.IsValid())
{
TimerHandle = Rem::Latent::SetTimer(*this, FTimerDelegate::CreateWeakLambda(this, [this]
{
// ...
Rem::Latent::StopTimer(*this, TimerHandle);
TimerHandle = {};
}), {.TimeToDelay = 1.0f, .LoopCount = 0/*loop infinite*/});
}
else
{
Rem::Latent::ResetTimerDelay(*this, TimerHandle);
}
}