-
Notifications
You must be signed in to change notification settings - Fork 58
Custom Tick Scheduling
Visualized ticks of the Sparkweed plant from Remote Explosives (half speed)
Notice: this article describes tools found in the A18 (HugsLib 4.0) version of the library. While the tools can be found in previous versions, their older variants require various workarounds to be used safely.
Ticking is an essential part of bringing your creations to life in Rimworld. The stock game offers 3 ticking intervals:
- Normal: 60 times a second (roughly every frame)
- Rare: every 250 Normal ticks (4.16 seconds)
- Long: every 2000 Normal ticks (33.33 seconds)
The Normal interval is the workhorse for most modded and stock Things in the game. Depending on what you are making, one or two updates a second are often quite sufficient- the rest is wasted CPU time.
public override void Tick() {
if ((Find.TickManager.TicksGame + thingIDNumber.HashOffset()) % 60 == 0) {
// logic here
}
}
Placed in a type that inherits from Thing
, this allows code to be executed at one second intervals. The HashOffset
part is optional- it prevents the game from stalling from many instances of the same Thing
performing their work during the same tick.
While this is a workable approach in small doses, it quickly degrades the performance of the game if you need to tick hundreds of instances of your custom Thing
.
HugsLib provides the DistributedTickScheduler
to tackle this problem. It allows to efficiently tick a high number of recipients that use the same tick interval:
public class CustomThing : Thing {
public override void SpawnSetup(Map map, bool respawningAfterLoad) {
base.SpawnSetup(map, respawningAfterLoad);
HugsLibController.Instance.DistributedTicker.RegisterTickability(CustomTick, 30, this);
}
private void CustomTick() {
// logic here
}
}
This registers a Thing
to receive recurring ticks every half second (30 Normal ticks). Since vanilla ticks are not used, they should be disabled in the XML Def of your Thing
:
<tickerType>Never</tickerType>
The calls will be uniformly distributed in time to maximize performance. This means that if we register 190 Thing
s with a 30 tick interval (like in the above image), every game tick only 6 to 7 calls will be made.
The registered method will continue to be called until the Thing
it is registered with is de-spawned, destroyed, or becomes discarded (which happens when a map is discarded). You can, however, unregister manually if you need to stop the ticks while your Thing
is still spawned:
HugsLibController.Instance.DistributedTicker.UnregisterTickability(this);
HugsLib also includes a convenient way to create delays for a given number of ticks:
HugsLibController.Instance.TickDelayScheduler.ScheduleCallback(() => {
// logic here
}, 10, this, false);
This registers a one-time callback to be executed in 10 game ticks from the time it is registered. Unlike with DistributedTickScheduler
, providing a Thing
to associate the callback with is optional. If provided, however, the callback will only fire if the Thing
is still spawned at callback time.
The last argument allows to register a recurring callback that will be automatically rescheduled after it is called. Note, that DistributedTickScheduler
is a more efficient solution if your plan is to tick a large number of Things
with the same interval.
Callbacks can be easily unscheduled to cancel a delay or recurring call:
HugsLibController.Instance.TickDelayScheduler.TryUnscheduleCallback(MyMethod);
To have the ability to unregister a call, it is necessary to keep a reference to the delegate you registered, or to use a named method.
Finally, both DistributedTickScheduler
and TickDelayScheduler
will respect game pausing and time acceleration, and will only work if a game is in progress.
Accessed via HugsLibController.Instance.DoLater
, this scheduler allows to run one-time callbacks at a future event.
This is useful if code needs to be run from the main thread, or to wait for the next frame or tick.
A new callback can be rescheduled the moment it is invoked. This allows to keep a recurring event listener until it is no longer needed. This example waits for 60 frames:
var numFrames = 0;
Action everyFrame = null;
everyFrame = () => {
if (numFrames < 60) {
HugsLibController.Instance.DoLater.DoNextUpdate(everyFrame);
numFrames++;
} else {
// finally, do a thing
}
};
everyFrame();
Executes a callback at the start of the next game tick.
Executes a callback at the start of the next frame (Unity Update).
Executes a callback at the start of the next OnGUI event.
Executes a callback the next time a map has finished loading. Receives the loaded map instance as argument.