Implement World: GameObject hierarchy, components, type-indexed queries
First real piece of M0 rather than scaffolding. GameWorld : IWorld owns GameObject creation/destruction, a type-indexed Dictionary<Type, HashSet<GameObject>> backing Query<T>(), and hierarchy bookkeeping (roots list, parent/children, cycle rejection on SetParent). Two corrections to the design doc found while implementing: - WorldMatrix can't be cached on Transform as described — Transform is a plain struct with no reference to the hierarchy it would need to compose against. Moved to GameObject.WorldMatrix, computed from the parent chain on read; docs/kernel-contract.md and the §3 example updated (go.Transform.WorldMatrix -> go.WorldMatrix). - The concrete World class collided with its own containing namespace (Engine.Kernel.World.World), which makes the bare name ambiguous for every consumer. Renamed to GameWorld; IWorld and the World folder/ namespace are unaffected, and the doc never named the concrete class either way, so nothing there needed to change. Also fixed a real correctness bug caught while writing World.Destroy: a GameObject can carry more than one component of the same type, so removing one from the type index has to check whether any others of that type remain before dropping the GameObject from the index set — tested directly (Query_Still_Finds_A_GameObject_With_A_Duplicate_ Component_After_One_Removal). 12 tests in Engine.Kernel.Tests cover creation, hierarchy (including cycle rejection), component add/remove/query, destroy cascading through a subtree, and WorldMatrix composition (verified against a worked-through parent+child translation, not just asserted). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
This commit is contained in:
+10
-6
@@ -56,12 +56,16 @@ nothing.
|
||||
| 06 | **Time & Log** | Frame clock, fixed-step accumulator, logging interface. Kept minimal. |
|
||||
|
||||
`GameObject.Transform` is the one field embedded directly rather than
|
||||
modeled as a `Component` subclass — it's a plain struct (position, rotation,
|
||||
scale, cached world matrix), because nearly every system in the engine
|
||||
modeled as a `Component` subclass — it's a plain struct holding local
|
||||
position, rotation, and scale, because nearly every system in the engine
|
||||
touches it every frame, and routing that through the same virtual-dispatch
|
||||
path as every other component would tax the one thing everything depends on.
|
||||
Everything else — `MeshRenderer`, `Rigidbody`, `AudioSource`, game-specific
|
||||
components — is a plain class, heap-allocated, no special treatment.
|
||||
path as every other component would tax the one thing everything depends
|
||||
on. `GameObject.WorldMatrix` composes it with the parent chain on every
|
||||
read rather than caching a value — a cache here would need invalidating on
|
||||
every reparent and every ancestor's change, which is more bookkeeping than
|
||||
a handful of matrix multiplies costs at indie scale. Everything else —
|
||||
`MeshRenderer`, `Rigidbody`, `AudioSource`, game-specific components — is a
|
||||
plain class, heap-allocated, no special treatment.
|
||||
|
||||
### Plugins — everything else, no exceptions
|
||||
|
||||
@@ -219,7 +223,7 @@ public sealed class RenderPlugin : IPlugin
|
||||
{
|
||||
// type-indexed lookup, not a scan — see the World row in §2
|
||||
foreach (var go in world.Query<MeshRenderer>())
|
||||
f.Draw(go.GetComponent<MeshRenderer>().Handle, go.Transform.WorldMatrix);
|
||||
f.Draw(go.GetComponent<MeshRenderer>().Handle, go.WorldMatrix);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user