feat: implement complete scene graph system with hierarchical editor support
- Add SceneNode and EntityNode classes for editor-only metadata storage - Implement SceneGraph view-model with O(1) entity lookup via internal caching - Create IdRemapTable for file-local to global entity ID remapping on load - Implement SceneSerializationContext for load/save operation tracking - Add JSON-serializable SceneAssetData, EntityData, and ComponentData models - Implement SceneSerializer for save/load with validation and reference remapping - Add comprehensive documentation: README.md, IMPLEMENTATION_GUIDE.md, SYSTEM_SUMMARY.md - Update Ghost.Editor.Core.csproj to reference Ghost.Entities assembly - Support parent-child relationships via Hierarchy component - Enforce no cross-scene entity references - Keep runtime minimal: only SceneID, Hierarchy, LocalToWorld components - All editor metadata (names, UI state) stored in editor-only SceneNode/EntityNode classes This implements the architecture from SceneGraph Plan.md with clean separation of concerns, minimal runtime footprint, and AOT compatibility.
This commit is contained in:
242
Ghost.Editor.Core/SceneGraph/IMPLEMENTATION_GUIDE.md
Normal file
242
Ghost.Editor.Core/SceneGraph/IMPLEMENTATION_GUIDE.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# Scene Graph Implementation Guide
|
||||
|
||||
This document provides an overview of the scene graph system implementation and integration points.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The scene graph system is **editor-only** and follows a clean separation between editor metadata and runtime data:
|
||||
|
||||
- **Editor Layer** (Ghost.Editor.Core): SceneNode, EntityNode, SceneGraph, Serialization
|
||||
- **Runtime Layer** (Ghost.Engine): Minimal components - SceneID, Hierarchy, LocalToWorld
|
||||
|
||||
## Core Classes
|
||||
|
||||
### SceneNode (Editor-only)
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/SceneNode.cs`
|
||||
- **Purpose**: Represents a scene in the editor hierarchy
|
||||
- **Metadata**: Name, SceneId, SceneGuid, List of child EntityNodes
|
||||
- **Key Methods**: FindEntityNode()
|
||||
|
||||
### EntityNode (Editor-only)
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/EntityNode.cs`
|
||||
- **Purpose**: Represents an entity in the editor hierarchy
|
||||
- **Metadata**: Name, EntityId, FileLocalId, ParentNode, Children, IsExpanded, IsSelected
|
||||
- **Key Methods**: FindRecursive(), GetDepth(), GetAllDescendants()
|
||||
|
||||
### SceneGraph (Editor View-Model)
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/SceneGraph.cs`
|
||||
- **Purpose**: Main view-model providing hierarchical access to scenes and entities
|
||||
- **Key Features**:
|
||||
- Maintains scenes and entity hierarchy
|
||||
- O(1) lookup via internal caches
|
||||
- Manages parent-child relationships
|
||||
- Provides queries for UI rendering
|
||||
- **Key Methods**:
|
||||
- AddScene(), RemoveScene(), GetSceneNode()
|
||||
- AddEntity(), RemoveEntity(), GetEntityNode()
|
||||
- SetEntityParent()
|
||||
- GetEntitiesInScene()
|
||||
- RebuildFromWorld()
|
||||
|
||||
### Serialization Classes
|
||||
|
||||
#### IdRemapTable
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/Serialization/IdRemapTable.cs`
|
||||
- **Purpose**: Maps file-local entity IDs to runtime global entity IDs
|
||||
- **Key Methods**:
|
||||
- Register(fileLocalId, globalEntityId)
|
||||
- GetGlobalId(), GetLocalId()
|
||||
- RemapReference() - throws if not found
|
||||
|
||||
#### SceneSerializationContext
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/Serialization/IdRemapTable.cs`
|
||||
- **Purpose**: Container for serialization metadata
|
||||
- **Contents**:
|
||||
- IdRemap: Remapping table
|
||||
- SceneId, EditorWorld
|
||||
- EntityOrder: List of entities in file order
|
||||
- ValidationErrors: Errors encountered during load
|
||||
|
||||
#### SceneAssetData (JSON model)
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/Serialization/SceneAssetData.cs`
|
||||
- **Purpose**: JSON-serializable representation of a scene
|
||||
- **Structure**:
|
||||
- Version, SceneGuid, Name, SceneId
|
||||
- Entities: List of EntityData (index = file-local ID)
|
||||
|
||||
#### ComponentData (JSON model)
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/Serialization/SceneAssetData.cs`
|
||||
- **Purpose**: JSON-serializable representation of a component instance
|
||||
- **Structure**:
|
||||
- ComponentTypeName: Full type name
|
||||
- Data: Dictionary of field name -> value
|
||||
|
||||
#### EntityData (JSON model)
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/Serialization/SceneAssetData.cs`
|
||||
- **Purpose**: JSON-serializable representation of an entity
|
||||
- **Structure**:
|
||||
- FileLocalId: Position in entities list
|
||||
- Name: Editor-only display name
|
||||
- ParentFileLocalId: Reference to parent (file-local)
|
||||
- Components: List of ComponentData
|
||||
|
||||
#### SceneSerializer
|
||||
- **Location**: `Ghost.Editor.Core/SceneGraph/Serialization/SceneSerializer.cs`
|
||||
- **Purpose**: Handles save/load operations
|
||||
- **Key Methods**:
|
||||
- SaveScene(sceneGraph, sceneId, editorWorld) -> SceneAssetData
|
||||
- LoadScene(sceneGraph, sceneData, editorWorld, context)
|
||||
- ValidateNoInvalidReferences()
|
||||
|
||||
## Integration Points
|
||||
|
||||
### 1. Component Serialization
|
||||
The system uses reflection to serialize/deserialize components. You need to:
|
||||
- Implement component field reflection in `SceneSerializer.SerializeEntityComponents()`
|
||||
- Handle special cases like Entity references (remap to file-local IDs)
|
||||
- Support both unmanaged and managed components (via ManagedEntity/ManagedEntityRef)
|
||||
|
||||
**Key challenge**: Entity references must be stored as file-local IDs and remapped on load.
|
||||
|
||||
### 2. World Integration
|
||||
The `SceneGraph.RebuildFromWorld()` method needs:
|
||||
- Query entities with `SceneID` component
|
||||
- Extract `Hierarchy` component data to build parent-child relationships
|
||||
- Build EntityNode tree from ECS data
|
||||
|
||||
**Expected query**:
|
||||
```csharp
|
||||
var query = world.QueryBuilder()
|
||||
.With<SceneID>()
|
||||
.With<Hierarchy>()
|
||||
.Build();
|
||||
```
|
||||
|
||||
### 3. Runtime Components Required
|
||||
Add to `Ghost.Engine/Components/`:
|
||||
- `SceneID`: Already exists - tags entities with scene membership
|
||||
- `Hierarchy`: Already exists - stores parent/firstChild/nextSibling
|
||||
- `LocalToWorld`: Already exists - for transform hierarchies (optional integration)
|
||||
|
||||
### 4. File I/O
|
||||
You need to implement:
|
||||
- JSON file loading/saving (use System.Text.Json)
|
||||
- Scene asset file paths (e.g., `Assets/Scenes/SceneName.scene.json`)
|
||||
- Asset database integration for scene asset tracking
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Saving a Scene
|
||||
|
||||
```
|
||||
SceneGraph (editor view-model)
|
||||
↓
|
||||
SceneSerializer.SaveScene()
|
||||
↓
|
||||
Serialize EntityNode tree to SceneAssetData
|
||||
- Build entities list in deterministic order
|
||||
- Convert Entity references to file-local IDs
|
||||
- Validate no cross-scene references
|
||||
↓
|
||||
SceneAssetData (JSON model)
|
||||
↓
|
||||
System.Text.Json serialization
|
||||
↓
|
||||
Scene file (.scene.json)
|
||||
```
|
||||
|
||||
### Loading a Scene
|
||||
|
||||
```
|
||||
Scene file (.scene.json)
|
||||
↓
|
||||
System.Text.Json deserialization
|
||||
↓
|
||||
SceneAssetData (JSON model)
|
||||
↓
|
||||
SceneSerializer.LoadScene()
|
||||
- Create entities in editor world
|
||||
- Build IdRemapTable (file-local -> global)
|
||||
- Remap component entity references
|
||||
- Establish parent-child relationships
|
||||
↓
|
||||
SceneGraph (editor view-model)
|
||||
↓
|
||||
UI displays hierarchy
|
||||
```
|
||||
|
||||
## File-Local ID Remapping
|
||||
|
||||
**Critical concept**: Entities must maintain stable file-local IDs for serialization.
|
||||
|
||||
**Serialization**:
|
||||
```
|
||||
Entities in scene (in order):
|
||||
[Entity A (global: 10), Entity B (global: 20), Entity C (global: 30)]
|
||||
|
||||
File-local IDs: 0, 1, 2
|
||||
|
||||
Entity references stored as file-local:
|
||||
- If Entity A refers to Entity B: store 1 (file-local of B)
|
||||
```
|
||||
|
||||
**Deserialization**:
|
||||
```
|
||||
1. Allocate new entities: Entity A' (global: 50), Entity B' (global: 51), Entity C' (global: 52)
|
||||
2. Build IdRemapTable:
|
||||
- 0 -> 50 (A')
|
||||
- 1 -> 51 (B')
|
||||
- 2 -> 52 (C')
|
||||
3. Remap references:
|
||||
- Entity A's reference to 1 -> becomes reference to 51 (Entity B')
|
||||
```
|
||||
|
||||
## Next Steps for Integration
|
||||
|
||||
1. **Implement component reflection** in `SceneSerializer`:
|
||||
- Use `System.Reflection` to get component type and fields
|
||||
- Handle custom serialization for entity references
|
||||
- Support nullable types and managed components
|
||||
|
||||
2. **Implement world query integration** in `SceneGraph.RebuildFromWorld()`:
|
||||
- Query entities with SceneID and Hierarchy components
|
||||
- Build EntityNode hierarchy from Hierarchy component data
|
||||
- Handle entities without parents (root entities in scene)
|
||||
|
||||
3. **Implement file I/O**:
|
||||
- Create scene asset loader/saver
|
||||
- Integrate with asset database
|
||||
- Handle file paths and metadata
|
||||
|
||||
4. **Add UI components** (in editor UI layer):
|
||||
- TreeView binding to SceneGraph.Scenes
|
||||
- Entity selection and renaming UI
|
||||
- Drag-drop parent reassignment
|
||||
|
||||
5. **Test edge cases**:
|
||||
- Loading scenes with missing entities
|
||||
- Cross-scene reference validation
|
||||
- Circular parent-child relationships
|
||||
- Very large scenes with many entities
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **Editor-only metadata**: Names, selection state, expansion state are stored in SceneNode/EntityNode only, not at runtime.
|
||||
|
||||
2. **File-local IDs**: Provide stable references for serialization independent of runtime entity allocation order.
|
||||
|
||||
3. **Minimal runtime**: Only SceneID, Hierarchy, LocalToWorld in runtime; no scene names, display data, etc.
|
||||
|
||||
4. **Reflection in editor**: Allows flexibility and OOP patterns that aren't AOT-compatible.
|
||||
|
||||
5. **No cross-scene references**: Enforced by validation; use queries/singletons for cross-scene access.
|
||||
|
||||
6. **Hierarchy as component**: Parent-child relationships use Hierarchy component, making them queryable in ECS systems.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Circular parent-child references**: Add validation in SetEntityParent()
|
||||
- **File-local ID collision**: Ensure Index-based ID assignment is stable
|
||||
- **Missing entity references**: Catch during load, report validation errors
|
||||
- **Type name changes**: Store full namespace+typename; handle version migration if types rename
|
||||
- **Managed component fields**: Require special serialization (MemoryPack recommended)
|
||||
Reference in New Issue
Block a user