meta files phase 3

This commit is contained in:
2026-01-03 04:09:19 +00:00
parent 673fa114fa
commit 6f40cb9177
5 changed files with 155 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
//! ECS world building from Unity documents
use crate::model::RawDocument;
use crate::parser::GuidResolver;
use crate::types::{
yaml_helpers, ComponentContext, FileID, GameObject, LinkingContext, PrefabInstanceComponent,
RectTransform, Transform, TypeFilter, UnityComponent,
@@ -19,11 +20,13 @@ use std::collections::HashMap;
///
/// # Arguments
/// - `documents`: Parsed Unity documents to build the world from
/// - `guid_resolver`: Optional GUID resolver for resolving MonoBehaviour scripts to class names
///
/// # Returns
/// A tuple of (World, FileID → Entity mapping)
pub fn build_world_from_documents(
documents: Vec<RawDocument>,
guid_resolver: Option<&GuidResolver>,
) -> Result<(World, HashMap<FileID, Entity>)> {
// Create World builder with registered component types
let mut builder = World::builder();
@@ -51,7 +54,7 @@ pub fn build_world_from_documents(
// PASS 2: Attach components to entities
let type_filter = TypeFilter::parse_all();
for doc in documents.iter().filter(|d| d.type_id != 1 && d.class_name != "GameObject") {
attach_component(&mut world, doc, &linking_ctx, &type_filter)?;
attach_component(&mut world, doc, &linking_ctx, &type_filter, guid_resolver)?;
}
// PASS 3: Execute all deferred linking callbacks
@@ -100,7 +103,8 @@ pub fn build_world_from_documents_into(
// PASS 2: Attach components to entities
let type_filter = TypeFilter::parse_all();
for doc in documents.iter().filter(|d| d.type_id != 1 && d.class_name != "GameObject") {
attach_component(world, doc, &linking_ctx, &type_filter)?;
// Note: Prefab instantiation doesn't need GUID resolution (uses base prefab's components)
attach_component(world, doc, &linking_ctx, &type_filter, None)?;
}
// PASS 3: Execute all deferred linking callbacks
@@ -125,6 +129,7 @@ fn spawn_game_object(world: &mut World, doc: &RawDocument) -> Result<Entity> {
entity: None,
linking_ctx: None,
yaml,
guid_resolver: None,
};
let go = GameObject::parse(yaml, &ctx)
@@ -142,6 +147,7 @@ fn attach_component(
doc: &RawDocument,
linking_ctx: &RefCell<LinkingContext>,
type_filter: &TypeFilter,
guid_resolver: Option<&GuidResolver>,
) -> Result<()> {
let yaml = doc
.as_mapping()
@@ -176,6 +182,7 @@ fn attach_component(
entity: Some(entity),
linking_ctx: Some(linking_ctx),
yaml,
guid_resolver,
};
// Check type filter to see if we should parse this component
@@ -209,6 +216,49 @@ fn attach_component(
linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity);
}
}
"MonoBehaviour" => {
// Extract m_Script GUID to resolve the actual class name
if let Some(resolver) = guid_resolver {
if let Some(script_ref) = yaml_helpers::get_external_ref(yaml, "m_Script") {
// Resolve GUID to class name
if let Some(class_name) = resolver.resolve_class_name(script_ref.guid.as_str()) {
// Try to find a registered custom component with this class name
let mut found_custom = false;
for reg in inventory::iter::<crate::types::ComponentRegistration> {
if reg.class_name == class_name {
found_custom = true;
// Parse and insert the component into the ECS world
if (reg.parse_and_insert)(yaml, &ctx, world, entity) {
// Successfully parsed and inserted
linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity);
}
break;
}
}
if !found_custom {
// GUID resolved but no registered component found
eprintln!(
"Warning: Skipping MonoBehaviour '{}' (no registered parser)",
class_name
);
}
} else {
// GUID not found in resolver
eprintln!(
"Warning: Could not resolve MonoBehaviour GUID: {}",
script_ref.guid
);
}
} else {
// No m_Script reference found
eprintln!("Warning: MonoBehaviour missing m_Script reference");
}
} else {
// No GUID resolver available
eprintln!("Warning: Skipping MonoBehaviour (no GUID resolver available)");
}
}
_ => {
// Check if this is a registered custom component
let mut found_custom = false;

View File

@@ -81,8 +81,29 @@ fn detect_file_type(path: &Path) -> FileType {
fn parse_scene(path: &Path, content: &str) -> Result<UnityFile> {
let raw_documents = parse_raw_documents(content)?;
// Try to find Unity project root and build GUID resolver
let guid_resolver = match find_project_root(path) {
Ok(project_root) => {
// Build GUID resolver from project
match GuidResolver::from_project(&project_root) {
Ok(resolver) => Some(resolver),
Err(e) => {
eprintln!("Warning: Failed to build GUID resolver: {}", e);
None
}
}
}
Err(_) => {
// Not part of a Unity project, or project root not found
None
}
};
// Build ECS world from documents
let (world, entity_map) = crate::ecs::build_world_from_documents(raw_documents)?;
let (world, entity_map) = crate::ecs::build_world_from_documents(
raw_documents,
guid_resolver.as_ref(),
)?;
Ok(UnityFile::Scene(UnityScene::new(
path.to_path_buf(),

View File

@@ -70,6 +70,8 @@ pub struct ComponentContext<'a> {
pub linking_ctx: Option<&'a RefCell<LinkingContext>>,
/// The raw YAML mapping for this component (for extracting FileRefs)
pub yaml: &'a Mapping,
/// GUID resolver for resolving MonoBehaviour script GUIDs to class names
pub guid_resolver: Option<&'a crate::parser::GuidResolver>,
}
/// Trait for Unity components that can be parsed from YAML

View File

@@ -524,6 +524,7 @@ impl PrefabResolver {
entity: None,
linking_ctx: None,
yaml: mapping,
guid_resolver: None,
};
if let Some(component) = PrefabInstanceComponent::parse(mapping, &ctx) {

View File

@@ -474,3 +474,81 @@ fn test_guid_resolution() {
println!("\n{}", "=".repeat(60));
}
/// Test parsing PlaySFX components from actual scene file
#[test]
fn test_playsfx_parsing() {
use cursebreaker_parser::UnityComponent;
/// PlaySFX component from VR_Horror_YouCantRun
#[derive(Debug, Clone, UnityComponent)]
#[unity_class("PlaySFX")]
pub struct PlaySFX {
#[unity_field("volume")]
pub volume: f64,
#[unity_field("startTime")]
pub start_time: f64,
#[unity_field("endTime")]
pub end_time: f64,
#[unity_field("isLoop")]
pub is_loop: bool,
}
let project_path = match clone_test_project(&TestProject::VR_HORROR) {
Ok(path) => path,
Err(e) => {
eprintln!("Failed to clone project: {}", e);
eprintln!("Skipping PlaySFX parsing test (git may not be available)");
return;
}
};
println!("\n{}", "=".repeat(60));
println!("Testing PlaySFX Component Parsing");
println!("{}", "=".repeat(60));
// Parse the 1F.unity scene that contains PlaySFX components
let scene_path = project_path.join("Assets/Scenes/TEST/Final_1F/1F.unity");
if !scene_path.exists() {
eprintln!("Scene file not found: {}", scene_path.display());
return;
}
println!("\n Parsing scene: {}", scene_path.display());
match cursebreaker_parser::UnityFile::from_path(&scene_path) {
Ok(cursebreaker_parser::UnityFile::Scene(scene)) => {
println!(" ✓ Scene parsed successfully");
println!(" - Total entities: {}", scene.entity_map.len());
// Try to get PlaySFX components
let playsfx_view = scene.world.borrow::<PlaySFX>();
let mut found_count = 0;
for entity in scene.entity_map.values() {
if playsfx_view.get(*entity).is_some() {
found_count += 1;
}
}
println!(" ✓ Found {} PlaySFX component(s)", found_count);
assert!(
found_count > 0,
"Should find at least one PlaySFX component in 1F.unity"
);
}
Ok(_) => {
panic!("File was not parsed as a scene");
}
Err(e) => {
panic!("Failed to parse scene: {}", e);
}
}
println!("\n{}", "=".repeat(60));
}