stripped components
This commit is contained in:
@@ -6,7 +6,7 @@ use crate::model::RawDocument;
|
||||
use crate::parser::{GuidResolver, PrefabGuidResolver};
|
||||
use crate::types::{
|
||||
yaml_helpers, ComponentContext, FileID, GameObject, LinkingContext, PrefabInstanceComponent,
|
||||
PrefabResolver, RectTransform, Transform, TypeFilter, UnityComponent,
|
||||
PrefabResolver, RectTransform, StrippedReference, Transform, TypeFilter, UnityComponent,
|
||||
};
|
||||
use crate::{Error, Result};
|
||||
use sparsey::{Entity, World};
|
||||
@@ -39,7 +39,8 @@ pub fn build_world_from_documents(
|
||||
.register::<GameObject>()
|
||||
.register::<Transform>()
|
||||
.register::<RectTransform>()
|
||||
.register::<PrefabInstanceComponent>();
|
||||
.register::<PrefabInstanceComponent>()
|
||||
.register::<StrippedReference>();
|
||||
|
||||
// Register all custom components from inventory
|
||||
for reg in inventory::iter::<crate::types::ComponentRegistration> {
|
||||
@@ -80,9 +81,35 @@ pub fn build_world_from_documents(
|
||||
}
|
||||
}
|
||||
|
||||
// PASS 1.5: Handle stripped components
|
||||
// Stripped components are references to prefab components that don't have full data in the scene
|
||||
for doc in documents.iter().filter(|d| d.is_stripped) {
|
||||
// Create an entity for this stripped reference
|
||||
let entity = world.create(());
|
||||
linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity);
|
||||
|
||||
// Parse and attach the StrippedReference component
|
||||
if let Some(yaml) = doc.as_mapping() {
|
||||
let ctx = ComponentContext {
|
||||
type_id: doc.type_id,
|
||||
file_id: doc.file_id,
|
||||
class_name: &doc.class_name,
|
||||
entity: Some(entity),
|
||||
linking_ctx: Some(&linking_ctx),
|
||||
yaml,
|
||||
guid_resolver,
|
||||
};
|
||||
|
||||
if let Some(stripped_ref) = StrippedReference::parse(yaml, &ctx) {
|
||||
world.insert(entity, (stripped_ref,));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PASS 2: Attach components to entities
|
||||
let type_filter = TypeFilter::parse_all();
|
||||
for doc in documents.iter().filter(|d| {
|
||||
!d.is_stripped &&
|
||||
d.type_id != 1 && d.class_name != "GameObject" &&
|
||||
d.type_id != 1001 && d.class_name != "PrefabInstance"
|
||||
}) {
|
||||
@@ -204,9 +231,36 @@ pub fn build_world_from_documents_into(
|
||||
}
|
||||
}
|
||||
|
||||
// PASS 1.5: Handle stripped components
|
||||
// Stripped components are references to prefab components that don't have full data in the scene
|
||||
for doc in documents.iter().filter(|d| d.is_stripped) {
|
||||
// Create an entity for this stripped reference
|
||||
let entity = world.create(());
|
||||
linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity);
|
||||
spawned_entities.push(entity);
|
||||
|
||||
// Parse and attach the StrippedReference component
|
||||
if let Some(yaml) = doc.as_mapping() {
|
||||
let ctx = ComponentContext {
|
||||
type_id: doc.type_id,
|
||||
file_id: doc.file_id,
|
||||
class_name: &doc.class_name,
|
||||
entity: Some(entity),
|
||||
linking_ctx: Some(&linking_ctx),
|
||||
yaml,
|
||||
guid_resolver,
|
||||
};
|
||||
|
||||
if let Some(stripped_ref) = StrippedReference::parse(yaml, &ctx) {
|
||||
world.insert(entity, (stripped_ref,));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PASS 2: Attach components to entities
|
||||
let type_filter = TypeFilter::parse_all();
|
||||
for doc in documents.iter().filter(|d| {
|
||||
!d.is_stripped &&
|
||||
d.type_id != 1 && d.class_name != "GameObject" &&
|
||||
d.type_id != 1001 && d.class_name != "PrefabInstance"
|
||||
}) {
|
||||
|
||||
@@ -189,6 +189,9 @@ pub struct RawDocument {
|
||||
|
||||
/// 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 {
|
||||
@@ -198,12 +201,14 @@ impl RawDocument {
|
||||
file_id: FileID,
|
||||
class_name: String,
|
||||
yaml: serde_yaml::Value,
|
||||
is_stripped: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
type_id,
|
||||
file_id,
|
||||
class_name,
|
||||
yaml,
|
||||
is_stripped,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -384,6 +384,7 @@ fn parse_raw_document(raw_doc: &str, type_filter: Option<&TypeFilter>) -> Result
|
||||
FileID::from_i64(tag.file_id),
|
||||
class_name,
|
||||
inner_yaml,
|
||||
tag.is_stripped,
|
||||
)))
|
||||
}
|
||||
|
||||
|
||||
@@ -15,15 +15,19 @@ pub struct UnityTag {
|
||||
|
||||
/// File ID (the number after &)
|
||||
pub file_id: i64,
|
||||
|
||||
/// Whether this component is stripped (exists in prefab, not in scene)
|
||||
pub is_stripped: bool,
|
||||
}
|
||||
|
||||
/// Get the Unity tag regex (compiled once and cached)
|
||||
fn unity_tag_regex() -> &'static Regex {
|
||||
static REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
REGEX.get_or_init(|| {
|
||||
// Matches: --- !u!<type_id> &<file_id>
|
||||
// Matches: --- !u!<type_id> &<file_id> [stripped]
|
||||
// Example: --- !u!1 &1866116814460599870
|
||||
Regex::new(r"^---\s+!u!(\d+)\s+&(-?\d+)").unwrap()
|
||||
// Example: --- !u!4 &104494228 stripped
|
||||
Regex::new(r"^---\s+!u!(\d+)\s+&(-?\d+)(?:\s+(stripped))?").unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,6 +42,7 @@ fn unity_tag_regex() -> &'static Regex {
|
||||
/// let tag = parse_unity_tag(doc).unwrap();
|
||||
/// assert_eq!(tag.type_id, 1);
|
||||
/// assert_eq!(tag.file_id, 12345);
|
||||
/// assert_eq!(tag.is_stripped, false);
|
||||
/// ```
|
||||
pub fn parse_unity_tag(document: &str) -> Option<UnityTag> {
|
||||
let re = unity_tag_regex();
|
||||
@@ -52,7 +57,14 @@ pub fn parse_unity_tag(document: &str) -> Option<UnityTag> {
|
||||
let type_id = captures.get(1)?.as_str().parse::<u32>().ok()?;
|
||||
let file_id = captures.get(2)?.as_str().parse::<i64>().ok()?;
|
||||
|
||||
Some(UnityTag { type_id, file_id })
|
||||
// Check if "stripped" keyword is present
|
||||
let is_stripped = captures.get(3).is_some();
|
||||
|
||||
Some(UnityTag {
|
||||
type_id,
|
||||
file_id,
|
||||
is_stripped,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -65,6 +77,7 @@ mod tests {
|
||||
let tag = parse_unity_tag(doc).unwrap();
|
||||
assert_eq!(tag.type_id, 1);
|
||||
assert_eq!(tag.file_id, 1866116814460599870);
|
||||
assert_eq!(tag.is_stripped, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -73,6 +86,7 @@ mod tests {
|
||||
let tag = parse_unity_tag(doc).unwrap();
|
||||
assert_eq!(tag.type_id, 224);
|
||||
assert_eq!(tag.file_id, 8151827567463220614);
|
||||
assert_eq!(tag.is_stripped, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -81,6 +95,16 @@ mod tests {
|
||||
let tag = parse_unity_tag(doc).unwrap();
|
||||
assert_eq!(tag.type_id, 114);
|
||||
assert_eq!(tag.file_id, -12345);
|
||||
assert_eq!(tag.is_stripped, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_unity_tag_stripped() {
|
||||
let doc = "--- !u!4 &104494228 stripped\nTransform:\n m_CorrespondingSourceObject: {fileID: 1381906096329791709}";
|
||||
let tag = parse_unity_tag(doc).unwrap();
|
||||
assert_eq!(tag.type_id, 4);
|
||||
assert_eq!(tag.file_id, 104494228);
|
||||
assert_eq!(tag.is_stripped, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -24,6 +24,6 @@ pub use type_filter::TypeFilter;
|
||||
pub use type_registry::{get_class_name, get_type_id};
|
||||
pub use unity_types::{
|
||||
BoxCollider, GameObject, MeshFilter, MeshRenderer, PrefabInstance, PrefabInstanceComponent,
|
||||
PrefabModification, PrefabResolver, RectTransform, Renderer, Transform,
|
||||
PrefabModification, PrefabResolver, RectTransform, Renderer, StrippedReference, Transform,
|
||||
};
|
||||
pub use values::{Color, ExternalRef, FileRef, Quaternion, Vector2, Vector3};
|
||||
|
||||
@@ -9,6 +9,7 @@ pub mod mesh_renderer;
|
||||
pub mod prefab_instance;
|
||||
pub mod renderer;
|
||||
pub mod sphere_collider;
|
||||
pub mod stripped_reference;
|
||||
pub mod transform;
|
||||
|
||||
pub use box_collider::BoxCollider;
|
||||
@@ -22,4 +23,5 @@ pub use prefab_instance::{
|
||||
};
|
||||
pub use renderer::Renderer;
|
||||
pub use sphere_collider::SphereCollider;
|
||||
pub use stripped_reference::StrippedReference;
|
||||
pub use transform::{RectTransform, Transform};
|
||||
|
||||
114
unity-parser/src/types/unity_types/stripped_reference.rs
Normal file
114
unity-parser/src/types/unity_types/stripped_reference.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
//! Stripped component reference
|
||||
//!
|
||||
//! Stripped components are references to components that exist in prefabs
|
||||
//! but whose data isn't duplicated in the scene file.
|
||||
|
||||
use crate::types::{yaml_helpers, ComponentContext, FileID, Guid, UnityComponent};
|
||||
use sparsey::Entity;
|
||||
|
||||
/// A stripped component reference
|
||||
///
|
||||
/// Stripped components appear in Unity scenes as lightweight references to
|
||||
/// prefab components. They have the format:
|
||||
/// ```yaml
|
||||
/// --- !u!4 &104494228 stripped
|
||||
/// Transform:
|
||||
/// m_CorrespondingSourceObject: {fileID: 1381906096329791709, guid: e959fe449ad88a946b99ba9e7e3617ef, type: 3}
|
||||
/// m_PrefabInstance: {fileID: 104494225}
|
||||
/// m_PrefabAsset: {fileID: 0}
|
||||
/// ```
|
||||
///
|
||||
/// The actual component data lives in the prefab file referenced by the GUID.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StrippedReference {
|
||||
/// The Unity type name of this component (e.g., "Transform", "GameObject")
|
||||
pub component_type: String,
|
||||
|
||||
/// FileID of the corresponding source object in the prefab
|
||||
pub source_file_id: Option<FileID>,
|
||||
|
||||
/// GUID of the prefab that contains the actual component data
|
||||
pub prefab_guid: Option<Guid>,
|
||||
|
||||
/// FileID of the PrefabInstance this stripped component belongs to
|
||||
pub prefab_instance: Option<FileID>,
|
||||
}
|
||||
|
||||
impl StrippedReference {
|
||||
/// Create a new StrippedReference
|
||||
pub fn new(
|
||||
component_type: String,
|
||||
source_file_id: Option<FileID>,
|
||||
prefab_guid: Option<Guid>,
|
||||
prefab_instance: Option<FileID>,
|
||||
) -> Self {
|
||||
Self {
|
||||
component_type,
|
||||
source_file_id,
|
||||
prefab_guid,
|
||||
prefab_instance,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the component type
|
||||
pub fn component_type(&self) -> &str {
|
||||
&self.component_type
|
||||
}
|
||||
|
||||
/// Get the source FileID in the prefab
|
||||
pub fn source_file_id(&self) -> Option<FileID> {
|
||||
self.source_file_id
|
||||
}
|
||||
|
||||
/// Get the prefab GUID
|
||||
pub fn prefab_guid(&self) -> Option<Guid> {
|
||||
self.prefab_guid
|
||||
}
|
||||
|
||||
/// Get the PrefabInstance FileID
|
||||
pub fn prefab_instance(&self) -> Option<FileID> {
|
||||
self.prefab_instance
|
||||
}
|
||||
}
|
||||
|
||||
impl UnityComponent for StrippedReference {
|
||||
/// Parse a stripped component reference from YAML
|
||||
///
|
||||
/// Extracts the m_CorrespondingSourceObject and m_PrefabInstance references.
|
||||
fn parse(yaml: &serde_yaml::Mapping, ctx: &ComponentContext) -> Option<Self> {
|
||||
use serde_yaml::Value;
|
||||
|
||||
// Extract m_CorrespondingSourceObject: {fileID: ..., guid: ..., type: 3}
|
||||
// This reference has both fileID (source object in prefab) and guid (prefab GUID)
|
||||
let source_obj = yaml
|
||||
.get(&Value::String("m_CorrespondingSourceObject".to_string()))
|
||||
.and_then(|v| v.as_mapping());
|
||||
|
||||
let source_file_id = source_obj
|
||||
.and_then(|obj| obj.get(&Value::String("fileID".to_string())))
|
||||
.and_then(|v| v.as_i64())
|
||||
.map(FileID::from_i64);
|
||||
|
||||
let prefab_guid = source_obj
|
||||
.and_then(|obj| obj.get(&Value::String("guid".to_string())))
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| Guid::from_hex(s).ok());
|
||||
|
||||
// Extract m_PrefabInstance: {fileID: ...}
|
||||
let prefab_instance_ref = yaml_helpers::get_file_ref(yaml, "m_PrefabInstance");
|
||||
let prefab_instance = prefab_instance_ref.map(|r| r.file_id);
|
||||
|
||||
Some(Self {
|
||||
component_type: ctx.class_name.to_string(),
|
||||
source_file_id,
|
||||
prefab_guid,
|
||||
prefab_instance,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::types::EcsInsertable for StrippedReference {
|
||||
fn insert_into_world(self, world: &mut sparsey::World, entity: Entity) {
|
||||
world.insert(entity, (self,));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user