From ff3c092d9eb3e0b961e77e4a5141f55b17ce24a7 Mon Sep 17 00:00:00 2001 From: Connor Date: Sat, 3 Jan 2026 13:39:28 +0000 Subject: [PATCH] early yaml exit --- README.md | 2 +- cursebreaker-parser/src/ecs/builder.rs | 6 +- cursebreaker-parser/src/lib.rs | 4 +- cursebreaker-parser/src/parser/mod.rs | 287 +++++++++++++++++-- cursebreaker-parser/src/types/type_filter.rs | 167 ++++++++++- resources_output.txt | 19 ++ 6 files changed, 448 insertions(+), 37 deletions(-) create mode 100644 resources_output.txt diff --git a/README.md b/README.md index 64bbc93..1f63cd8 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ Benchmarks on VR Horror project (21 scenes, 77 C# scripts): - [x] Prefab instantiation and overrides ### Future Enhancements -- [ ] Prefab GUID resolution (nested prefabs) +- [x] Prefab GUID resolution (nested prefabs) - [ ] Full AssetDatabase resolution (materials, textures) - [ ] Persistent GUID cache for instant loading - [ ] Watch mode for live Unity project monitoring diff --git a/cursebreaker-parser/src/ecs/builder.rs b/cursebreaker-parser/src/ecs/builder.rs index 48f1da2..b54e558 100644 --- a/cursebreaker-parser/src/ecs/builder.rs +++ b/cursebreaker-parser/src/ecs/builder.rs @@ -13,11 +13,11 @@ use std::collections::HashMap; /// Build a Sparsey ECS World from raw Unity documents /// -/// This uses a 3.5-pass approach: +/// This uses a 4-pass approach: /// 1. Create entities for all GameObjects /// 2. Attach components (Transform, RectTransform, etc.) to entities -/// 2.5. Resolve and instantiate prefab instances (NEW) -/// 3. Resolve Transform hierarchy (parent/children Entity references) +/// 3. Resolve and instantiate prefab instances +/// 4. Resolve Callbacks generated when parsing components /// /// # Arguments /// - `documents`: Parsed Unity documents to build the world from diff --git a/cursebreaker-parser/src/lib.rs b/cursebreaker-parser/src/lib.rs index 33e95e6..d8cd94f 100644 --- a/cursebreaker-parser/src/lib.rs +++ b/cursebreaker-parser/src/lib.rs @@ -39,8 +39,8 @@ pub mod types; pub use error::{Error, Result}; pub use model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene}; pub use parser::{ - find_project_root, meta::MetaFile, parse_unity_file, parse_unity_file_filtered, GuidResolver, - PrefabGuidResolver, + find_project_root, meta::MetaFile, parse_unity_file, parse_unity_file_filtered, + GuidResolver, PrefabGuidResolver, }; // TODO: Re-enable once project module is updated // pub use project::UnityProject; diff --git a/cursebreaker-parser/src/parser/mod.rs b/cursebreaker-parser/src/parser/mod.rs index 211640c..9f5c206 100644 --- a/cursebreaker-parser/src/parser/mod.rs +++ b/cursebreaker-parser/src/parser/mod.rs @@ -13,7 +13,7 @@ pub use unity_tag::{parse_unity_tag, UnityTag}; pub use yaml::split_yaml_documents; use crate::model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene}; -use crate::types::FileID; +use crate::types::{FileID, Guid, TypeFilter}; use crate::{Error, Result}; use regex::Regex; use std::path::Path; @@ -43,35 +43,46 @@ use std::path::Path; /// # Ok::<(), cursebreaker_parser::Error>(()) /// ``` pub fn parse_unity_file(path: &Path) -> Result { - parse_unity_file_filtered(path, None) + parse_unity_file_filtered(path, None, None) } -/// Parse a Unity file with optional regex filtering +/// Parse a Unity file with optional regex filtering and type filtering /// -/// Same as `parse_unity_file`, but allows filtering files by path pattern. +/// Same as `parse_unity_file`, but allows filtering files by path pattern and Unity types. /// If the path doesn't match the regex, returns an error. /// /// # Arguments /// /// * `path` - Path to the Unity file to parse /// * `filter` - Optional regex to match against the file path. If None, parses all files (default behavior). +/// * `type_filter` - Optional filter for Unity types and MonoBehaviour GUIDs. If None, parses all types (default behavior). /// /// # Example /// /// ```no_run -/// use cursebreaker_parser::parser::parse_unity_file_filtered; +/// use cursebreaker_parser::parser::{parse_unity_file_filtered}; +/// use cursebreaker_parser::TypeFilter; /// use regex::Regex; /// use std::path::Path; +/// use std::collections::HashSet; /// /// // Only parse files with "Test" in the name /// let filter = Regex::new(r"Test").unwrap(); -/// let file = parse_unity_file_filtered(Path::new("TestScene.unity"), Some(&filter))?; +/// let file = parse_unity_file_filtered(Path::new("TestScene.unity"), Some(&filter), None)?; /// -/// // Parse everything (same as parse_unity_file) -/// let file2 = parse_unity_file_filtered(Path::new("Scene.unity"), None)?; +/// // Only parse Transform and GameObject types +/// let mut types = HashSet::new(); +/// types.insert("Transform".to_string()); +/// types.insert("GameObject".to_string()); +/// let type_filter = TypeFilter::with_unity_types(types); +/// let file2 = parse_unity_file_filtered(Path::new("Scene.unity"), None, Some(&type_filter))?; /// # Ok::<(), cursebreaker_parser::Error>(()) /// ``` -pub fn parse_unity_file_filtered(path: &Path, filter: Option<&Regex>) -> Result { +pub fn parse_unity_file_filtered( + path: &Path, + filter: Option<&Regex>, + type_filter: Option<&TypeFilter>, +) -> Result { // Apply filter if provided if let Some(regex) = filter { let path_str = path.to_str().ok_or_else(|| { @@ -86,11 +97,11 @@ pub fn parse_unity_file_filtered(path: &Path, filter: Option<&Regex>) -> Result< } } - parse_unity_file_impl(path) + parse_unity_file_impl(path, type_filter) } /// Internal implementation of Unity file parsing -fn parse_unity_file_impl(path: &Path) -> Result { +fn parse_unity_file_impl(path: &Path, type_filter: Option<&TypeFilter>) -> Result { // Read the file let content = std::fs::read_to_string(path)?; @@ -102,9 +113,9 @@ fn parse_unity_file_impl(path: &Path) -> Result { // Parse based on file type match file_type { - FileType::Scene => parse_scene(path, &content), - FileType::Prefab => parse_prefab(path, &content), - FileType::Asset => parse_asset(path, &content), + FileType::Scene => parse_scene(path, &content, type_filter), + FileType::Prefab => parse_prefab(path, &content, type_filter), + FileType::Asset => parse_asset(path, &content, type_filter), FileType::Unknown => Err(Error::invalid_format(format!( "Unknown file extension: {}", path.display() @@ -131,8 +142,8 @@ fn detect_file_type(path: &Path) -> FileType { } /// Parse a scene file into an ECS World -fn parse_scene(path: &Path, content: &str) -> Result { - let raw_documents = parse_raw_documents(content)?; +fn parse_scene(path: &Path, content: &str, type_filter: Option<&TypeFilter>) -> Result { + let raw_documents = parse_raw_documents(content, type_filter)?; // Try to find Unity project root and build both GUID resolvers let (guid_resolver, prefab_guid_resolver) = match find_project_root(path) { @@ -186,8 +197,8 @@ fn parse_scene(path: &Path, content: &str) -> Result { } /// Parse a prefab file into raw YAML documents -fn parse_prefab(path: &Path, content: &str) -> Result { - let raw_documents = parse_raw_documents(content)?; +fn parse_prefab(path: &Path, content: &str, type_filter: Option<&TypeFilter>) -> Result { + let raw_documents = parse_raw_documents(content, type_filter)?; Ok(UnityFile::Prefab(UnityPrefab::new( path.to_path_buf(), @@ -196,8 +207,8 @@ fn parse_prefab(path: &Path, content: &str) -> Result { } /// Parse an asset file into raw YAML documents -fn parse_asset(path: &Path, content: &str) -> Result { - let raw_documents = parse_raw_documents(content)?; +fn parse_asset(path: &Path, content: &str, type_filter: Option<&TypeFilter>) -> Result { + let raw_documents = parse_raw_documents(content, type_filter)?; Ok(UnityFile::Asset(UnityAsset::new( path.to_path_buf(), @@ -205,20 +216,20 @@ fn parse_asset(path: &Path, content: &str) -> Result { ))) } -/// Parse raw YAML documents from file content -fn parse_raw_documents(content: &str) -> Result> { +/// Parse raw YAML documents from file content with optional type filtering +fn parse_raw_documents(content: &str, type_filter: Option<&TypeFilter>) -> Result> { // Split into individual YAML documents let raw_docs = split_yaml_documents(content)?; // Parse each document raw_docs .iter() - .filter_map(|raw| parse_raw_document(raw).transpose()) + .filter_map(|raw| parse_raw_document(raw, type_filter).transpose()) .collect() } -/// Parse a single raw YAML document into a RawDocument -fn parse_raw_document(raw_doc: &str) -> Result> { +/// Parse a single raw YAML document into a RawDocument with optional type filtering +fn parse_raw_document(raw_doc: &str, type_filter: Option<&TypeFilter>) -> Result> { // Parse the Unity tag line (e.g., "--- !u!1 &12345") let tag = match parse_unity_tag(raw_doc) { Some(tag) => tag, @@ -232,7 +243,42 @@ fn parse_raw_document(raw_doc: &str) -> Result> { return Ok(None); } - // Parse YAML but don't convert to PropertyValue yet + // Early filtering: Extract class name without full YAML parsing + if let Some(filter) = type_filter { + if filter.is_filtering() { + // Extract the class name efficiently + let class_name = match extract_class_name(yaml_content) { + Some(name) => name, + None => return Ok(None), // Can't extract class name, skip + }; + + // Check if this is a MonoBehaviour + if class_name == "MonoBehaviour" { + // For MonoBehaviour, we need to check the m_Script GUID + match extract_monobehaviour_guid(yaml_content) { + Some(guid) => { + if !filter.should_parse_guid(&guid) { + // GUID not in whitelist, skip this document + return Ok(None); + } + } + None => { + // Can't extract GUID, skip this MonoBehaviour + return Ok(None); + } + } + } else { + // For non-MonoBehaviour, check the Unity type whitelist + if !filter.should_parse_type(class_name) { + // Type not in whitelist, skip this document + return Ok(None); + } + } + } + } + + // If we reach here, the document passed the filter (or no filter was applied) + // Now do the full YAML parsing let yaml_value: serde_yaml::Value = serde_yaml::from_str(yaml_content)?; // Unity documents have format "GameObject: { ... }" @@ -284,6 +330,70 @@ fn extract_yaml_content(raw_doc: &str) -> &str { } } +/// Extract the Unity class name from YAML content without full parsing +/// +/// Unity documents have the format: +/// ```yaml +/// ClassName: +/// field1: value1 +/// field2: value2 +/// ``` +/// +/// This function extracts "ClassName" efficiently without parsing the entire YAML. +fn extract_class_name(yaml_content: &str) -> Option<&str> { + // Find the first line that's not empty + let first_line = yaml_content.lines().find(|line| !line.trim().is_empty())?; + + // Class name is the first non-whitespace text before ':' + let class_name = first_line.trim().strip_suffix(':')?; + + Some(class_name) +} + +/// Extract the m_Script GUID from a MonoBehaviour YAML document without full parsing +/// +/// MonoBehaviour documents have the format: +/// ```yaml +/// MonoBehaviour: +/// m_Script: {fileID: 11500000, guid: d39ddbf1c2c3d1a4baa070e5e76548bd, type: 3} +/// ... +/// ``` +/// +/// Or multi-line format: +/// ```yaml +/// MonoBehaviour: +/// m_Script: +/// fileID: 11500000 +/// guid: d39ddbf1c2c3d1a4baa070e5e76548bd +/// type: 3 +/// ``` +/// +/// This function extracts the GUID value efficiently. +fn extract_monobehaviour_guid(yaml_content: &str) -> Option { + // Look for any line with "guid: <32 hex chars>" + // This works for both inline and multi-line formats + for line in yaml_content.lines() { + if line.contains("guid:") { + // Find "guid: " and extract the 32-character hex string after it + if let Some(guid_start) = line.find("guid:") { + let after_guid = &line[guid_start + 5..].trim(); + + // Extract the hex string (32 characters) + let guid_str: String = after_guid + .chars() + .take_while(|c| c.is_ascii_hexdigit()) + .collect(); + + if guid_str.len() == 32 { + return Guid::from_hex(&guid_str).ok(); + } + } + } + } + + None +} + #[cfg(test)] mod tests { use super::*; @@ -332,7 +442,7 @@ mod tests { let path = Path::new("TestScene.unity"); // Should match and attempt to parse (will fail because file doesn't exist) - let result = parse_unity_file_filtered(path, Some(&filter)); + let result = parse_unity_file_filtered(path, Some(&filter), None); assert!(result.is_err()); // Error should be IO error (file not found), not filter error @@ -357,7 +467,7 @@ mod tests { let path = Path::new("MainScene.unity"); // Should reject due to filter - let result = parse_unity_file_filtered(path, Some(&filter)); + let result = parse_unity_file_filtered(path, Some(&filter), None); assert!(result.is_err()); // Error should be filter error @@ -379,7 +489,7 @@ mod tests { let path = Path::new("AnyScene.unity"); // No filter should accept any path (will fail with IO error) - let result = parse_unity_file_filtered(path, None); + let result = parse_unity_file_filtered(path, None, None); assert!(result.is_err()); // Should be IO error, not filter error @@ -402,10 +512,127 @@ mod tests { // parse_unity_file should work the same as filtered with None let result1 = parse_unity_file(path); - let result2 = parse_unity_file_filtered(path, None); + let result2 = parse_unity_file_filtered(path, None, None); // Both should have the same error (IO error for missing file) assert!(result1.is_err()); assert!(result2.is_err()); } + + #[test] + fn test_extract_class_name() { + let yaml = "GameObject:\n m_Name: Test"; + assert_eq!(extract_class_name(yaml), Some("GameObject")); + + let yaml2 = "Transform:\n m_LocalPosition: {x: 1, y: 2, z: 3}"; + assert_eq!(extract_class_name(yaml2), Some("Transform")); + + let yaml3 = "MonoBehaviour:\n m_Script: {fileID: 11500000}"; + assert_eq!(extract_class_name(yaml3), Some("MonoBehaviour")); + + let empty = ""; + assert_eq!(extract_class_name(empty), None); + } + + #[test] + fn test_extract_monobehaviour_guid() { + let yaml = "MonoBehaviour:\n m_Script: {fileID: 11500000, guid: d39ddbf1c2c3d1a4baa070e5e76548bd, type: 3}"; + let guid = extract_monobehaviour_guid(yaml); + assert!(guid.is_some()); + assert_eq!( + guid.unwrap().to_hex(), + "d39ddbf1c2c3d1a4baa070e5e76548bd" + ); + + // Multi-line format + let yaml2 = "MonoBehaviour:\n m_Script:\n fileID: 11500000\n guid: abc123def456789012345678901234ab\n type: 3"; + let guid2 = extract_monobehaviour_guid(yaml2); + assert!(guid2.is_some()); + assert_eq!( + guid2.unwrap().to_hex(), + "abc123def456789012345678901234ab" + ); + + let no_guid = "MonoBehaviour:\n m_Name: Test"; + assert_eq!(extract_monobehaviour_guid(no_guid), None); + } + + #[test] + fn test_type_filter_document_parse_all() { + let filter = TypeFilter::parse_all(); + assert!(filter.should_parse_type("Transform")); + assert!(filter.should_parse_type("GameObject")); + assert!(filter.should_parse_type("AnyType")); + assert!(!filter.is_filtering()); + } + + #[test] + fn test_type_filter_document_with_unity_types() { + use std::collections::HashSet; + + let mut types = HashSet::new(); + types.insert("Transform".to_string()); + types.insert("GameObject".to_string()); + + let filter = TypeFilter::with_unity_types(types); + + assert!(filter.should_parse_type("Transform")); + assert!(filter.should_parse_type("GameObject")); + assert!(!filter.should_parse_type("RectTransform")); + assert!(filter.is_filtering()); + + // Should still accept any MonoBehaviour GUID since we didn't set a GUID filter + let guid = Guid::from_hex("d39ddbf1c2c3d1a4baa070e5e76548bd").unwrap(); + assert!(filter.should_parse_guid(&guid)); + } + + #[test] + fn test_type_filter_document_with_monobehaviour_guids() { + use std::collections::HashSet; + + let mut guids = HashSet::new(); + let guid1 = Guid::from_hex("d39ddbf1c2c3d1a4baa070e5e76548bd").unwrap(); + let guid2 = Guid::from_hex("abc123def456789012345678901234ab").unwrap(); + guids.insert(guid1); + guids.insert(guid2); + + let filter = TypeFilter::with_monobehaviour_guids(guids); + + assert!(filter.should_parse_guid(&guid1)); + assert!(filter.should_parse_guid(&guid2)); + + let guid3 = Guid::from_hex("00000000000000000000000000000000").unwrap(); + assert!(!filter.should_parse_guid(&guid3)); + + assert!(filter.is_filtering()); + + // Should still accept any Unity type since we didn't set a type filter + assert!(filter.should_parse_type("Transform")); + assert!(filter.should_parse_type("AnyType")); + } + + #[test] + fn test_type_filter_document_with_both() { + use std::collections::HashSet; + + let mut types = HashSet::new(); + types.insert("Transform".to_string()); + + let mut guids = HashSet::new(); + let guid1 = Guid::from_hex("d39ddbf1c2c3d1a4baa070e5e76548bd").unwrap(); + guids.insert(guid1); + + let filter = TypeFilter::with_both(types, guids); + + // Only Transform should pass + assert!(filter.should_parse_type("Transform")); + assert!(!filter.should_parse_type("GameObject")); + + // Only guid1 should pass + assert!(filter.should_parse_guid(&guid1)); + let guid2 = Guid::from_hex("abc123def456789012345678901234ab").unwrap(); + assert!(!filter.should_parse_guid(&guid2)); + + assert!(filter.is_filtering()); + } } \ No newline at end of file diff --git a/cursebreaker-parser/src/types/type_filter.rs b/cursebreaker-parser/src/types/type_filter.rs index 4d337cc..162ed41 100644 --- a/cursebreaker-parser/src/types/type_filter.rs +++ b/cursebreaker-parser/src/types/type_filter.rs @@ -3,9 +3,14 @@ //! This module provides functionality to selectively parse only specific Unity //! component types, improving performance and reducing memory usage. +use crate::types::Guid; use std::collections::HashSet; -/// Filter for controlling which Unity types get parsed +/// Filter for controlling which Unity types and MonoBehaviour scripts get parsed +/// +/// This filter operates at two levels: +/// 1. **Document-level (YAML parsing)**: Skips parsing YAML for unwanted Unity types and MonoBehaviour GUIDs +/// 2. **ECS-level (component insertion)**: Controls which components get inserted into the ECS world /// /// By default, all types are parsed. Use `TypeFilter::new()` to create /// a filter that only parses specific types. @@ -19,6 +24,10 @@ pub struct TypeFilter { /// If None, all custom types are parsed custom_types: Option>, + /// Set of MonoBehaviour script GUIDs to parse + /// If None, all MonoBehaviour scripts are parsed + monobehaviour_guids: Option>, + /// Whether to parse all types (default) parse_all: bool, } @@ -29,6 +38,7 @@ impl TypeFilter { Self { unity_types: None, custom_types: None, + monobehaviour_guids: None, parse_all: true, } } @@ -52,6 +62,7 @@ impl TypeFilter { Self { unity_types: Some(unity_types.into_iter().map(|s| s.into()).collect()), custom_types: Some(custom_types.into_iter().map(|s| s.into()).collect()), + monobehaviour_guids: None, parse_all: false, } } @@ -61,6 +72,7 @@ impl TypeFilter { Self { unity_types: Some(types.into_iter().map(|s| s.into()).collect()), custom_types: Some(HashSet::new()), + monobehaviour_guids: None, parse_all: false, } } @@ -70,6 +82,37 @@ impl TypeFilter { Self { unity_types: Some(HashSet::new()), custom_types: Some(types.into_iter().map(|s| s.into()).collect()), + monobehaviour_guids: None, + parse_all: false, + } + } + + /// Create a filter with specific Unity types and MonoBehaviour GUIDs + pub fn with_unity_types(types: HashSet) -> Self { + Self { + unity_types: Some(types), + custom_types: None, + monobehaviour_guids: None, + parse_all: false, + } + } + + /// Create a filter with specific MonoBehaviour GUIDs + pub fn with_monobehaviour_guids(guids: HashSet) -> Self { + Self { + unity_types: None, + custom_types: None, + monobehaviour_guids: Some(guids), + parse_all: false, + } + } + + /// Create a filter with both Unity types and MonoBehaviour GUIDs + pub fn with_both(types: HashSet, guids: HashSet) -> Self { + Self { + unity_types: Some(types), + custom_types: None, + monobehaviour_guids: Some(guids), parse_all: false, } } @@ -106,6 +149,44 @@ impl TypeFilter { self.should_parse_unity(type_name) } } + + /// Check if a Unity class name should be parsed at the document level + /// + /// This is used during YAML parsing to skip unwanted documents. + /// Returns true if there's no filter, or if the class is in the whitelist. + pub fn should_parse_type(&self, class_name: &str) -> bool { + if self.parse_all { + return true; + } + + match &self.unity_types { + None => true, // No filter = parse all + Some(whitelist) => whitelist.contains(class_name), + } + } + + /// Check if a MonoBehaviour GUID should be parsed + /// + /// This is used during YAML parsing to skip unwanted MonoBehaviour scripts. + /// Returns true if there's no filter, or if the GUID is in the whitelist. + pub fn should_parse_guid(&self, guid: &Guid) -> bool { + if self.parse_all { + return true; + } + + match &self.monobehaviour_guids { + None => true, // No filter = parse all + Some(whitelist) => whitelist.contains(guid), + } + } + + /// Check if we're filtering anything at all + pub fn is_filtering(&self) -> bool { + !self.parse_all + && (self.unity_types.is_some() + || self.custom_types.is_some() + || self.monobehaviour_guids.is_some()) + } } impl Default for TypeFilter { @@ -158,4 +239,88 @@ mod tests { assert!(filter.should_parse_custom("PlaySFX")); assert!(!filter.should_parse_custom("Interact")); } + + #[test] + fn test_with_unity_types() { + use std::collections::HashSet; + + let mut types = HashSet::new(); + types.insert("Transform".to_string()); + types.insert("GameObject".to_string()); + + let filter = TypeFilter::with_unity_types(types); + + assert!(filter.should_parse_type("Transform")); + assert!(filter.should_parse_type("GameObject")); + assert!(!filter.should_parse_type("RectTransform")); + assert!(filter.is_filtering()); + + // Should still accept any MonoBehaviour GUID since we didn't set a GUID filter + let guid = Guid::from_hex("d39ddbf1c2c3d1a4baa070e5e76548bd").unwrap(); + assert!(filter.should_parse_guid(&guid)); + } + + #[test] + fn test_with_monobehaviour_guids() { + use std::collections::HashSet; + + let mut guids = HashSet::new(); + let guid1 = Guid::from_hex("d39ddbf1c2c3d1a4baa070e5e76548bd").unwrap(); + let guid2 = Guid::from_hex("abc123def456789012345678901234ab").unwrap(); + guids.insert(guid1); + guids.insert(guid2); + + let filter = TypeFilter::with_monobehaviour_guids(guids); + + assert!(filter.should_parse_guid(&guid1)); + assert!(filter.should_parse_guid(&guid2)); + + let guid3 = Guid::from_hex("00000000000000000000000000000000").unwrap(); + assert!(!filter.should_parse_guid(&guid3)); + + assert!(filter.is_filtering()); + + // Should still accept any Unity type since we didn't set a type filter + assert!(filter.should_parse_type("Transform")); + assert!(filter.should_parse_type("AnyType")); + } + + #[test] + fn test_with_both() { + use std::collections::HashSet; + + let mut types = HashSet::new(); + types.insert("Transform".to_string()); + + let mut guids = HashSet::new(); + let guid1 = Guid::from_hex("d39ddbf1c2c3d1a4baa070e5e76548bd").unwrap(); + guids.insert(guid1); + + let filter = TypeFilter::with_both(types, guids); + + // Only Transform should pass + assert!(filter.should_parse_type("Transform")); + assert!(!filter.should_parse_type("GameObject")); + + // Only guid1 should pass + assert!(filter.should_parse_guid(&guid1)); + let guid2 = Guid::from_hex("abc123def456789012345678901234ab").unwrap(); + assert!(!filter.should_parse_guid(&guid2)); + + assert!(filter.is_filtering()); + } + + #[test] + fn test_is_filtering() { + let filter_all = TypeFilter::parse_all(); + assert!(!filter_all.is_filtering()); + + let filter_unity = TypeFilter::unity_only(vec!["Transform"]); + assert!(filter_unity.is_filtering()); + + let mut guids = std::collections::HashSet::new(); + guids.insert(Guid::from_hex("d39ddbf1c2c3d1a4baa070e5e76548bd").unwrap()); + let filter_guids = TypeFilter::with_monobehaviour_guids(guids); + assert!(filter_guids.is_filtering()); + } } diff --git a/resources_output.txt b/resources_output.txt new file mode 100644 index 0000000..bfcfe5b --- /dev/null +++ b/resources_output.txt @@ -0,0 +1,19 @@ +Cursebreaker Resources - 10_3.unity Scene +====================================================================== + +Total resources found: 2 + +---------------------------------------------------------------------- + +Resource: HarvestableSpawner_2Copper Ore + TypeID: 2 + MaxHealth: 0 + Position: (1788.727173, 40.725288, 172.017670) + +Resource: HarvestableSpawner_38Dandelions + TypeID: 38 + MaxHealth: 0 + Position: (1746.709717, 44.599632, 299.696503) + +====================================================================== +End of resource data