matrix_sdk_ui/spaces/
graph.rs1use std::collections::{BTreeMap, BTreeSet};
16
17use ruma::{OwnedRoomId, RoomId};
18
19#[derive(Debug)]
20struct SpaceGraphNode {
21 id: OwnedRoomId,
22 parents: BTreeSet<OwnedRoomId>,
23 children: BTreeSet<OwnedRoomId>,
24}
25
26impl SpaceGraphNode {
27 fn new(id: OwnedRoomId) -> Self {
28 Self { id, parents: BTreeSet::new(), children: BTreeSet::new() }
29 }
30}
31
32#[derive(Debug)]
36pub(super) struct SpaceGraph {
37 nodes: BTreeMap<OwnedRoomId, SpaceGraphNode>,
38}
39
40impl Default for SpaceGraph {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46impl SpaceGraph {
47 pub(super) fn new() -> Self {
49 Self { nodes: BTreeMap::new() }
50 }
51
52 pub(super) fn root_nodes(&self) -> Vec<&RoomId> {
55 self.nodes
56 .values()
57 .filter(|node| node.parents.is_empty())
58 .map(|node| node.id.as_ref())
59 .collect()
60 }
61
62 pub(super) fn children_of(&self, node_id: &RoomId) -> Vec<&RoomId> {
65 self.nodes
66 .get(node_id)
67 .map_or(vec![], |node| node.children.iter().map(|id| id.as_ref()).collect())
68 }
69
70 pub(super) fn add_node(&mut self, node_id: OwnedRoomId) {
72 self.nodes.entry(node_id.clone()).or_insert(SpaceGraphNode::new(node_id));
73 }
74
75 pub(super) fn add_edge(&mut self, parent_id: OwnedRoomId, child_id: OwnedRoomId) {
78 let parent_entry =
79 self.nodes.entry(parent_id.clone()).or_insert(SpaceGraphNode::new(parent_id.clone()));
80 parent_entry.children.insert(child_id.clone());
81
82 let child_entry =
83 self.nodes.entry(child_id.clone()).or_insert(SpaceGraphNode::new(child_id));
84 child_entry.parents.insert(parent_id);
85 }
86
87 pub(super) fn remove_cycles(&mut self) {
91 let mut visited = BTreeSet::new();
92 let mut stack = BTreeSet::new();
93
94 let mut edges_to_remove = Vec::new();
95
96 for node_id in self.nodes.keys() {
97 self.dfs_remove_cycles(node_id, &mut visited, &mut stack, &mut edges_to_remove);
98 }
99
100 for (parent, child) in edges_to_remove {
101 if let Some(node) = self.nodes.get_mut(&parent) {
102 node.children.remove(&child);
103 }
104 if let Some(node) = self.nodes.get_mut(&child) {
105 node.parents.remove(&parent);
106 }
107 }
108 }
109
110 fn dfs_remove_cycles(
111 &self,
112 node_id: &OwnedRoomId,
113 visited: &mut BTreeSet<OwnedRoomId>,
114 stack: &mut BTreeSet<OwnedRoomId>,
115 edges_to_remove: &mut Vec<(OwnedRoomId, OwnedRoomId)>,
116 ) {
117 if !visited.insert(node_id.clone()) {
118 return;
119 }
120
121 stack.insert(node_id.clone());
122
123 if let Some(node) = self.nodes.get(node_id) {
124 for child in &node.children {
125 if stack.contains(child) {
126 edges_to_remove.push((node_id.clone(), child.clone()));
128 } else {
129 self.dfs_remove_cycles(child, visited, stack, edges_to_remove);
130 }
131 }
132 }
133
134 stack.remove(node_id);
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use ruma::room_id;
141
142 use super::*;
143
144 #[test]
145 fn test_add_edge_and_root_nodes() {
146 let mut graph = SpaceGraph::new();
147
148 let a = room_id!("!a:example.org").to_owned();
149 let b = room_id!("!b:example.org").to_owned();
150 let c = room_id!("!c:example.org").to_owned();
151
152 graph.add_edge(a.clone(), b.clone());
153 graph.add_edge(a.clone(), c.clone());
154
155 assert_eq!(graph.root_nodes(), vec![&a]);
156
157 assert!(graph.nodes[&b].parents.contains(&a));
158 assert!(graph.nodes[&c].parents.contains(&a));
159
160 assert_eq!(graph.children_of(&a), vec![&b, &c]);
161 }
162
163 #[test]
164 fn test_remove_cycles() {
165 let mut graph = SpaceGraph::new();
166
167 let a = room_id!("!a:example.org").to_owned();
168 let b = room_id!("!b:example.org").to_owned();
169 let c = room_id!("!c:example.org").to_owned();
170
171 graph.add_edge(a.clone(), b.clone());
172 graph.add_edge(b, c.clone());
173 graph.add_edge(c.clone(), a.clone()); assert!(graph.nodes[&c].children.contains(&a));
176
177 graph.remove_cycles();
178
179 assert!(!graph.nodes[&c].children.contains(&a));
180 assert!(!graph.nodes[&a].parents.contains(&c));
181 }
182
183 #[test]
184 fn test_disconnected_graph_roots() {
185 let mut graph = SpaceGraph::new();
186
187 let a = room_id!("!a:example.org").to_owned();
188 let b = room_id!("!b:example.org").to_owned();
189 graph.add_edge(a.clone(), b);
190
191 let x = room_id!("!x:example.org").to_owned();
192 let y = room_id!("!y:example.org").to_owned();
193 graph.add_edge(x.clone(), y);
194
195 let mut roots = graph.root_nodes();
196 roots.sort_by_key(|key| key.to_string());
197
198 let expected: Vec<&OwnedRoomId> = vec![&a, &x];
199 assert_eq!(roots, expected);
200 }
201
202 #[test]
203 fn test_multiple_parents() {
204 let mut graph = SpaceGraph::new();
205
206 let a = room_id!("!a:example.org").to_owned();
207 let b = room_id!("!b:example.org").to_owned();
208 let c = room_id!("!c:example.org").to_owned();
209 let d = room_id!("!d:example.org").to_owned();
210
211 graph.add_edge(a.clone(), c.clone());
212 graph.add_edge(b.clone(), c.clone());
213 graph.add_edge(c.clone(), d);
214
215 let mut roots = graph.root_nodes();
216 roots.sort_by_key(|key| key.to_string());
217
218 let expected = vec![&a, &b];
219 assert_eq!(roots, expected);
220
221 let c_parents = &graph.nodes[&c].parents;
222 assert!(c_parents.contains(&a));
223 assert!(c_parents.contains(&b));
224 }
225}