study: single-threaded command-queue tick (thin World, no lock) #54

Closed
opened 2026-07-19 00:53:43 +02:00 by marco · 2 comments
Owner

World is now a pure lock+delegate facade (#49), but its surface is still broad — ~30 methods across every domain — because the single _gate lock forces it to be the gateway for every operation. It reads like a god-class even though each method is thin.

The deeper fix: connection handlers enqueue intents (as TryEnqueueMove already does); a single-threaded tick applies them via the systems. World becomes state + tick orchestration — no lock, no broad mutating facade.

Why it is worth studying now: the spell system (its first real client) wants exactly this — server-paced word emission is enqueue-intent + process-at-tick. Doing it before/with the spell system avoids building the spell system against the current lock model and refactoring later.

Trade-offs to work through:

  • Synchronous results today (equip true/false, pickup result, command SystemMessage) become asynchronous — returned via the tick result / a message. Rework the handler flow.
  • ~1 tick (≤100ms) latency on intents — fine for a tick-based game.
  • The whole concurrency story (the P0 races we fixed) disappears — single thread.
  • Big refactor of the networking↔world boundary.

Definition of Done (for the study)

  • A written analysis: how each current synchronous World call maps to an intent + async feedback, and what the handler flow becomes.
  • A recommendation (do it before the spell system, or keep the lock model) with the reasoning.
  • If go: a phased plan that keeps the suite green at each step.
World is now a pure lock+delegate facade (#49), but its **surface is still broad** — ~30 methods across every domain — because the single `_gate` lock forces it to be the gateway for every operation. It reads like a god-class even though each method is thin. **The deeper fix**: connection handlers **enqueue intents** (as `TryEnqueueMove` already does); a **single-threaded tick** applies them via the systems. World becomes **state + tick orchestration** — no lock, no broad mutating facade. **Why it is worth studying now**: the **spell system** (its first real client) wants exactly this — server-paced word emission *is* enqueue-intent + process-at-tick. Doing it before/with the spell system avoids building the spell system against the current lock model and refactoring later. **Trade-offs to work through**: - Synchronous results today (equip true/false, pickup result, command `SystemMessage`) become **asynchronous** — returned via the tick result / a message. Rework the handler flow. - ~1 tick (≤100ms) latency on intents — fine for a tick-based game. - The whole concurrency story (the P0 races we fixed) **disappears** — single thread. - Big refactor of the networking↔world boundary. ## Definition of Done (for the study) - [ ] A written analysis: how each current synchronous World call maps to an intent + async feedback, and what the handler flow becomes. - [ ] A recommendation (do it before the spell system, or keep the lock model) with the reasoning. - [ ] If go: a phased plan that keeps the suite green at each step.
Author
Owner

Study written: docs/command-queue-study.md (PR above). Decision: adopt B (single-threaded simulation, ModernUO-style). Validated against ModernUO (single game thread, no world lock, packets processed on the game thread → immediate feedback). Migration via a single-threaded executor, phased so the suite stays green each step (Phase 1: executor foundation → route World access → move reads off connection threads → remove the lock). Combat under B: pipeline stays in a system, per-type OnHit reaction hooks on the entity.

Study written: `docs/command-queue-study.md` (PR above). **Decision: adopt B (single-threaded simulation, ModernUO-style).** Validated against ModernUO (single game thread, no world lock, packets processed on the game thread → immediate feedback). Migration via a single-threaded executor, phased so the suite stays green each step (Phase 1: executor foundation → route World access → move reads off connection threads → remove the lock). Combat under B: pipeline stays in a system, per-type `OnHit` reaction hooks on the entity.
Author
Owner

Implemented and merged in #56 (main 28df348): single-threaded Simulation executor, tick moved onto the sim thread (WorldTick/SimulationHost), every World access routed through InvokeAsync, and World._gate removed. World now holds no lock. Study conclusions realized.

Implemented and merged in #56 (main `28df348`): single-threaded `Simulation` executor, tick moved onto the sim thread (`WorldTick`/`SimulationHost`), every `World` access routed through `InvokeAsync`, and `World._gate` removed. World now holds no lock. Study conclusions realized.
marco closed this issue 2026-07-19 01:34:00 +02:00
Sign in to join this conversation.
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#54
No description provided.