use super::types::*;
use crate::asset::SerializedFile;
use crate::bundle::AssetBundle;
use crate::error::Result;
use std::collections::HashMap;
use std::time::Instant;
pub struct MetadataExtractor {
config: ExtractionConfig,
}
impl MetadataExtractor {
pub fn new() -> Self {
Self {
config: ExtractionConfig::default(),
}
}
pub fn with_config(config: ExtractionConfig) -> Self {
Self { config }
}
pub fn with_settings(
include_dependencies: bool,
include_hierarchy: bool,
include_performance: bool,
max_objects: Option<usize>,
) -> Self {
Self {
config: ExtractionConfig {
include_dependencies,
include_hierarchy,
max_objects,
include_performance,
include_object_details: true,
},
}
}
pub fn extract_from_bundle(&self, bundle: &AssetBundle) -> Result<Vec<ExtractionResult>> {
let start_time = Instant::now();
let mut results = Vec::new();
for asset in &bundle.assets {
let result = self.extract_from_asset(asset)?;
results.push(result);
}
let total_time = start_time.elapsed().as_secs_f64() * 1000.0;
let asset_count = results.len() as f64;
for result in &mut results {
result.metadata.performance.parse_time_ms = total_time / asset_count;
}
Ok(results)
}
pub fn extract_from_asset(&self, asset: &SerializedFile) -> Result<ExtractionResult> {
let start_time = Instant::now();
let mut result = ExtractionResult::new(AssetMetadata::new());
let objects_to_analyze: Vec<&crate::asset::ObjectInfo> =
if let Some(max) = self.config.max_objects {
asset.objects.iter().take(max).collect()
} else {
asset.objects.iter().collect()
};
result.metadata.file_info = self.extract_file_info(asset);
result.metadata.object_stats = self.extract_object_statistics(&objects_to_analyze);
if self.config.include_dependencies {
match self.extract_dependencies(&objects_to_analyze) {
Ok(deps) => result.metadata.dependencies = deps,
Err(e) => {
result.add_warning(format!("Failed to extract dependencies: {}", e));
result.metadata.dependencies = DependencyInfo {
external_references: Vec::new(),
internal_references: Vec::new(),
dependency_graph: DependencyGraph {
nodes: Vec::new(),
edges: Vec::new(),
root_objects: Vec::new(),
leaf_objects: Vec::new(),
},
circular_dependencies: Vec::new(),
};
}
}
}
if self.config.include_hierarchy {
match self.extract_relationships(&objects_to_analyze) {
Ok(rels) => result.metadata.relationships = rels,
Err(e) => {
result.add_warning(format!("Failed to extract relationships: {}", e));
result.metadata.relationships = AssetRelationships {
gameobject_hierarchy: Vec::new(),
component_relationships: Vec::new(),
asset_references: Vec::new(),
};
}
}
}
if self.config.include_performance {
let elapsed = start_time.elapsed().as_secs_f64() * 1000.0;
result.metadata.performance = self.extract_performance_metrics(asset, elapsed);
}
Ok(result)
}
fn extract_file_info(&self, asset: &SerializedFile) -> FileInfo {
FileInfo {
file_size: asset.header.file_size,
unity_version: asset.unity_version.clone(),
target_platform: format!("{}", asset.target_platform),
compression_type: "None".to_string(), file_format_version: asset.header.version,
}
}
fn extract_object_statistics(&self, objects: &[&crate::asset::ObjectInfo]) -> ObjectStatistics {
let mut objects_by_type: HashMap<String, usize> = HashMap::new();
let mut memory_by_type: HashMap<String, u64> = HashMap::new();
let mut total_memory = 0u64;
let mut object_summaries = Vec::new();
for obj in objects {
let class_name = self.get_class_name_from_type_id(obj.type_id);
*objects_by_type.entry(class_name.clone()).or_insert(0) += 1;
let byte_size = obj.byte_size as u64;
*memory_by_type.entry(class_name.clone()).or_insert(0u64) += byte_size;
total_memory += byte_size;
if self.config.include_object_details {
object_summaries.push(ObjectSummary {
path_id: obj.path_id,
class_name: class_name.clone(),
name: Some(format!("Object_{}", obj.path_id)), byte_size: obj.byte_size,
dependencies: Vec::new(), });
}
}
object_summaries.sort_by(|a, b| b.byte_size.cmp(&a.byte_size));
if object_summaries.len() > 100 {
object_summaries.truncate(100); }
let largest_type = memory_by_type
.iter()
.max_by_key(|&(_, &size)| size)
.map(|(name, _)| name.clone());
let average_size = if objects.is_empty() {
0.0
} else {
total_memory as f64 / objects.len() as f64
};
ObjectStatistics {
total_objects: objects.len(),
objects_by_type,
largest_objects: object_summaries,
memory_usage: MemoryUsage {
total_bytes: total_memory,
by_type: memory_by_type,
largest_type,
average_object_size: average_size,
},
}
}
fn extract_dependencies(
&self,
_objects: &[&crate::asset::ObjectInfo],
) -> Result<DependencyInfo> {
Ok(DependencyInfo {
external_references: Vec::new(),
internal_references: Vec::new(),
dependency_graph: DependencyGraph {
nodes: Vec::new(),
edges: Vec::new(),
root_objects: Vec::new(),
leaf_objects: Vec::new(),
},
circular_dependencies: Vec::new(),
})
}
fn extract_relationships(
&self,
_objects: &[&crate::asset::ObjectInfo],
) -> Result<AssetRelationships> {
Ok(AssetRelationships {
gameobject_hierarchy: Vec::new(),
component_relationships: Vec::new(),
asset_references: Vec::new(),
})
}
fn extract_performance_metrics(
&self,
asset: &SerializedFile,
parse_time_ms: f64,
) -> PerformanceMetrics {
let object_count = asset.objects.len() as f64;
let object_parse_rate = if parse_time_ms > 0.0 {
(object_count * 1000.0) / parse_time_ms
} else {
0.0
};
let complexity_score = self.calculate_complexity_score(asset);
PerformanceMetrics {
parse_time_ms,
memory_peak_mb: 0.0, object_parse_rate,
complexity_score,
}
}
fn calculate_complexity_score(&self, asset: &SerializedFile) -> f64 {
let object_count = asset.objects.len() as f64;
let type_count = asset.types.len() as f64;
let external_count = asset.externals.len() as f64;
let base_score = object_count * 0.1 + type_count * 0.5 + external_count * 0.3;
(base_score / 100.0).min(100.0)
}
fn get_class_name_from_type_id(&self, type_id: i32) -> String {
match type_id {
class_ids::GAME_OBJECT => "GameObject".to_string(),
class_ids::TRANSFORM => "Transform".to_string(),
class_ids::MATERIAL => "Material".to_string(),
class_ids::TEXTURE_2D => "Texture2D".to_string(),
class_ids::MESH => "Mesh".to_string(),
class_ids::SHADER => "Shader".to_string(),
class_ids::ANIMATION_CLIP => "AnimationClip".to_string(),
class_ids::AUDIO_CLIP => "AudioClip".to_string(),
class_ids::ANIMATOR_CONTROLLER => "AnimatorController".to_string(),
class_ids::MONO_BEHAVIOUR => "MonoBehaviour".to_string(),
class_ids::SPRITE => "Sprite".to_string(),
_ => format!("UnknownType_{}", type_id),
}
}
pub fn config(&self) -> &ExtractionConfig {
&self.config
}
pub fn set_config(&mut self, config: ExtractionConfig) {
self.config = config;
}
}
impl Default for MetadataExtractor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extractor_creation() {
let extractor = MetadataExtractor::new();
assert!(extractor.config().include_dependencies);
assert!(extractor.config().include_hierarchy);
}
#[test]
fn test_class_name_mapping() {
let extractor = MetadataExtractor::new();
assert_eq!(extractor.get_class_name_from_type_id(1), "GameObject");
assert_eq!(extractor.get_class_name_from_type_id(28), "Texture2D");
assert_eq!(
extractor.get_class_name_from_type_id(999),
"UnknownType_999"
);
}
#[test]
fn test_complexity_calculation() {
let _extractor = MetadataExtractor::new();
}
}