unity_asset_binary/metadata/
extractor.rs

1//! Metadata extraction implementation
2//!
3//! This module provides the main metadata extraction functionality for Unity assets.
4
5use super::types::*;
6use crate::error::Result;
7use crate::{AssetBundle, SerializedFile};
8use std::collections::HashMap;
9use std::time::Instant;
10
11/// Metadata extractor for Unity assets
12///
13/// This struct provides methods for extracting comprehensive metadata
14/// from Unity assets including statistics, dependencies, and relationships.
15pub struct MetadataExtractor {
16    config: ExtractionConfig,
17}
18
19impl MetadataExtractor {
20    /// Create a new metadata extractor with default settings
21    pub fn new() -> Self {
22        Self {
23            config: ExtractionConfig::default(),
24        }
25    }
26
27    /// Create a metadata extractor with custom configuration
28    pub fn with_config(config: ExtractionConfig) -> Self {
29        Self { config }
30    }
31
32    /// Create a metadata extractor with custom settings (legacy API)
33    pub fn with_settings(
34        include_dependencies: bool,
35        include_hierarchy: bool,
36        include_performance: bool,
37        max_objects: Option<usize>,
38    ) -> Self {
39        Self {
40            config: ExtractionConfig {
41                include_dependencies,
42                include_hierarchy,
43                max_objects,
44                include_performance,
45                include_object_details: true,
46            },
47        }
48    }
49
50    /// Extract metadata from an AssetBundle
51    pub fn extract_from_bundle(&self, bundle: &AssetBundle) -> Result<Vec<ExtractionResult>> {
52        let start_time = Instant::now();
53        let mut results = Vec::new();
54
55        for asset in &bundle.assets {
56            let result = self.extract_from_asset(asset)?;
57            results.push(result);
58        }
59
60        // Add bundle-level performance metrics
61        let total_time = start_time.elapsed().as_secs_f64() * 1000.0;
62        let asset_count = results.len() as f64;
63
64        for result in &mut results {
65            result.metadata.performance.parse_time_ms = total_time / asset_count;
66        }
67
68        Ok(results)
69    }
70
71    /// Extract metadata from a SerializedFile
72    pub fn extract_from_asset(&self, asset: &SerializedFile) -> Result<ExtractionResult> {
73        let start_time = Instant::now();
74        let mut result = ExtractionResult::new(AssetMetadata::new());
75
76        // Get objects to analyze
77        let objects_to_analyze: Vec<&crate::asset::ObjectInfo> =
78            if let Some(max) = self.config.max_objects {
79                asset.objects.iter().take(max).collect()
80            } else {
81                asset.objects.iter().collect()
82            };
83
84        // Extract basic file info
85        result.metadata.file_info = self.extract_file_info(asset);
86
87        // Extract object statistics
88        result.metadata.object_stats = self.extract_object_statistics(&objects_to_analyze);
89
90        // Extract dependencies if enabled
91        if self.config.include_dependencies {
92            match self.extract_dependencies(&objects_to_analyze) {
93                Ok(deps) => result.metadata.dependencies = deps,
94                Err(e) => {
95                    result.add_warning(format!("Failed to extract dependencies: {}", e));
96                    result.metadata.dependencies = DependencyInfo {
97                        external_references: Vec::new(),
98                        internal_references: Vec::new(),
99                        dependency_graph: DependencyGraph {
100                            nodes: Vec::new(),
101                            edges: Vec::new(),
102                            root_objects: Vec::new(),
103                            leaf_objects: Vec::new(),
104                        },
105                        circular_dependencies: Vec::new(),
106                    };
107                }
108            }
109        }
110
111        // Extract relationships if enabled
112        if self.config.include_hierarchy {
113            match self.extract_relationships(&objects_to_analyze) {
114                Ok(rels) => result.metadata.relationships = rels,
115                Err(e) => {
116                    result.add_warning(format!("Failed to extract relationships: {}", e));
117                    result.metadata.relationships = AssetRelationships {
118                        gameobject_hierarchy: Vec::new(),
119                        component_relationships: Vec::new(),
120                        asset_references: Vec::new(),
121                    };
122                }
123            }
124        }
125
126        // Extract performance metrics if enabled
127        if self.config.include_performance {
128            let elapsed = start_time.elapsed().as_secs_f64() * 1000.0;
129            result.metadata.performance = self.extract_performance_metrics(asset, elapsed);
130        }
131
132        Ok(result)
133    }
134
135    /// Extract basic file information
136    fn extract_file_info(&self, asset: &SerializedFile) -> FileInfo {
137        FileInfo {
138            file_size: asset.header.file_size as u64,
139            unity_version: asset.unity_version.clone(),
140            target_platform: format!("{}", asset.target_platform),
141            compression_type: "None".to_string(), // TODO: Detect compression
142            file_format_version: asset.header.version,
143        }
144    }
145
146    /// Extract object statistics
147    fn extract_object_statistics(&self, objects: &[&crate::asset::ObjectInfo]) -> ObjectStatistics {
148        let mut objects_by_type: HashMap<String, usize> = HashMap::new();
149        let mut memory_by_type: HashMap<String, u64> = HashMap::new();
150        let mut total_memory = 0u64;
151        let mut object_summaries = Vec::new();
152
153        for obj in objects {
154            // Get class name from type_id (simplified mapping)
155            let class_name = self.get_class_name_from_type_id(obj.type_id);
156
157            // Count objects by type
158            *objects_by_type.entry(class_name.clone()).or_insert(0) += 1;
159
160            // Sum memory by type
161            let byte_size = obj.byte_size as u64;
162            *memory_by_type.entry(class_name.clone()).or_insert(0u64) += byte_size;
163            total_memory += byte_size;
164
165            // Create object summary if detailed extraction is enabled
166            if self.config.include_object_details {
167                object_summaries.push(ObjectSummary {
168                    path_id: obj.path_id,
169                    class_name: class_name.clone(),
170                    name: Some(format!("Object_{}", obj.path_id)), // Simplified name
171                    byte_size: obj.byte_size,
172                    dependencies: Vec::new(), // TODO: Extract dependencies
173                });
174            }
175        }
176
177        // Sort by size and keep largest objects
178        object_summaries.sort_by(|a, b| b.byte_size.cmp(&a.byte_size));
179        if object_summaries.len() > 100 {
180            object_summaries.truncate(100); // Keep top 100
181        }
182
183        // Find largest type
184        let largest_type = memory_by_type
185            .iter()
186            .max_by_key(|&(_, &size)| size)
187            .map(|(name, _)| name.clone());
188
189        // Calculate average object size
190        let average_size = if objects.is_empty() {
191            0.0
192        } else {
193            total_memory as f64 / objects.len() as f64
194        };
195
196        ObjectStatistics {
197            total_objects: objects.len(),
198            objects_by_type,
199            largest_objects: object_summaries,
200            memory_usage: MemoryUsage {
201                total_bytes: total_memory,
202                by_type: memory_by_type,
203                largest_type,
204                average_object_size: average_size,
205            },
206        }
207    }
208
209    /// Extract dependency information (simplified implementation)
210    fn extract_dependencies(
211        &self,
212        _objects: &[&crate::asset::ObjectInfo],
213    ) -> Result<DependencyInfo> {
214        // TODO: Implement proper dependency extraction for new ObjectInfo structure
215        // This is a placeholder implementation
216        Ok(DependencyInfo {
217            external_references: Vec::new(),
218            internal_references: Vec::new(),
219            dependency_graph: DependencyGraph {
220                nodes: Vec::new(),
221                edges: Vec::new(),
222                root_objects: Vec::new(),
223                leaf_objects: Vec::new(),
224            },
225            circular_dependencies: Vec::new(),
226        })
227    }
228
229    /// Extract relationship information (simplified implementation)
230    fn extract_relationships(
231        &self,
232        _objects: &[&crate::asset::ObjectInfo],
233    ) -> Result<AssetRelationships> {
234        // TODO: Implement proper relationship extraction for new ObjectInfo structure
235        // This is a placeholder implementation
236        Ok(AssetRelationships {
237            gameobject_hierarchy: Vec::new(),
238            component_relationships: Vec::new(),
239            asset_references: Vec::new(),
240        })
241    }
242
243    /// Extract performance metrics
244    fn extract_performance_metrics(
245        &self,
246        asset: &SerializedFile,
247        parse_time_ms: f64,
248    ) -> PerformanceMetrics {
249        let object_count = asset.objects.len() as f64;
250        let object_parse_rate = if parse_time_ms > 0.0 {
251            (object_count * 1000.0) / parse_time_ms
252        } else {
253            0.0
254        };
255
256        // Calculate complexity score based on various factors
257        let complexity_score = self.calculate_complexity_score(asset);
258
259        PerformanceMetrics {
260            parse_time_ms,
261            memory_peak_mb: 0.0, // TODO: Implement memory tracking
262            object_parse_rate,
263            complexity_score,
264        }
265    }
266
267    /// Calculate complexity score for the asset
268    fn calculate_complexity_score(&self, asset: &SerializedFile) -> f64 {
269        let object_count = asset.objects.len() as f64;
270        let type_count = asset.types.len() as f64;
271        let external_count = asset.externals.len() as f64;
272
273        // Simple complexity calculation
274        let base_score = object_count * 0.1 + type_count * 0.5 + external_count * 0.3;
275
276        // Normalize to 0-100 scale
277        (base_score / 100.0).min(100.0)
278    }
279
280    /// Get class name from Unity type ID
281    fn get_class_name_from_type_id(&self, type_id: i32) -> String {
282        match type_id {
283            class_ids::GAME_OBJECT => "GameObject".to_string(),
284            class_ids::TRANSFORM => "Transform".to_string(),
285            class_ids::MATERIAL => "Material".to_string(),
286            class_ids::TEXTURE_2D => "Texture2D".to_string(),
287            class_ids::MESH => "Mesh".to_string(),
288            class_ids::SHADER => "Shader".to_string(),
289            class_ids::ANIMATION_CLIP => "AnimationClip".to_string(),
290            class_ids::AUDIO_CLIP => "AudioClip".to_string(),
291            class_ids::ANIMATOR_CONTROLLER => "AnimatorController".to_string(),
292            class_ids::MONO_BEHAVIOUR => "MonoBehaviour".to_string(),
293            class_ids::SPRITE => "Sprite".to_string(),
294            _ => format!("UnknownType_{}", type_id),
295        }
296    }
297
298    /// Get the current configuration
299    pub fn config(&self) -> &ExtractionConfig {
300        &self.config
301    }
302
303    /// Update the configuration
304    pub fn set_config(&mut self, config: ExtractionConfig) {
305        self.config = config;
306    }
307}
308
309impl Default for MetadataExtractor {
310    fn default() -> Self {
311        Self::new()
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn test_extractor_creation() {
321        let extractor = MetadataExtractor::new();
322        assert!(extractor.config().include_dependencies);
323        assert!(extractor.config().include_hierarchy);
324    }
325
326    #[test]
327    fn test_class_name_mapping() {
328        let extractor = MetadataExtractor::new();
329        assert_eq!(extractor.get_class_name_from_type_id(1), "GameObject");
330        assert_eq!(extractor.get_class_name_from_type_id(28), "Texture2D");
331        assert_eq!(
332            extractor.get_class_name_from_type_id(999),
333            "UnknownType_999"
334        );
335    }
336
337    #[test]
338    fn test_complexity_calculation() {
339        let _extractor = MetadataExtractor::new();
340        // This would need a mock SerializedFile for proper testing
341        // For now, just test that the method exists and doesn't panic
342    }
343}