refactor(world): read-only entity views world.Mobiles / world.Items (drop scalar count props) #158

Closed
opened 2026-07-22 22:12:27 +02:00 by marco · 0 comments
Owner

Unify how World exposes its entity collections behind cohesive read-only query views, replacing the growing set of scalar count props (CreatureCount, PlayerCount, ItemCount, PlacedItemCount, …).

Motivation

Today World grows a new scalar prop for each count we need (CreatureCount, PlacedItemCount, now ItemCount), and there is no ergonomic instance access for items (only FindMobile for mobiles). Owner's proposal: expose

world.Mobiles.Find(m => m.IsPlayer).Count()   // players
world.Mobiles.Find(m => !m.IsPlayer).Count()  // creatures
world.Items.Count()
world.Items.Find(id)                          // like FindMobile

More readable, fewer props to carry around, and it mirrors ModernUO (which exposes World.Mobiles / World.Items as global collections). ModernUO reference: we follow the collection-on-World shape; we diverge by exposing a read-only view (ModernUO's are fairly open) so mutation stays behind the World facade.

Design constraints (to settle in the review)

  • Thin-facade / World HARD GATE: expose a read-only view type (query: Find(predicate), Count, enumerate), NOT the mutating registry — callers must not be able to Spawn/Give/PickUp around the World facade. Mutation stays as World methods.
  • Single-threaded sim: the views are read on the sim thread like everything else; no lock added. A scrape-thread reader (metrics) must still go through the sim-thread snapshot, never the live view.
  • Encapsulation: promoting Item to a public entity (with intrinsic self-validating mutators, like Mobile) so world.Items.Find(id) returns something useful — this is the piece that unlocks FindItem. Decide whether to do it here or split.
  • Act on the instance: world.Items.Find(id) returning the item instance aligns with the existing world.FindMobile(id) + mobile.SetHits(...) pattern.

Scope

  • Introduce read-only views world.Mobiles and world.Items (query + count + enumerate).
  • Migrate call sites off CreatureCount/PlacedItemCount/ItemCount/FindMobile to the views (keep FindMobile or re-express as world.Mobiles.Find(id)).
  • Promote Item to a public entity if world.Items.Find(id) is in scope.
  • Update the metrics (#157) call sites (UpdateWorldCounts) to the new views.

Notes

  • Blocked-on / follows #157 (Prometheus metrics), which lands world.ItemCount as the minimal interim.
  • Needs a critical-design-review pass (World-facade change) before implementation.

Definition of Done

  • world.Mobiles and world.Items are read-only query views (no mutator reachable through them); mutation remains only via World methods (verifiable: no public Spawn/Give/etc. on the exposed type).
  • The scalar count props they replace are removed (CreatureCount/PlacedItemCount/ItemCount) with no remaining references.
  • Player and creature counts are expressible as world.Mobiles.Find(...)-style queries.
  • Invariants Check persisted (thin-facade, HARD GATE, single-thread, act-on-instance).
  • Base DoD: tests green, whole solution builds, zero warnings; multi-platform preserved.
Unify how `World` exposes its entity collections behind cohesive **read-only query views**, replacing the growing set of scalar count props (`CreatureCount`, `PlayerCount`, `ItemCount`, `PlacedItemCount`, …). ## Motivation Today `World` grows a new scalar prop for each count we need (`CreatureCount`, `PlacedItemCount`, now `ItemCount`), and there is no ergonomic instance access for items (only `FindMobile` for mobiles). Owner's proposal: expose ``` world.Mobiles.Find(m => m.IsPlayer).Count() // players world.Mobiles.Find(m => !m.IsPlayer).Count() // creatures world.Items.Count() world.Items.Find(id) // like FindMobile ``` More readable, fewer props to carry around, and it mirrors ModernUO (which exposes `World.Mobiles` / `World.Items` as global collections). **ModernUO reference**: we follow the collection-on-World shape; we diverge by exposing a **read-only** view (ModernUO's are fairly open) so mutation stays behind the World facade. ## Design constraints (to settle in the review) - **Thin-facade / World HARD GATE**: expose a **read-only** view type (query: `Find(predicate)`, `Count`, enumerate), NOT the mutating registry — callers must not be able to `Spawn`/`Give`/`PickUp` around the World facade. Mutation stays as World methods. - **Single-threaded sim**: the views are read on the sim thread like everything else; no lock added. A scrape-thread reader (metrics) must still go through the sim-thread snapshot, never the live view. - **Encapsulation**: promoting `Item` to a public entity (with intrinsic self-validating mutators, like `Mobile`) so `world.Items.Find(id)` returns something useful — this is the piece that unlocks `FindItem`. Decide whether to do it here or split. - **Act on the instance**: `world.Items.Find(id)` returning the item instance aligns with the existing `world.FindMobile(id)` + `mobile.SetHits(...)` pattern. ## Scope - Introduce read-only views `world.Mobiles` and `world.Items` (query + count + enumerate). - Migrate call sites off `CreatureCount`/`PlacedItemCount`/`ItemCount`/`FindMobile` to the views (keep `FindMobile` or re-express as `world.Mobiles.Find(id)`). - Promote `Item` to a public entity if `world.Items.Find(id)` is in scope. - Update the metrics (#157) call sites (`UpdateWorldCounts`) to the new views. ## Notes - Blocked-on / follows #157 (Prometheus metrics), which lands `world.ItemCount` as the minimal interim. - Needs a `critical-design-review` pass (World-facade change) before implementation. ## Definition of Done - [ ] `world.Mobiles` and `world.Items` are read-only query views (no mutator reachable through them); mutation remains only via `World` methods (verifiable: no public `Spawn`/`Give`/etc. on the exposed type). - [ ] The scalar count props they replace are removed (`CreatureCount`/`PlacedItemCount`/`ItemCount`) with no remaining references. - [ ] Player and creature counts are expressible as `world.Mobiles.Find(...)`-style queries. - [ ] Invariants Check persisted (thin-facade, HARD GATE, single-thread, act-on-instance). - [ ] Base DoD: tests green, whole solution builds, zero warnings; multi-platform preserved.
marco closed this issue 2026-07-22 23:01:17 +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#158
No description provided.