Skip to main content

ferroui_base/rendering/composition/expressions/
expression_tracked_values.rs

1//! The set of objects an expression reads from.
2
3use std::collections::HashSet;
4
5use super::expression_evaluation_context::IExpressionObject;
6
7/// The identity of an expression object (see [`IExpressionObject::key`]).
8///
9/// The reference implementation keeps references to the tracked objects and
10/// compares them by reference; this port keeps their identity keys instead,
11/// so that the collection neither borrows nor owns server objects. Whoever
12/// enumerates the collection resolves the keys back to objects.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct ExpressionObjectKey(pub usize);
15
16/// The distinct objects an expression reads from, in the order they were
17/// first added.
18#[derive(Clone, Debug, Default)]
19pub struct ExpressionTrackedObjects {
20    list: Vec<ExpressionObjectKey>,
21    hash_set: HashSet<ExpressionObjectKey>,
22}
23
24impl ExpressionTrackedObjects {
25    /// Creates an empty collection.
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    /// Tracks an object. The member name is not recorded.
31    pub fn add(&mut self, obj: &dyn IExpressionObject, member: &str) {
32        self.add_key(obj.key(), member);
33    }
34
35    /// Tracks an object given by its identity key.
36    pub fn add_key(&mut self, obj: ExpressionObjectKey, _member: &str) {
37        if self.hash_set.insert(obj) {
38            self.list.push(obj);
39        }
40    }
41
42    /// Removes every tracked object.
43    pub fn clear(&mut self) {
44        self.list.clear();
45        self.hash_set.clear();
46    }
47
48    /// Enumerates the tracked objects in the order they were first added.
49    pub fn iter(&self) -> std::slice::Iter<'_, ExpressionObjectKey> {
50        self.list.iter()
51    }
52}
53
54impl<'a> IntoIterator for &'a ExpressionTrackedObjects {
55    type Item = &'a ExpressionObjectKey;
56    type IntoIter = std::slice::Iter<'a, ExpressionObjectKey>;
57
58    fn into_iter(self) -> Self::IntoIter {
59        self.list.iter()
60    }
61}
62
63/// A pool of [`ExpressionTrackedObjects`] collections (the nested `Pool` type of the
64/// reference implementation).
65#[derive(Debug, Default)]
66pub struct ExpressionTrackedObjectsPool {
67    stack: Vec<ExpressionTrackedObjects>,
68}
69
70impl ExpressionTrackedObjectsPool {
71    /// Creates an empty pool.
72    pub fn new() -> Self {
73        Self::default()
74    }
75
76    /// Takes a collection out of the pool, or creates one. A pooled
77    /// collection is handed out as it was returned (it is not cleared).
78    pub fn get(&mut self) -> ExpressionTrackedObjects {
79        self.stack.pop().unwrap_or_default()
80    }
81
82    /// Puts a collection back. The pool keeps only the most recently
83    /// returned collection.
84    pub fn return_(&mut self, obj: ExpressionTrackedObjects) {
85        self.stack.clear();
86        self.stack.push(obj);
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::rendering::composition::expressions::ExpressionVariant;
94
95    struct Obj(#[allow(dead_code)] u8);
96
97    impl IExpressionObject for Obj {
98        fn get_property(&self, _name: &str) -> ExpressionVariant {
99            ExpressionVariant::Invalid
100        }
101    }
102
103    struct Keyed(usize);
104
105    impl IExpressionObject for Keyed {
106        fn get_property(&self, _name: &str) -> ExpressionVariant {
107            ExpressionVariant::Invalid
108        }
109
110        fn key(&self) -> ExpressionObjectKey {
111            ExpressionObjectKey(self.0)
112        }
113    }
114
115    #[test]
116    fn tracks_each_object_once_in_insertion_order() {
117        let (a, b) = (Obj(1), Obj(2));
118        let mut tracked = ExpressionTrackedObjects::new();
119        tracked.add(&b, "Offset");
120        tracked.add(&a, "Offset");
121        tracked.add(&b, "Size");
122        tracked.add(&a as &dyn IExpressionObject, "Opacity");
123
124        let keys: Vec<_> = tracked.iter().copied().collect();
125        assert_eq!(keys, vec![b.key(), a.key()]);
126        assert_ne!(a.key(), b.key());
127        assert_eq!((&tracked).into_iter().count(), 2);
128
129        tracked.clear();
130        assert_eq!(tracked.iter().count(), 0);
131        tracked.add(&a, "Offset");
132        assert_eq!(tracked.iter().count(), 1);
133    }
134
135    #[test]
136    fn uses_the_key_of_the_object() {
137        let mut tracked = ExpressionTrackedObjects::new();
138        tracked.add(&Keyed(7), "A");
139        tracked.add(&Keyed(7), "B");
140        tracked.add(&Keyed(9), "A");
141        tracked.add_key(ExpressionObjectKey(9), "C");
142        let keys: Vec<_> = tracked.iter().map(|k| k.0).collect();
143        assert_eq!(keys, vec![7, 9]);
144    }
145
146    #[test]
147    fn pool_keeps_only_the_last_returned_collection() {
148        let mut pool = ExpressionTrackedObjectsPool::new();
149        assert_eq!(pool.get().iter().count(), 0);
150
151        let mut first = ExpressionTrackedObjects::new();
152        first.add_key(ExpressionObjectKey(1), "A");
153        let mut second = ExpressionTrackedObjects::new();
154        second.add_key(ExpressionObjectKey(2), "A");
155        second.add_key(ExpressionObjectKey(3), "A");
156        pool.return_(first);
157        pool.return_(second);
158
159        // The collection comes back as it was returned.
160        assert_eq!(pool.get().iter().count(), 2);
161        assert_eq!(pool.get().iter().count(), 0);
162    }
163}