transform children linking
This commit is contained in:
54
SUMMARY.md
54
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<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:**
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user