diff --git a/Cargo.lock b/Cargo.lock index d8bb638..7a81c0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "atomic_refcell" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" + [[package]] name = "cursebreaker-parser" version = "0.1.0" @@ -21,6 +27,7 @@ dependencies = [ "regex", "serde", "serde_yaml", + "sparsey", "thiserror", ] @@ -45,6 +52,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + [[package]] name = "hashbrown" version = "0.16.1" @@ -58,7 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -132,6 +145,12 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "ryu" version = "1.0.22" @@ -181,6 +200,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sparsey" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb745f2600e2b85825349ae307cbddba8061afa1dc83f3901bf3cc546fc2c542" +dependencies = [ + "atomic_refcell", + "hashbrown 0.15.5", + "rustc-hash", +] + [[package]] name = "syn" version = "2.0.111" diff --git a/Cargo.toml b/Cargo.toml index 8489e9d..0f0594b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,9 @@ regex = "1.10" # Math types (Vector2, Vector3, Quaternion, etc.) glam = { version = "0.29", features = ["serde"] } +# ECS (Entity Component System) +sparsey = "0.13" + [dev-dependencies] # Testing utilities pretty_assertions = "1.4" diff --git a/src/types/game_object.rs b/src/types/game_object.rs index f85060a..85e5019 100644 --- a/src/types/game_object.rs +++ b/src/types/game_object.rs @@ -1,7 +1,6 @@ //! GameObject wrapper for ergonomic access to GameObject properties use crate::model::UnityDocument; -use crate::types::{FileID, FileRef}; /// A wrapper around a UnityDocument that represents a GameObject /// @@ -17,19 +16,16 @@ use crate::types::{FileID, FileRef}; /// if let Some(go) = GameObject::parse(doc) { /// println!("GameObject: {}", go.name().unwrap_or("Unnamed")); /// println!(" Active: {}", go.is_active()); -/// println!(" Components: {}", go.components().len()); /// } /// } /// # Ok::<(), cursebreaker_parser::Error>(()) /// ``` #[derive(Debug, Clone)] pub struct GameObject { - file_id: FileID, name: Option, is_active: bool, layer: Option, tag: Option, - components: Vec, } impl GameObject { @@ -64,29 +60,11 @@ impl GameObject { .and_then(|p| p.get("m_TagString")) .and_then(|v| v.as_i64()); - let components = props - .and_then(|p| p.get("m_Component")) - .and_then(|v| v.as_array()) - .map(|arr| { - arr.iter() - .filter_map(|item| { - item.as_object().and_then(|obj| { - obj.get("component") - .and_then(|v| v.as_file_ref()) - .copied() - }) - }) - .collect() - }) - .unwrap_or_default(); - Some(Self { - file_id: document.file_id, name, is_active, layer, tag, - components, }) } @@ -109,22 +87,14 @@ impl GameObject { pub fn tag(&self) -> Option { self.tag } - - /// Get the list of component references attached to this GameObject - pub fn components(&self) -> &[FileRef] { - &self.components - } - - /// Get the file ID of this GameObject - pub fn file_id(&self) -> FileID { - self.file_id - } } #[cfg(test)] mod tests { use super::*; + use crate::model::UnityDocument; use crate::property::PropertyValue; + use crate::types::FileID; use indexmap::IndexMap; fn create_test_game_object() -> UnityDocument { @@ -134,7 +104,6 @@ mod tests { go_props.insert("m_Name".to_string(), PropertyValue::String("TestObject".to_string())); go_props.insert("m_IsActive".to_string(), PropertyValue::Boolean(true)); go_props.insert("m_Layer".to_string(), PropertyValue::Integer(0)); - go_props.insert("m_Component".to_string(), PropertyValue::Array(vec![])); properties.insert("GameObject".to_string(), PropertyValue::Object(go_props)); @@ -174,13 +143,6 @@ mod tests { assert_eq!(go.layer(), Some(0)); } - #[test] - fn test_game_object_file_id() { - let doc = create_test_game_object(); - let go = GameObject::parse(&doc).unwrap(); - assert_eq!(go.file_id().as_i64(), 12345); - } - #[test] fn test_non_game_object() { let mut doc = create_test_game_object(); diff --git a/src/types/transform.rs b/src/types/transform.rs index 45176fd..fbd33ce 100644 --- a/src/types/transform.rs +++ b/src/types/transform.rs @@ -1,7 +1,8 @@ //! Transform and RectTransform component wrappers use crate::model::UnityDocument; -use crate::types::{Component, FileID, FileRef, Quaternion, Vector2, Vector3}; +use crate::types::{Quaternion, Vector2, Vector3}; +use sparsey::Entity; /// A wrapper around a UnityDocument that represents a Transform component /// @@ -24,13 +25,11 @@ use crate::types::{Component, FileID, FileRef, Quaternion, Vector2, Vector3}; /// ``` #[derive(Debug, Clone)] pub struct Transform { - file_id: FileID, - game_object: Option, local_position: Option, local_rotation: Option, local_scale: Option, - parent: Option, - children: Vec, + parent: Option, + children: Vec, } impl Transform { @@ -47,11 +46,6 @@ impl Transform { .get(&document.class_name) .and_then(|v| v.as_object()); - let game_object = props - .and_then(|p| p.get("m_GameObject")) - .and_then(|v| v.as_file_ref()) - .copied(); - let local_position = props .and_then(|p| p.get("m_LocalPosition")) .and_then(|v| v.as_vector3()) @@ -67,30 +61,15 @@ impl Transform { .and_then(|v| v.as_vector3()) .copied(); - let parent = props - .and_then(|p| p.get("m_Father")) - .and_then(|v| v.as_file_ref()) - .copied(); - - let children = props - .and_then(|p| p.get("m_Children")) - .and_then(|v| v.as_array()) - .map(|arr| { - arr.iter() - .filter_map(|item| item.as_file_ref()) - .copied() - .collect() - }) - .unwrap_or_default(); + // Note: parent and children entities will be set later when building the ECS world + // The FileRef data is still in the UnityDocument but needs to be converted to entities separately Some(Self { - file_id: document.file_id, - game_object, local_position, local_rotation, local_scale, - parent, - children, + parent: None, + children: Vec::new(), }) } @@ -110,27 +89,28 @@ impl Transform { } /// Get the parent transform reference - pub fn parent(&self) -> Option { + pub fn parent(&self) -> Option { self.parent } + /// Set the parent transform entity + pub fn set_parent(&mut self, parent: Option) { + self.parent = parent; + } + /// Get the list of child transform references - pub fn children(&self) -> &[FileRef] { + pub fn children(&self) -> &[Entity] { &self.children } -} -impl Component for Transform { - fn game_object(&self) -> Option { - self.game_object + /// Set the child transform entities + pub fn set_children(&mut self, children: Vec) { + self.children = children; } - fn is_enabled(&self) -> bool { - true // Transforms are always enabled - } - - fn file_id(&self) -> FileID { - self.file_id + /// Add a child transform entity + pub fn add_child(&mut self, child: Entity) { + self.children.push(child); } } @@ -256,33 +236,35 @@ impl RectTransform { } /// Get the parent transform reference (from Transform) - pub fn parent(&self) -> Option { + pub fn parent(&self) -> Option { self.transform.parent() } + /// Set the parent transform entity (from Transform) + pub fn set_parent(&mut self, parent: Option) { + self.transform.set_parent(parent); + } + /// Get the list of child transform references (from Transform) - pub fn children(&self) -> &[FileRef] { + pub fn children(&self) -> &[Entity] { self.transform.children() } -} -impl Component for RectTransform { - fn game_object(&self) -> Option { - self.transform.game_object() + /// Set the child transform entities (from Transform) + pub fn set_children(&mut self, children: Vec) { + self.transform.set_children(children); } - fn is_enabled(&self) -> bool { - self.transform.is_enabled() - } - - fn file_id(&self) -> FileID { - self.transform.file_id() + /// Add a child transform entity (from Transform) + pub fn add_child(&mut self, child: Entity) { + self.transform.add_child(child); } } #[cfg(test)] mod tests { use super::*; + use crate::model::UnityDocument; use crate::property::PropertyValue; use crate::types::FileID; use indexmap::IndexMap; @@ -303,11 +285,6 @@ mod tests { "m_LocalScale".to_string(), PropertyValue::Vector3(Vector3::ONE), ); - transform_props.insert( - "m_GameObject".to_string(), - PropertyValue::FileRef(crate::types::FileRef::new(FileID::from_i64(67890))), - ); - transform_props.insert("m_Children".to_string(), PropertyValue::Array(vec![])); properties.insert("Transform".to_string(), PropertyValue::Object(transform_props)); @@ -399,13 +376,6 @@ mod tests { assert_eq!(scale, &Vector3::ONE); } - #[test] - fn test_transform_file_id() { - let doc = create_test_transform(); - let transform = Transform::parse(&doc).unwrap(); - assert_eq!(transform.file_id().as_i64(), 12345); - } - #[test] fn test_rect_transform_creation() { let doc = create_test_rect_transform();