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