project load first
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user