Without an Area of Interest (AoI) system, every player position update would need to be broadcast to every connected session—an O(n²) problem that collapses server throughput as player count grows. The AoI system divides the world into a grid of square cells and maintains per-cell session lists so that movement notifications, spawn events, and monster broadcasts are sent only to players whose cells overlap with the source entity’s neighbourhood. A nine-cell neighbourhood search (3×3 grid centred on the source) is sufficient because each cell is exactly oneDocumentation 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.
MovementAoiViewDistance wide.
Cell Grid
The world is partitioned using two Config.h constants:(x, y) maps to a cell coordinate:
unordered_map keyed by a 64-bit integer formed by packing the two 32-bit cell coordinates:
aoiMutex_ (a std::shared_mutex); readers (peer lookups) take a shared lock and writers (session registration, position updates, removal) take an exclusive lock.
Player AoI
Register
When a player successfully enters the game and a
PlayerEntity is attached, RegisterAoiSession(session) inserts the session into aoiCells_ at its initial spawn cell and records the AoiSessionEntry in aoiSessions_.Update on movement
During the PlayerMovement tick phase, after
StepMovement() returns a new MovementSnapshot, UpdateAoiSession(session, snapshot) is called. If the session’s cell has changed, it is removed from the old cell bucket and inserted into the new one. aoiSessions_ is updated to reflect the new cell.Peer lookup
FindPlayerAoiPeers(snapshot) and CollectPlayerAoiPeers(...) search the 3×3 neighbourhood of cells centred on the mover’s cell (up to 9 cells) and collect all Session* entries whose characters are within IsInMovementAoi() range.Visibility Tracking
Beyond cell membership, the server tracks which pairs of players are actually visible to each other using a slot-based bitset. EachSession has a fixed poolSlot_ index (0–1023 for a 1,024-session pool). The visibility state is stored in two parallel vectors indexed by slot:
playerVisibilityBitsBySlot_[slotA] is a bitmask where bit slotB is set when player A can currently see player B. This allows O(1) visibility checks and efficient diff computation during UpdatePlayerVisibility.
UpdatePlayerVisibility detects that enteredPeerIds is non-empty, the server sends S_SpawnOtherCharacter to the moving player and also to each entering peer. When exitedPeerIds is non-empty, S_DespawnCharacter is sent in both directions.
Enter and Exit Events
Enter AoI → SpawnOtherCharacter
When player A moves into player B’s cell neighbourhood and the visibility bitset transition from invisible to visible, the server sends
S_SpawnOtherCharacter (0x0206) to B describing A’s current position and yaw, and vice-versa.Exit AoI → DespawnCharacter
When the movers no longer share any of the 9 neighbourhood cells, the visibility bit is cleared and
S_DespawnCharacter (0x0207) is sent to both sessions so each client can remove the entity from its local world.Monster AoI
Monsters use the same cell grid but have a separate observer tracking map and a configurable refresh interval:monsterSnapshotAccumulator_ is incremented by deltaSeconds. When it exceeds MonsterAoiRefreshIntervalSeconds, RefreshMonsterAoi() iterates all live monsters, calls FindMonsterAoiWatchers() (same 9-cell search using the monster’s position), and updates monsterObservers_. Sessions that newly entered a monster’s AoI receive S_SpawnMonster; sessions that exited receive S_DespawnMonster. Active observers receive S_MonsterMoveNotify.
FindMonsterAoiWatchers is analogous to FindPlayerAoiPeers but receives a MonsterEntity instead of a MovementSnapshot:
When a monster is killed,
HandleMonsterDeath() calls DespawnMonsterFromObservers() to send S_DespawnMonster to all current observers, then calls ScheduleMonsterRespawn() which posts a TimerManager callback delayed by MonsterRespawnDelaySeconds = 30 seconds. After 30 seconds SpawnMonsterAtSpawnPoint() re-creates the monster entity and begins broadcasting S_SpawnMonster to AoI watchers again.