The server’s authoritative game state lives inside a single-threaded simulation loop. All network threads—IOCP workers receiving UDP movement inputs, TCP packet handlers processing attacks and inventory operations—submit work by enqueuingDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/kenzz55/ue5-iocp-mmo-server/llms.txt
Use this file to discover all available pages before exploring further.
Command lambdas into a thread-safe queue. The simulation thread wakes on each 33 ms tick, drains the command queue, then advances game state through six ordered phases. Because only the simulation thread ever mutates GameWorld, no additional locks are needed on the game objects themselves.
GameSimulation Class
Start() launches the simulation thread and arms a high-resolution timer (Windows timeBeginPeriod(1) on supported builds) to improve sleep accuracy near the 33 ms boundary. Stop() signals the thread and waits for it to exit, executing any remaining commands before returning.
Command Queue
Commands are stored asTimedCommand structs that capture both the callable and the time the command was enqueued:
| Config constant | Value | Meaning |
|---|---|---|
MaxGameSimulationCommandQueueSize | 65,536 | Enqueue() returns false (command dropped) when queue exceeds this depth |
GameSimulationTickIntervalMs | 33 | Target tick period (≈ 30 Hz) |
Enqueue() acquires commandMutex_, pushes the TimedCommand, and signals wakeCondition_ to unblock the simulation thread if it is sleeping. EnqueueAndWait() additionally blocks the calling thread on a second condition variable until the command lambda returns, which is useful for synchronous queries from external threads.
Tick Phases
Each tick the simulation executes six ordered phases. The phases are defined inNetworkMetrics::GameSimulationPhase and measured individually for Prometheus:
Phase details
Phase details
CharacterStateSave — Calls
GameWorld::TakeDirtyPlayerStates() with a rotating bucket index (characterStateSaveBucketIndex_) so the 1,024-session pool is spread across CharacterStateSaveBucketCount = 10 buckets. Each bucket is saved every CharacterStateSaveBucketIntervalSeconds = 1.0 s.PlayerMovement — Iterates activeMoveSessions_ and calls Session::StepMovement(deltaSeconds) for each session that has a pending move input. StepMovement integrates velocity using Config.h physics constants (MaxWalkSpeed, MaxAcceleration, BrakingDeceleration, GroundFriction) and returns an updated MovementSnapshot that is then published to MovementReplication.PlayerCombat — Resolves pendingAttacks_ whose start time plus animation offset has elapsed. Performs a capsule sweep in GameWorld::ApplyDamageToMonstersInSweep() and broadcasts S_EntityHpChanged to AoI observers.MonsterCombat — Resolves pendingMonsterAttacks_ using the stored hitDelaySeconds. Applies damage to the target player via GameWorld::ApplyDamageToPlayer().MonsterSimulation — Calls GameWorld::StepMonsters(deltaSeconds, outAttacks). Monster AI runs a simple state machine: Idle → Chase → RangedAttack / MeleeAttack → Return. New attack events are collected in outAttacks and pushed into pendingMonsterAttacks_ for the next combat phase.MonsterNotifications — Accumulates monsterSnapshotAccumulator_. When it exceeds MonsterAoiRefreshIntervalSeconds = 0.2 s, calls RefreshMonsterAoi() to update observer lists and broadcast PacketIdMonsterMoveNotify to each watcher.Metrics and Latency Buckets
GameSimulation::MetricsSnapshot exposes counters that are periodically transferred to NetworkMetrics for Prometheus export:
Network threads (IOCP workers) enqueue commands; they never read or write
GameWorld directly. Only the simulation thread calls GameWorld mutation methods. This single-writer design means GameWorld reads from the simulation thread need no locks, and concurrent reads from other threads use only std::shared_mutex for snapshot operations.