fyrox_resource/
graph.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Resource dependency graph. See [`ResourceDependencyGraph`] docs for more info.
22
23use crate::{collect_used_resources, state::ResourceState, untyped::UntypedResource};
24use fxhash::FxHashSet;
25
26/// A node of [`ResourceDependencyGraph`].
27pub struct ResourceGraphNode {
28    /// A resource associated with the graph node.
29    pub resource: UntypedResource,
30    /// A list of children nodes of the graph.
31    pub children: Vec<ResourceGraphNode>,
32}
33
34impl ResourceGraphNode {
35    /// Creates a new resource graph node for a given untyped resource. This method is recursive -
36    /// it will initialize the entire sub-graph of dependencies automatically.
37    pub fn new(resource: &UntypedResource) -> Self {
38        let mut children = Vec::new();
39
40        // Look for dependent resources.
41        let mut dependent_resources = FxHashSet::default();
42
43        let header = resource.0.lock();
44        if let ResourceState::Ok(ref resource_data) = header.state {
45            (**resource_data).as_reflect(&mut |entity| {
46                collect_used_resources(entity, &mut dependent_resources);
47            });
48        }
49
50        children.extend(
51            dependent_resources
52                .into_iter()
53                .map(|r| ResourceGraphNode::new(&r)),
54        );
55
56        Self {
57            resource: resource.clone(),
58            children,
59        }
60    }
61
62    /// Recursively prints the dependency graph node and its descendant nodes to the specified string, applying
63    /// specified level offset.
64    pub fn pretty_print(&self, level: usize, out: &mut String) {
65        *out += &format!(
66            "{}{}\n",
67            String::from('\t').repeat(level),
68            self.resource.kind()
69        );
70
71        for child in self.children.iter() {
72            child.pretty_print(level + 1, out);
73        }
74    }
75
76    /// Iterates over each dependency graph node and applying the specific function to them.
77    pub fn for_each<F: FnMut(&UntypedResource)>(&self, func: &mut F) {
78        func(&self.resource);
79
80        for child in self.children.iter() {
81            child.for_each(func)
82        }
83    }
84}
85
86/// Resource dependency graph allows you to collect all dependencies of a resource in structured form.
87/// Internally, it uses reflection to look into resources content and find dependent resources. An example
88/// of dependent resource is very simple: if you have a 3D model, then it most likely has a bunch of
89/// textures - these textures are dependent resources. A more complex example - a game level could depend
90/// on lots of prefabs, which in their turn may depend on other prefabs, textures, sounds, etc.
91pub struct ResourceDependencyGraph {
92    /// Root node of the graph.
93    pub root: ResourceGraphNode,
94}
95
96impl ResourceDependencyGraph {
97    /// Creates a new resource dependency graph starting from a given untyped resource.
98    pub fn new(resource: &UntypedResource) -> Self {
99        Self {
100            root: ResourceGraphNode::new(resource),
101        }
102    }
103
104    /// Iterates over each dependency graph node and applying the specific function to them.
105    pub fn for_each<F: FnMut(&UntypedResource)>(&self, mut func: F) {
106        self.root.for_each(&mut func)
107    }
108
109    /// Prints the entire dependency graph into a string.
110    pub fn pretty_print(&self) -> String {
111        let mut out = String::new();
112        self.root.pretty_print(0, &mut out);
113        out
114    }
115}
116#[cfg(test)]
117mod test {
118    use std::path::PathBuf;
119
120    use fyrox_core::uuid::Uuid;
121
122    use super::*;
123
124    #[test]
125    fn resource_graph_node_new() {
126        let resource = UntypedResource::default();
127        let node = ResourceGraphNode::new(&resource);
128
129        assert_eq!(node.resource, resource);
130        assert_eq!(node.children.len(), 0);
131    }
132
133    #[test]
134    fn resource_graph_node_pretty_print() {
135        let mut s = String::new();
136        let mut node = ResourceGraphNode::new(&UntypedResource::new_pending(
137            PathBuf::from("/foo").into(),
138            Uuid::default(),
139        ));
140        let node2 = ResourceGraphNode::new(&UntypedResource::new_pending(
141            PathBuf::from("/bar").into(),
142            Uuid::default(),
143        ));
144        node.children.push(node2);
145        node.pretty_print(1, &mut s);
146
147        assert_eq!(s, "\tExternal (/foo)\n\t\tExternal (/bar)\n".to_string());
148    }
149
150    #[test]
151    fn resource_graph_node_for_each() {
152        let mut node = ResourceGraphNode::new(&UntypedResource::default());
153        node.children
154            .push(ResourceGraphNode::new(&UntypedResource::default()));
155        let mut uuids = Vec::new();
156
157        node.for_each(&mut |r| uuids.push(r.type_uuid()));
158        assert_eq!(uuids, [Uuid::default(), Uuid::default()]);
159    }
160
161    #[test]
162    fn resource_dependency_graph_new() {
163        let resource = UntypedResource::default();
164        let graph = ResourceDependencyGraph::new(&resource);
165
166        assert_eq!(graph.root.resource, resource);
167        assert_eq!(graph.root.children.len(), 0);
168    }
169
170    #[test]
171    fn resource_dependency_pretty_print() {
172        let mut graph = ResourceDependencyGraph::new(&UntypedResource::new_pending(
173            PathBuf::from("/foo").into(),
174            Uuid::default(),
175        ));
176        graph
177            .root
178            .children
179            .push(ResourceGraphNode::new(&UntypedResource::new_pending(
180                PathBuf::from("/bar").into(),
181                Uuid::default(),
182            )));
183
184        let s = graph.pretty_print();
185        assert_eq!(s, "External (/foo)\n\tExternal (/bar)\n".to_string());
186    }
187
188    #[test]
189    fn resource_dependency_for_each() {
190        let mut graph = ResourceDependencyGraph::new(&UntypedResource::new_pending(
191            PathBuf::from("/foo").into(),
192            Uuid::default(),
193        ));
194        graph
195            .root
196            .children
197            .push(ResourceGraphNode::new(&UntypedResource::new_pending(
198                PathBuf::from("/bar").into(),
199                Uuid::default(),
200            )));
201
202        let mut uuids = Vec::new();
203        graph.for_each(&mut |r: &UntypedResource| uuids.push(r.type_uuid()));
204        assert_eq!(uuids, [Uuid::default(), Uuid::default()]);
205    }
206}