Up to Main Index Up to Journal for September, 2026
JOURNAL FOR MONDAY 28TH SEPTEMBER, 2026
______________________________________________________________________________
SUBJECT: WolfMUD Concurrency & Extreme Schedulers
DATE: Mon 28 Sep 23:07:40 BST 2026
I’m currently trying to keep my focus on only two of my current projects. If
I don’t, I’ll end up getting absolutely nothing done. Those projects are Mere,
my custom programming language, and WolfMUD. Mere is currently stuck in the
necessary hell of writing endless tests. While not hard it’s a boring slog
that requires me to be in the exact right mood to make any progress.
WolfMUD, on the other hand, is a playground — always has been and always will
be. Having not touched the codebase for ages, I’ve been pulling it apart and
poking at its internals. A fun process of working out what the code is doing
and rediscovering what past-me was thinking.
A lot of the old code and architectural choices still hold up well. Other
parts are a bit of a mess, and I know I can do better. People have told me
before that the current codebase can be a little hard to follow and modify.
As I analyze where to take WolfMUD next, my primary aim is simple: ruthless
simplification.
The Case For A Single-Threaded Core
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
One major architectural shift I am exploring is making the core of WolfMUD
completely single-threaded.
By “the core” I mean the state-transition engine. It holds all data for zones,
rooms, and players, alongside the command logic that manipulates that data.
Making it single-threaded might sound odd in 2026. Most computers have
multiple CPU cores, and Go features legendary native concurrency primitives.
However, a MUD’s processing overhead isn’t concentrated in its state
transitions — it is in its I/O layer. There will still be plenty of work for
other CPU cores handling network traffic, buffering TCP sockets, parsing
inputs, and folding text strings. By isolating the state-transition engine to
a single thread, we let other CPU cores crunch the networking data while the
core engine focuses on pure, uninterrupted logic.
The Concurrency Puzzle & A Custom Heap
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
If the core is single-threaded, how do we handle millions of time-sliced
events like NPC actions, mob spawning, combat rounds, and item clean-up?
I’ve been experimenting with several traditional event queue/scheduler
implementations — including relative delta queues and standard priority
queues. Using standard slices requires massive memory-shuffling routines on
out-of-order changes, while standard linked-list delta queues collapse into
exponential bottlenecks when hit with chaotic timelines.
To solve this, I designed a Map-Backed Min-Heap Scheduler. By swapping out
Go’s heavy time.Time structs for raw int64 Unix Epoch seconds and reading time
from an atomic user-space coarse clock, I stripped away all OS kernel
system-call overhead. To solve the classic heap problem of slow O(N) random
event cancellations, I backed the tree with a primitive uint64 identity hash
map that tracks item array indexes in real time.
The structural characteristics look like this:
Map-Backed Heap Scheduler
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Data Structure: Binary Tree (Slice) + Identity Hash Map
Time Granularity: 1 Second (Unix Epoch int64)
Append to End: O(logN)
Out of Order Inserts: O(logN)
Next Event Pop: O(logN) (Short-circuited in user space)
Delete (by ID): O(logN) (Instant map-backed lookup)
Memory Layout: Highly Cache Friendly (Pointer-free keys)
Stress-Testing The Thresholds
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
The performance of this hybrid layout is exceptional. In my stress-test
benchmarks, the engine pulled off some spectacular numbers on a single thread
while queuing 100 million events:
• Load Throughput: Ingested 100,000,000 completely chaotic, out-of-order
events in 15.6 seconds.
• Random Deletions: Discarded 50,000,000 scheduled events at random in 9.1
seconds — a massive victory enabled by the map-index layer bypassing
linear array scans.
• The Pop Drain: Drained a burst of 256,832 ready events in just 222
milliseconds, maintaining an average extraction speed of roughly 860
nanoseconds per firing event.
For a more reasonable one million queued events:
• Load Throughput: Ingested one million events in 98ms.
• Random Deletions: Discarded 500,000 scheduled events at random in 64ms.
• The Pop Drain: Drained a burst of 152 ready events in 125µs. An average of
822 nanoseconds per firing event.
When no events are ready to fire, checking the queue head drops into a quick
user-space integer evaluation that exits in less than a millisecond. If a
single core ever does become a performance bottleneck at this scale, it will
be trivial to run multiple core engines with the zones split across them,
using a simple router to direct incoming player commands to the correct core.
Maintaining Mechanical Sympathy
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
The ultimate advantage of this design is that synchronisation and locking
complexity completely evaporates from the game logic.
Commands will arrive on a channel, state changes will process sequentially,
and responses will flush out on channels. It completely eliminates the complex
lock/release/relock dance that needs to be managed under heavy concurrent
loads today. It leaves the code cleaner, makes it far more maintainable for
anyone, and keeps memory footprints small enough to host tens of thousands of
players on minimal hardware.
Another less obvious, but incredibly useful, benefit of this separation is
that the core engine can be spun up in single-player or “text adventure” mode.
Because the game mechanics are completely decoupled from the multi-threaded
I/O layers, you can run the engine stand-alone. This is incredibly handy when
you are locally developing and testing new zones, or when you simply want to
create a lightweight, text adventure without the overhead of a full-blown MUD
server.
Implementing all of this will require either a massive refactor or a top-down
rewrite. Thankfully, because the core logic is already separated from the
synchronization layers, huge swathes of existing code can be reused cleanly.
I already have a mock system running with multiple players and a single
lock-free core. The numbers don’t lie, and the results so far are encouraging.
--
Diddymus
Up to Main Index Up to Journal for September, 2026