C++ Entity Identity and Ownership Without ECS¶
Selection should refer to the same body after unrelated deletion. That is an identity requirement, not a reason to introduce component stores.
Reproduce the Index Problem¶
A vector contains A then C. The UI remembers index 1 for C. Erasing A moves C to index 0, so the saved index no longer identifies C.
Pointers are not a general fix: vector growth and erase can invalidate them. The world instead issues a Handle and resolves it when needed.
Main-Course Policy¶
identity.hpp implements:
- A world-specific owner marker.
- A monotonically issued nonzero number.
- No reuse, even after deletion.
- Empty, foreign, and deleted handles fail resolution.
- Counter exhaustion throws instead of wrapping.
- Worlds cannot be copied or moved.
- Inspection returns owned snapshots.
An allocation failure may consume a number; gaps are acceptable. The diagnostic number() is not the full identity. Another world can issue the same number.
Resolve at the Ownership Boundary¶
Issuer::owns verifies the owner. IdentityWorld::inspect searches for a live entry with that number. The world owns records and decides whether a handle remains live.
The resolution method makes both checks visible:
std::nullopt means no live body resolves. Returning a dummy body at position zero would be indistinguishable from a real body there.
Copied handles do not own bodies. Deletion invalidates every copy. Retaining a handle after world destruction preserves a distinguishable marker, not a live simulation.
Run the Selection Scenario¶
C remains selected after A is deleted. Tests also force number collisions between worlds, grow storage, reject stale copies, and test counter exhaustion directly.
Identity Is Independent of Layout¶
The later ECS uses the same policy, but it already works with records. Lookup is deliberately simple; introducing handles is not a speed claim.
A saved snapshot is a past value. Resolve again for current state rather than expecting a copied value to update itself.
Do Not Add Generations Yet¶
With no ID reuse there is no reused-slot ambiguity. The main course needs no generation counter, free list, or retirement policy.
A real slot-reuse requirement leads to the optional generations experiment, not the next mandatory chapter.
Exercise¶
Two worlds both issue number 1. Why is selecting by number wrong? After deletion, what should the UI display when inspection returns no value?
Next: change processing boundaries without changing identity.