early yaml exit
This commit is contained in:
@@ -369,7 +369,7 @@ Benchmarks on VR Horror project (21 scenes, 77 C# scripts):
|
|||||||
- [x] Prefab instantiation and overrides
|
- [x] Prefab instantiation and overrides
|
||||||
|
|
||||||
### Future Enhancements
|
### Future Enhancements
|
||||||
- [ ] Prefab GUID resolution (nested prefabs)
|
- [x] Prefab GUID resolution (nested prefabs)
|
||||||
- [ ] Full AssetDatabase resolution (materials, textures)
|
- [ ] Full AssetDatabase resolution (materials, textures)
|
||||||
- [ ] Persistent GUID cache for instant loading
|
- [ ] Persistent GUID cache for instant loading
|
||||||
- [ ] Watch mode for live Unity project monitoring
|
- [ ] Watch mode for live Unity project monitoring
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
/// Build a Sparsey ECS World from raw Unity documents
|
/// 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
|
/// 1. Create entities for all GameObjects
|
||||||
/// 2. Attach components (Transform, RectTransform, etc.) to entities
|
/// 2. Attach components (Transform, RectTransform, etc.) to entities
|
||||||
/// 2.5. Resolve and instantiate prefab instances (NEW)
|
/// 3. Resolve and instantiate prefab instances
|
||||||
/// 3. Resolve Transform hierarchy (parent/children Entity references)
|
/// 4. Resolve Callbacks generated when parsing components
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// - `documents`: Parsed Unity documents to build the world from
|
/// - `documents`: Parsed Unity documents to build the world from
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ pub mod types;
|
|||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene};
|
pub use model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene};
|
||||||
pub use parser::{
|
pub use parser::{
|
||||||
find_project_root, meta::MetaFile, parse_unity_file, parse_unity_file_filtered, GuidResolver,
|
find_project_root, meta::MetaFile, parse_unity_file, parse_unity_file_filtered,
|
||||||
PrefabGuidResolver,
|
GuidResolver, PrefabGuidResolver,
|
||||||
};
|
};
|
||||||
// TODO: Re-enable once project module is updated
|
// TODO: Re-enable once project module is updated
|
||||||
// pub use project::UnityProject;
|
// pub use project::UnityProject;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub use unity_tag::{parse_unity_tag, UnityTag};
|
|||||||
pub use yaml::split_yaml_documents;
|
pub use yaml::split_yaml_documents;
|
||||||
|
|
||||||
use crate::model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene};
|
use crate::model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene};
|
||||||
use crate::types::FileID;
|
use crate::types::{FileID, Guid, TypeFilter};
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -43,35 +43,46 @@ use std::path::Path;
|
|||||||
/// # Ok::<(), cursebreaker_parser::Error>(())
|
/// # Ok::<(), cursebreaker_parser::Error>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub fn parse_unity_file(path: &Path) -> Result<UnityFile> {
|
pub fn parse_unity_file(path: &Path) -> Result<UnityFile> {
|
||||||
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.
|
/// If the path doesn't match the regex, returns an error.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `path` - Path to the Unity file to parse
|
/// * `path` - Path to the Unity file to parse
|
||||||
/// * `filter` - Optional regex to match against the file path. If None, parses all files (default behavior).
|
/// * `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
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```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 regex::Regex;
|
||||||
/// use std::path::Path;
|
/// use std::path::Path;
|
||||||
|
/// use std::collections::HashSet;
|
||||||
///
|
///
|
||||||
/// // Only parse files with "Test" in the name
|
/// // Only parse files with "Test" in the name
|
||||||
/// let filter = Regex::new(r"Test").unwrap();
|
/// 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)
|
/// // Only parse Transform and GameObject types
|
||||||
/// let file2 = parse_unity_file_filtered(Path::new("Scene.unity"), None)?;
|
/// 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>(())
|
/// # Ok::<(), cursebreaker_parser::Error>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub fn parse_unity_file_filtered(path: &Path, filter: Option<&Regex>) -> Result<UnityFile> {
|
pub fn parse_unity_file_filtered(
|
||||||
|
path: &Path,
|
||||||
|
filter: Option<&Regex>,
|
||||||
|
type_filter: Option<&TypeFilter>,
|
||||||
|
) -> Result<UnityFile> {
|
||||||
// Apply filter if provided
|
// Apply filter if provided
|
||||||
if let Some(regex) = filter {
|
if let Some(regex) = filter {
|
||||||
let path_str = path.to_str().ok_or_else(|| {
|
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
|
/// Internal implementation of Unity file parsing
|
||||||
fn parse_unity_file_impl(path: &Path) -> Result<UnityFile> {
|
fn parse_unity_file_impl(path: &Path, type_filter: Option<&TypeFilter>) -> Result<UnityFile> {
|
||||||
// Read the file
|
// Read the file
|
||||||
let content = std::fs::read_to_string(path)?;
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
|
||||||
@@ -102,9 +113,9 @@ fn parse_unity_file_impl(path: &Path) -> Result<UnityFile> {
|
|||||||
|
|
||||||
// Parse based on file type
|
// Parse based on file type
|
||||||
match file_type {
|
match file_type {
|
||||||
FileType::Scene => parse_scene(path, &content),
|
FileType::Scene => parse_scene(path, &content, type_filter),
|
||||||
FileType::Prefab => parse_prefab(path, &content),
|
FileType::Prefab => parse_prefab(path, &content, type_filter),
|
||||||
FileType::Asset => parse_asset(path, &content),
|
FileType::Asset => parse_asset(path, &content, type_filter),
|
||||||
FileType::Unknown => Err(Error::invalid_format(format!(
|
FileType::Unknown => Err(Error::invalid_format(format!(
|
||||||
"Unknown file extension: {}",
|
"Unknown file extension: {}",
|
||||||
path.display()
|
path.display()
|
||||||
@@ -131,8 +142,8 @@ fn detect_file_type(path: &Path) -> FileType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a scene file into an ECS World
|
/// Parse a scene file into an ECS World
|
||||||
fn parse_scene(path: &Path, content: &str) -> Result<UnityFile> {
|
fn parse_scene(path: &Path, content: &str, type_filter: Option<&TypeFilter>) -> Result<UnityFile> {
|
||||||
let raw_documents = parse_raw_documents(content)?;
|
let raw_documents = parse_raw_documents(content, type_filter)?;
|
||||||
|
|
||||||
// Try to find Unity project root and build both GUID resolvers
|
// Try to find Unity project root and build both GUID resolvers
|
||||||
let (guid_resolver, prefab_guid_resolver) = match find_project_root(path) {
|
let (guid_resolver, prefab_guid_resolver) = match find_project_root(path) {
|
||||||
@@ -186,8 +197,8 @@ fn parse_scene(path: &Path, content: &str) -> Result<UnityFile> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a prefab file into raw YAML documents
|
/// Parse a prefab file into raw YAML documents
|
||||||
fn parse_prefab(path: &Path, content: &str) -> Result<UnityFile> {
|
fn parse_prefab(path: &Path, content: &str, type_filter: Option<&TypeFilter>) -> Result<UnityFile> {
|
||||||
let raw_documents = parse_raw_documents(content)?;
|
let raw_documents = parse_raw_documents(content, type_filter)?;
|
||||||
|
|
||||||
Ok(UnityFile::Prefab(UnityPrefab::new(
|
Ok(UnityFile::Prefab(UnityPrefab::new(
|
||||||
path.to_path_buf(),
|
path.to_path_buf(),
|
||||||
@@ -196,8 +207,8 @@ fn parse_prefab(path: &Path, content: &str) -> Result<UnityFile> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an asset file into raw YAML documents
|
/// Parse an asset file into raw YAML documents
|
||||||
fn parse_asset(path: &Path, content: &str) -> Result<UnityFile> {
|
fn parse_asset(path: &Path, content: &str, type_filter: Option<&TypeFilter>) -> Result<UnityFile> {
|
||||||
let raw_documents = parse_raw_documents(content)?;
|
let raw_documents = parse_raw_documents(content, type_filter)?;
|
||||||
|
|
||||||
Ok(UnityFile::Asset(UnityAsset::new(
|
Ok(UnityFile::Asset(UnityAsset::new(
|
||||||
path.to_path_buf(),
|
path.to_path_buf(),
|
||||||
@@ -205,20 +216,20 @@ fn parse_asset(path: &Path, content: &str) -> Result<UnityFile> {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse raw YAML documents from file content
|
/// Parse raw YAML documents from file content with optional type filtering
|
||||||
fn parse_raw_documents(content: &str) -> Result<Vec<RawDocument>> {
|
fn parse_raw_documents(content: &str, type_filter: Option<&TypeFilter>) -> Result<Vec<RawDocument>> {
|
||||||
// Split into individual YAML documents
|
// Split into individual YAML documents
|
||||||
let raw_docs = split_yaml_documents(content)?;
|
let raw_docs = split_yaml_documents(content)?;
|
||||||
|
|
||||||
// Parse each document
|
// Parse each document
|
||||||
raw_docs
|
raw_docs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|raw| parse_raw_document(raw).transpose())
|
.filter_map(|raw| parse_raw_document(raw, type_filter).transpose())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a single raw YAML document into a RawDocument
|
/// Parse a single raw YAML document into a RawDocument with optional type filtering
|
||||||
fn parse_raw_document(raw_doc: &str) -> Result<Option<RawDocument>> {
|
fn parse_raw_document(raw_doc: &str, type_filter: Option<&TypeFilter>) -> Result<Option<RawDocument>> {
|
||||||
// Parse the Unity tag line (e.g., "--- !u!1 &12345")
|
// Parse the Unity tag line (e.g., "--- !u!1 &12345")
|
||||||
let tag = match parse_unity_tag(raw_doc) {
|
let tag = match parse_unity_tag(raw_doc) {
|
||||||
Some(tag) => tag,
|
Some(tag) => tag,
|
||||||
@@ -232,7 +243,42 @@ fn parse_raw_document(raw_doc: &str) -> Result<Option<RawDocument>> {
|
|||||||
return Ok(None);
|
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)?;
|
let yaml_value: serde_yaml::Value = serde_yaml::from_str(yaml_content)?;
|
||||||
|
|
||||||
// Unity documents have format "GameObject: { ... }"
|
// 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<Guid> {
|
||||||
|
// 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -332,7 +442,7 @@ mod tests {
|
|||||||
let path = Path::new("TestScene.unity");
|
let path = Path::new("TestScene.unity");
|
||||||
|
|
||||||
// Should match and attempt to parse (will fail because file doesn't exist)
|
// 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());
|
assert!(result.is_err());
|
||||||
|
|
||||||
// Error should be IO error (file not found), not filter error
|
// Error should be IO error (file not found), not filter error
|
||||||
@@ -357,7 +467,7 @@ mod tests {
|
|||||||
let path = Path::new("MainScene.unity");
|
let path = Path::new("MainScene.unity");
|
||||||
|
|
||||||
// Should reject due to filter
|
// 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());
|
assert!(result.is_err());
|
||||||
|
|
||||||
// Error should be filter error
|
// Error should be filter error
|
||||||
@@ -379,7 +489,7 @@ mod tests {
|
|||||||
let path = Path::new("AnyScene.unity");
|
let path = Path::new("AnyScene.unity");
|
||||||
|
|
||||||
// No filter should accept any path (will fail with IO error)
|
// 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());
|
assert!(result.is_err());
|
||||||
|
|
||||||
// Should be IO error, not filter error
|
// Should be IO error, not filter error
|
||||||
@@ -402,10 +512,127 @@ mod tests {
|
|||||||
|
|
||||||
// parse_unity_file should work the same as filtered with None
|
// parse_unity_file should work the same as filtered with None
|
||||||
let result1 = parse_unity_file(path);
|
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)
|
// Both should have the same error (IO error for missing file)
|
||||||
assert!(result1.is_err());
|
assert!(result1.is_err());
|
||||||
assert!(result2.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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,14 @@
|
|||||||
//! This module provides functionality to selectively parse only specific Unity
|
//! This module provides functionality to selectively parse only specific Unity
|
||||||
//! component types, improving performance and reducing memory usage.
|
//! component types, improving performance and reducing memory usage.
|
||||||
|
|
||||||
|
use crate::types::Guid;
|
||||||
use std::collections::HashSet;
|
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
|
/// By default, all types are parsed. Use `TypeFilter::new()` to create
|
||||||
/// a filter that only parses specific types.
|
/// a filter that only parses specific types.
|
||||||
@@ -19,6 +24,10 @@ pub struct TypeFilter {
|
|||||||
/// If None, all custom types are parsed
|
/// If None, all custom types are parsed
|
||||||
custom_types: Option<HashSet<String>>,
|
custom_types: Option<HashSet<String>>,
|
||||||
|
|
||||||
|
/// Set of MonoBehaviour script GUIDs to parse
|
||||||
|
/// If None, all MonoBehaviour scripts are parsed
|
||||||
|
monobehaviour_guids: Option<HashSet<Guid>>,
|
||||||
|
|
||||||
/// Whether to parse all types (default)
|
/// Whether to parse all types (default)
|
||||||
parse_all: bool,
|
parse_all: bool,
|
||||||
}
|
}
|
||||||
@@ -29,6 +38,7 @@ impl TypeFilter {
|
|||||||
Self {
|
Self {
|
||||||
unity_types: None,
|
unity_types: None,
|
||||||
custom_types: None,
|
custom_types: None,
|
||||||
|
monobehaviour_guids: None,
|
||||||
parse_all: true,
|
parse_all: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +62,7 @@ impl TypeFilter {
|
|||||||
Self {
|
Self {
|
||||||
unity_types: Some(unity_types.into_iter().map(|s| s.into()).collect()),
|
unity_types: Some(unity_types.into_iter().map(|s| s.into()).collect()),
|
||||||
custom_types: Some(custom_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,
|
parse_all: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +72,7 @@ impl TypeFilter {
|
|||||||
Self {
|
Self {
|
||||||
unity_types: Some(types.into_iter().map(|s| s.into()).collect()),
|
unity_types: Some(types.into_iter().map(|s| s.into()).collect()),
|
||||||
custom_types: Some(HashSet::new()),
|
custom_types: Some(HashSet::new()),
|
||||||
|
monobehaviour_guids: None,
|
||||||
parse_all: false,
|
parse_all: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +82,37 @@ impl TypeFilter {
|
|||||||
Self {
|
Self {
|
||||||
unity_types: Some(HashSet::new()),
|
unity_types: Some(HashSet::new()),
|
||||||
custom_types: Some(types.into_iter().map(|s| s.into()).collect()),
|
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<String>) -> 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<Guid>) -> 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<String>, guids: HashSet<Guid>) -> Self {
|
||||||
|
Self {
|
||||||
|
unity_types: Some(types),
|
||||||
|
custom_types: None,
|
||||||
|
monobehaviour_guids: Some(guids),
|
||||||
parse_all: false,
|
parse_all: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +149,44 @@ impl TypeFilter {
|
|||||||
self.should_parse_unity(type_name)
|
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 {
|
impl Default for TypeFilter {
|
||||||
@@ -158,4 +239,88 @@ mod tests {
|
|||||||
assert!(filter.should_parse_custom("PlaySFX"));
|
assert!(filter.should_parse_custom("PlaySFX"));
|
||||||
assert!(!filter.should_parse_custom("Interact"));
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
resources_output.txt
Normal file
19
resources_output.txt
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user