Repository navigation
Timer wheel design
The design for the timer wheel: the millisecond timers, waits and cooldowns each owner keeps on its own thread. The first owners to use it are the maps, which run it once per tick (Map scheduler design, Owners design). Step 3's second stage, one character alone in the world, builds it; Roadmap says how far that has got.
Every rule is in milliseconds, never in ticks. An owner schedules a timer for a time on its clock, and each tick advances its wheel to now, firing whatever came due in order. The tick rate only sets how finely those times land.
A rule counted in ticks changes whenever the tick rate does, and the tick is set per instance type. In milliseconds, a cooldown or an effect tick means the same thing on every map, and changing a map's tick is a quantization knob, nothing more.
Timers live on a hashed wheel of 1,024 slots, one millisecond each. Advancing walks every millisecond since the last advance and fires the timers due in it. A timer more than one turn of the wheel away waits in its slot and is passed over until its time comes.
Timers fire in order: earliest first, and within one millisecond in the order they were scheduled. A timer scheduled for a time already past fires on the next advance.
A timer carries a value, never a reference. Its payload is a small struct handed to the owner when it fires, so scheduling, firing and cancelling allocate nothing once the wheel's storage has grown to its peak. A map's timer names what it's for and the character it's for by id, and the map looks the character up again when it fires, as every queued piece of work does (Owners design).
A handle cancels only its own timer. Each handle carries a generation, so a handle kept after its timer fired, or after its slot was reused, cancels nothing.
One owner, one wheel, one thread. The wheel isn't thread-safe and doesn't need to be: only the owner that holds it touches it, inside its own tick.
A timer that throws doesn't lose the others. The rest due in that millisecond fire on the next advance.
A long gap costs nothing when there's nothing to fire. An empty wheel jumps straight to now. A map advances its wheel to just before now whenever it's empty, so a map that opens long after the server started, or sits empty for hours, schedules its first timer from the present rather than walking every millisecond since.
Cooldowns are absolute timestamps, so a creature asleep in an empty cell keeps correct respawns and cooldowns without ticking.
None. The slot count and the clock come from the owner that holds the wheel.
- Nothing in the design.
| Idea | What killed it |
|---|---|
| Rules counted in ticks | Changing the tick rate would change every rule. In milliseconds, the rate is only a quantization knob |
| Claim | Basis |
|---|---|
| A steady schedule-and-fire cycle allocates nothing | Measured by the wheel's tests once it's built |
- None yet; the combat track's effects and cooldowns are its first heavy users.
Start here
How the server works
- Server core design
- Host
- Gateway
- Owners
- Persistence
- Command layer
- Harness
- Map scheduler design
- Timer wheel design
The protocol
Working on it