1pub mod analyzer;
37pub mod extractor;
38pub mod types;
39
40pub use analyzer::{DependencyAnalyzer, RelationshipAnalyzer};
42pub use extractor::MetadataExtractor;
43pub use types::{
44 AssetMetadata,
46 AssetReference,
47 AssetRelationships,
49 ComponentRelationship,
50 DependencyGraph,
51 DependencyInfo,
53 ExternalObjectRef,
54 ExternalReference,
55 ExtractionConfig,
56 ExtractionResult,
57 ExtractionStats,
58 FileInfo,
59 GameObjectHierarchy,
60 InternalReference,
61 MemoryUsage,
62 ObjectStatistics,
63 ObjectSummary,
64 PerformanceMetrics,
66 class_ids,
68};
69
70use crate::asset::SerializedFile;
71use crate::bundle::AssetBundle;
72
73pub struct MetadataProcessor {
78 extractor: MetadataExtractor,
79 dependency_analyzer: Option<DependencyAnalyzer>,
80 relationship_analyzer: Option<RelationshipAnalyzer>,
81}
82
83fn apply_dependency_info_to_relationships(
84 dependencies: &DependencyInfo,
85 relationships: &mut AssetRelationships,
86) {
87 let mut by_from: std::collections::HashMap<i64, Vec<i64>> = std::collections::HashMap::new();
88 for r in &dependencies.internal_references {
89 by_from.entry(r.from_object).or_default().push(r.to_object);
90 }
91 for v in by_from.values_mut() {
92 v.sort_unstable();
93 v.dedup();
94 }
95
96 let mut by_from_external: std::collections::HashMap<i64, Vec<ExternalObjectRef>> =
97 std::collections::HashMap::new();
98 for r in &dependencies.external_references {
99 let ext = ExternalObjectRef {
100 file_id: r.file_id,
101 path_id: r.path_id,
102 file_path: r.file_path.clone(),
103 guid: r.guid,
104 };
105 for from in &r.referenced_by {
106 by_from_external.entry(*from).or_default().push(ext.clone());
107 }
108 }
109 for v in by_from_external.values_mut() {
110 v.sort_by_key(|e| (e.file_id, e.path_id));
111 v.dedup_by_key(|e| (e.file_id, e.path_id));
112 }
113
114 for rel in &mut relationships.component_relationships {
115 rel.dependencies = by_from.get(&rel.component_id).cloned().unwrap_or_default();
116 rel.external_dependencies = by_from_external
117 .get(&rel.component_id)
118 .cloned()
119 .unwrap_or_default();
120 }
121
122 let gameobject_ids: std::collections::HashSet<i64> = relationships
124 .gameobject_hierarchy
125 .iter()
126 .map(|h| h.gameobject_id)
127 .collect();
128 let component_ids: std::collections::HashSet<i64> = relationships
129 .component_relationships
130 .iter()
131 .map(|c| c.component_id)
132 .collect();
133
134 let mut referenced_by_internal: std::collections::HashMap<i64, Vec<i64>> =
135 std::collections::HashMap::new();
136 for r in &dependencies.internal_references {
137 referenced_by_internal
138 .entry(r.to_object)
139 .or_default()
140 .push(r.from_object);
141 }
142 for v in referenced_by_internal.values_mut() {
143 v.sort_unstable();
144 v.dedup();
145 }
146
147 let mut refs: Vec<AssetReference> = Vec::new();
148
149 for (asset_id, referenced_by) in referenced_by_internal {
150 let asset_type = if gameobject_ids.contains(&asset_id) {
151 "GameObject".to_string()
152 } else if component_ids.contains(&asset_id) {
153 "Component".to_string()
154 } else {
155 "Object".to_string()
156 };
157 refs.push(AssetReference {
158 asset_id,
159 asset_type,
160 referenced_by,
161 file_path: None,
162 });
163 }
164
165 for r in &dependencies.external_references {
166 refs.push(AssetReference {
167 asset_id: r.path_id,
168 asset_type: format!("ExternalObject(file_id={})", r.file_id),
169 referenced_by: r.referenced_by.clone(),
170 file_path: r.file_path.clone(),
171 });
172 }
173
174 refs.sort_by(|a, b| {
175 match (b.file_path.is_some()).cmp(&a.file_path.is_some()) {
177 std::cmp::Ordering::Equal => {}
178 ord => return ord,
179 }
180 match b.referenced_by.len().cmp(&a.referenced_by.len()) {
181 std::cmp::Ordering::Equal => {}
182 ord => return ord,
183 }
184 a.asset_id.cmp(&b.asset_id)
185 });
186
187 relationships.asset_references = refs;
188}
189
190impl MetadataProcessor {
191 pub fn new() -> Self {
193 Self {
194 extractor: MetadataExtractor::new(),
195 dependency_analyzer: None,
196 relationship_analyzer: None,
197 }
198 }
199
200 pub fn with_config(config: ExtractionConfig) -> Self {
202 let enable_advanced = config.include_dependencies || config.include_hierarchy;
203
204 Self {
205 extractor: MetadataExtractor::with_config(config),
206 dependency_analyzer: if enable_advanced {
207 Some(DependencyAnalyzer::new())
208 } else {
209 None
210 },
211 relationship_analyzer: if enable_advanced {
212 Some(RelationshipAnalyzer::new())
213 } else {
214 None
215 },
216 }
217 }
218
219 pub fn process_asset(
221 &mut self,
222 asset: &SerializedFile,
223 ) -> crate::error::Result<ExtractionResult> {
224 let mut result = self.extractor.extract_from_asset(asset)?;
225
226 if let Some(ref mut analyzer) = self.dependency_analyzer
228 && self.extractor.config().include_dependencies
229 {
230 let objects: Vec<&crate::asset::ObjectInfo> =
231 if let Some(max) = self.extractor.config().max_objects {
232 asset.objects.iter().take(max).collect()
233 } else {
234 asset.objects.iter().collect()
235 };
236
237 match analyzer.analyze_dependencies_in_asset(asset, &objects) {
238 Ok(deps) => {
239 if self.extractor.config().include_object_details {
240 let mut by_from: std::collections::HashMap<i64, Vec<i64>> =
241 std::collections::HashMap::new();
242 for r in &deps.internal_references {
243 by_from.entry(r.from_object).or_default().push(r.to_object);
244 }
245 for v in by_from.values_mut() {
246 v.sort_unstable();
247 v.dedup();
248 }
249 for summary in &mut result.metadata.object_stats.largest_objects {
250 summary.dependencies =
251 by_from.get(&summary.path_id).cloned().unwrap_or_default();
252 }
253 }
254 result.metadata.dependencies = deps;
255 }
256 Err(e) => {
257 result.add_warning(format!("Enhanced dependency analysis failed: {}", e));
258 }
259 }
260 }
261
262 if let Some(ref mut analyzer) = self.relationship_analyzer
264 && self.extractor.config().include_hierarchy
265 {
266 let objects: Vec<&crate::asset::ObjectInfo> =
267 if let Some(max) = self.extractor.config().max_objects {
268 asset.objects.iter().take(max).collect()
269 } else {
270 asset.objects.iter().collect()
271 };
272
273 match analyzer.analyze_relationships_in_asset(asset, &objects) {
274 Ok(mut rels) => {
275 if self.extractor.config().include_dependencies {
276 apply_dependency_info_to_relationships(
277 &result.metadata.dependencies,
278 &mut rels,
279 );
280 }
281 result.metadata.relationships = rels;
282 }
283 Err(e) => {
284 result.add_warning(format!("Enhanced relationship analysis failed: {}", e));
285 }
286 }
287 }
288
289 Ok(result)
290 }
291
292 pub fn process_bundle(
294 &mut self,
295 bundle: &AssetBundle,
296 ) -> crate::error::Result<Vec<ExtractionResult>> {
297 let mut results = Vec::new();
298
299 for asset in &bundle.assets {
300 let result = self.process_asset(asset)?;
301 results.push(result);
302 }
303
304 Ok(results)
305 }
306
307 pub fn config(&self) -> &ExtractionConfig {
309 self.extractor.config()
310 }
311
312 pub fn set_config(&mut self, config: ExtractionConfig) {
314 let enable_advanced = config.include_dependencies || config.include_hierarchy;
315
316 self.extractor.set_config(config);
317
318 if enable_advanced {
320 if self.dependency_analyzer.is_none() {
321 self.dependency_analyzer = Some(DependencyAnalyzer::new());
322 }
323 if self.relationship_analyzer.is_none() {
324 self.relationship_analyzer = Some(RelationshipAnalyzer::new());
325 }
326 }
327 }
328
329 pub fn clear_caches(&mut self) {
331 if let Some(ref mut analyzer) = self.dependency_analyzer {
332 analyzer.clear_cache();
333 }
334 if let Some(ref mut analyzer) = self.relationship_analyzer {
335 analyzer.clear_cache();
336 }
337 }
338
339 pub fn has_advanced_analysis(&self) -> bool {
341 self.dependency_analyzer.is_some() || self.relationship_analyzer.is_some()
342 }
343}
344
345impl Default for MetadataProcessor {
346 fn default() -> Self {
347 Self::new()
348 }
349}
350
351pub fn create_processor() -> MetadataProcessor {
354 MetadataProcessor::default()
355}
356
357pub fn create_performance_processor() -> MetadataProcessor {
359 let config = ExtractionConfig {
360 include_dependencies: false,
361 include_hierarchy: false,
362 max_objects: Some(1000),
363 include_performance: true,
364 include_object_details: false,
365 };
366 MetadataProcessor::with_config(config)
367}
368
369pub fn create_comprehensive_processor() -> MetadataProcessor {
371 let config = ExtractionConfig {
372 include_dependencies: true,
373 include_hierarchy: true,
374 max_objects: None,
375 include_performance: true,
376 include_object_details: true,
377 };
378 MetadataProcessor::with_config(config)
379}
380
381pub fn extract_basic_metadata(asset: &SerializedFile) -> crate::error::Result<AssetMetadata> {
383 let mut processor = MetadataProcessor::with_config(ExtractionConfig::default());
384 let result = processor.process_asset(asset)?;
385 Ok(result.metadata)
386}
387
388pub fn extract_metadata_with_config(
390 asset: &SerializedFile,
391 config: ExtractionConfig,
392) -> crate::error::Result<ExtractionResult> {
393 let mut processor = MetadataProcessor::with_config(config);
394 processor.process_asset(asset)
395}
396
397pub fn get_asset_statistics(asset: &SerializedFile) -> AssetStatistics {
399 AssetStatistics {
400 object_count: asset.objects.len(),
401 type_count: asset.types.len(),
402 external_count: asset.externals.len(),
403 file_size: asset.header.file_size,
404 unity_version: asset.unity_version.clone(),
405 format_version: asset.header.version,
406 }
407}
408
409#[derive(Debug, Clone)]
411pub struct AssetStatistics {
412 pub object_count: usize,
413 pub type_count: usize,
414 pub external_count: usize,
415 pub file_size: u64,
416 pub unity_version: String,
417 pub format_version: u32,
418}
419
420#[derive(Debug, Clone)]
422pub struct ProcessingOptions {
423 pub enable_caching: bool,
424 pub max_cache_size: usize,
425 pub parallel_processing: bool,
426 pub memory_limit_mb: Option<usize>,
427}
428
429impl Default for ProcessingOptions {
430 fn default() -> Self {
431 Self {
432 enable_caching: true,
433 max_cache_size: 1000,
434 parallel_processing: false,
435 memory_limit_mb: None,
436 }
437 }
438}
439
440pub fn is_extraction_supported(asset: &SerializedFile) -> bool {
442 asset.header.version >= 10
444}
445
446pub fn get_recommended_config(asset: &SerializedFile) -> ExtractionConfig {
448 let object_count = asset.objects.len();
449
450 if object_count > 10000 {
451 ExtractionConfig {
453 include_dependencies: false,
454 include_hierarchy: false,
455 max_objects: Some(5000),
456 include_performance: true,
457 include_object_details: false,
458 }
459 } else if object_count > 1000 {
460 ExtractionConfig {
462 include_dependencies: true,
463 include_hierarchy: false,
464 max_objects: Some(2000),
465 include_performance: true,
466 include_object_details: true,
467 }
468 } else {
469 ExtractionConfig::default()
471 }
472}
473
474#[cfg(test)]
475mod processor_tests {
476 use super::*;
477
478 #[test]
479 fn test_apply_dependency_info_to_relationships_fills_component_dependencies() {
480 let dependencies = DependencyInfo {
481 external_references: vec![ExternalReference {
482 file_id: 2,
483 path_id: 999,
484 referenced_by: vec![11],
485 file_path: Some("library/external.assets".to_string()),
486 guid: Some([7u8; 16]),
487 }],
488 internal_references: vec![
489 InternalReference {
490 from_object: 10,
491 to_object: 1,
492 reference_type: "Direct".to_string(),
493 },
494 InternalReference {
495 from_object: 10,
496 to_object: 2,
497 reference_type: "Direct".to_string(),
498 },
499 InternalReference {
500 from_object: 11,
501 to_object: 3,
502 reference_type: "Direct".to_string(),
503 },
504 ],
505 dependency_graph: DependencyGraph {
506 nodes: Vec::new(),
507 edges: Vec::new(),
508 root_objects: Vec::new(),
509 leaf_objects: Vec::new(),
510 },
511 circular_dependencies: Vec::new(),
512 };
513
514 let mut relationships = AssetRelationships {
515 gameobject_hierarchy: Vec::new(),
516 component_relationships: vec![
517 ComponentRelationship {
518 component_id: 10,
519 component_type: "Transform".to_string(),
520 gameobject_id: 100,
521 dependencies: Vec::new(),
522 external_dependencies: Vec::new(),
523 },
524 ComponentRelationship {
525 component_id: 11,
526 component_type: "MeshRenderer".to_string(),
527 gameobject_id: 100,
528 dependencies: Vec::new(),
529 external_dependencies: Vec::new(),
530 },
531 ComponentRelationship {
532 component_id: 12,
533 component_type: "Unknown".to_string(),
534 gameobject_id: 100,
535 dependencies: Vec::new(),
536 external_dependencies: Vec::new(),
537 },
538 ],
539 asset_references: Vec::new(),
540 };
541
542 apply_dependency_info_to_relationships(&dependencies, &mut relationships);
543
544 assert_eq!(
545 relationships.component_relationships[0].dependencies,
546 vec![1, 2]
547 );
548 assert_eq!(
549 relationships.component_relationships[1].dependencies,
550 vec![3]
551 );
552 assert_eq!(
553 relationships.component_relationships[2].dependencies,
554 Vec::<i64>::new()
555 );
556
557 assert_eq!(
558 relationships.component_relationships[0]
559 .external_dependencies
560 .len(),
561 0
562 );
563 assert_eq!(
564 relationships.component_relationships[1]
565 .external_dependencies
566 .len(),
567 1
568 );
569 assert_eq!(
570 relationships.component_relationships[1].external_dependencies[0],
571 ExternalObjectRef {
572 file_id: 2,
573 path_id: 999,
574 file_path: Some("library/external.assets".to_string()),
575 guid: Some([7u8; 16]),
576 }
577 );
578
579 let ext_ref = relationships
580 .asset_references
581 .iter()
582 .find(|r| r.asset_id == 999)
583 .expect("external asset reference exists");
584 assert_eq!(ext_ref.asset_type, "ExternalObject(file_id=2)");
585 assert_eq!(
586 ext_ref.file_path,
587 Some("library/external.assets".to_string())
588 );
589 assert_eq!(ext_ref.referenced_by, vec![11]);
590
591 let internal_ref_1 = relationships
592 .asset_references
593 .iter()
594 .find(|r| r.asset_id == 1)
595 .expect("internal asset reference 1 exists");
596 assert_eq!(internal_ref_1.file_path, None);
597 assert_eq!(internal_ref_1.referenced_by, vec![10]);
598
599 let internal_ref_3 = relationships
600 .asset_references
601 .iter()
602 .find(|r| r.asset_id == 3)
603 .expect("internal asset reference 3 exists");
604 assert_eq!(internal_ref_3.referenced_by, vec![11]);
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611
612 #[test]
613 fn test_processor_creation() {
614 let processor = create_processor();
615 assert!(!processor.has_advanced_analysis());
616 }
617
618 #[test]
619 fn test_comprehensive_processor() {
620 let processor = create_comprehensive_processor();
621 assert!(processor.has_advanced_analysis());
622 assert!(processor.config().include_dependencies);
623 assert!(processor.config().include_hierarchy);
624 }
625
626 #[test]
627 fn test_performance_processor() {
628 let processor = create_performance_processor();
629 assert!(!processor.config().include_dependencies);
630 assert!(!processor.config().include_hierarchy);
631 assert_eq!(processor.config().max_objects, Some(1000));
632 }
633
634 #[test]
635 fn test_extraction_support() {
636 }
639}