diff --git a/SUMMARY.md b/SUMMARY.md index eee9d1e..07146ee 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -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(_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, Vec)> = 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::(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:** diff --git a/src/ecs/builder.rs b/src/ecs/builder.rs index 8d29e41..c7742a3 100644 --- a/src/ecs/builder.rs +++ b/src/ecs/builder.rs @@ -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::() .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 { 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 { fn attach_component( world: &mut World, doc: &RawDocument, - entity_map: &mut HashMap, + linking_ctx: &RefCell, ) -> 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(_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, -) -> Result<()> { - // Collect hierarchy updates - let mut updates: Vec<(Entity, Option, Vec)> = 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, -) -> Vec { - 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() -} diff --git a/src/types/component.rs b/src/types/component.rs index 5c0fc4f..4db471c 100644 --- a/src/types/component.rs +++ b/src/types/component.rs @@ -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) + 'static>; + +/// Context for managing entity linking during world building +pub struct LinkingContext { + /// Map from FileID to Entity + entity_map: HashMap, + /// Callbacks to execute after all components are parsed + callbacks: Vec, +} + +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 { + 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 { + &self.entity_map + } + + /// Get mutable access to the entity map + pub fn entity_map_mut(&mut self) -> &mut HashMap { + &mut self.entity_map + } + + /// Execute all registered callbacks + pub fn execute_callbacks(self, world: &mut sparsey::World) -> HashMap { + 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, + /// Linking context for deferred entity resolution (wrapped in RefCell for interior mutability) + pub linking_ctx: Option<&'a RefCell>, + /// The raw YAML mapping for this component (for extracting FileRefs) + pub yaml: &'a Mapping, } /// Trait for Unity components that can be parsed from YAML diff --git a/src/types/mod.rs b/src/types/mod.rs index 34f041b..fc0c479 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -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; diff --git a/src/types/transform.rs b/src/types/transform.rs index 57b56b1..e1a446a 100644 --- a/src/types/transform.rs +++ b/src/types/transform.rs @@ -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 { + fn parse(yaml: &serde_yaml::Mapping, ctx: &ComponentContext) -> Option { 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::().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 { - // 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::().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,