meta phase 1

This commit is contained in:
2026-01-03 03:54:17 +00:00
parent 7e3d338373
commit 673fa114fa
6 changed files with 830 additions and 5 deletions

View File

@@ -38,15 +38,15 @@ pub mod types;
// Re-exports
pub use error::{Error, Result};
pub use model::{RawDocument, UnityAsset, UnityFile, UnityPrefab, UnityScene};
pub use parser::{meta::MetaFile, parse_unity_file};
pub use parser::{find_project_root, meta::MetaFile, parse_unity_file, GuidResolver};
// TODO: Re-enable once project module is updated
// pub use project::UnityProject;
pub use property::PropertyValue;
pub use types::{
get_class_name, get_type_id, Color, ComponentContext, ComponentRegistration, EcsInsertable,
ExternalRef, FileID, FileRef, GameObject, LocalID, PrefabInstance, PrefabInstanceComponent,
PrefabModification, PrefabResolver, Quaternion, RectTransform, Transform, TypeFilter,
UnityComponent, UnityReference, Vector2, Vector3, yaml_helpers,
ExternalRef, FileID, FileRef, GameObject, Guid, LocalID, PrefabInstance,
PrefabInstanceComponent, PrefabModification, PrefabResolver, Quaternion, RectTransform,
Transform, TypeFilter, UnityComponent, UnityReference, Vector2, Vector3, yaml_helpers,
};
// Re-export the derive macro from the macro crate

View File

@@ -0,0 +1,492 @@
//! Unity GUID resolution for MonoBehaviour scripts
//!
//! This module resolves Unity GUIDs to their corresponding MonoBehaviour class names
//! by scanning .meta files and parsing C# scripts.
//!
//! # How Unity GUID Resolution Works
//!
//! 1. Every asset has a `.meta` file with a unique GUID
//! 2. MonoBehaviour components reference scripts via GUID in `m_Script`
//! 3. We scan `.cs.meta` files to build a GUID → Script Path mapping
//! 4. We parse the `.cs` files to extract the class name
//! 5. Result: GUID → Class Name mapping for component registration
//!
//! # Example
//!
//! ```no_run
//! use cursebreaker_parser::parser::GuidResolver;
//! use std::path::Path;
//!
//! // Build resolver from Unity project directory
//! let project_path = Path::new("path/to/UnityProject");
//! let resolver = GuidResolver::from_project(project_path)?;
//!
//! // Resolve a GUID to class name
//! let guid = "091c537484687e9419460cdcd7038234";
//! if let Some(class_name) = resolver.resolve_class_name(guid) {
//! println!("GUID {} → {}", guid, class_name);
//! }
//! # Ok::<(), cursebreaker_parser::Error>(())
//! ```
use crate::parser::meta::MetaFile;
use crate::types::Guid;
use crate::{Error, Result};
use regex::Regex;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
/// Resolves Unity GUIDs to MonoBehaviour class names
///
/// This struct builds a mapping from GUID to class name by scanning
/// a Unity project's `.cs.meta` files and parsing the corresponding
/// C# scripts to extract class names.
///
/// Uses a 128-bit `Guid` type for efficient storage and fast comparisons.
#[derive(Debug, Clone)]
pub struct GuidResolver {
/// Map from GUID to MonoBehaviour class name
guid_to_class: HashMap<Guid, String>,
}
impl GuidResolver {
/// Create a new empty GuidResolver
pub fn new() -> Self {
Self {
guid_to_class: HashMap::new(),
}
}
/// Build a GuidResolver by scanning a Unity project directory
///
/// This scans for all `.cs.meta` files (MonoBehaviour scripts),
/// parses them to extract GUIDs, then parses the corresponding
/// C# files to extract class names.
///
/// # Arguments
///
/// * `project_path` - Path to the Unity project root (containing Assets/ folder)
///
/// # Examples
///
/// ```no_run
/// use cursebreaker_parser::parser::GuidResolver;
/// use std::path::Path;
///
/// let resolver = GuidResolver::from_project(Path::new("MyUnityProject"))?;
/// # Ok::<(), cursebreaker_parser::Error>(())
/// ```
pub fn from_project(project_path: impl AsRef<Path>) -> Result<Self> {
let project_path = project_path.as_ref();
// Verify this looks like a Unity project
let assets_dir = project_path.join("Assets");
if !assets_dir.exists() {
return Err(Error::invalid_format(format!(
"Not a Unity project: missing Assets/ directory at {}",
project_path.display()
)));
}
let mut resolver = Self::new();
// Walk the Assets directory looking for .cs.meta files
for entry in WalkDir::new(&assets_dir)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
// Only process .cs.meta files (MonoBehaviour scripts)
if !is_cs_meta_file(path) {
continue;
}
// Parse the .meta file to get the GUID
let meta = match MetaFile::from_path(path) {
Ok(meta) => meta,
Err(e) => {
eprintln!("Warning: Failed to parse {}: {}", path.display(), e);
continue;
}
};
// Get the corresponding .cs file path
let cs_path = path.with_file_name(
path.file_name()
.and_then(|s| s.to_str())
.and_then(|s| s.strip_suffix(".meta"))
.unwrap_or(""),
);
// Extract the class name from the .cs file
let class_name = match extract_class_name(&cs_path) {
Ok(Some(name)) => name,
Ok(None) => {
// No MonoBehaviour class found in this file
continue;
}
Err(e) => {
eprintln!(
"Warning: Failed to extract class name from {}: {}",
cs_path.display(),
e
);
continue;
}
};
// Parse the GUID string to Guid type
let guid = match Guid::from_hex(meta.guid()) {
Ok(g) => g,
Err(e) => {
eprintln!(
"Warning: Invalid GUID in {}: {}",
path.display(),
e
);
continue;
}
};
// Store the mapping
resolver.guid_to_class.insert(guid, class_name);
}
Ok(resolver)
}
/// Resolve a GUID to its class name
///
/// Accepts either a `&Guid` or a string that can be parsed as a GUID.
///
/// # Arguments
///
/// * `guid` - The Unity GUID (e.g., "091c537484687e9419460cdcd7038234" or a `Guid`)
///
/// # Returns
///
/// The class name if the GUID is found, otherwise None
///
/// # Examples
///
/// ```no_run
/// # use cursebreaker_parser::{GuidResolver, Guid};
/// # use std::path::Path;
/// # let resolver = GuidResolver::from_project(Path::new("."))?;
/// // Resolve by string
/// if let Some(class_name) = resolver.resolve_class_name("091c537484687e9419460cdcd7038234") {
/// println!("Found class: {}", class_name);
/// }
///
/// // Resolve by Guid
/// let guid = Guid::from_hex("091c537484687e9419460cdcd7038234")?;
/// if let Some(class_name) = resolver.resolve_class_name(&guid) {
/// println!("Found class: {}", class_name);
/// }
/// # Ok::<(), cursebreaker_parser::Error>(())
/// ```
pub fn resolve_class_name<G: AsGuid>(&self, guid: G) -> Option<&str> {
guid.as_guid()
.and_then(|g| self.guid_to_class.get(&g))
.map(|s| s.as_str())
}
/// Get the number of GUID mappings
pub fn len(&self) -> usize {
self.guid_to_class.len()
}
/// Check if the resolver is empty
pub fn is_empty(&self) -> bool {
self.guid_to_class.is_empty()
}
/// Insert a GUID → class name mapping manually
///
/// Useful for testing or adding custom mappings
///
/// # Examples
///
/// ```
/// use cursebreaker_parser::{GuidResolver, Guid};
///
/// let mut resolver = GuidResolver::new();
/// let guid = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
/// resolver.insert(guid, "PlaySFX".to_string());
/// ```
pub fn insert(&mut self, guid: Guid, class_name: String) {
self.guid_to_class.insert(guid, class_name);
}
/// Get all resolved GUIDs
///
/// Returns an iterator over all GUIDs in the resolver.
pub fn guids(&self) -> impl Iterator<Item = Guid> + '_ {
self.guid_to_class.keys().copied()
}
}
impl Default for GuidResolver {
fn default() -> Self {
Self::new()
}
}
/// Trait for types that can be converted to a GUID for lookup
///
/// This allows the `resolve_class_name` method to accept both `&Guid` and `&str`.
pub trait AsGuid {
/// Convert to a GUID, returning None if conversion fails
fn as_guid(&self) -> Option<Guid>;
}
impl AsGuid for Guid {
fn as_guid(&self) -> Option<Guid> {
Some(*self)
}
}
impl AsGuid for &Guid {
fn as_guid(&self) -> Option<Guid> {
Some(**self)
}
}
impl AsGuid for str {
fn as_guid(&self) -> Option<Guid> {
Guid::from_hex(self).ok()
}
}
impl AsGuid for &str {
fn as_guid(&self) -> Option<Guid> {
Guid::from_hex(self).ok()
}
}
impl AsGuid for String {
fn as_guid(&self) -> Option<Guid> {
Guid::from_hex(self).ok()
}
}
/// Check if a path points to a C# .meta file
fn is_cs_meta_file(path: &Path) -> bool {
path.extension()
.and_then(|s| s.to_str())
.map(|ext| ext == "meta")
.unwrap_or(false)
&& path
.file_name()
.and_then(|s| s.to_str())
.map(|name| name.ends_with(".cs.meta"))
.unwrap_or(false)
}
/// Extract the MonoBehaviour class name from a C# script file
///
/// This looks for patterns like:
/// - `public class ClassName : MonoBehaviour`
/// - `class ClassName : MonoBehaviour`
/// - `public class ClassName:MonoBehaviour`
///
/// # Arguments
///
/// * `cs_path` - Path to the .cs file
///
/// # Returns
///
/// Ok(Some(class_name)) if a MonoBehaviour class was found
/// Ok(None) if no MonoBehaviour class was found
/// Err if the file couldn't be read
fn extract_class_name(cs_path: &Path) -> Result<Option<String>> {
let content = std::fs::read_to_string(cs_path)?;
// Regex to match MonoBehaviour class declarations
// Matches: public class ClassName : MonoBehaviour
// Also matches: class ClassName:MonoBehaviour (no space)
// Captures the class name in group 1
let class_regex = Regex::new(
r"(?:public\s+)?class\s+(\w+)\s*:\s*MonoBehaviour"
).unwrap();
// Find the first MonoBehaviour class in the file
if let Some(captures) = class_regex.captures(&content) {
if let Some(class_name) = captures.get(1) {
return Ok(Some(class_name.as_str().to_string()));
}
}
Ok(None)
}
/// Find the Unity project root from any path within the project
///
/// Searches upward from the given path until it finds a directory
/// containing an "Assets" folder.
///
/// # Arguments
///
/// * `path` - Any path within the Unity project (file or directory)
///
/// # Returns
///
/// The project root path if found, otherwise an error
///
/// # Examples
///
/// ```no_run
/// use cursebreaker_parser::parser::find_project_root;
/// use std::path::Path;
///
/// let scene_path = Path::new("MyProject/Assets/Scenes/Main.unity");
/// let project_root = find_project_root(scene_path)?;
/// assert_eq!(project_root.file_name().unwrap(), "MyProject");
/// # Ok::<(), cursebreaker_parser::Error>(())
/// ```
pub fn find_project_root(path: impl AsRef<Path>) -> Result<PathBuf> {
let path = path.as_ref();
// Start from the file's directory (or the directory itself)
let mut current = if path.is_file() {
path.parent().ok_or_else(|| {
Error::invalid_format("Cannot get parent directory")
})?
} else {
path
};
// Search upward for Assets/ directory
loop {
let assets_dir = current.join("Assets");
if assets_dir.exists() && assets_dir.is_dir() {
return Ok(current.to_path_buf());
}
// Move up one directory
current = current.parent().ok_or_else(|| {
Error::invalid_format(format!(
"Could not find Unity project root from {}",
path.display()
))
})?;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_cs_meta_file() {
assert!(is_cs_meta_file(Path::new("PlaySFX.cs.meta")));
assert!(is_cs_meta_file(Path::new("Assets/Scripts/PlaySFX.cs.meta")));
assert!(!is_cs_meta_file(Path::new("PlaySFX.cs")));
assert!(!is_cs_meta_file(Path::new("Scene.unity.meta")));
assert!(!is_cs_meta_file(Path::new("texture.png.meta")));
}
#[test]
fn test_extract_class_name() {
// Test standard MonoBehaviour class
let content = r#"
using UnityEngine;
public class PlaySFX : MonoBehaviour
{
public float volume = 1.0f;
}
"#;
let temp_file = std::env::temp_dir().join("test_playsfx.cs");
std::fs::write(&temp_file, content).unwrap();
let result = extract_class_name(&temp_file).unwrap();
assert_eq!(result, Some("PlaySFX".to_string()));
std::fs::remove_file(&temp_file).ok();
}
#[test]
fn test_extract_class_name_no_public() {
// Test without public modifier
let content = r#"
using UnityEngine;
class MyBehaviour : MonoBehaviour
{
void Start() {}
}
"#;
let temp_file = std::env::temp_dir().join("test_mybehaviour.cs");
std::fs::write(&temp_file, content).unwrap();
let result = extract_class_name(&temp_file).unwrap();
assert_eq!(result, Some("MyBehaviour".to_string()));
std::fs::remove_file(&temp_file).ok();
}
#[test]
fn test_extract_class_name_no_space() {
// Test with no space after colon
let content = r#"
using UnityEngine;
public class TestScript:MonoBehaviour
{
void Update() {}
}
"#;
let temp_file = std::env::temp_dir().join("test_testscript.cs");
std::fs::write(&temp_file, content).unwrap();
let result = extract_class_name(&temp_file).unwrap();
assert_eq!(result, Some("TestScript".to_string()));
std::fs::remove_file(&temp_file).ok();
}
#[test]
fn test_extract_class_name_not_monobehaviour() {
// Test class that doesn't inherit from MonoBehaviour
let content = r#"
using UnityEngine;
public class HelperClass
{
public int value;
}
"#;
let temp_file = std::env::temp_dir().join("test_helper.cs");
std::fs::write(&temp_file, content).unwrap();
let result = extract_class_name(&temp_file).unwrap();
assert_eq!(result, None);
std::fs::remove_file(&temp_file).ok();
}
#[test]
fn test_guid_resolver_manual() {
let mut resolver = GuidResolver::new();
assert!(resolver.is_empty());
let guid = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
resolver.insert(guid, "PlaySFX".to_string());
assert_eq!(resolver.len(), 1);
// Test resolving by string
assert_eq!(
resolver.resolve_class_name("091c537484687e9419460cdcd7038234"),
Some("PlaySFX")
);
// Test resolving by Guid
assert_eq!(
resolver.resolve_class_name(&guid),
Some("PlaySFX")
);
// Test nonexistent GUID
assert_eq!(
resolver.resolve_class_name("00000000000000000000000000000000"),
None
);
}
}

View File

@@ -1,9 +1,11 @@
//! Unity YAML parsing module
pub mod guid_resolver;
pub mod meta;
mod unity_tag;
mod yaml;
pub use guid_resolver::{find_project_root, GuidResolver};
pub use meta::{get_meta_path, MetaFile};
pub use unity_tag::{parse_unity_tag, UnityTag};
pub use yaml::split_yaml_documents;

View File

@@ -0,0 +1,255 @@
//! Unity GUID type
//!
//! Unity uses 128-bit GUIDs to uniquely identify assets across a project.
//! GUIDs are stored as 32 hexadecimal characters (e.g., "091c537484687e9419460cdcd7038234").
//!
//! This module provides a type-safe, efficient representation of Unity GUIDs
//! using a 128-bit integer for fast comparisons and minimal memory footprint.
use crate::{Error, Result};
use std::fmt;
use std::str::FromStr;
/// A Unity GUID represented as a 128-bit integer
///
/// Unity GUIDs are 32 hexadecimal characters representing a 128-bit value.
/// This type stores the GUID as a `u128` for efficient storage and comparison.
///
/// # Example
///
/// ```
/// use cursebreaker_parser::Guid;
/// use std::str::FromStr;
///
/// // Parse from string
/// let guid = Guid::from_str("091c537484687e9419460cdcd7038234").unwrap();
///
/// // Convert back to string
/// assert_eq!(guid.to_string(), "091c537484687e9419460cdcd7038234");
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Guid(u128);
impl Guid {
/// Create a new GUID from a 128-bit integer
///
/// # Example
///
/// ```
/// use cursebreaker_parser::Guid;
///
/// let guid = Guid::from_u128(0x091c537484687e9419460cdcd7038234);
/// ```
pub const fn from_u128(value: u128) -> Self {
Guid(value)
}
/// Get the underlying 128-bit integer value
///
/// # Example
///
/// ```
/// use cursebreaker_parser::Guid;
///
/// let guid = Guid::from_u128(42);
/// assert_eq!(guid.as_u128(), 42);
/// ```
pub const fn as_u128(&self) -> u128 {
self.0
}
/// Create a GUID from a hex string
///
/// The string must be exactly 32 hexadecimal characters.
///
/// # Errors
///
/// Returns an error if the string is not valid hex or not 32 characters.
///
/// # Example
///
/// ```
/// use cursebreaker_parser::Guid;
///
/// let guid = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
/// ```
pub fn from_hex(s: &str) -> Result<Self> {
// Unity GUIDs are always 32 hex characters (128 bits)
if s.len() != 32 {
return Err(Error::invalid_format(format!(
"GUID must be 32 hex characters, got {}",
s.len()
)));
}
// Parse hex string to u128
let value = u128::from_str_radix(s, 16).map_err(|e| {
Error::invalid_format(format!("Invalid hex in GUID '{}': {}", s, e))
})?;
Ok(Guid(value))
}
/// Convert the GUID to a lowercase hex string
///
/// Returns a 32-character hex string representing the GUID.
///
/// # Example
///
/// ```
/// use cursebreaker_parser::Guid;
/// use std::str::FromStr;
///
/// let guid = Guid::from_str("091c537484687e9419460cdcd7038234").unwrap();
/// assert_eq!(guid.to_hex(), "091c537484687e9419460cdcd7038234");
/// ```
pub fn to_hex(&self) -> String {
format!("{:032x}", self.0)
}
/// A zero GUID (all bits set to 0)
pub const ZERO: Guid = Guid(0);
}
impl FromStr for Guid {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_hex(s)
}
}
impl fmt::Display for Guid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:032x}", self.0)
}
}
impl From<u128> for Guid {
fn from(value: u128) -> Self {
Guid(value)
}
}
impl From<Guid> for u128 {
fn from(guid: Guid) -> Self {
guid.0
}
}
// Implement serde serialization if serde feature is enabled
#[cfg(feature = "serde")]
impl serde::Serialize for Guid {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_hex())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Guid {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Guid::from_hex(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_guid_from_hex() {
let guid = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
assert_eq!(guid.to_hex(), "091c537484687e9419460cdcd7038234");
}
#[test]
fn test_guid_from_str() {
let guid: Guid = "091c537484687e9419460cdcd7038234".parse().unwrap();
assert_eq!(guid.to_string(), "091c537484687e9419460cdcd7038234");
}
#[test]
fn test_guid_invalid_length() {
assert!(Guid::from_hex("123").is_err());
assert!(Guid::from_hex("091c537484687e9419460cdcd70382341234").is_err());
}
#[test]
fn test_guid_invalid_hex() {
assert!(Guid::from_hex("091c537484687e9419460cdcd703823g").is_err());
assert!(Guid::from_hex("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ").is_err());
}
#[test]
fn test_guid_zero() {
let zero = Guid::ZERO;
assert_eq!(zero.as_u128(), 0);
assert_eq!(zero.to_hex(), "00000000000000000000000000000000");
}
#[test]
fn test_guid_from_u128() {
let value: u128 = 0x091c537484687e9419460cdcd7038234;
let guid = Guid::from_u128(value);
assert_eq!(guid.as_u128(), value);
assert_eq!(guid.to_hex(), "091c537484687e9419460cdcd7038234");
}
#[test]
fn test_guid_equality() {
let guid1 = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
let guid2 = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
let guid3 = Guid::from_hex("091c537484687e9419460cdcd7038235").unwrap();
assert_eq!(guid1, guid2);
assert_ne!(guid1, guid3);
}
#[test]
fn test_guid_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
let guid1 = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
let guid2 = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
let guid3 = Guid::from_hex("091c537484687e9419460cdcd7038235").unwrap();
set.insert(guid1);
set.insert(guid2); // Duplicate, shouldn't increase size
set.insert(guid3);
assert_eq!(set.len(), 2);
}
#[test]
fn test_guid_ordering() {
let guid1 = Guid::from_hex("091c537484687e9419460cdcd7038234").unwrap();
let guid2 = Guid::from_hex("091c537484687e9419460cdcd7038235").unwrap();
assert!(guid1 < guid2);
assert!(guid2 > guid1);
}
#[test]
fn test_guid_uppercase_hex() {
// Unity GUIDs are lowercase, but we should handle uppercase too
let guid = Guid::from_hex("091C537484687E9419460CDCD7038234").unwrap();
// Output is always lowercase
assert_eq!(guid.to_hex(), "091c537484687e9419460cdcd7038234");
}
#[test]
fn test_guid_conversion() {
let value: u128 = 12345678901234567890;
let guid: Guid = value.into();
let back: u128 = guid.into();
assert_eq!(value, back);
}
}

View File

@@ -6,6 +6,7 @@
mod component;
mod game_object;
mod guid;
mod ids;
mod prefab_instance;
mod reference;
@@ -19,6 +20,7 @@ pub use component::{
LinkingContext, UnityComponent,
};
pub use game_object::GameObject;
pub use guid::Guid;
pub use ids::{FileID, LocalID};
pub use prefab_instance::{
PrefabInstance, PrefabInstanceComponent, PrefabModification, PrefabResolver,

View File

@@ -1,6 +1,6 @@
//! Integration tests for parsing real Unity projects
use cursebreaker_parser::UnityFile;
use cursebreaker_parser::{GuidResolver, UnityFile};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
@@ -400,3 +400,77 @@ fn benchmark_parsing() {
stats.print_summary();
}
/// Test GUID resolution for MonoBehaviour scripts
#[test]
fn test_guid_resolution() {
let project_path = match clone_test_project(&TestProject::VR_HORROR) {
Ok(path) => path,
Err(e) => {
eprintln!("Failed to clone project: {}", e);
eprintln!("Skipping GUID resolution test (git may not be available)");
return;
}
};
println!("\n{}", "=".repeat(60));
println!("Testing GUID Resolution");
println!("{}", "=".repeat(60));
// Build GUID resolver from project
println!("\nBuilding GuidResolver from project...");
let resolver = GuidResolver::from_project(&project_path)
.expect("Should successfully build GuidResolver");
println!(" ✓ Found {} GUID mappings", resolver.len());
assert!(
!resolver.is_empty(),
"Should find at least some C# scripts with GUIDs"
);
// Test the known PlaySFX GUID from the roadmap
let playsfx_guid_str = "091c537484687e9419460cdcd7038234";
println!("\nTesting known GUID resolution:");
println!(" GUID: {}", playsfx_guid_str);
// Test resolution by string
match resolver.resolve_class_name(playsfx_guid_str) {
Some(class_name) => {
println!(" ✓ Resolved by string to: {}", class_name);
assert_eq!(
class_name, "PlaySFX",
"PlaySFX GUID should resolve to 'PlaySFX' class name"
);
}
None => {
panic!("Failed to resolve PlaySFX GUID. Available GUIDs: {:?}",
resolver.guids().take(5).collect::<Vec<_>>());
}
}
// Test resolution by Guid type
use cursebreaker_parser::Guid;
let playsfx_guid = Guid::from_hex(playsfx_guid_str).unwrap();
match resolver.resolve_class_name(&playsfx_guid) {
Some(class_name) => {
println!(" ✓ Resolved by Guid type to: {}", class_name);
assert_eq!(class_name, "PlaySFX");
}
None => panic!("Failed to resolve PlaySFX GUID by Guid type"),
}
// Show sample of resolved GUIDs
println!("\nSample of resolved GUIDs:");
for guid in resolver.guids().take(5) {
if let Some(class_name) = resolver.resolve_class_name(&guid) {
println!(" {}{}", guid, class_name);
}
}
// Demonstrate memory efficiency
println!("\nMemory efficiency:");
println!(" Each Guid: {} bytes (u128)", std::mem::size_of::<Guid>());
println!(" Each String GUID: ~{} bytes (heap allocated)", 32 + std::mem::size_of::<String>());
println!("\n{}", "=".repeat(60));
}