1use std::cell::Cell;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[repr(u8)]
50pub enum CachedProperty {
51 HasLoop = 0,
53 HasMulti = 1,
56 HasMutual = 2,
58 IsWeaklyConnected = 3,
60 IsStronglyConnected = 4,
62 IsDag = 5,
64 IsForest = 6,
66}
67
68pub(crate) const PROP_COUNT: u8 = 7;
72
73impl CachedProperty {
74 #[inline]
75 fn bit(self) -> u32 {
76 1u32 << (self as u8)
77 }
78}
79
80#[derive(Debug, Clone, Default)]
86pub(crate) struct PropertyCache {
87 known: Cell<u32>,
89 values: Cell<u32>,
92}
93
94impl PropertyCache {
95 pub(crate) fn new() -> Self {
96 Self::default()
97 }
98
99 pub(crate) fn has(&self, prop: CachedProperty) -> bool {
100 self.known.get() & prop.bit() != 0
101 }
102
103 pub(crate) fn get(&self, prop: CachedProperty) -> Option<bool> {
104 if self.has(prop) {
105 Some(self.values.get() & prop.bit() != 0)
106 } else {
107 None
108 }
109 }
110
111 pub(crate) fn set(&self, prop: CachedProperty, value: bool) {
115 let bit = prop.bit();
116 self.known.set(self.known.get() | bit);
117 let cur = self.values.get();
118 self.values.set(if value { cur | bit } else { cur & !bit });
119 }
120
121 pub(crate) fn invalidate(&self, prop: CachedProperty) {
125 self.known.set(self.known.get() & !prop.bit());
126 }
127
128 pub(crate) fn invalidate_all(&self) {
132 self.known.set(0);
133 }
134
135 pub(crate) fn invalidate_conditionally(
147 &self,
148 keep_always: u32,
149 keep_when_false: u32,
150 keep_when_true: u32,
151 ) {
152 let mut invalidate = !keep_always;
153 let known = self.known.get();
154 let values = self.values.get();
155 let maybe_keep = known & invalidate & (keep_when_false | keep_when_true);
156 if maybe_keep != 0 {
157 for i in 0..PROP_COUNT {
158 let mask = 1u32 << i;
159 if maybe_keep & mask != 0 {
160 let cached_value = values & mask != 0;
161 if (keep_when_false & mask != 0 && !cached_value)
162 || (keep_when_true & mask != 0 && cached_value)
163 {
164 invalidate &= !mask;
165 }
166 }
167 }
168 }
169 self.known.set(known & !invalidate);
170 }
171}
172
173pub(crate) fn invalidate_after_add_vertices(cache: &PropertyCache) {
182 use CachedProperty::{
183 HasLoop, HasMulti, HasMutual, IsDag, IsForest, IsStronglyConnected, IsWeaklyConnected,
184 };
185 cache.invalidate_conditionally(
186 HasLoop.bit() | HasMulti.bit() | HasMutual.bit() | IsDag.bit() | IsForest.bit(),
187 IsWeaklyConnected.bit() | IsStronglyConnected.bit(),
188 0,
189 );
190}
191
192pub(crate) fn invalidate_after_add_edges(cache: &PropertyCache) {
203 use CachedProperty::{
204 HasLoop, HasMulti, HasMutual, IsDag, IsForest, IsStronglyConnected, IsWeaklyConnected,
205 };
206 cache.invalidate_conditionally(
207 0,
208 IsDag.bit() | IsForest.bit(),
209 IsWeaklyConnected.bit()
210 | IsStronglyConnected.bit()
211 | HasLoop.bit()
212 | HasMulti.bit()
213 | HasMutual.bit(),
214 );
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn empty_cache_misses() {
223 let c = PropertyCache::new();
224 assert!(!c.has(CachedProperty::IsDag));
225 assert_eq!(c.get(CachedProperty::IsDag), None);
226 }
227
228 #[test]
229 fn set_then_get_round_trips() {
230 let c = PropertyCache::new();
231 c.set(CachedProperty::IsDag, true);
232 c.set(CachedProperty::IsForest, false);
233 assert_eq!(c.get(CachedProperty::IsDag), Some(true));
234 assert_eq!(c.get(CachedProperty::IsForest), Some(false));
235 assert_eq!(c.get(CachedProperty::HasLoop), None);
236 }
237
238 #[test]
239 fn overwrite_changes_value() {
240 let c = PropertyCache::new();
241 c.set(CachedProperty::HasLoop, true);
242 assert_eq!(c.get(CachedProperty::HasLoop), Some(true));
243 c.set(CachedProperty::HasLoop, false);
244 assert_eq!(c.get(CachedProperty::HasLoop), Some(false));
245 }
246
247 #[test]
248 fn invalidate_one_keeps_others() {
249 let c = PropertyCache::new();
250 c.set(CachedProperty::HasLoop, true);
251 c.set(CachedProperty::IsDag, false);
252 c.invalidate(CachedProperty::HasLoop);
253 assert_eq!(c.get(CachedProperty::HasLoop), None);
254 assert_eq!(c.get(CachedProperty::IsDag), Some(false));
255 }
256
257 #[test]
258 fn invalidate_all_clears_everything() {
259 let c = PropertyCache::new();
260 c.set(CachedProperty::HasLoop, true);
261 c.set(CachedProperty::IsDag, false);
262 c.invalidate_all();
263 for p in [
264 CachedProperty::HasLoop,
265 CachedProperty::IsDag,
266 CachedProperty::IsForest,
267 ] {
268 assert_eq!(c.get(p), None);
269 }
270 }
271
272 #[test]
273 fn invalidate_conditionally_keep_always() {
274 let c = PropertyCache::new();
275 c.set(CachedProperty::IsDag, true);
276 c.set(CachedProperty::HasLoop, false);
277 c.invalidate_conditionally(CachedProperty::IsDag.bit(), 0, 0);
278 assert_eq!(c.get(CachedProperty::IsDag), Some(true));
279 assert_eq!(c.get(CachedProperty::HasLoop), None);
280 }
281
282 #[test]
283 fn invalidate_conditionally_keep_when_false() {
284 let c = PropertyCache::new();
285 c.set(CachedProperty::IsDag, false);
286 c.invalidate_conditionally(0, CachedProperty::IsDag.bit(), 0);
287 assert_eq!(c.get(CachedProperty::IsDag), Some(false));
288
289 let c = PropertyCache::new();
291 c.set(CachedProperty::IsDag, true);
292 c.invalidate_conditionally(0, CachedProperty::IsDag.bit(), 0);
293 assert_eq!(c.get(CachedProperty::IsDag), None);
294 }
295
296 #[test]
297 fn invalidate_conditionally_keep_when_true() {
298 let c = PropertyCache::new();
299 c.set(CachedProperty::HasLoop, true);
300 c.invalidate_conditionally(0, 0, CachedProperty::HasLoop.bit());
301 assert_eq!(c.get(CachedProperty::HasLoop), Some(true));
302
303 let c = PropertyCache::new();
304 c.set(CachedProperty::HasLoop, false);
305 c.invalidate_conditionally(0, 0, CachedProperty::HasLoop.bit());
306 assert_eq!(c.get(CachedProperty::HasLoop), None);
307 }
308
309 #[test]
310 fn add_edges_policy_keeps_dag_false_and_loop_true() {
311 let c = PropertyCache::new();
312 c.set(CachedProperty::IsDag, false);
313 c.set(CachedProperty::HasLoop, true);
314 c.set(CachedProperty::IsForest, true); c.set(CachedProperty::HasMulti, false); invalidate_after_add_edges(&c);
317 assert_eq!(c.get(CachedProperty::IsDag), Some(false));
318 assert_eq!(c.get(CachedProperty::HasLoop), Some(true));
319 assert_eq!(c.get(CachedProperty::IsForest), None);
320 assert_eq!(c.get(CachedProperty::HasMulti), None);
321 }
322
323 #[test]
324 fn add_vertices_policy_keeps_edge_props_unconditionally() {
325 let c = PropertyCache::new();
326 c.set(CachedProperty::HasLoop, false);
327 c.set(CachedProperty::HasMulti, true);
328 c.set(CachedProperty::HasMutual, true);
329 c.set(CachedProperty::IsDag, true);
330 c.set(CachedProperty::IsForest, false);
331 c.set(CachedProperty::IsWeaklyConnected, true); invalidate_after_add_vertices(&c);
333 assert_eq!(c.get(CachedProperty::HasLoop), Some(false));
334 assert_eq!(c.get(CachedProperty::HasMulti), Some(true));
335 assert_eq!(c.get(CachedProperty::HasMutual), Some(true));
336 assert_eq!(c.get(CachedProperty::IsDag), Some(true));
337 assert_eq!(c.get(CachedProperty::IsForest), Some(false));
338 assert_eq!(c.get(CachedProperty::IsWeaklyConnected), None);
339 }
340
341 #[test]
342 fn clone_preserves_cache() {
343 let c = PropertyCache::new();
344 c.set(CachedProperty::IsDag, true);
345 let c2 = c.clone();
346 assert_eq!(c2.get(CachedProperty::IsDag), Some(true));
347 }
348}