project restructure

This commit is contained in:
2026-01-03 14:04:12 +00:00
parent ff3c092d9e
commit bfd451aca9
45 changed files with 93 additions and 80 deletions

View File

@@ -11,7 +11,8 @@
"Bash(cargo check:*)", "Bash(cargo check:*)",
"Bash(ls:*)", "Bash(ls:*)",
"Bash(find:*)", "Bash(find:*)",
"Bash(grep:*)" "Bash(grep:*)",
"Bash(wc:*)"
], ],
"additionalDirectories": [ "additionalDirectories": [
"/home/connor/repos/CBAssets/" "/home/connor/repos/CBAssets/"

56
Cargo.lock generated
View File

@@ -23,34 +23,6 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c"
[[package]]
name = "cursebreaker-parser"
version = "0.1.0"
dependencies = [
"cursebreaker-parser-macros",
"glam",
"indexmap",
"inventory",
"lru",
"once_cell",
"pretty_assertions",
"regex",
"serde",
"serde_yaml",
"sparsey",
"thiserror",
"walkdir",
]
[[package]]
name = "cursebreaker-parser-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "diff" name = "diff"
version = "0.1.13" version = "0.1.13"
@@ -318,6 +290,34 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unity-parser"
version = "0.1.0"
dependencies = [
"glam",
"indexmap",
"inventory",
"lru",
"once_cell",
"pretty_assertions",
"regex",
"serde",
"serde_yaml",
"sparsey",
"thiserror",
"unity-parser-macros",
"walkdir",
]
[[package]]
name = "unity-parser-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "unsafe-libyaml" name = "unsafe-libyaml"
version = "0.2.11" version = "0.2.11"

View File

@@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["cursebreaker-parser", "cursebreaker-parser-macros"] members = ["unity-parser", "unity-parser-macros"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
@@ -7,4 +7,4 @@ version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["Your Name <your.email@example.com>"] authors = ["Your Name <your.email@example.com>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/yourusername/cursebreaker-parser-rust" repository = "https://github.com/yourusername/unity-parser-rust"

View File

@@ -1,4 +1,4 @@
# Cursebreaker Parser # Unity Parser
A high-performance Rust library for parsing Unity project files (.unity scenes, .prefab prefabs, and .asset ScriptableObjects) with automatic MonoBehaviour component discovery and ECS integration. A high-performance Rust library for parsing Unity project files (.unity scenes, .prefab prefabs, and .asset ScriptableObjects) with automatic MonoBehaviour component discovery and ECS integration.
@@ -32,7 +32,7 @@ Add to your `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]
cursebreaker-parser = "0.1" unity-parser = "0.1"
sparsey = "0.13" # For ECS queries sparsey = "0.13" # For ECS queries
``` ```
@@ -41,7 +41,7 @@ sparsey = "0.13" # For ECS queries
### Parse a Unity Scene ### Parse a Unity Scene
```rust ```rust
use cursebreaker_parser::UnityFile; use unity_parser::UnityFile;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = UnityFile::from_path("Scene.unity")?; let file = UnityFile::from_path("Scene.unity")?;
@@ -51,7 +51,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Scene with {} entities", scene.entity_map.len()); println!("Scene with {} entities", scene.entity_map.len());
// Query transforms // Query transforms
let transforms = scene.world.borrow::<cursebreaker_parser::Transform>(); let transforms = scene.world.borrow::<unity_parser::Transform>();
for (file_id, entity) in &scene.entity_map { for (file_id, entity) in &scene.entity_map {
if let Some(transform) = transforms.get(*entity) { if let Some(transform) = transforms.get(*entity) {
println!("Entity {} at {:?}", file_id, transform.local_position()); println!("Entity {} at {:?}", file_id, transform.local_position());
@@ -73,7 +73,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
### Define Custom Components ### Define Custom Components
```rust ```rust
use cursebreaker_parser::UnityComponent; use unity_parser::UnityComponent;
#[derive(Debug, Clone, UnityComponent)] #[derive(Debug, Clone, UnityComponent)]
#[unity_class("PlaySFX")] #[unity_class("PlaySFX")]
@@ -133,7 +133,7 @@ Parse only scenes matching specific patterns:
```rust ```rust
use regex::Regex; use regex::Regex;
use cursebreaker_parser::parse_unity_file_filtered; use unity_parser::parse_unity_file_filtered;
// Only parse production scenes // Only parse production scenes
let filter = Regex::new(r"Assets/Scenes/Production/")?; let filter = Regex::new(r"Assets/Scenes/Production/")?;
@@ -161,7 +161,7 @@ let filter = Regex::new(r"Level[1-5]\.unity$")?;
Selectively parse components for better performance: Selectively parse components for better performance:
```rust ```rust
use cursebreaker_parser::{TypeFilter, parse_with_types}; use unity_parser::{TypeFilter, parse_with_types};
// Parse only transforms and custom components // Parse only transforms and custom components
let filter = TypeFilter::parse_with_types(&["Transform", "PlaySFX"]); let filter = TypeFilter::parse_with_types(&["Transform", "PlaySFX"]);
@@ -232,7 +232,7 @@ Raw YAML Documents
### Find All Components of a Type ### Find All Components of a Type
```rust ```rust
use cursebreaker_parser::UnityFile; use unity_parser::UnityFile;
let scene = UnityFile::from_path("Scene.unity")?; let scene = UnityFile::from_path("Scene.unity")?;
@@ -378,7 +378,7 @@ Benchmarks on VR Horror project (21 scenes, 77 C# scripts):
## Project Structure ## Project Structure
``` ```
cursebreaker-parser/ unity-parser/
├── src/ ├── src/
│ ├── ecs/ # ECS world building │ ├── ecs/ # ECS world building
│ ├── model/ # UnityFile, Scene, Prefab models │ ├── model/ # UnityFile, Scene, Prefab models
@@ -387,11 +387,15 @@ cursebreaker-parser/
│ │ ├── meta.rs # .meta file parsing │ │ ├── meta.rs # .meta file parsing
│ │ └── yaml.rs # YAML document splitting │ │ └── yaml.rs # YAML document splitting
│ ├── types/ # Unity types and components │ ├── types/ # Unity types and components
│ │ ├── unity_types/ # Unity-specific types
│ │ │ ├── game_object.rs
│ │ │ ├── prefab_instance.rs
│ │ │ └── transform.rs
│ │ ├── guid.rs # 128-bit GUID type │ │ ├── guid.rs # 128-bit GUID type
│ │ ├── component.rs # Component trait system │ │ ├── component.rs # Component trait system
│ │ └── ... │ │ └── ...
│ └── lib.rs │ └── lib.rs
├── cursebreaker-parser-macros/ # Derive macro crate ├── unity-parser-macros/ # Derive macro crate
├── examples/ # Usage examples ├── examples/ # Usage examples
├── tests/ # Integration tests ├── tests/ # Integration tests
└── test_data/ # Test Unity projects └── test_data/ # Test Unity projects

View File

@@ -1,11 +1,11 @@
[package] [package]
name = "cursebreaker-parser-macros" name = "unity-parser-macros"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["Your Name <your.email@example.com>"] authors = ["Your Name <your.email@example.com>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Procedural macros for cursebreaker-parser" description = "Procedural macros for unity-parser"
repository = "https://github.com/yourusername/cursebreaker-parser-rust" repository = "https://github.com/yourusername/unity-parser-rust"
[lib] [lib]
proc-macro = true proc-macro = true

View File

@@ -1,4 +1,4 @@
//! Procedural macros for cursebreaker-parser //! Procedural macros for unity-parser
//! //!
//! This crate provides the `#[derive(UnityComponent)]` macro for automatically //! This crate provides the `#[derive(UnityComponent)]` macro for automatically
//! generating Unity component parsing code. //! generating Unity component parsing code.

View File

@@ -1,17 +1,17 @@
[package] [package]
name = "cursebreaker-parser" name = "unity-parser"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["Your Name <your.email@example.com>"] authors = ["Your Name <your.email@example.com>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "A high-performance Rust library for parsing Unity project files (.unity, .prefab, .asset)" description = "A high-performance Rust library for parsing Unity project files (.unity, .prefab, .asset)"
repository = "https://github.com/yourusername/cursebreaker-parser-rust" repository = "https://github.com/yourusername/unity-parser-rust"
keywords = ["unity", "parser", "yaml", "gamedev"] keywords = ["unity", "parser", "yaml", "gamedev"]
categories = ["parser-implementations", "game-development"] categories = ["parser-implementations", "game-development"]
rust-version = "1.70" rust-version = "1.70"
[lib] [lib]
name = "cursebreaker_parser" name = "unity_parser"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
@@ -47,7 +47,7 @@ once_cell = "1.19"
inventory = "0.3" inventory = "0.3"
# Procedural macro for derive(UnityComponent) # Procedural macro for derive(UnityComponent)
cursebreaker-parser-macros = { path = "../cursebreaker-parser-macros" } unity-parser-macros = { path = "../unity-parser-macros" }
[dev-dependencies] [dev-dependencies]
# Testing utilities # Testing utilities

View File

@@ -1,4 +1,4 @@
use cursebreaker_parser::UnityFile; use unity_parser::UnityFile;
use std::path::Path; use std::path::Path;
fn main() { fn main() {

View File

@@ -1,7 +1,7 @@
//! Example demonstrating how to define custom Unity MonoBehaviour components //! Example demonstrating how to define custom Unity MonoBehaviour components
//! using the #[derive(UnityComponent)] macro. //! using the #[derive(UnityComponent)] macro.
use cursebreaker_parser::{yaml_helpers, ComponentContext, UnityComponent}; use unity_parser::{yaml_helpers, ComponentContext, UnityComponent};
/// Custom Unity MonoBehaviour component for playing sound effects /// Custom Unity MonoBehaviour component for playing sound effects
/// ///
@@ -71,7 +71,7 @@ isLoop: 1
let mapping = yaml.as_mapping().unwrap(); let mapping = yaml.as_mapping().unwrap();
// Create a dummy context // Create a dummy context
use cursebreaker_parser::{ComponentContext, FileID}; use unity_parser::{ComponentContext, FileID};
let ctx = ComponentContext { let ctx = ComponentContext {
type_id: 114, type_id: 114,
file_id: FileID::from_i64(12345), file_id: FileID::from_i64(12345),

View File

@@ -5,7 +5,7 @@
//! 2. Using the parse_with_types! macro for selective parsing //! 2. Using the parse_with_types! macro for selective parsing
//! 3. Querying the ECS world for components //! 3. Querying the ECS world for components
use cursebreaker_parser::{parse_with_types, ComponentContext, EcsInsertable, FileID, TypeFilter, UnityComponent}; use unity_parser::{parse_with_types, ComponentContext, EcsInsertable, FileID, TypeFilter, UnityComponent};
/// Custom Unity MonoBehaviour component /// Custom Unity MonoBehaviour component
#[derive(Debug, Clone, UnityComponent)] #[derive(Debug, Clone, UnityComponent)]

View File

@@ -6,7 +6,7 @@
//! 3. Querying the ECS world for components //! 3. Querying the ECS world for components
//! 4. Accessing Transform data for component locations //! 4. Accessing Transform data for component locations
use cursebreaker_parser::{UnityComponent, UnityFile}; use unity_parser::{UnityComponent, UnityFile};
use std::path::Path; use std::path::Path;
/// PlaySFX component from VR_Horror_YouCantRun /// PlaySFX component from VR_Horror_YouCantRun
@@ -71,9 +71,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(UnityFile::Scene(scene)) => { Ok(UnityFile::Scene(scene)) => {
// Get views for all component types we need // Get views for all component types we need
let playsfx_view = scene.world.borrow::<PlaySFX>(); let playsfx_view = scene.world.borrow::<PlaySFX>();
let transform_view = scene.world.borrow::<cursebreaker_parser::Transform>(); let transform_view = scene.world.borrow::<unity_parser::Transform>();
let rect_transform_view = scene.world.borrow::<cursebreaker_parser::RectTransform>(); let rect_transform_view = scene.world.borrow::<unity_parser::RectTransform>();
let gameobject_view = scene.world.borrow::<cursebreaker_parser::GameObject>(); let gameobject_view = scene.world.borrow::<unity_parser::GameObject>();
// Find all entities that have PlaySFX // Find all entities that have PlaySFX
let mut found_count = 0; let mut found_count = 0;

View File

@@ -10,7 +10,7 @@
//! doesn't yet support resolving components from nested prefabs. This example //! doesn't yet support resolving components from nested prefabs. This example
//! parses the prefab files directly instead. //! parses the prefab files directly instead.
use cursebreaker_parser::{GuidResolver, UnityComponent, UnityFile}; use unity_parser::{GuidResolver, UnityComponent, UnityFile};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;

View File

@@ -6,7 +6,7 @@
//! 3. Extracting typeId and transform positions //! 3. Extracting typeId and transform positions
//! 4. Writing resource data to an output file //! 4. Writing resource data to an output file
use cursebreaker_parser::{UnityComponent, UnityFile}; use unity_parser::{UnityComponent, UnityFile};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
@@ -58,8 +58,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get views for component types we need // Get views for component types we need
let resource_view = scene.world.borrow::<InteractableResource>(); let resource_view = scene.world.borrow::<InteractableResource>();
let transform_view = scene.world.borrow::<cursebreaker_parser::Transform>(); let transform_view = scene.world.borrow::<unity_parser::Transform>();
let gameobject_view = scene.world.borrow::<cursebreaker_parser::GameObject>(); let gameobject_view = scene.world.borrow::<unity_parser::GameObject>();
// Find all entities that have Interactable_Resource // Find all entities that have Interactable_Resource
let mut found_resources = Vec::new(); let mut found_resources = Vec::new();

View File

@@ -6,7 +6,7 @@
//! # Example //! # Example
//! //!
//! ```no_run //! ```no_run
//! use cursebreaker_parser::UnityFile; //! use unity_parser::UnityFile;
//! //!
//! let file = UnityFile::from_path("Scene.unity")?; //! let file = UnityFile::from_path("Scene.unity")?;
//! match file { //! match file {
@@ -21,7 +21,7 @@
//! println!("Asset with {} documents", asset.documents.len()); //! println!("Asset with {} documents", asset.documents.len());
//! } //! }
//! } //! }
//! # Ok::<(), cursebreaker_parser::Error>(()) //! # Ok::<(), unity_parser::Error>(())
//! ``` //! ```
// Public modules // Public modules
@@ -53,4 +53,4 @@ pub use types::{
}; };
// Re-export the derive macro from the macro crate // Re-export the derive macro from the macro crate
pub use cursebreaker_parser_macros::UnityComponent; pub use unity_parser_macros::UnityComponent;

View File

@@ -5,28 +5,25 @@
//! wrappers for GameObjects and Components. //! wrappers for GameObjects and Components.
mod component; mod component;
mod game_object;
mod guid; mod guid;
mod ids; mod ids;
mod prefab_instance;
mod reference; mod reference;
mod transform;
mod type_filter; mod type_filter;
mod type_registry; mod type_registry;
mod unity_types;
mod values; mod values;
pub use component::{ pub use component::{
yaml_helpers, ComponentContext, ComponentRegistration, EcsInsertable, LinkCallback, yaml_helpers, ComponentContext, ComponentRegistration, EcsInsertable, LinkCallback,
LinkingContext, UnityComponent, LinkingContext, UnityComponent,
}; };
pub use game_object::GameObject;
pub use guid::Guid; pub use guid::Guid;
pub use ids::{FileID, LocalID}; pub use ids::{FileID, LocalID};
pub use prefab_instance::{
PrefabInstance, PrefabInstanceComponent, PrefabModification, PrefabResolver,
};
pub use reference::UnityReference; pub use reference::UnityReference;
pub use transform::{RectTransform, Transform};
pub use type_filter::TypeFilter; pub use type_filter::TypeFilter;
pub use type_registry::{get_class_name, get_type_id}; pub use type_registry::{get_class_name, get_type_id};
pub use unity_types::{
GameObject, PrefabInstance, PrefabInstanceComponent, PrefabModification, PrefabResolver,
RectTransform, Transform,
};
pub use values::{Color, ExternalRef, FileRef, Quaternion, Vector2, Vector3}; pub use values::{Color, ExternalRef, FileRef, Quaternion, Vector2, Vector3};

View File

@@ -0,0 +1,11 @@
//! Unity-specific types (GameObjects, Transforms, PrefabInstances)
pub mod game_object;
pub mod prefab_instance;
pub mod transform;
pub use game_object::GameObject;
pub use prefab_instance::{
PrefabInstance, PrefabInstanceComponent, PrefabModification, PrefabResolver,
};
pub use transform::{RectTransform, Transform};

View File

@@ -1,6 +1,6 @@
//! Integration tests for parsing real Unity projects //! Integration tests for parsing real Unity projects
use cursebreaker_parser::{GuidResolver, UnityFile}; use unity_parser::{GuidResolver, UnityFile};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::time::Instant; use std::time::Instant;
@@ -449,7 +449,7 @@ fn test_guid_resolution() {
} }
// Test resolution by Guid type // Test resolution by Guid type
use cursebreaker_parser::Guid; use unity_parser::Guid;
let playsfx_guid = Guid::from_hex(playsfx_guid_str).unwrap(); let playsfx_guid = Guid::from_hex(playsfx_guid_str).unwrap();
match resolver.resolve_class_name(&playsfx_guid) { match resolver.resolve_class_name(&playsfx_guid) {
Some(class_name) => { Some(class_name) => {
@@ -478,7 +478,7 @@ fn test_guid_resolution() {
/// Test parsing PlaySFX components from actual scene file /// Test parsing PlaySFX components from actual scene file
#[test] #[test]
fn test_playsfx_parsing() { fn test_playsfx_parsing() {
use cursebreaker_parser::UnityComponent; use unity_parser::UnityComponent;
/// PlaySFX component from VR_Horror_YouCantRun /// PlaySFX component from VR_Horror_YouCantRun
#[derive(Debug, Clone, UnityComponent)] #[derive(Debug, Clone, UnityComponent)]
@@ -520,8 +520,8 @@ fn test_playsfx_parsing() {
println!("\n Parsing scene: {}", scene_path.display()); println!("\n Parsing scene: {}", scene_path.display());
match cursebreaker_parser::UnityFile::from_path(&scene_path) { match unity_parser::UnityFile::from_path(&scene_path) {
Ok(cursebreaker_parser::UnityFile::Scene(scene)) => { Ok(unity_parser::UnityFile::Scene(scene)) => {
println!(" ✓ Scene parsed successfully"); println!(" ✓ Scene parsed successfully");
println!(" - Total entities: {}", scene.entity_map.len()); println!(" - Total entities: {}", scene.entity_map.len());

View File

@@ -1,6 +1,6 @@
//! Tests for the #[derive(UnityComponent)] macro //! Tests for the #[derive(UnityComponent)] macro
use cursebreaker_parser::{ComponentContext, FileID, UnityComponent}; use unity_parser::{ComponentContext, FileID, UnityComponent};
/// Test component matching the PlaySFX script from VR_Horror_YouCantRun /// Test component matching the PlaySFX script from VR_Horror_YouCantRun
#[derive(Debug, Clone, UnityComponent)] #[derive(Debug, Clone, UnityComponent)]
@@ -133,7 +133,7 @@ fn test_component_registration() {
let mut found_play_sfx = false; let mut found_play_sfx = false;
let mut found_test_component = false; let mut found_test_component = false;
for reg in inventory::iter::<cursebreaker_parser::ComponentRegistration> { for reg in inventory::iter::<unity_parser::ComponentRegistration> {
if reg.class_name == "PlaySFX" { if reg.class_name == "PlaySFX" {
found_play_sfx = true; found_play_sfx = true;
assert_eq!(reg.type_id, 114); assert_eq!(reg.type_id, 114);
@@ -177,7 +177,7 @@ isLoop: 0
}; };
// Find the PlaySFX registration and call its parser // Find the PlaySFX registration and call its parser
for reg in inventory::iter::<cursebreaker_parser::ComponentRegistration> { for reg in inventory::iter::<unity_parser::ComponentRegistration> {
if reg.class_name == "PlaySFX" { if reg.class_name == "PlaySFX" {
let result = (reg.parser)(mapping, &ctx); let result = (reg.parser)(mapping, &ctx);
assert!(result.is_some(), "Registered parser failed to parse"); assert!(result.is_some(), "Registered parser failed to parse");