project load first

This commit is contained in:
2026-01-05 08:12:55 +00:00
parent c58488c5fa
commit 1867c5559b
8 changed files with 940 additions and 32 deletions

View File

@@ -38,13 +38,11 @@ pub mod types;
// Re-exports
pub use error::{Error, Result};
pub use model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene};
pub use model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene, UnityProject};
pub use parser::{
find_project_root, meta::MetaFile, parse_unity_file, parse_unity_file_filtered,
GuidResolver, PrefabGuidResolver,
};
// TODO: Re-enable once project module is updated
// pub use project::UnityProject;
pub use property::PropertyValue;
pub use types::{
get_class_name, get_type_id, Color, ComponentContext, ComponentRegistration, EcsInsertable,

View File

@@ -4,10 +4,12 @@
//! Unity files can be Scenes (.unity), Prefabs (.prefab), or Assets (.asset), each
//! with different handling requirements.
use crate::parser::{GuidResolver, PrefabGuidResolver};
use crate::types::FileID;
use log::info;
use sparsey::{Entity, World};
use std::collections::HashMap;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
/// A parsed Unity file - can be a Scene, Prefab, or Asset
#[derive(Debug)]
@@ -210,3 +212,132 @@ impl RawDocument {
self.yaml.as_mapping()
}
}
/// A Unity project with pre-built GUID resolvers for efficient scene parsing
///
/// This struct holds pre-initialized GUID mappings for both MonoBehaviour scripts
/// and prefab files. By building these mappings once at initialization, you can
/// parse multiple scenes efficiently without rescanning the project for each file.
///
/// # Example
///
/// ```no_run
/// use unity_parser::UnityProject;
/// use std::path::Path;
///
/// // Initialize project once - scans entire project for .meta files
/// let project = UnityProject::from_path("/home/user/repos/CBAssets")?;
///
/// // Parse multiple scenes efficiently using pre-built resolvers
/// let scene1 = project.parse_scene("_GameAssets/Scenes/Tiles/10_3.unity")?;
/// let scene2 = project.parse_scene("_GameAssets/Scenes/Tiles/11_5.unity")?;
/// # Ok::<(), unity_parser::Error>(())
/// ```
#[derive(Debug, Clone)]
pub struct UnityProject {
/// Project root path
pub root: PathBuf,
/// Script GUID resolver (MonoBehaviour scripts)
pub guid_resolver: GuidResolver,
/// Prefab GUID resolver (Prefab files)
pub prefab_resolver: PrefabGuidResolver,
}
impl UnityProject {
/// Create a new UnityProject by scanning the entire project directory
///
/// This scans ALL subdirectories under the project root for .meta files
/// to build comprehensive GUID mappings for both scripts and prefabs.
///
/// Unlike the individual resolver constructors which only scan Assets/ or
/// _GameAssets/, this scans the entire project root to find assets in
/// directories like AssetDumpster/, AssetPacks/, etc.
///
/// # Arguments
///
/// * `root` - Path to the Unity project root directory
///
/// # Example
///
/// ```no_run
/// use unity_parser::UnityProject;
///
/// let project = UnityProject::from_path("/home/user/repos/CBAssets")?;
/// println!("Found {} script GUIDs", project.guid_resolver.len());
/// println!("Found {} prefab GUIDs", project.prefab_resolver.len());
/// # Ok::<(), unity_parser::Error>(())
/// ```
pub fn from_path(root: impl Into<PathBuf>) -> crate::Result<Self> {
let root = root.into();
info!("🔍 Initializing Unity project from: {}", root.display());
// Build script GUID resolver by scanning entire project root
let guid_resolver = GuidResolver::from_project_root(&root)?;
info!(" ✓ Script GUID resolver built ({} mappings)", guid_resolver.len());
// Build prefab GUID resolver by scanning entire project root
let prefab_resolver = PrefabGuidResolver::from_project_root(&root)?;
info!(" ✓ Prefab GUID resolver built ({} mappings)", prefab_resolver.len());
Ok(Self {
root,
guid_resolver,
prefab_resolver,
})
}
/// Parse a Unity scene using the pre-built GUID resolvers
///
/// This is more efficient than `UnityFile::from_path` when parsing multiple
/// scenes because the GUID resolvers are already initialized.
///
/// # Arguments
///
/// * `path` - Path to the scene file (relative to project root or absolute)
///
/// # Example
///
/// ```no_run
/// # use unity_parser::UnityProject;
/// # let project = UnityProject::from_path("/home/user/repos/CBAssets")?;
/// let scene = project.parse_scene("_GameAssets/Scenes/Tiles/10_3.unity")?;
/// println!("Scene has {} entities", scene.entity_map.len());
/// # Ok::<(), unity_parser::Error>(())
/// ```
pub fn parse_scene(&self, path: impl AsRef<Path>) -> crate::Result<UnityScene> {
let path = self.resolve_path(path.as_ref());
crate::parser::parse_scene_with_project(&path, self)
}
/// Parse a Unity prefab using the pre-built GUID resolvers
///
/// # Arguments
///
/// * `path` - Path to the prefab file (relative to project root or absolute)
pub fn parse_prefab(&self, path: impl AsRef<Path>) -> crate::Result<UnityPrefab> {
let path = self.resolve_path(path.as_ref());
crate::parser::parse_prefab_with_project(&path, self)
}
/// Parse a Unity asset file using the pre-built GUID resolvers
///
/// # Arguments
///
/// * `path` - Path to the asset file (relative to project root or absolute)
pub fn parse_asset(&self, path: impl AsRef<Path>) -> crate::Result<UnityAsset> {
let path = self.resolve_path(path.as_ref());
crate::parser::parse_asset_with_project(&path, self)
}
/// Resolve a path (if relative, make it relative to project root)
fn resolve_path(&self, path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
self.root.join(path)
}
}
}

View File

@@ -66,6 +66,9 @@ impl GuidResolver {
/// parses them to extract GUIDs, then parses the corresponding
/// C# files to extract class names.
///
/// **Note**: This only scans the Assets/ or _GameAssets/ directory.
/// For scanning the entire project root, use `from_project_root`.
///
/// # Arguments
///
/// * `project_path` - Path to the Unity project root (containing Assets/ folder)
@@ -94,10 +97,49 @@ impl GuidResolver {
)));
};
Self::scan_directory(&assets_dir)
}
/// Build a GuidResolver by scanning the entire project root
///
/// Unlike `from_project` which only scans Assets/ or _GameAssets/,
/// this scans ALL subdirectories to find .cs.meta files. This is useful
/// when assets are spread across multiple directories (e.g., AssetDumpster/,
/// AssetPacks/, etc.).
///
/// # Arguments
///
/// * `project_root` - Path to the Unity project root directory
///
/// # Examples
///
/// ```no_run
/// use unity_parser::parser::GuidResolver;
/// use std::path::Path;
///
/// // Scans entire project root including AssetDumpster/, AssetPacks/, etc.
/// let resolver = GuidResolver::from_project_root(Path::new("/home/user/repos/CBAssets"))?;
/// # Ok::<(), unity_parser::Error>(())
/// ```
pub fn from_project_root(project_root: impl AsRef<Path>) -> Result<Self> {
let project_root = project_root.as_ref();
if !project_root.is_dir() {
return Err(Error::invalid_format(format!(
"Project root is not a directory: {}",
project_root.display()
)));
}
Self::scan_directory(project_root)
}
/// Internal method to scan a directory for .cs.meta files
fn scan_directory(dir: &Path) -> Result<Self> {
let mut resolver = Self::new();
// Walk the Assets directory looking for .cs.meta files
for entry in WalkDir::new(&assets_dir)
// Walk the directory looking for .cs.meta files
for entry in WalkDir::new(dir)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok())

View File

@@ -14,7 +14,7 @@ pub use yaml::split_yaml_documents;
use log::{info, warn};
use crate::model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene};
use crate::model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene, UnityProject};
use crate::types::{FileID, Guid, TypeFilter};
use crate::{Error, Result};
use regex::Regex;
@@ -198,6 +198,83 @@ fn parse_scene(path: &Path, content: &str, type_filter: Option<&TypeFilter>) ->
)))
}
/// Parse a scene file using pre-built GUID resolvers from a UnityProject
///
/// This is more efficient than `parse_scene` when parsing multiple scenes
/// because the GUID resolvers are already initialized.
///
/// # Arguments
///
/// * `path` - Path to the scene file
/// * `project` - Pre-initialized UnityProject with GUID resolvers
pub fn parse_scene_with_project(path: &Path, project: &UnityProject) -> Result<UnityScene> {
// Read the file
let content = std::fs::read_to_string(path)?;
// Validate Unity header
validate_unity_header(&content, path)?;
// Parse raw documents
let raw_documents = parse_raw_documents(&content, None)?;
// Build ECS world from documents using project's resolvers
let (world, entity_map) = crate::ecs::build_world_from_documents(
raw_documents,
Some(&project.guid_resolver),
Some(&project.prefab_resolver),
)?;
Ok(UnityScene::new(
path.to_path_buf(),
world,
entity_map,
))
}
/// Parse a prefab file using pre-built GUID resolvers from a UnityProject
///
/// # Arguments
///
/// * `path` - Path to the prefab file
/// * `project` - Pre-initialized UnityProject with GUID resolvers
pub fn parse_prefab_with_project(path: &Path, _project: &UnityProject) -> Result<UnityPrefab> {
// Read the file
let content = std::fs::read_to_string(path)?;
// Validate Unity header
validate_unity_header(&content, path)?;
// Parse raw documents
let raw_documents = parse_raw_documents(&content, None)?;
Ok(UnityPrefab::new(
path.to_path_buf(),
raw_documents,
))
}
/// Parse an asset file using pre-built GUID resolvers from a UnityProject
///
/// # Arguments
///
/// * `path` - Path to the asset file
/// * `project` - Pre-initialized UnityProject with GUID resolvers
pub fn parse_asset_with_project(path: &Path, _project: &UnityProject) -> Result<UnityAsset> {
// Read the file
let content = std::fs::read_to_string(path)?;
// Validate Unity header
validate_unity_header(&content, path)?;
// Parse raw documents
let raw_documents = parse_raw_documents(&content, None)?;
Ok(UnityAsset::new(
path.to_path_buf(),
raw_documents,
))
}
/// Parse a prefab file into raw YAML documents
fn parse_prefab(path: &Path, content: &str, type_filter: Option<&TypeFilter>) -> Result<UnityFile> {
let raw_documents = parse_raw_documents(content, type_filter)?;

View File

@@ -62,6 +62,9 @@ impl PrefabGuidResolver {
/// This scans for all `.prefab.meta` files and extracts their GUIDs
/// to build a GUID → Prefab Path mapping.
///
/// **Note**: This only scans the Assets/ or _GameAssets/ directory.
/// For scanning the entire project root, use `from_project_root`.
///
/// # Arguments
///
/// * `project_path` - Path to the Unity project root (containing Assets/ folder)
@@ -90,10 +93,49 @@ impl PrefabGuidResolver {
)));
};
Self::scan_directory(&assets_dir)
}
/// Build a PrefabGuidResolver by scanning the entire project root
///
/// Unlike `from_project` which only scans Assets/ or _GameAssets/,
/// this scans ALL subdirectories to find .prefab.meta files. This is useful
/// when prefabs are spread across multiple directories (e.g., AssetDumpster/,
/// AssetPacks/, etc.).
///
/// # Arguments
///
/// * `project_root` - Path to the Unity project root directory
///
/// # Examples
///
/// ```no_run
/// use unity_parser::parser::PrefabGuidResolver;
/// use std::path::Path;
///
/// // Scans entire project root including AssetDumpster/, AssetPacks/, etc.
/// let resolver = PrefabGuidResolver::from_project_root(Path::new("/home/user/repos/CBAssets"))?;
/// # Ok::<(), unity_parser::Error>(())
/// ```
pub fn from_project_root(project_root: impl AsRef<Path>) -> Result<Self> {
let project_root = project_root.as_ref();
if !project_root.is_dir() {
return Err(Error::invalid_format(format!(
"Project root is not a directory: {}",
project_root.display()
)));
}
Self::scan_directory(project_root)
}
/// Internal method to scan a directory for .prefab.meta files
fn scan_directory(dir: &Path) -> Result<Self> {
let mut resolver = Self::new();
// Walk the Assets directory looking for .prefab.meta files
for entry in WalkDir::new(&assets_dir)
// Walk the directory looking for .prefab.meta files
for entry in WalkDir::new(dir)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok())