sparsey ecs

This commit is contained in:
2025-12-31 14:12:56 +09:00
parent aebb5e6783
commit 8baafbdb0c
4 changed files with 71 additions and 106 deletions

32
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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<String>,
is_active: bool,
layer: Option<i64>,
tag: Option<i64>,
components: Vec<FileRef>,
}
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<i64> {
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();

View File

@@ -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<FileRef>,
local_position: Option<Vector3>,
local_rotation: Option<Quaternion>,
local_scale: Option<Vector3>,
parent: Option<FileRef>,
children: Vec<FileRef>,
parent: Option<Entity>,
children: Vec<Entity>,
}
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<FileRef> {
pub fn parent(&self) -> Option<Entity> {
self.parent
}
/// Set the parent transform entity
pub fn set_parent(&mut self, parent: Option<Entity>) {
self.parent = parent;
}
/// Get the list of child transform references
pub fn children(&self) -> &[FileRef] {
pub fn children(&self) -> &[Entity] {
&self.children
}
/// Set the child transform entities
pub fn set_children(&mut self, children: Vec<Entity>) {
self.children = children;
}
impl Component for Transform {
fn game_object(&self) -> Option<FileRef> {
self.game_object
}
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<FileRef> {
pub fn parent(&self) -> Option<Entity> {
self.transform.parent()
}
/// Set the parent transform entity (from Transform)
pub fn set_parent(&mut self, parent: Option<Entity>) {
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()
}
/// Set the child transform entities (from Transform)
pub fn set_children(&mut self, children: Vec<Entity>) {
self.transform.set_children(children);
}
impl Component for RectTransform {
fn game_object(&self) -> Option<FileRef> {
self.transform.game_object()
}
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();