feat(gameserver): single-threaded world simulation, no lock (#54) #56

Merged
marco merged 3 commits from feat/simulation-executor into main 2026-07-19 01:33:26 +02:00
Owner

Single-threaded world simulation (ModernUO-style) — closes the command-queue study (#54).

What changed

World is now touched by exactly one thread — the Simulation's — and holds no lock.

  • Phase 1 Simulation executor: an unbounded Channel<Func<Task>> drained by one thread; InvokeAsync<T> marshals work from any thread and returns its result; a PeriodicTimer posts the tick onto the same queue (serialized with all other work).
  • Phase 2 the tick moved onto the sim thread: WorldTick (the full AoI reconcile) + SimulationHost (BackgroundService running the executor). GameLoopService deleted.
  • Phase 3 every World access routed through Simulation.InvokeAsync: GameSessionHandler (connect/disconnect/move/say/pickup/drop/equip/unequip/attack/GM/target), WorldPersister snapshot. Cross-domain reads a caller needs together collapse into one InvokeAsync.
  • Phase 4 World._gate removed: every method is a pure delegate, atomicity comes from single-threaded execution.

InterestManager keeps its own lock — its Enter/Leave run on connection threads, so it is deliberately outside the sim.

Verification

just lint — build succeeded, 0 warnings. just test — full suite green (GameServer 115, Shared 51, Client.Core 46, Auth 12, Assets 4, AssetExtractor 7).

Definition of Done

  • World has no lock; all callers reach it via Simulation.InvokeAsync (or are the tick / single-threaded startup).
  • The tick and all connection work run on the same single thread (no data race possible on World state).
  • Connect / disconnect / persistence read each atomic in one InvokeAsync.
  • GameLoopService gone; SimulationHost + WorldTick replace it.
  • Whole solution builds, zero warnings, tests green, multi-platform preserved.
Single-threaded world simulation (ModernUO-style) — closes the command-queue study (#54). ## What changed `World` is now touched by exactly **one** thread — the `Simulation`'s — and holds **no lock**. - **Phase 1** `Simulation` executor: an unbounded `Channel<Func<Task>>` drained by one thread; `InvokeAsync<T>` marshals work from any thread and returns its result; a `PeriodicTimer` posts the tick onto the same queue (serialized with all other work). - **Phase 2** the tick moved onto the sim thread: `WorldTick` (the full AoI reconcile) + `SimulationHost` (BackgroundService running the executor). `GameLoopService` deleted. - **Phase 3** every `World` access routed through `Simulation.InvokeAsync`: `GameSessionHandler` (connect/disconnect/move/say/pickup/drop/equip/unequip/attack/GM/target), `WorldPersister` snapshot. Cross-domain reads a caller needs together collapse into one `InvokeAsync`. - **Phase 4** `World._gate` removed: every method is a pure delegate, atomicity comes from single-threaded execution. `InterestManager` keeps its own lock — its `Enter`/`Leave` run on connection threads, so it is deliberately outside the sim. ## Verification `just lint` — build succeeded, 0 warnings. `just test` — full suite green (GameServer 115, Shared 51, Client.Core 46, Auth 12, Assets 4, AssetExtractor 7). ## Definition of Done - [x] `World` has no lock; all callers reach it via `Simulation.InvokeAsync` (or are the tick / single-threaded startup). - [x] The tick and all connection work run on the same single thread (no data race possible on World state). - [x] Connect / disconnect / persistence read each atomic in one `InvokeAsync`. - [x] `GameLoopService` gone; `SimulationHost` + `WorldTick` replace it. - [x] Whole solution builds, zero warnings, tests green, multi-platform preserved.
docs: single-threaded simulation study (#54) + ModernUO as architectural reference
All checks were successful
ci / Lint & Test (pull_request) Successful in 1m49s
271a5c529a
Decision from #54: adopt the ModernUO-style single-threaded simulation (option B) — thin
World, no world lock, IO threads marshal messages to one sim thread. Validated against
ModernUO (single game thread, timer wheel, packets processed on the game thread, no world
lock). Combat model: keep resolution in a system, per-type OnHit reaction hooks on the
entity (the middle way). Records ModernUO-as-reference as a working-agreement principle.
feat(gameserver): single-threaded Simulation executor (command-queue Phase 1, #54)
All checks were successful
ci / Lint & Test (pull_request) Successful in 1m23s
358d0c40b7
The foundation for the ModernUO-style single-threaded world: one reader drains a work
queue, running each item to completion before the next, so posted work never overlaps and
needs no lock. A timer posts the tick onto the same queue, serializing ticks with posted
work. Not yet wired into the live loop — later phases route the handlers' World access
through InvokeAsync and drop World's _gate. Unit-tested for serialization + result/exception
propagation.
feat(gameserver): single-threaded world simulation, no lock (#54)
All checks were successful
ci / Lint & Test (pull_request) Successful in 1m56s
96d4cc2169
Route every World access through the Simulation executor and drop
World's _gate lock. Connection/IO threads marshal work onto the single
sim thread via Simulation.InvokeAsync; the tick runs on that same thread
(WorldTick + SimulationHost, replacing GameLoopService). Atomicity now
comes from single-threaded execution, so World holds no lock and each
method is a pure delegate. Cross-domain reads a caller needs together
(connect, disconnect, persistence snapshot) collapse into one InvokeAsync.
InterestManager keeps its own lock — its Enter/Leave run on connection
threads. ModernUO-style, closes the command-queue study (#54).
marco changed title from feat(gameserver): single-threaded Simulation executor (command-queue Phase 1, #54) to feat(gameserver): single-threaded world simulation, no lock (#54) 2026-07-19 01:29:18 +02:00
marco merged commit 28df348509 into main 2026-07-19 01:33:26 +02:00
marco deleted branch feat/simulation-executor 2026-07-19 01:33:26 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo!56
No description provided.