# Prefab Instantiation Deep Dive This document explains how prefabs and nested prefabs are instantiated in the Unity Parser. ## Table of Contents 1. [Core Concepts](#core-concepts) 2. [Prefab Representation](#prefab-representation) 3. [Simple Prefab Instantiation](#simple-prefab-instantiation) 4. [Nested Prefab System](#nested-prefab-system) 5. [The 4-Pass ECS Building Process](#the-4-pass-ecs-building-process) 6. [FileID Remapping](#fileid-remapping) 7. [Code Examples](#code-examples) --- ## Core Concepts ### What is a Prefab? In Unity, a **prefab** is a reusable template that can be instantiated multiple times in scenes. Think of it like a blueprint: - A prefab file (`.prefab`) contains GameObjects and Components as YAML - When placed in a scene, Unity creates an **instance** of that prefab - Each instance can have **modifications** (overrides) applied to it ### What is a Nested Prefab? A **nested prefab** is a prefab that contains instances of other prefabs within it. For example: - `Player.prefab` might contain a nested `Weapon.prefab` - When you instantiate `Player.prefab`, it must also instantiate `Weapon.prefab` - This can go multiple levels deep ### Key Design Decision: Why Raw YAML? Prefabs are stored as **raw YAML documents** (`UnityPrefab`) rather than fully-parsed ECS worlds (`UnityScene`). This is because: 1. **Efficient Cloning**: Prefabs need to be instantiated multiple times with different values 2. **YAML Overrides**: Unity stores modifications as YAML property path overrides (e.g., `m_LocalPosition.x = 100`) 3. **FileID Remapping**: Each instance needs unique FileIDs to avoid collisions Scenes, on the other hand, are parsed directly into Sparsey ECS worlds since they don't need cloning. --- ## Prefab Representation ### UnityPrefab Structure From `unity-parser/src/model/mod.rs:93-146`: ```rust pub struct UnityPrefab { /// Path to the prefab file pub path: PathBuf, /// Raw YAML documents that make up this prefab pub documents: Vec, } ``` Each `RawDocument` contains: - `type_id`: Unity type ID (e.g., 1 = GameObject, 4 = Transform) - `file_id`: Unique identifier within the file - `class_name`: The Unity class (e.g., "GameObject", "Transform", "MonoBehaviour") - `yaml`: The raw YAML content as serde_yaml::Value ### Loading a Prefab When you call `UnityFile::from_path("Player.prefab")`, the parser: 1. Reads the file content 2. Validates the Unity YAML header 3. Splits the YAML into separate documents (by `--- !u!N &ID` separators) 4. Creates `RawDocument` objects with metadata extracted 5. Returns `UnityFile::Prefab(UnityPrefab { path, documents })` **Important**: At this stage, NO ECS world is created. The prefab stays as raw YAML. --- ## Simple Prefab Instantiation ### Step-by-Step Process Let's walk through instantiating a simple prefab (no nesting): #### 1. Create a PrefabInstance From `unity-parser/src/types/unity_types/prefab_instance.rs:49-70`: ```rust let prefab = UnityFile::from_path("Player.prefab")?; let mut instance = prefab.instantiate(); ``` `instantiate()` calls `PrefabInstance::new()` which: - Clones all documents from the source prefab - Initializes FileID remapping (creates new unique IDs) - Remaps all FileID references in the YAML #### 2. Apply Overrides (Optional) You can modify the prefab before spawning: ```rust instance.override_value(file_id, "m_Name", "Player1".into())?; instance.override_value(file_id, "m_LocalPosition.x", 100.0.into())?; ``` This stores overrides in a HashMap that will be applied before spawning. #### 3. Spawn into ECS World ```rust let entities = instance.spawn_into(&mut world, &mut entity_map, guid_resolver, prefab_resolver)?; ``` `spawn_into()` (`prefab_instance.rs:291-309`): 1. Applies all stored overrides to the YAML 2. Calls `build_world_from_documents_into()` to create entities 3. Returns a Vec of spawned entities ### The Spawning Process `build_world_from_documents_into()` from `unity-parser/src/ecs/builder.rs:160-265`: **Pass 1**: Create entities for GameObjects - Iterates through documents with `type_id == 1` (GameObject) - Spawns ECS entities with `GameObject` component - Adds to entity_map (FileID → Entity) **Pass 2**: Attach components - Iterates through remaining documents (Transform, RectTransform, MonoBehaviour, etc.) - Looks up `m_GameObject` reference to find owner entity - Parses and attaches component to entity **Pass 3**: Execute deferred linking - Resolves Transform parent/child relationships - Converts FileID references to Entity handles --- ## Nested Prefab System ### How Unity Represents Nested Prefabs When you place a prefab inside another prefab in Unity, it creates a **PrefabInstance** document: ```yaml --- !u!1001 &1234567890 PrefabInstance: m_SourcePrefab: {fileID: 0, guid: "091c537484687e9419460cdcd7038234", type: 3} m_Modification: - target: {fileID: 5678} propertyPath: m_Name value: "ModifiedName" - target: {fileID: 5679} propertyPath: m_LocalPosition.x value: 10.5 ``` ### PrefabInstanceComponent From `unity-parser/src/types/unity_types/prefab_instance.rs:322-366`: ```rust pub struct PrefabInstanceComponent { /// External reference to the source prefab (by GUID) pub prefab_ref: ExternalRef, // Contains GUID string /// Modifications applied to the nested prefab pub modifications: Vec, } pub struct PrefabModification { pub target_file_id: FileID, // Which object to modify pub property_path: String, // Dot notation: "m_Name", "m_LocalPosition.x" pub value: Value, // The new value } ``` ### GUID Resolution Before we can instantiate a nested prefab, we need to resolve its GUID to a file path. **PrefabGuidResolver** (`unity-parser/src/parser/prefab_guid_resolver.rs`): 1. **Initialization**: Scans Unity project directory for `.prefab.meta` files 2. **GUID Extraction**: Parses each `.meta` file to get the GUID 3. **Mapping**: Builds a HashMap: `Guid → PathBuf` Example: ``` Assets/Prefabs/Player.prefab.meta contains: guid: 091c537484687e9419460cdcd7038234 → Maps: 0x091c537484687e9419460cdcd7038234 → "Assets/Prefabs/Player.prefab" ``` ### PrefabResolver **PrefabResolver** (`prefab_instance.rs:430-706`) handles loading and recursive instantiation: ```rust pub struct PrefabResolver<'a> { /// Cache of loaded prefabs (GUID → Prefab) prefab_cache: HashMap>, /// Mapping from GUID to file path guid_to_path: HashMap, /// Stack for circular reference detection instantiation_stack: Vec, /// GUID resolver for MonoBehaviour scripts guid_resolver: Option<&'a GuidResolver>, /// Prefab GUID resolver prefab_guid_resolver: Option<&'a PrefabGuidResolver>, } ``` ### Nested Prefab Instantiation Flow From `prefab_instance.rs:496-572` - `instantiate_from_component()`: ``` PrefabInstanceComponent found in scene ↓ 1. Extract GUID from component.prefab_ref ↓ 2. Load prefab via GUID resolver: GUID → Path → UnityPrefab ↓ 3. Create PrefabInstance (clone + remap FileIDs) ↓ 4. Apply modifications from component.modifications ↓ 5. Spawn prefab into world (creates entities) ↓ 6. Link spawned root to parent entity (if provided) ↓ Returns: Vec of spawned entities ``` ### Recursive Nested Prefabs For deeply nested prefabs (prefabs containing prefabs containing prefabs...): **instantiate_recursive()** (`prefab_instance.rs:574-643`): ``` Start with root prefab ↓ 1. Check for circular references (using instantiation_stack) ↓ 2. Push prefab ID to stack ↓ 3. Create PrefabInstance ↓ 4. Scan documents for nested PrefabInstance components ↓ 5. For each nested prefab: - Load the referenced prefab by GUID - Apply its modifications - Recursively call instantiate_recursive() ↓ 6. Spawn this prefab's entities ↓ 7. Pop from stack ``` This handles arbitrary nesting depth while preventing infinite loops from circular references. --- ## The 4-Pass ECS Building Process When parsing a Unity scene, the ECS builder uses a multi-pass approach to handle prefabs. From `unity-parser/src/ecs/builder.rs:31-138`: ### Pass 1: Create GameObject Entities ```rust for doc in documents.iter().filter(|d| d.type_id == 1 || d.class_name == "GameObject") { let entity = spawn_game_object(&mut world, doc)?; entity_map.insert(doc.file_id, entity); } // Also create entities for PrefabInstances for doc in documents.iter().filter(|d| d.type_id == 1001 || d.class_name == "PrefabInstance") { let entity = world.create(()); entity_map.insert(doc.file_id, entity); // Parse and attach PrefabInstanceComponent if let Some(prefab_comp) = PrefabInstanceComponent::parse(yaml, &ctx) { world.insert(entity, (prefab_comp,)); } } ``` At this stage: - All GameObjects → Entities - All PrefabInstances → Entities with `PrefabInstanceComponent` attached - entity_map tracks FileID → Entity ### Pass 2: Attach Components ```rust for doc in documents.iter().filter(|d| d.type_id != 1 && d.class_name != "GameObject") { attach_component(&mut world, doc, &linking_ctx, &type_filter, guid_resolver)?; } ``` - Parses Transform, RectTransform, MonoBehaviour, etc. - Looks up `m_GameObject` reference to find owner entity - Attaches parsed component to entity ### Pass 2.5: Resolve Prefab Instances (NEW!) This is where the magic happens for nested prefabs (`builder.rs:92-132`): ```rust if let Some(prefab_resolver_ref) = prefab_guid_resolver { let mut prefab_resolver = PrefabResolver::from_resolvers(guid_resolver, prefab_resolver_ref); // Query for entities with PrefabInstanceComponent let prefab_entities: Vec<_> = world.query::<&PrefabInstanceComponent>().collect(); for (entity, component) in prefab_entities { // Instantiate the referenced prefab match prefab_resolver.instantiate_from_component( &component, Some(entity), // Parent entity &mut world, &mut entity_map, ) { Ok(spawned) => { info!("Spawned {} entities from prefab", spawned.len()); } Err(e) => { warn!("Failed to instantiate prefab: {}", e); } } // Remove PrefabInstanceComponent after resolution world.remove::<(PrefabInstanceComponent,)>(entity); } } ``` **Key Points**: 1. Only runs if a PrefabGuidResolver is provided 2. Finds all entities with `PrefabInstanceComponent` 3. For each one: - Resolves GUID → loads prefab - Creates instance with modifications - Spawns into current world - Links to parent entity 4. Removes `PrefabInstanceComponent` (no longer needed) ### Pass 3: Execute Deferred Linking ```rust let entity_map = linking_ctx.execute_callbacks(&mut world); ``` - Resolves Transform parent/child relationships - Converts FileID references to actual Entity handles - This happens AFTER prefab instantiation so that prefab entities are in the map --- ## FileID Remapping ### Why Remap FileIDs? Unity FileIDs are unique within a single file, but when instantiating multiple prefab instances, we need to ensure no collisions: ``` Scene.unity: GameObject &100 ← FileID = 100 Transform &101 ← FileID = 101 Player.prefab (first instance): GameObject &100 ← COLLISION! Transform &200 Player.prefab (second instance): GameObject &100 ← COLLISION! Transform &200 ``` ### The Solution From `prefab_instance.rs:72-114`: **Step 1**: Generate unique IDs for each document ```rust fn generate_file_id(&mut self) -> FileID { let id = self.next_file_id; // Starts at i64::MAX self.next_file_id -= 1; // Decrement FileID::from_i64(id) } ``` - Uses i64::MAX and decrements: `9223372036854775807`, `9223372036854775806`, ... - Scene FileIDs are typically small positive numbers (1, 100, 1000) - This avoids collisions **Step 2**: Build mapping table ```rust fn initialize_file_id_mapping(&mut self) { for original_id in original_ids { let new_id = self.generate_file_id(); self.file_id_map.insert(original_id, new_id); } } ``` Example mapping: ``` Original → New 100 → 9223372036854775807 200 → 9223372036854775806 ``` **Step 3**: Remap all references ```rust fn remap_yaml_file_refs(&mut self) { // Update document's own FileID for doc in &mut self.documents { doc.file_id = self.file_id_map[&doc.file_id]; } // Update all FileRef references in YAML: {fileID: N} for doc in &mut self.documents { Self::remap_value(&mut doc.yaml, &file_id_map); } } ``` This recursively walks the YAML tree and replaces all `{fileID: 100}` with `{fileID: 9223372036854775807}`. ### Handling Overrides When applying overrides before spawning: ```rust instance.override_value(original_file_id, "m_Name", "Player1".into())?; ``` The API accepts the **original** FileID for convenience, but internally: ```rust fn apply_overrides(&mut self) -> Result<()> { for ((file_id, path), value) in &self.overrides { // Map original FileID → remapped FileID let remapped_id = self.file_id_map.get(file_id)?; // Find document with remapped ID let doc = self.documents.iter_mut() .find(|d| d.file_id == *remapped_id)?; // Apply value change set_yaml_value(&mut doc.yaml, path, value)?; } } ``` --- ## Code Examples ### Example 1: Manual Prefab Instantiation ```rust use unity_parser::{UnityFile, UnityPrefab}; use sparsey::World; use std::collections::HashMap; fn main() -> Result<(), Box> { // Load prefab let file = UnityFile::from_path("Assets/Prefabs/Player.prefab")?; let prefab = match file { UnityFile::Prefab(p) => p, _ => panic!("Expected prefab"), }; // Create instance with modifications let mut instance = prefab.instantiate(); instance.override_value(file_id, "m_Name", "Player1".into())?; instance.override_value(file_id, "m_LocalPosition.x", 100.0.into())?; // Spawn into world let mut world = World::new(); let mut entity_map = HashMap::new(); let entities = instance.spawn_into(&mut world, &mut entity_map, None, None)?; println!("Spawned {} entities", entities.len()); Ok(()) } ``` ### Example 2: Automatic Scene Parsing with Nested Prefabs ```rust use unity_parser::UnityProject; fn main() -> Result<(), Box> { // Initialize project (builds GUID resolvers) let project = UnityProject::from_path("/home/user/UnityProject")?; // Parse scene - automatically resolves and instantiates prefabs let scene = project.parse_scene("Assets/Scenes/Level1.unity")?; println!("Scene has {} entities", scene.entity_map.len()); // Query entities let game_objects = scene.world.borrow::(); let transforms = scene.world.borrow::(); for (file_id, entity) in &scene.entity_map { if let Some(go) = game_objects.get(*entity) { if let Some(transform) = transforms.get(*entity) { println!("GameObject '{}' at {:?}", go.name(), transform.local_position()); } } } Ok(()) } ``` ### Example 3: Recursive Prefab Loading ```rust use unity_parser::{UnityFile, PrefabResolver, PrefabGuidResolver}; use sparsey::World; fn main() -> Result<(), Box> { // Build prefab GUID resolver let prefab_guid_resolver = PrefabGuidResolver::from_project("UnityProject")?; // Create prefab resolver let mut prefab_resolver = PrefabResolver::from_resolvers(None, &prefab_guid_resolver); // Load a prefab with nested prefabs let file = UnityFile::from_path("Assets/Prefabs/ComplexPrefab.prefab")?; let prefab = match file { UnityFile::Prefab(p) => p, _ => panic!("Expected prefab"), }; // Recursively instantiate (handles nested prefabs automatically) let mut world = World::new(); let mut entity_map = HashMap::new(); let entities = prefab_resolver.instantiate_recursive( &prefab, &mut world, &mut entity_map, )?; println!("Recursively spawned {} entities", entities.len()); Ok(()) } ``` --- ## Summary ### Prefab Instantiation Flow ``` UnityPrefab (raw YAML) ↓ instantiate() ↓ PrefabInstance (cloned YAML with remapped FileIDs) ↓ override_value() (optional) ↓ spawn_into() ↓ ECS World (Sparsey entities with components) ``` ### Nested Prefab Resolution Flow ``` Scene contains PrefabInstance document ↓ Pass 1: Create entity with PrefabInstanceComponent ↓ Pass 2.5: Find all PrefabInstanceComponent entities ↓ For each: GUID → Path → Load Prefab ↓ Create instance + apply modifications ↓ Recursively check for nested PrefabInstances ↓ Spawn all entities into world ↓ Link to parent entity ``` ### Key Takeaways 1. **Prefabs stay as YAML** until instantiation for efficient cloning and overrides 2. **FileID remapping** prevents collisions when instantiating multiple times 3. **PrefabGuidResolver** maps GUIDs to file paths for automatic loading 4. **Pass 2.5** in the ECS builder handles automatic prefab instantiation 5. **Recursive instantiation** handles arbitrary nesting depth with circular reference detection 6. **Modifications** are stored as property path + value pairs and applied before spawning ### Files to Explore - `unity-parser/src/types/unity_types/prefab_instance.rs` - PrefabInstance, PrefabResolver - `unity-parser/src/parser/prefab_guid_resolver.rs` - GUID → Path mapping - `unity-parser/src/ecs/builder.rs` - 4-pass ECS building with prefab resolution - `unity-parser/src/model/mod.rs` - UnityPrefab, UnityScene data structures