unity_asset_binary/metadata/
analyzer.rs1use super::types::*;
7use crate::error::Result;
8use std::collections::{HashMap, HashSet};
9
10pub struct DependencyAnalyzer {
15 dependency_cache: HashMap<i64, Vec<i64>>,
17 reverse_dependency_cache: HashMap<i64, Vec<i64>>,
19}
20
21impl DependencyAnalyzer {
22 pub fn new() -> Self {
24 Self {
25 dependency_cache: HashMap::new(),
26 reverse_dependency_cache: HashMap::new(),
27 }
28 }
29
30 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 for obj in objects {
42 all_nodes.insert(obj.path_id);
43 }
44
45 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_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_refs.push(ExternalReference {
61 file_id: 0, path_id: dep_id,
63 referenced_by: vec![obj.path_id],
64 });
65 }
66 }
67 }
68
69 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 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 fn extract_object_dependencies(&mut self, obj: &crate::asset::ObjectInfo) -> Result<Vec<i64>> {
94 if let Some(cached) = self.dependency_cache.get(&obj.path_id) {
96 return Ok(cached.clone());
97 }
98
99 let dependencies = Vec::new();
103
104 self.dependency_cache
106 .insert(obj.path_id, dependencies.clone());
107
108 Ok(dependencies)
109 }
110
111 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 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 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 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 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 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 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 pub fn clear_cache(&mut self) {
207 self.dependency_cache.clear();
208 self.reverse_dependency_cache.clear();
209 }
210
211 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
223pub struct RelationshipAnalyzer {
228 hierarchy_cache: HashMap<i64, GameObjectHierarchy>,
230}
231
232impl RelationshipAnalyzer {
233 pub fn new() -> Self {
235 Self {
236 hierarchy_cache: HashMap::new(),
237 }
238 }
239
240 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 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 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 for comp in components {
282 if let Ok(relationship) = self.analyze_component_relationship(comp) {
283 component_relationships.push(relationship);
284 }
285 }
286
287 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 #[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 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, components: Vec::new(),
318 depth: 0,
319 })
320 }
321
322 fn analyze_component_relationship(
324 &self,
325 component: &crate::asset::ObjectInfo,
326 ) -> Result<ComponentRelationship> {
327 Ok(ComponentRelationship {
330 component_id: component.path_id,
331 component_type: self.get_component_type_name(component.type_id),
332 gameobject_id: 0, dependencies: Vec::new(),
334 })
335 }
336
337 fn analyze_asset_reference(&self, asset: &crate::asset::ObjectInfo) -> Result<AssetReference> {
339 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 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 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 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}