unity_asset_binary/metadata/
extractor.rs1use super::types::*;
6use crate::error::Result;
7use crate::{AssetBundle, SerializedFile};
8use std::collections::HashMap;
9use std::time::Instant;
10
11pub struct MetadataExtractor {
16 config: ExtractionConfig,
17}
18
19impl MetadataExtractor {
20 pub fn new() -> Self {
22 Self {
23 config: ExtractionConfig::default(),
24 }
25 }
26
27 pub fn with_config(config: ExtractionConfig) -> Self {
29 Self { config }
30 }
31
32 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 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 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 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 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 result.metadata.file_info = self.extract_file_info(asset);
86
87 result.metadata.object_stats = self.extract_object_statistics(&objects_to_analyze);
89
90 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 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 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 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(), file_format_version: asset.header.version,
143 }
144 }
145
146 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 let class_name = self.get_class_name_from_type_id(obj.type_id);
156
157 *objects_by_type.entry(class_name.clone()).or_insert(0) += 1;
159
160 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 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)), byte_size: obj.byte_size,
172 dependencies: Vec::new(), });
174 }
175 }
176
177 object_summaries.sort_by(|a, b| b.byte_size.cmp(&a.byte_size));
179 if object_summaries.len() > 100 {
180 object_summaries.truncate(100); }
182
183 let largest_type = memory_by_type
185 .iter()
186 .max_by_key(|&(_, &size)| size)
187 .map(|(name, _)| name.clone());
188
189 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 fn extract_dependencies(
211 &self,
212 _objects: &[&crate::asset::ObjectInfo],
213 ) -> Result<DependencyInfo> {
214 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 fn extract_relationships(
231 &self,
232 _objects: &[&crate::asset::ObjectInfo],
233 ) -> Result<AssetRelationships> {
234 Ok(AssetRelationships {
237 gameobject_hierarchy: Vec::new(),
238 component_relationships: Vec::new(),
239 asset_references: Vec::new(),
240 })
241 }
242
243 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 let complexity_score = self.calculate_complexity_score(asset);
258
259 PerformanceMetrics {
260 parse_time_ms,
261 memory_peak_mb: 0.0, object_parse_rate,
263 complexity_score,
264 }
265 }
266
267 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 let base_score = object_count * 0.1 + type_count * 0.5 + external_count * 0.3;
275
276 (base_score / 100.0).min(100.0)
278 }
279
280 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 pub fn config(&self) -> &ExtractionConfig {
300 &self.config
301 }
302
303 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 }
343}