use std::cell::Cell;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum CachedProperty {
HasLoop = 0,
HasMulti = 1,
HasMutual = 2,
IsWeaklyConnected = 3,
IsStronglyConnected = 4,
IsDag = 5,
IsForest = 6,
}
pub(crate) const PROP_COUNT: u8 = 7;
impl CachedProperty {
#[inline]
fn bit(self) -> u32 {
1u32 << (self as u8)
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct PropertyCache {
known: Cell<u32>,
values: Cell<u32>,
}
impl PropertyCache {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn has(&self, prop: CachedProperty) -> bool {
self.known.get() & prop.bit() != 0
}
pub(crate) fn get(&self, prop: CachedProperty) -> Option<bool> {
if self.has(prop) {
Some(self.values.get() & prop.bit() != 0)
} else {
None
}
}
pub(crate) fn set(&self, prop: CachedProperty, value: bool) {
let bit = prop.bit();
self.known.set(self.known.get() | bit);
let cur = self.values.get();
self.values.set(if value { cur | bit } else { cur & !bit });
}
pub(crate) fn invalidate(&self, prop: CachedProperty) {
self.known.set(self.known.get() & !prop.bit());
}
pub(crate) fn invalidate_all(&self) {
self.known.set(0);
}
pub(crate) fn invalidate_conditionally(
&self,
keep_always: u32,
keep_when_false: u32,
keep_when_true: u32,
) {
let mut invalidate = !keep_always;
let known = self.known.get();
let values = self.values.get();
let maybe_keep = known & invalidate & (keep_when_false | keep_when_true);
if maybe_keep != 0 {
for i in 0..PROP_COUNT {
let mask = 1u32 << i;
if maybe_keep & mask != 0 {
let cached_value = values & mask != 0;
if (keep_when_false & mask != 0 && !cached_value)
|| (keep_when_true & mask != 0 && cached_value)
{
invalidate &= !mask;
}
}
}
}
self.known.set(known & !invalidate);
}
}
pub(crate) fn invalidate_after_add_vertices(cache: &PropertyCache) {
use CachedProperty::{
HasLoop, HasMulti, HasMutual, IsDag, IsForest, IsStronglyConnected, IsWeaklyConnected,
};
cache.invalidate_conditionally(
HasLoop.bit() | HasMulti.bit() | HasMutual.bit() | IsDag.bit() | IsForest.bit(),
IsWeaklyConnected.bit() | IsStronglyConnected.bit(),
0,
);
}
pub(crate) fn invalidate_after_add_edges(cache: &PropertyCache) {
use CachedProperty::{
HasLoop, HasMulti, HasMutual, IsDag, IsForest, IsStronglyConnected, IsWeaklyConnected,
};
cache.invalidate_conditionally(
0,
IsDag.bit() | IsForest.bit(),
IsWeaklyConnected.bit()
| IsStronglyConnected.bit()
| HasLoop.bit()
| HasMulti.bit()
| HasMutual.bit(),
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_cache_misses() {
let c = PropertyCache::new();
assert!(!c.has(CachedProperty::IsDag));
assert_eq!(c.get(CachedProperty::IsDag), None);
}
#[test]
fn set_then_get_round_trips() {
let c = PropertyCache::new();
c.set(CachedProperty::IsDag, true);
c.set(CachedProperty::IsForest, false);
assert_eq!(c.get(CachedProperty::IsDag), Some(true));
assert_eq!(c.get(CachedProperty::IsForest), Some(false));
assert_eq!(c.get(CachedProperty::HasLoop), None);
}
#[test]
fn overwrite_changes_value() {
let c = PropertyCache::new();
c.set(CachedProperty::HasLoop, true);
assert_eq!(c.get(CachedProperty::HasLoop), Some(true));
c.set(CachedProperty::HasLoop, false);
assert_eq!(c.get(CachedProperty::HasLoop), Some(false));
}
#[test]
fn invalidate_one_keeps_others() {
let c = PropertyCache::new();
c.set(CachedProperty::HasLoop, true);
c.set(CachedProperty::IsDag, false);
c.invalidate(CachedProperty::HasLoop);
assert_eq!(c.get(CachedProperty::HasLoop), None);
assert_eq!(c.get(CachedProperty::IsDag), Some(false));
}
#[test]
fn invalidate_all_clears_everything() {
let c = PropertyCache::new();
c.set(CachedProperty::HasLoop, true);
c.set(CachedProperty::IsDag, false);
c.invalidate_all();
for p in [
CachedProperty::HasLoop,
CachedProperty::IsDag,
CachedProperty::IsForest,
] {
assert_eq!(c.get(p), None);
}
}
#[test]
fn invalidate_conditionally_keep_always() {
let c = PropertyCache::new();
c.set(CachedProperty::IsDag, true);
c.set(CachedProperty::HasLoop, false);
c.invalidate_conditionally(CachedProperty::IsDag.bit(), 0, 0);
assert_eq!(c.get(CachedProperty::IsDag), Some(true));
assert_eq!(c.get(CachedProperty::HasLoop), None);
}
#[test]
fn invalidate_conditionally_keep_when_false() {
let c = PropertyCache::new();
c.set(CachedProperty::IsDag, false);
c.invalidate_conditionally(0, CachedProperty::IsDag.bit(), 0);
assert_eq!(c.get(CachedProperty::IsDag), Some(false));
let c = PropertyCache::new();
c.set(CachedProperty::IsDag, true);
c.invalidate_conditionally(0, CachedProperty::IsDag.bit(), 0);
assert_eq!(c.get(CachedProperty::IsDag), None);
}
#[test]
fn invalidate_conditionally_keep_when_true() {
let c = PropertyCache::new();
c.set(CachedProperty::HasLoop, true);
c.invalidate_conditionally(0, 0, CachedProperty::HasLoop.bit());
assert_eq!(c.get(CachedProperty::HasLoop), Some(true));
let c = PropertyCache::new();
c.set(CachedProperty::HasLoop, false);
c.invalidate_conditionally(0, 0, CachedProperty::HasLoop.bit());
assert_eq!(c.get(CachedProperty::HasLoop), None);
}
#[test]
fn add_edges_policy_keeps_dag_false_and_loop_true() {
let c = PropertyCache::new();
c.set(CachedProperty::IsDag, false);
c.set(CachedProperty::HasLoop, true);
c.set(CachedProperty::IsForest, true); c.set(CachedProperty::HasMulti, false); invalidate_after_add_edges(&c);
assert_eq!(c.get(CachedProperty::IsDag), Some(false));
assert_eq!(c.get(CachedProperty::HasLoop), Some(true));
assert_eq!(c.get(CachedProperty::IsForest), None);
assert_eq!(c.get(CachedProperty::HasMulti), None);
}
#[test]
fn add_vertices_policy_keeps_edge_props_unconditionally() {
let c = PropertyCache::new();
c.set(CachedProperty::HasLoop, false);
c.set(CachedProperty::HasMulti, true);
c.set(CachedProperty::HasMutual, true);
c.set(CachedProperty::IsDag, true);
c.set(CachedProperty::IsForest, false);
c.set(CachedProperty::IsWeaklyConnected, true); invalidate_after_add_vertices(&c);
assert_eq!(c.get(CachedProperty::HasLoop), Some(false));
assert_eq!(c.get(CachedProperty::HasMulti), Some(true));
assert_eq!(c.get(CachedProperty::HasMutual), Some(true));
assert_eq!(c.get(CachedProperty::IsDag), Some(true));
assert_eq!(c.get(CachedProperty::IsForest), Some(false));
assert_eq!(c.get(CachedProperty::IsWeaklyConnected), None);
}
#[test]
fn clone_preserves_cache() {
let c = PropertyCache::new();
c.set(CachedProperty::IsDag, true);
let c2 = c.clone();
assert_eq!(c2.get(CachedProperty::IsDag), Some(true));
}
}