378 lines
12 KiB
Rust
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)
|
|
}
|
|
}
|
|
}
|