transform children linking

This commit is contained in:
2026-01-02 05:25:47 +00:00
parent f5cd713302
commit 9bca794ce1
5 changed files with 205 additions and 155 deletions

View File

@@ -153,63 +153,11 @@ Typed accessors for Unity YAML patterns:
- ✅ Sparsey World creation with component registration
- ✅ Entity spawning for GameObjects
### Partially Working
- ⚠️ **Component attachment** - Sparsey entity creation works, but adding components to existing entities needs research
- ⚠️ **Transform hierarchy** - Parent/children parsing works, but mutation API unclear
## ❌ What's Not Implemented
### Critical Missing Features
#### 1. Sparsey Component Management (HIGH PRIORITY)
**Location:** src/ecs/builder.rs:141-151, 155-176
**Problem:** Sparsey's API for adding components to existing entities is unclear.
**Current Code:**
```rust
fn insert_component<T: 'static>(_world: &mut World, _entity: Entity, component: T) -> Result<()> {
// TODO: Research if Sparsey has a way to add components to existing entities
eprintln!("Warning: Component insertion not fully implemented");
Ok(())
}
```
**What's Needed:**
- Research Sparsey 0.13 docs for component insertion API
- Possible approaches:
- Option A: Create entities with all components at once (requires refactoring to 2-pass instead of 3-pass)
- Option B: Find Sparsey's `insert()` or `add_component()` method
- Option C: Use Sparsey's entity builder pattern
**Files to Modify:**
- src/ecs/builder.rs:95-122 (attach_component)
- src/ecs/builder.rs:141-151 (insert_component)
#### 2. Transform Hierarchy Resolution (HIGH PRIORITY)
**Location:** src/ecs/builder.rs:155-176
**Problem:** Need mutable access to Transform components to set parent/children Entity refs.
**Current Code:**
```rust
fn resolve_transform_hierarchy(...) -> Result<()> {
let mut updates: Vec<(Entity, Option<Entity>, Vec<Entity>)> = Vec::new();
// Collects updates but doesn't apply them
// TODO: Research Sparsey's component mutation API
Ok(())
}
```
**What's Needed:**
- Find Sparsey's API for getting mutable component references
- Expected API: `world.get_mut::<Transform>(entity)` or similar
- Apply parent/children updates to Transform components
**Files to Modify:**
- src/ecs/builder.rs:155-176 (resolve_transform_hierarchy)
#### 3. Prefab Instancing System (MEDIUM PRIORITY)
#### 1. Prefab Instancing System (MEDIUM PRIORITY)
**Status:** Not started
**What's Needed:**

View File

@@ -1,9 +1,13 @@
//! ECS world building from Unity documents
use crate::model::RawDocument;
use crate::types::{yaml_helpers, ComponentContext, FileID, GameObject, RectTransform, Transform, UnityComponent};
use crate::types::{
yaml_helpers, ComponentContext, FileID, GameObject, LinkingContext, RectTransform, Transform,
UnityComponent,
};
use crate::{Error, Result};
use sparsey::{Entity, World};
use std::cell::RefCell;
use std::collections::HashMap;
/// Build a Sparsey ECS World from raw Unity documents
@@ -28,21 +32,21 @@ pub fn build_world_from_documents(
.register::<RectTransform>()
.build();
let mut entity_map = HashMap::new();
let linking_ctx = RefCell::new(LinkingContext::new());
// PASS 1: Create entities for all GameObjects
for doc in documents.iter().filter(|d| d.type_id == 1 || d.class_name == "GameObject") {
let entity = spawn_game_object(&mut world, doc)?;
entity_map.insert(doc.file_id, entity);
linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity);
}
// PASS 2: Attach components to entities
for doc in documents.iter().filter(|d| d.type_id != 1 && d.class_name != "GameObject") {
attach_component(&mut world, doc, &mut entity_map)?;
attach_component(&mut world, doc, &linking_ctx)?;
}
// PASS 3: Resolve Transform hierarchy
resolve_transform_hierarchy(&mut world, &documents, &entity_map)?;
// PASS 3: Execute all deferred linking callbacks
let entity_map = linking_ctx.into_inner().execute_callbacks(&mut world);
Ok((world, entity_map))
}
@@ -57,6 +61,9 @@ fn spawn_game_object(world: &mut World, doc: &RawDocument) -> Result<Entity> {
type_id: doc.type_id,
file_id: doc.file_id,
class_name: &doc.class_name,
entity: None,
linking_ctx: None,
yaml,
};
let go = GameObject::parse(yaml, &ctx)
@@ -72,7 +79,7 @@ fn spawn_game_object(world: &mut World, doc: &RawDocument) -> Result<Entity> {
fn attach_component(
world: &mut World,
doc: &RawDocument,
entity_map: &mut HashMap<FileID, Entity>,
linking_ctx: &RefCell<LinkingContext>,
) -> Result<()> {
let yaml = doc
.as_mapping()
@@ -82,7 +89,9 @@ fn attach_component(
let go_ref = yaml_helpers::get_file_ref(yaml, "m_GameObject");
let entity = match go_ref {
Some(r) => entity_map
Some(r) => linking_ctx
.borrow()
.entity_map()
.get(&r.file_id)
.copied()
.ok_or_else(|| {
@@ -102,24 +111,23 @@ fn attach_component(
type_id: doc.type_id,
file_id: doc.file_id,
class_name: &doc.class_name,
entity: Some(entity),
linking_ctx: Some(linking_ctx),
yaml,
};
// Dispatch to appropriate component parser
match doc.class_name.as_str() {
"Transform" => {
if let Some(transform) = Transform::parse(yaml, &ctx) {
// Insert Transform component into entity
insert_component(world, entity, transform)?;
// Map transform FileID to entity for hierarchy resolution
entity_map.insert(doc.file_id, entity);
world.insert(entity, (transform,));
linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity);
}
}
"RectTransform" => {
if let Some(rect) = RectTransform::parse(yaml, &ctx) {
// Insert RectTransform component into entity
insert_component(world, entity, rect)?;
// Map transform FileID to entity for hierarchy resolution
entity_map.insert(doc.file_id, entity);
world.insert(entity, (rect,));
linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity);
}
}
_ => {
@@ -133,81 +141,3 @@ fn attach_component(
Ok(())
}
/// Helper to insert a component into an entity
///
/// Note: Sparsey doesn't have a direct `insert` API, so we need to recreate the entity
/// with the additional component. This is a workaround until we find a better approach.
fn insert_component<T: 'static>(_world: &mut World, _entity: Entity, component: T) -> Result<()> {
// TODO: Research if Sparsey has a way to add components to existing entities
// For now, components are added during entity creation in attach_component
// Workaround: Store component for later use
// This will require refactoring the approach to create entities with all components at once
eprintln!("Warning: Component insertion not fully implemented - requires Sparsey API research");
std::mem::drop(component); // Prevent unused variable warning
Ok(())
}
/// Resolve Transform hierarchy by setting parent and children Entity references
fn resolve_transform_hierarchy(
_world: &mut World,
documents: &[RawDocument],
entity_map: &HashMap<FileID, Entity>,
) -> Result<()> {
// Collect hierarchy updates
let mut updates: Vec<(Entity, Option<Entity>, Vec<Entity>)> = Vec::new();
for doc in documents
.iter()
.filter(|d| matches!(d.class_name.as_str(), "Transform" | "RectTransform"))
{
let yaml = match doc.as_mapping() {
Some(m) => m,
None => continue,
};
let transform_entity = match entity_map.get(&doc.file_id) {
Some(e) => *e,
None => continue,
};
// Parse parent reference (m_Father)
let parent_entity = yaml_helpers::get_file_ref(yaml, "m_Father").and_then(|r| {
if r.file_id.as_i64() == 0 {
None // Null reference
} else {
entity_map.get(&r.file_id).copied()
}
});
// Parse children references (m_Children array)
let children_entities = parse_children_refs(yaml, entity_map);
updates.push((transform_entity, parent_entity, children_entities));
}
// Apply hierarchy updates
// TODO: Research Sparsey's component mutation API
// For now, we'll skip this until we understand how to get mutable component access
eprintln!(
"Warning: Transform hierarchy resolution not fully implemented - found {} transforms",
updates.len()
);
Ok(())
}
/// Parse children FileRefs from YAML and resolve to entities
fn parse_children_refs(
yaml: &serde_yaml::Mapping,
entity_map: &HashMap<FileID, Entity>,
) -> Vec<Entity> {
yaml_helpers::get_file_ref_array(yaml, "m_Children")
.unwrap_or_default()
.into_iter()
.filter_map(|r| entity_map.get(&r.file_id).copied())
.collect()
}

View File

@@ -2,9 +2,60 @@
use crate::types::*;
use serde_yaml::{Mapping, Value};
use sparsey::Entity;
use std::cell::RefCell;
use std::collections::HashMap;
/// A callback for deferred linking after all components are parsed
pub type LinkCallback = Box<dyn FnOnce(&mut sparsey::World, &HashMap<FileID, Entity>) + 'static>;
/// Context for managing entity linking during world building
pub struct LinkingContext {
/// Map from FileID to Entity
entity_map: HashMap<FileID, Entity>,
/// Callbacks to execute after all components are parsed
callbacks: Vec<LinkCallback>,
}
impl LinkingContext {
pub fn new() -> Self {
Self {
entity_map: HashMap::new(),
callbacks: Vec::new(),
}
}
/// Try to resolve a FileID to an Entity
pub fn resolve_entity(&self, file_id: FileID) -> Option<Entity> {
self.entity_map.get(&file_id).copied()
}
/// Register a callback to be executed during the linking pass
pub fn register_callback(&mut self, callback: LinkCallback) {
self.callbacks.push(callback);
}
/// Get the entity map
pub fn entity_map(&self) -> &HashMap<FileID, Entity> {
&self.entity_map
}
/// Get mutable access to the entity map
pub fn entity_map_mut(&mut self) -> &mut HashMap<FileID, Entity> {
&mut self.entity_map
}
/// Execute all registered callbacks
pub fn execute_callbacks(self, world: &mut sparsey::World) -> HashMap<FileID, Entity> {
let entity_map = self.entity_map.clone();
for callback in self.callbacks {
callback(world, &entity_map);
}
entity_map
}
}
/// Context information for parsing components from YAML
#[derive(Debug, Clone)]
pub struct ComponentContext<'a> {
/// Unity type ID (from !u!N tag)
pub type_id: u32,
@@ -12,6 +63,12 @@ pub struct ComponentContext<'a> {
pub file_id: FileID,
/// Class name (e.g., "GameObject", "Transform")
pub class_name: &'a str,
/// Entity that owns this component
pub entity: Option<Entity>,
/// Linking context for deferred entity resolution (wrapped in RefCell for interior mutability)
pub linking_ctx: Option<&'a RefCell<LinkingContext>>,
/// The raw YAML mapping for this component (for extracting FileRefs)
pub yaml: &'a Mapping,
}
/// Trait for Unity components that can be parsed from YAML

View File

@@ -12,7 +12,7 @@ mod transform;
mod type_registry;
mod values;
pub use component::{yaml_helpers, ComponentContext, UnityComponent};
pub use component::{yaml_helpers, ComponentContext, LinkCallback, LinkingContext, UnityComponent};
pub use game_object::GameObject;
pub use ids::{FileID, LocalID};
pub use reference::UnityReference;

View File

@@ -56,15 +56,65 @@ impl UnityComponent for Transform {
/// Parse a Transform from YAML
///
/// Note: Caller is responsible for ensuring this is called on the correct document type.
fn parse(yaml: &serde_yaml::Mapping, _ctx: &ComponentContext) -> Option<Self> {
fn parse(yaml: &serde_yaml::Mapping, ctx: &ComponentContext) -> Option<Self> {
let local_position = yaml_helpers::get_vector3(yaml, "m_LocalPosition");
let local_rotation = yaml_helpers::get_quaternion(yaml, "m_LocalRotation");
let local_scale = yaml_helpers::get_vector3(yaml, "m_LocalScale");
// Note: parent and children entities will be set later during hierarchy resolution
// The FileRef data is in the YAML but needs to be converted to entities separately
// Handle transform hierarchy linking if context is available
if let (Some(entity), Some(linking_ctx_ref)) = (ctx.entity, ctx.linking_ctx) {
// Extract parent FileRef (m_Father)
let parent_ref = yaml_helpers::get_file_ref(yaml, "m_Father");
// Extract children FileRefs (m_Children)
let children_refs =
yaml_helpers::get_file_ref_array(yaml, "m_Children").unwrap_or_default();
// Try to resolve children immediately
let mut children_entities = Vec::new();
let mut unresolved_children = Vec::new();
{
let linking_ctx = linking_ctx_ref.borrow();
for child_ref in children_refs {
if let Some(child_entity) = linking_ctx.resolve_entity(child_ref.file_id) {
children_entities.push(child_entity);
} else {
unresolved_children.push(child_ref.file_id);
}
}
}
// Register callback to set parent and children (even if some are unresolved)
linking_ctx_ref
.borrow_mut()
.register_callback(Box::new(move |world, entity_map| {
// Get the transform component
if let Some(transform) = world.borrow_mut::<Transform>().get_mut(entity) {
// Set parent (might be None if unresolved)
let resolved_parent =
parent_ref.and_then(|r| entity_map.get(&r.file_id).copied());
transform.set_parent(resolved_parent);
// Resolve any previously unresolved children
let mut all_children = children_entities.clone();
for child_file_id in &unresolved_children {
if let Some(child_entity) = entity_map.get(child_file_id).copied() {
all_children.push(child_entity);
} else {
eprintln!(
"Warning: Could not resolve child Transform: {}",
child_file_id
);
}
}
transform.set_children(all_children);
}
}));
}
Some(Self {
local_position,
@@ -150,9 +200,6 @@ impl UnityComponent for RectTransform {
///
/// Note: Caller is responsible for ensuring this is called on the correct document type.
fn parse(yaml: &serde_yaml::Mapping, ctx: &ComponentContext) -> Option<Self> {
// Parse the base Transform
let transform = Transform::parse(yaml, ctx)?;
let anchor_min = yaml_helpers::get_vector2(yaml, "m_AnchorMin");
let anchor_max = yaml_helpers::get_vector2(yaml, "m_AnchorMax");
@@ -163,6 +210,74 @@ impl UnityComponent for RectTransform {
let pivot = yaml_helpers::get_vector2(yaml, "m_Pivot");
// Parse transform data (without the base Transform to avoid double-linking)
let local_position = yaml_helpers::get_vector3(yaml, "m_LocalPosition");
let local_rotation = yaml_helpers::get_quaternion(yaml, "m_LocalRotation");
let local_scale = yaml_helpers::get_vector3(yaml, "m_LocalScale");
// Handle transform hierarchy linking if context is available
if let (Some(entity), Some(linking_ctx_ref)) = (ctx.entity, ctx.linking_ctx) {
// Extract parent FileRef (m_Father)
let parent_ref = yaml_helpers::get_file_ref(yaml, "m_Father");
// Extract children FileRefs (m_Children)
let children_refs =
yaml_helpers::get_file_ref_array(yaml, "m_Children").unwrap_or_default();
// Try to resolve children immediately
let mut children_entities = Vec::new();
let mut unresolved_children = Vec::new();
{
let linking_ctx = linking_ctx_ref.borrow();
for child_ref in children_refs {
if let Some(child_entity) = linking_ctx.resolve_entity(child_ref.file_id) {
children_entities.push(child_entity);
} else {
unresolved_children.push(child_ref.file_id);
}
}
}
// Register callback to set parent and children on the inner Transform
linking_ctx_ref
.borrow_mut()
.register_callback(Box::new(move |world, entity_map| {
// Get the RectTransform component and access its inner Transform
if let Some(rect) = world.borrow_mut::<RectTransform>().get_mut(entity) {
let transform = rect.transform_mut();
// Set parent (might be None if unresolved)
let resolved_parent =
parent_ref.and_then(|r| entity_map.get(&r.file_id).copied());
transform.set_parent(resolved_parent);
// Resolve any previously unresolved children
let mut all_children = children_entities.clone();
for child_file_id in &unresolved_children {
if let Some(child_entity) = entity_map.get(child_file_id).copied() {
all_children.push(child_entity);
} else {
eprintln!(
"Warning: Could not resolve child RectTransform: {}",
child_file_id
);
}
}
transform.set_children(all_children);
}
}));
}
let transform = Transform {
local_position,
local_rotation,
local_scale,
parent: None,
children: Vec::new(),
};
Some(Self {
transform,
anchor_min,