elefant_tools/
object_id.rs

1use serde::{Deserialize, Serialize};
2
3/// Used for tracking dependencies between objects and to handle renames.
4///
5/// ObjectId has a bit odd equality behavior. If any of the values are none, then all an ObjectId
6/// is considered equal to any other ObjectId. This makes it easier to deal with in tests, while
7/// still making it possible to use it as a key to link things..
8#[derive(Copy, Clone, Debug, Default, PartialOrd, Serialize, Deserialize)]
9pub struct ObjectId {
10    value: Option<usize>,
11}
12
13impl ObjectId {
14    /// Creates a new object id with the specified value
15    pub fn new(value: usize) -> Self {
16        ObjectId { value: Some(value) }
17    }
18}
19
20impl From<usize> for ObjectId {
21    fn from(value: usize) -> Self {
22        ObjectId::new(value)
23    }
24}
25
26impl PartialEq for ObjectId {
27    fn eq(&self, other: &Self) -> bool {
28        if let (Some(self_value), Some(other_value)) = (self.value, other.value) {
29            self_value == other_value
30        } else {
31            true
32        }
33    }
34}
35
36impl Eq for ObjectId {}
37
38/// Provides a way to generate non-conflicting ObjectIds within
39/// the same run, while ensuring the generation is deterministic.
40///
41/// This allows to exact id checking in Tests when relevant.
42pub struct ObjectIdGenerator {
43    next_id: usize,
44}
45
46impl ObjectIdGenerator {
47    /// Creates a new ObjectIdGenerator
48    pub fn new() -> Self {
49        Self { next_id: 1 }
50    }
51
52    /// Generates the next ObjectId
53    pub fn next(&mut self) -> ObjectId {
54        let id = self.next_id;
55        self.next_id += 1;
56        ObjectId::new(id)
57    }
58}
59
60/// Trait for objects that have dependencies
61///
62/// This is used for topological sorting of objects to ensure
63/// they are created in the correct order.
64pub trait HaveDependencies {
65    /// Returns the [ObjectId]s this object depends on
66    fn depends_on(&self) -> &Vec<ObjectId>;
67    /// Returns the [ObjectId] of this object
68    fn object_id(&self) -> ObjectId;
69}
70
71/// Trait for iterators that can be sorted by dependencies
72pub trait DependencySortable: Iterator {
73    /// Sorts the items in the iterator by dependencies.
74    ///
75    /// # Panics
76    /// This will panic if circular dependencies are detected.
77    fn sort_by_dependencies(self) -> Vec<Self::Item>;
78}
79
80impl<I> DependencySortable for I
81where
82    I: Iterator + Sized,
83    I::Item: HaveDependencies,
84{
85    fn sort_by_dependencies(self) -> Vec<Self::Item> {
86        let mut sorted: Vec<Self::Item> = self.collect();
87
88        if sorted.is_empty() {
89            return sorted;
90        }
91
92        // Move everything with 0 dependencies to the front
93        let mut i = 0;
94        let mut j = sorted.len() - 1;
95        while i < j {
96            if sorted[i].depends_on().is_empty() {
97                i += 1;
98            } else if !sorted[j].depends_on().is_empty() {
99                j -= 1;
100            } else {
101                sorted.swap(i, j);
102                i += 1;
103                j -= 1;
104            }
105        }
106
107        // Sort the rest by dependencies
108        let mut swaps = 0;
109        let max_swaps = sorted.len() * sorted.len();
110        loop {
111            let mut swapped = false;
112            for i in 0..sorted.len() {
113                for j in i + 1..sorted.len() {
114                    if sorted[i].depends_on().contains(&sorted[j].object_id()) {
115                        sorted.swap(i, j);
116                        swapped = true;
117                        swaps += 1;
118                    }
119                }
120            }
121            if !swapped {
122                break;
123            }
124
125            if swaps > max_swaps {
126                panic!("Circular dependencies detected");
127            }
128        }
129
130        sorted
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use itertools::Itertools;
138    use std::fmt::{Debug, Formatter};
139    use std::panic::catch_unwind;
140
141    #[derive(Eq, Clone)]
142    struct TestItem {
143        object_id: ObjectId,
144        depends_on: Vec<ObjectId>,
145    }
146    impl HaveDependencies for TestItem {
147        fn depends_on(&self) -> &Vec<ObjectId> {
148            &self.depends_on
149        }
150
151        fn object_id(&self) -> ObjectId {
152            self.object_id
153        }
154    }
155
156    impl PartialEq for TestItem {
157        fn eq(&self, other: &Self) -> bool {
158            self.object_id == other.object_id
159        }
160    }
161
162    impl Debug for TestItem {
163        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
164            write!(f, "{}", self.object_id.value.unwrap())
165        }
166    }
167
168    #[test]
169    fn sorted_by_dependencies_1() {
170        let items = vec![
171            TestItem {
172                object_id: 1.into(),
173                depends_on: vec![],
174            },
175            TestItem {
176                object_id: 2.into(),
177                depends_on: vec![1.into()],
178            },
179            TestItem {
180                object_id: 3.into(),
181                depends_on: vec![1.into()],
182            },
183        ];
184
185        for items in items.into_iter().permutations(3) {
186            let sorted = items.into_iter().sort_by_dependencies();
187
188            assert!(matches!(sorted[0].object_id.value.unwrap(), 1));
189            assert!(matches!(sorted[1].object_id.value.unwrap(), 2 | 3));
190            assert!(matches!(sorted[2].object_id.value.unwrap(), 2 | 3));
191        }
192    }
193
194    #[test]
195    fn sorted_by_dependencies_2() {
196        let items = vec![
197            TestItem {
198                object_id: 1.into(),
199                depends_on: vec![3.into()],
200            },
201            TestItem {
202                object_id: 2.into(),
203                depends_on: vec![],
204            },
205            TestItem {
206                object_id: 3.into(),
207                depends_on: vec![],
208            },
209        ];
210
211        for items in items.into_iter().permutations(3) {
212            let sorted = items.into_iter().sort_by_dependencies();
213
214            assert!(matches!(sorted[0].object_id.value.unwrap(), 2 | 3));
215            assert!(matches!(sorted[1].object_id.value.unwrap(), 2 | 3));
216            assert!(matches!(sorted[2].object_id.value.unwrap(), 1));
217        }
218    }
219
220    #[test]
221    fn sorted_by_dependencies_3() {
222        let items = vec![
223            TestItem {
224                object_id: 1.into(),
225                depends_on: vec![3.into()],
226            },
227            TestItem {
228                object_id: 2.into(),
229                depends_on: vec![],
230            },
231            TestItem {
232                object_id: 3.into(),
233                depends_on: vec![2.into()],
234            },
235        ];
236
237        for items in items.into_iter().permutations(3) {
238            let sorted = items.into_iter().sort_by_dependencies();
239
240            assert!(matches!(sorted[0].object_id.value.unwrap(), 2));
241            assert!(matches!(sorted[1].object_id.value.unwrap(), 3));
242            assert!(matches!(sorted[2].object_id.value.unwrap(), 1));
243        }
244    }
245
246    #[test]
247    fn sorted_by_dependencies_4() {
248        let items = vec![
249            TestItem {
250                object_id: 1.into(),
251                depends_on: vec![3.into(), 2.into()],
252            },
253            TestItem {
254                object_id: 2.into(),
255                depends_on: vec![],
256            },
257            TestItem {
258                object_id: 3.into(),
259                depends_on: vec![2.into()],
260            },
261        ];
262
263        for items in items.into_iter().permutations(3) {
264            let sorted = items.into_iter().sort_by_dependencies();
265
266            assert!(matches!(sorted[0].object_id.value.unwrap(), 2));
267            assert!(matches!(sorted[1].object_id.value.unwrap(), 3));
268            assert!(matches!(sorted[2].object_id.value.unwrap(), 1));
269        }
270    }
271
272    #[test]
273    fn circular_dependencies() {
274        let result = catch_unwind(|| {
275            let items = vec![
276                TestItem {
277                    object_id: 1.into(),
278                    depends_on: vec![3.into()],
279                },
280                TestItem {
281                    object_id: 2.into(),
282                    depends_on: vec![1.into()],
283                },
284                TestItem {
285                    object_id: 3.into(),
286                    depends_on: vec![2.into()],
287                },
288            ];
289
290            items.into_iter().sort_by_dependencies();
291        });
292
293        assert!(result.is_err());
294    }
295
296    #[test]
297    fn handles_empty_input() {
298        let items: Vec<TestItem> = vec![];
299        let sorted = items.into_iter().sort_by_dependencies();
300        assert_eq!(sorted, vec![]);
301    }
302
303    #[test]
304    fn handles_single_item_input() {
305        let items: Vec<TestItem> = vec![TestItem {
306            object_id: 1.into(),
307            depends_on: vec![],
308        }];
309        let sorted = items.into_iter().sort_by_dependencies();
310        assert_eq!(
311            sorted,
312            vec![TestItem {
313                object_id: 1.into(),
314                depends_on: vec![],
315            },]
316        );
317    }
318}