Files
cursebreaker-parser-rust/unity-parser/src/model/mod.rs
2026-01-11 03:03:39 +00:00

378 lines
12 KiB
Rust

//! Core data model for Unity files
//!
//! This module provides the fundamental types for representing parsed Unity files.
//! 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::{Path, PathBuf};
/// A parsed Unity file - can be a Scene, Prefab, or Asset
#[derive(Debug)]
pub enum UnityFile {
/// Scene file (.unity) with fully-parsed ECS World
Scene(UnityScene),
/// Prefab file (.prefab) with raw YAML for instancing
Prefab(UnityPrefab),
/// Asset file (.asset) with raw YAML
Asset(UnityAsset),
}
impl UnityFile {
/// Parse a Unity file from the given path
pub fn from_path(path: impl Into<PathBuf>) -> crate::Result<Self> {
let path = path.into();
crate::parser::parse_unity_file(&path)
}
/// Get the file path
pub fn path(&self) -> &PathBuf {
match self {
UnityFile::Scene(s) => &s.path,
UnityFile::Prefab(p) => &p.path,
UnityFile::Asset(a) => &a.path,
}
}
/// Check if this is a scene
pub fn is_scene(&self) -> bool {
matches!(self, UnityFile::Scene(_))
}
/// Check if this is a prefab
pub fn is_prefab(&self) -> bool {
matches!(self, UnityFile::Prefab(_))
}
/// Check if this is an asset
pub fn is_asset(&self) -> bool {
matches!(self, UnityFile::Asset(_))
}
}
/// A Unity scene with fully-parsed ECS World
///
/// Scenes contain GameObjects and Components parsed into a Sparsey ECS world.
/// The entity_map provides mapping from Unity FileIDs to ECS entities.
#[derive(Debug)]
pub struct UnityScene {
/// Path to the scene file
pub path: PathBuf,
/// ECS World containing all entities and components
pub world: World,
/// Mapping from Unity FileID to ECS Entity
pub entity_map: HashMap<FileID, Entity>,
}
impl UnityScene {
/// Create a new UnityScene
pub fn new(path: PathBuf, world: World, entity_map: HashMap<FileID, Entity>) -> Self {
Self {
path,
world,
entity_map,
}
}
/// Get an entity by its Unity FileID
pub fn get_entity(&self, file_id: FileID) -> Option<Entity> {
self.entity_map.get(&file_id).copied()
}
}
/// A Unity prefab with raw YAML for instancing
///
/// Prefabs are kept as raw YAML documents to enable efficient cloning and
/// value overriding during instantiation into scenes.
#[derive(Debug, Clone)]
pub struct UnityPrefab {
/// Path to the prefab file
pub path: PathBuf,
/// Raw YAML documents that make up this prefab
pub documents: Vec<RawDocument>,
}
impl UnityPrefab {
/// Create a new UnityPrefab
pub fn new(path: PathBuf, documents: Vec<RawDocument>) -> Self {
Self { path, documents }
}
/// Get a document by its FileID
pub fn get_document(&self, file_id: FileID) -> Option<&RawDocument> {
self.documents.iter().find(|doc| doc.file_id == file_id)
}
/// Get all documents of a specific type
pub fn get_documents_by_type(&self, type_id: u32) -> Vec<&RawDocument> {
self.documents
.iter()
.filter(|doc| doc.type_id == type_id)
.collect()
}
/// Get all documents with a specific class name
pub fn get_documents_by_class(&self, class_name: &str) -> Vec<&RawDocument> {
self.documents
.iter()
.filter(|doc| doc.class_name == class_name)
.collect()
}
/// Create a new instance of this prefab for spawning into a scene
///
/// This clones the prefab's documents and prepares them for instantiation
/// with unique FileIDs to avoid collisions.
///
/// # Returns
/// A `PrefabInstance` that can be customized with overrides and spawned
///
/// # Example
/// ```ignore
/// let mut instance = prefab.instantiate(None);
/// instance.override_value(file_id, "m_Name", "Player1".into())?;
/// let entities = instance.spawn_into(&mut world, &mut entity_map)?;
/// ```
pub fn instantiate(&self, file_id_counter: Option<std::sync::Arc<std::cell::Cell<i64>>>) -> crate::types::PrefabInstance {
crate::types::PrefabInstance::new(self, file_id_counter)
}
}
/// A Unity asset file with raw YAML
///
/// Assets (like ScriptableObjects) are handled similarly to prefabs.
#[derive(Debug, Clone)]
pub struct UnityAsset {
/// Path to the asset file
pub path: PathBuf,
/// Raw YAML documents that make up this asset
pub documents: Vec<RawDocument>,
}
impl UnityAsset {
/// Create a new UnityAsset
pub fn new(path: PathBuf, documents: Vec<RawDocument>) -> Self {
Self { path, documents }
}
/// Get a document by its FileID
pub fn get_document(&self, file_id: FileID) -> Option<&RawDocument> {
self.documents.iter().find(|doc| doc.file_id == file_id)
}
}
/// A raw YAML document with Unity metadata
///
/// This represents a single Unity object (GameObject, Component, etc.) with
/// its metadata (type_id, file_id, class_name) and raw YAML content.
///
/// The `yaml` field contains the inner YAML mapping (the contents after the
/// class name wrapper like `GameObject: { ... }`).
#[derive(Debug, Clone)]
pub struct RawDocument {
/// Unity type ID (from !u!N tag)
pub type_id: u32,
/// File ID (from &ID anchor)
pub file_id: FileID,
/// Class name (e.g., "GameObject", "Transform", "RectTransform")
pub class_name: String,
/// Raw YAML value (inner mapping after class wrapper)
pub yaml: serde_yaml::Value,
/// Whether this component is stripped (exists in prefab, not in scene)
pub is_stripped: bool,
}
impl RawDocument {
/// Create a new RawDocument
pub fn new(
type_id: u32,
file_id: FileID,
class_name: String,
yaml: serde_yaml::Value,
is_stripped: bool,
) -> Self {
Self {
type_id,
file_id,
class_name,
yaml,
is_stripped,
}
}
/// Get the YAML as a mapping, if it is one
pub fn as_mapping(&self) -> Option<&serde_yaml::Mapping> {
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 scene using the pre-built GUID resolvers with optional type filtering
///
/// # Arguments
///
/// * `path` - Path to the scene file (relative to project root or absolute)
/// * `type_filter` - Optional filter for Unity types and MonoBehaviour class names
///
/// # Example
///
/// ```no_run
/// # use unity_parser::{UnityProject, TypeFilter};
/// # let project = UnityProject::from_path("/home/user/repos/CBAssets")?;
/// let filter = TypeFilter::new(
/// vec!["GameObject", "Transform"],
/// vec!["InteractableResource"]
/// );
/// let scene = project.parse_scene_filtered("_GameAssets/Scenes/Tiles/10_3.unity", Some(&filter))?;
/// println!("Scene has {} entities", scene.entity_map.len());
/// # Ok::<(), unity_parser::Error>(())
/// ```
pub fn parse_scene_filtered(
&self,
path: impl AsRef<Path>,
type_filter: Option<&crate::types::TypeFilter>
) -> crate::Result<UnityScene> {
let path = self.resolve_path(path.as_ref());
crate::parser::parse_scene_with_project_filtered(&path, self, type_filter)
}
/// 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)
}
}
}