unity_asset_binary/metadata/
analyzer.rs

1//! Dependency and relationship analysis
2//!
3//! This module provides advanced analysis capabilities for Unity assets,
4//! including dependency tracking and relationship mapping.
5
6use super::types::*;
7use crate::error::Result;
8use std::collections::{HashMap, HashSet};
9
10/// Dependency analyzer for Unity assets
11///
12/// This struct provides methods for analyzing dependencies and relationships
13/// between Unity objects within and across assets.
14pub struct DependencyAnalyzer {
15    /// Cache for analyzed dependencies
16    dependency_cache: HashMap<i64, Vec<i64>>,
17    /// Cache for reverse dependencies
18    reverse_dependency_cache: HashMap<i64, Vec<i64>>,
19}
20
21impl DependencyAnalyzer {
22    /// Create a new dependency analyzer
23    pub fn new() -> Self {
24        Self {
25            dependency_cache: HashMap::new(),
26            reverse_dependency_cache: HashMap::new(),
27        }
28    }
29
30    /// Analyze dependencies for a set of objects
31    pub fn analyze_dependencies(
32        &mut self,
33        objects: &[&crate::asset::ObjectInfo],
34    ) -> Result<DependencyInfo> {
35        let mut external_refs = Vec::new();
36        let mut internal_refs = Vec::new();
37        let mut all_nodes = HashSet::new();
38        let mut edges = Vec::new();
39
40        // First pass: collect all object IDs
41        for obj in objects {
42            all_nodes.insert(obj.path_id);
43        }
44
45        // Second pass: analyze each object's dependencies
46        for obj in objects {
47            let dependencies = self.extract_object_dependencies(obj)?;
48
49            for dep_id in dependencies {
50                if all_nodes.contains(&dep_id) {
51                    // Internal reference
52                    internal_refs.push(InternalReference {
53                        from_object: obj.path_id,
54                        to_object: dep_id,
55                        reference_type: "Direct".to_string(),
56                    });
57                    edges.push((obj.path_id, dep_id));
58                } else {
59                    // External reference (simplified)
60                    external_refs.push(ExternalReference {
61                        file_id: 0, // TODO: Determine actual file ID
62                        path_id: dep_id,
63                        referenced_by: vec![obj.path_id],
64                    });
65                }
66            }
67        }
68
69        // Build dependency graph
70        let nodes: Vec<i64> = all_nodes.into_iter().collect();
71        let root_objects = self.find_root_objects(&nodes, &edges);
72        let leaf_objects = self.find_leaf_objects(&nodes, &edges);
73
74        let dependency_graph = DependencyGraph {
75            nodes,
76            edges,
77            root_objects,
78            leaf_objects,
79        };
80
81        // Detect circular dependencies
82        let circular_deps = self.detect_circular_dependencies(&dependency_graph)?;
83
84        Ok(DependencyInfo {
85            external_references: external_refs,
86            internal_references: internal_refs,
87            dependency_graph,
88            circular_dependencies: circular_deps,
89        })
90    }
91
92    /// Extract dependencies from a single object (simplified implementation)
93    fn extract_object_dependencies(&mut self, obj: &crate::asset::ObjectInfo) -> Result<Vec<i64>> {
94        // Check cache first
95        if let Some(cached) = self.dependency_cache.get(&obj.path_id) {
96            return Ok(cached.clone());
97        }
98
99        // TODO: Implement proper dependency extraction from object data
100        // This would require parsing the object's serialized data based on its type
101        // For now, return empty dependencies
102        let dependencies = Vec::new();
103
104        // Cache the result
105        self.dependency_cache
106            .insert(obj.path_id, dependencies.clone());
107
108        Ok(dependencies)
109    }
110
111    /// Find root objects (objects with no incoming dependencies)
112    fn find_root_objects(&self, nodes: &[i64], edges: &[(i64, i64)]) -> Vec<i64> {
113        let mut has_incoming: HashSet<i64> = HashSet::new();
114
115        for (_, to) in edges {
116            has_incoming.insert(*to);
117        }
118
119        nodes
120            .iter()
121            .filter(|node| !has_incoming.contains(node))
122            .copied()
123            .collect()
124    }
125
126    /// Find leaf objects (objects with no outgoing dependencies)
127    fn find_leaf_objects(&self, nodes: &[i64], edges: &[(i64, i64)]) -> Vec<i64> {
128        let mut has_outgoing: HashSet<i64> = HashSet::new();
129
130        for (from, _) in edges {
131            has_outgoing.insert(*from);
132        }
133
134        nodes
135            .iter()
136            .filter(|node| !has_outgoing.contains(node))
137            .copied()
138            .collect()
139    }
140
141    /// Detect circular dependencies using DFS
142    fn detect_circular_dependencies(&self, graph: &DependencyGraph) -> Result<Vec<Vec<i64>>> {
143        let mut visited = HashSet::new();
144        let mut rec_stack = HashSet::new();
145        let mut cycles = Vec::new();
146
147        // Build adjacency list
148        let mut adj_list: HashMap<i64, Vec<i64>> = HashMap::new();
149        for node in &graph.nodes {
150            adj_list.insert(*node, Vec::new());
151        }
152        for (from, to) in &graph.edges {
153            adj_list.get_mut(from).unwrap().push(*to);
154        }
155
156        // DFS for each unvisited node
157        for &node in &graph.nodes {
158            if !visited.contains(&node) {
159                let mut path = Vec::new();
160                Self::dfs_detect_cycle(
161                    node,
162                    &adj_list,
163                    &mut visited,
164                    &mut rec_stack,
165                    &mut path,
166                    &mut cycles,
167                );
168            }
169        }
170
171        Ok(cycles)
172    }
173
174    /// DFS helper for cycle detection
175    fn dfs_detect_cycle(
176        node: i64,
177        adj_list: &HashMap<i64, Vec<i64>>,
178        visited: &mut HashSet<i64>,
179        rec_stack: &mut HashSet<i64>,
180        path: &mut Vec<i64>,
181        cycles: &mut Vec<Vec<i64>>,
182    ) {
183        visited.insert(node);
184        rec_stack.insert(node);
185        path.push(node);
186
187        if let Some(neighbors) = adj_list.get(&node) {
188            for &neighbor in neighbors {
189                if !visited.contains(&neighbor) {
190                    Self::dfs_detect_cycle(neighbor, adj_list, visited, rec_stack, path, cycles);
191                } else if rec_stack.contains(&neighbor) {
192                    // Found a cycle
193                    if let Some(cycle_start) = path.iter().position(|&x| x == neighbor) {
194                        let cycle = path[cycle_start..].to_vec();
195                        cycles.push(cycle);
196                    }
197                }
198            }
199        }
200
201        path.pop();
202        rec_stack.remove(&node);
203    }
204
205    /// Clear internal caches
206    pub fn clear_cache(&mut self) {
207        self.dependency_cache.clear();
208        self.reverse_dependency_cache.clear();
209    }
210
211    /// Get cached dependencies for an object
212    pub fn get_cached_dependencies(&self, object_id: i64) -> Option<&Vec<i64>> {
213        self.dependency_cache.get(&object_id)
214    }
215}
216
217impl Default for DependencyAnalyzer {
218    fn default() -> Self {
219        Self::new()
220    }
221}
222
223/// Relationship analyzer for Unity assets
224///
225/// This struct provides methods for analyzing relationships between
226/// GameObjects, Components, and other Unity objects.
227pub struct RelationshipAnalyzer {
228    /// Cache for GameObject hierarchies
229    hierarchy_cache: HashMap<i64, GameObjectHierarchy>,
230}
231
232impl RelationshipAnalyzer {
233    /// Create a new relationship analyzer
234    pub fn new() -> Self {
235        Self {
236            hierarchy_cache: HashMap::new(),
237        }
238    }
239
240    /// Analyze relationships for a set of objects
241    pub fn analyze_relationships(
242        &mut self,
243        objects: &[&crate::asset::ObjectInfo],
244    ) -> Result<AssetRelationships> {
245        let mut gameobject_hierarchy = Vec::new();
246        let mut component_relationships = Vec::new();
247        let mut asset_references = Vec::new();
248
249        // Separate objects by type
250        let mut gameobjects = Vec::new();
251        let mut transforms = Vec::new();
252        let mut components = Vec::new();
253        let mut assets = Vec::new();
254
255        for obj in objects {
256            match obj.type_id {
257                class_ids::GAME_OBJECT => gameobjects.push(obj),
258                class_ids::TRANSFORM => transforms.push(obj),
259                class_ids::COMPONENT | class_ids::BEHAVIOUR | class_ids::MONO_BEHAVIOUR => {
260                    components.push(obj)
261                }
262                _ => assets.push(obj),
263            }
264        }
265
266        // Analyze GameObject hierarchy (simplified for now)
267        for go in gameobjects {
268            let hierarchy = GameObjectHierarchy {
269                gameobject_id: go.path_id,
270                name: format!("GameObject_{}", go.path_id),
271                parent_id: None,
272                children_ids: Vec::new(),
273                transform_id: 0,
274                components: Vec::new(),
275                depth: 0,
276            };
277            gameobject_hierarchy.push(hierarchy);
278        }
279
280        // Analyze component relationships
281        for comp in components {
282            if let Ok(relationship) = self.analyze_component_relationship(comp) {
283                component_relationships.push(relationship);
284            }
285        }
286
287        // Analyze asset references
288        for asset in assets {
289            if let Ok(reference) = self.analyze_asset_reference(asset) {
290                asset_references.push(reference);
291            }
292        }
293
294        Ok(AssetRelationships {
295            gameobject_hierarchy,
296            component_relationships,
297            asset_references,
298        })
299    }
300
301    /// Analyze GameObject hierarchy (simplified implementation)
302    #[allow(dead_code)]
303    fn analyze_gameobject_hierarchy(
304        &mut self,
305        gameobject: &crate::asset::ObjectInfo,
306        _transforms: &[&crate::asset::ObjectInfo],
307    ) -> Result<GameObjectHierarchy> {
308        // TODO: Implement proper GameObject hierarchy analysis
309        // This would require parsing the GameObject's serialized data
310
311        Ok(GameObjectHierarchy {
312            gameobject_id: gameobject.path_id,
313            name: format!("GameObject_{}", gameobject.path_id),
314            parent_id: None,
315            children_ids: Vec::new(),
316            transform_id: 0, // TODO: Find associated Transform
317            components: Vec::new(),
318            depth: 0,
319        })
320    }
321
322    /// Analyze component relationship (simplified implementation)
323    fn analyze_component_relationship(
324        &self,
325        component: &crate::asset::ObjectInfo,
326    ) -> Result<ComponentRelationship> {
327        // TODO: Implement proper component relationship analysis
328
329        Ok(ComponentRelationship {
330            component_id: component.path_id,
331            component_type: self.get_component_type_name(component.type_id),
332            gameobject_id: 0, // TODO: Find associated GameObject
333            dependencies: Vec::new(),
334        })
335    }
336
337    /// Analyze asset reference (simplified implementation)
338    fn analyze_asset_reference(&self, asset: &crate::asset::ObjectInfo) -> Result<AssetReference> {
339        // TODO: Implement proper asset reference analysis
340
341        Ok(AssetReference {
342            asset_id: asset.path_id,
343            asset_type: self.get_asset_type_name(asset.type_id),
344            referenced_by: Vec::new(),
345            file_path: None,
346        })
347    }
348
349    /// Get component type name from type ID
350    fn get_component_type_name(&self, type_id: i32) -> String {
351        match type_id {
352            class_ids::TRANSFORM => "Transform".to_string(),
353            class_ids::MONO_BEHAVIOUR => "MonoBehaviour".to_string(),
354            _ => format!("Component_{}", type_id),
355        }
356    }
357
358    /// Get asset type name from type ID
359    fn get_asset_type_name(&self, type_id: i32) -> String {
360        match type_id {
361            class_ids::TEXTURE_2D => "Texture2D".to_string(),
362            class_ids::MESH => "Mesh".to_string(),
363            class_ids::MATERIAL => "Material".to_string(),
364            class_ids::AUDIO_CLIP => "AudioClip".to_string(),
365            class_ids::SPRITE => "Sprite".to_string(),
366            _ => format!("Asset_{}", type_id),
367        }
368    }
369
370    /// Clear internal caches
371    pub fn clear_cache(&mut self) {
372        self.hierarchy_cache.clear();
373    }
374}
375
376impl Default for RelationshipAnalyzer {
377    fn default() -> Self {
378        Self::new()
379    }
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385
386    #[test]
387    fn test_dependency_analyzer_creation() {
388        let analyzer = DependencyAnalyzer::new();
389        assert!(analyzer.dependency_cache.is_empty());
390    }
391
392    #[test]
393    fn test_relationship_analyzer_creation() {
394        let analyzer = RelationshipAnalyzer::new();
395        assert!(analyzer.hierarchy_cache.is_empty());
396    }
397
398    #[test]
399    fn test_root_leaf_detection() {
400        let analyzer = DependencyAnalyzer::new();
401        let nodes = vec![1, 2, 3, 4];
402        let edges = vec![(1, 2), (2, 3), (4, 3)];
403
404        let roots = analyzer.find_root_objects(&nodes, &edges);
405        let leaves = analyzer.find_leaf_objects(&nodes, &edges);
406
407        assert!(roots.contains(&1));
408        assert!(roots.contains(&4));
409        assert!(leaves.contains(&3));
410    }
411}