unity_asset_binary/metadata/
extractor.rs1use super::types::*;
6use crate::asset::SerializedFile;
7use crate::bundle::AssetBundle;
8use crate::error::Result;
9use std::collections::HashMap;
10use std::time::Instant;
11
12pub struct MetadataExtractor {
17 config: ExtractionConfig,
18}
19
20impl MetadataExtractor {
21 pub fn new() -> Self {
23 Self {
24 config: ExtractionConfig::default(),
25 }
26 }
27
28 pub fn with_config(config: ExtractionConfig) -> Self {
30 Self { config }
31 }
32
33 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 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 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 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 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 result.metadata.file_info = self.extract_file_info(asset);
87
88 result.metadata.object_stats = self.extract_object_statistics(&objects_to_analyze);
90
91 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 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 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 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(), file_format_version: asset.header.version,
144 }
145 }
146
147 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 let class_name = self.get_class_name_from_type_id(obj.type_id);
157
158 *objects_by_type.entry(class_name.clone()).or_insert(0) += 1;
160
161 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 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)), byte_size: obj.byte_size,
173 dependencies: Vec::new(), });
175 }
176 }
177
178 object_summaries.sort_by(|a, b| b.byte_size.cmp(&a.byte_size));
180 if object_summaries.len() > 100 {
181 object_summaries.truncate(100); }
183
184 let largest_type = memory_by_type
186 .iter()
187 .max_by_key(|&(_, &size)| size)
188 .map(|(name, _)| name.clone());
189
190 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 fn extract_dependencies(
212 &self,
213 _objects: &[&crate::asset::ObjectInfo],
214 ) -> Result<DependencyInfo> {
215 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 fn extract_relationships(
232 &self,
233 _objects: &[&crate::asset::ObjectInfo],
234 ) -> Result<AssetRelationships> {
235 Ok(AssetRelationships {
238 gameobject_hierarchy: Vec::new(),
239 component_relationships: Vec::new(),
240 asset_references: Vec::new(),
241 })
242 }
243
244 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 let complexity_score = self.calculate_complexity_score(asset);
259
260 PerformanceMetrics {
261 parse_time_ms,
262 memory_peak_mb: 0.0, object_parse_rate,
264 complexity_score,
265 }
266 }
267
268 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 let base_score = object_count * 0.1 + type_count * 0.5 + external_count * 0.3;
276
277 (base_score / 100.0).min(100.0)
279 }
280
281 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 pub fn config(&self) -> &ExtractionConfig {
301 &self.config
302 }
303
304 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 }
344}