stripped components
This commit is contained in:
@@ -6,7 +6,7 @@ use crate::model::RawDocument;
|
|||||||
use crate::parser::{GuidResolver, PrefabGuidResolver};
|
use crate::parser::{GuidResolver, PrefabGuidResolver};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
yaml_helpers, ComponentContext, FileID, GameObject, LinkingContext, PrefabInstanceComponent,
|
yaml_helpers, ComponentContext, FileID, GameObject, LinkingContext, PrefabInstanceComponent,
|
||||||
PrefabResolver, RectTransform, Transform, TypeFilter, UnityComponent,
|
PrefabResolver, RectTransform, StrippedReference, Transform, TypeFilter, UnityComponent,
|
||||||
};
|
};
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use sparsey::{Entity, World};
|
use sparsey::{Entity, World};
|
||||||
@@ -39,7 +39,8 @@ pub fn build_world_from_documents(
|
|||||||
.register::<GameObject>()
|
.register::<GameObject>()
|
||||||
.register::<Transform>()
|
.register::<Transform>()
|
||||||
.register::<RectTransform>()
|
.register::<RectTransform>()
|
||||||
.register::<PrefabInstanceComponent>();
|
.register::<PrefabInstanceComponent>()
|
||||||
|
.register::<StrippedReference>();
|
||||||
|
|
||||||
// Register all custom components from inventory
|
// Register all custom components from inventory
|
||||||
for reg in inventory::iter::<crate::types::ComponentRegistration> {
|
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
|
// PASS 2: Attach components to entities
|
||||||
let type_filter = TypeFilter::parse_all();
|
let type_filter = TypeFilter::parse_all();
|
||||||
for doc in documents.iter().filter(|d| {
|
for doc in documents.iter().filter(|d| {
|
||||||
|
!d.is_stripped &&
|
||||||
d.type_id != 1 && d.class_name != "GameObject" &&
|
d.type_id != 1 && d.class_name != "GameObject" &&
|
||||||
d.type_id != 1001 && d.class_name != "PrefabInstance"
|
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
|
// PASS 2: Attach components to entities
|
||||||
let type_filter = TypeFilter::parse_all();
|
let type_filter = TypeFilter::parse_all();
|
||||||
for doc in documents.iter().filter(|d| {
|
for doc in documents.iter().filter(|d| {
|
||||||
|
!d.is_stripped &&
|
||||||
d.type_id != 1 && d.class_name != "GameObject" &&
|
d.type_id != 1 && d.class_name != "GameObject" &&
|
||||||
d.type_id != 1001 && d.class_name != "PrefabInstance"
|
d.type_id != 1001 && d.class_name != "PrefabInstance"
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -189,6 +189,9 @@ pub struct RawDocument {
|
|||||||
|
|
||||||
/// Raw YAML value (inner mapping after class wrapper)
|
/// Raw YAML value (inner mapping after class wrapper)
|
||||||
pub yaml: serde_yaml::Value,
|
pub yaml: serde_yaml::Value,
|
||||||
|
|
||||||
|
/// Whether this component is stripped (exists in prefab, not in scene)
|
||||||
|
pub is_stripped: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawDocument {
|
impl RawDocument {
|
||||||
@@ -198,12 +201,14 @@ impl RawDocument {
|
|||||||
file_id: FileID,
|
file_id: FileID,
|
||||||
class_name: String,
|
class_name: String,
|
||||||
yaml: serde_yaml::Value,
|
yaml: serde_yaml::Value,
|
||||||
|
is_stripped: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
type_id,
|
type_id,
|
||||||
file_id,
|
file_id,
|
||||||
class_name,
|
class_name,
|
||||||
yaml,
|
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),
|
FileID::from_i64(tag.file_id),
|
||||||
class_name,
|
class_name,
|
||||||
inner_yaml,
|
inner_yaml,
|
||||||
|
tag.is_stripped,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,19 @@ pub struct UnityTag {
|
|||||||
|
|
||||||
/// File ID (the number after &)
|
/// File ID (the number after &)
|
||||||
pub file_id: i64,
|
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)
|
/// Get the Unity tag regex (compiled once and cached)
|
||||||
fn unity_tag_regex() -> &'static Regex {
|
fn unity_tag_regex() -> &'static Regex {
|
||||||
static REGEX: OnceLock<Regex> = OnceLock::new();
|
static REGEX: OnceLock<Regex> = OnceLock::new();
|
||||||
REGEX.get_or_init(|| {
|
REGEX.get_or_init(|| {
|
||||||
// Matches: --- !u!<type_id> &<file_id>
|
// Matches: --- !u!<type_id> &<file_id> [stripped]
|
||||||
// Example: --- !u!1 &1866116814460599870
|
// 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();
|
/// let tag = parse_unity_tag(doc).unwrap();
|
||||||
/// assert_eq!(tag.type_id, 1);
|
/// assert_eq!(tag.type_id, 1);
|
||||||
/// assert_eq!(tag.file_id, 12345);
|
/// assert_eq!(tag.file_id, 12345);
|
||||||
|
/// assert_eq!(tag.is_stripped, false);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn parse_unity_tag(document: &str) -> Option<UnityTag> {
|
pub fn parse_unity_tag(document: &str) -> Option<UnityTag> {
|
||||||
let re = unity_tag_regex();
|
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 type_id = captures.get(1)?.as_str().parse::<u32>().ok()?;
|
||||||
let file_id = captures.get(2)?.as_str().parse::<i64>().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)]
|
#[cfg(test)]
|
||||||
@@ -65,6 +77,7 @@ mod tests {
|
|||||||
let tag = parse_unity_tag(doc).unwrap();
|
let tag = parse_unity_tag(doc).unwrap();
|
||||||
assert_eq!(tag.type_id, 1);
|
assert_eq!(tag.type_id, 1);
|
||||||
assert_eq!(tag.file_id, 1866116814460599870);
|
assert_eq!(tag.file_id, 1866116814460599870);
|
||||||
|
assert_eq!(tag.is_stripped, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -73,6 +86,7 @@ mod tests {
|
|||||||
let tag = parse_unity_tag(doc).unwrap();
|
let tag = parse_unity_tag(doc).unwrap();
|
||||||
assert_eq!(tag.type_id, 224);
|
assert_eq!(tag.type_id, 224);
|
||||||
assert_eq!(tag.file_id, 8151827567463220614);
|
assert_eq!(tag.file_id, 8151827567463220614);
|
||||||
|
assert_eq!(tag.is_stripped, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -81,6 +95,16 @@ mod tests {
|
|||||||
let tag = parse_unity_tag(doc).unwrap();
|
let tag = parse_unity_tag(doc).unwrap();
|
||||||
assert_eq!(tag.type_id, 114);
|
assert_eq!(tag.type_id, 114);
|
||||||
assert_eq!(tag.file_id, -12345);
|
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]
|
#[test]
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ pub use type_filter::TypeFilter;
|
|||||||
pub use type_registry::{get_class_name, get_type_id};
|
pub use type_registry::{get_class_name, get_type_id};
|
||||||
pub use unity_types::{
|
pub use unity_types::{
|
||||||
BoxCollider, GameObject, MeshFilter, MeshRenderer, PrefabInstance, PrefabInstanceComponent,
|
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};
|
pub use values::{Color, ExternalRef, FileRef, Quaternion, Vector2, Vector3};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub mod mesh_renderer;
|
|||||||
pub mod prefab_instance;
|
pub mod prefab_instance;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod sphere_collider;
|
pub mod sphere_collider;
|
||||||
|
pub mod stripped_reference;
|
||||||
pub mod transform;
|
pub mod transform;
|
||||||
|
|
||||||
pub use box_collider::BoxCollider;
|
pub use box_collider::BoxCollider;
|
||||||
@@ -22,4 +23,5 @@ pub use prefab_instance::{
|
|||||||
};
|
};
|
||||||
pub use renderer::Renderer;
|
pub use renderer::Renderer;
|
||||||
pub use sphere_collider::SphereCollider;
|
pub use sphere_collider::SphereCollider;
|
||||||
|
pub use stripped_reference::StrippedReference;
|
||||||
pub use transform::{RectTransform, Transform};
|
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