Skip to content

Map scheduler

rebaserHEAD edited this page Oct 23, 2026 · 2 revisions

explanation, committed verified 2026-10-23 dev 8fd4714 run 2026-10-23

How the map scheduler works as built, and why: every map ticked to its own deadline, on a shared pool or a thread of its own. The facts to look up are on Map scheduler ticks and Map scheduler metrics; what a map does inside a tick is on Owners map, and what's still to build is on Map scheduler design.

How it fits together

The shape

flowchart TD
    MD["Map directory"] -->|"register, unregister"| MS["Map scheduler"]
    MS -->|"earliest due deadline"| P["Pool threads"]
    MS -->|"placed dedicated"| D["Dedicated thread"]
    P -->|"one tick, to completion"| M["A map"]
    D -->|"its ticks only"| H["A heavy map"]
Loading

A map is ticked, never threaded. A pool of threads takes whichever map's deadline is earliest and has come, runs one tick to completion, and puts the map back with its next deadline. A map placed on a dedicated thread runs alone on it. No map ticks on two threads at once.

A deadline missed is skipped, not replayed. A map's next deadline is the first one on its period not yet past, so a slow tick never turns into a burst of catch-up ticks, and the deadlines passed over are counted.

Nothing ticks in step. Each map's first deadline is staggered by a random fraction of one tick, so maps opened together don't all tick in the same millisecond.

The wait is precise on both systems. On Windows a thread waits on a high-resolution timer, because .NET's timed waits there step in 10 to 16 ms, too coarse for a 25 ms tick; on Linux it waits on its wake event with the deadline as the timeout, which also wakes a pool thread at once when a map registers. A play session on Windows held 40 ticks a second with none skipped.

A fault costs one tick, not the map's place. A tick that throws is logged and counted, and the map ticks again at its next deadline.

Stopping waits for the tick in progress. Unregistering a running map completes when its current tick ends, and the server's shutdown stops every map while the scheduler still ticks, so each map hands its characters back on its own last tick.

Why it's built this way

Every house is an instance, so a server holds far more maps than cores, and most of them are idle. A pool runs the long tail cheaply, and a dedicated thread is there for the few maps heavy enough to need one.

Idea What killed it
A thread per map Every house is an instance, so thousands of mostly idle threads would each wake for their own tick, and once they outnumber the cores they're preempted mid-tick

Knobs

The scheduler's settings, with their defaults and what the host checks, are on Host settings.

Maps:PoolThreads is the shared pool that runs every map without a thread of its own; by default it leaves three logical processors for the gateway, the realm and account owners and the garbage collector. Maps:DedicatedThreads is how many heavy maps may each hold a thread; a placement past that runs in the pool. Maps:TickMs is the tick, 25, 50, 125 or 250 ms, each dividing 250 so an effect timed on a multiple of 250 ms lands on a tick. The tick times Maps:ReplicationEveryTicks is the update interval, the one the client is told to send its movement at, and it has to fall within 25 to 1,000 ms.

Known edges

  • Every map shares one tick and one update interval; per instance type comes later (Map scheduler design).
  • A map that keeps overrunning isn't promoted to a dedicated thread, and repeated faults don't stop a map.
  • Nothing holds pool and dedicated threads together within the cores; only the pool's default leaves three free.

Where the code lives

Piece Path
The scheduler, its deadline queue and its waits Source/NexusTomorrow.Maps/Scheduling/
The settings and their rules Source/NexusTomorrow.Maps/MapOptions.cs, checked by Source/NexusTomorrow.Settings/
The metrics Source/NexusTomorrow.Maps/MapMetrics.cs
Tests Tests/NexusTomorrow.Maps.Tests/Scheduling/

Clone this wiki locally