use std::collections::BTreeSet;
use bitflags::bitflags;
use crate::sdf;
use crate::sdf::schema::FieldKey;
use crate::sdf::{ChangeEntry, ChangeList, Path};
use super::Cache;
#[derive(Debug, Default)]
pub(crate) struct Changes {
pub cache: CacheChanges,
pub layer_stack: LayerStackChanges,
}
#[derive(Debug, Default)]
pub struct CacheChanges {
pub(crate) did_change_significantly: BTreeSet<Path>,
pub(crate) did_change_prims: BTreeSet<Path>,
#[allow(dead_code)]
pub(crate) did_change_specs: BTreeSet<Path>,
}
bitflags! {
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct LayerStackChanges: u8 {
const LAYERS = 1 << 0;
const OFFSETS = 1 << 1;
const RELOCATES = 1 << 2;
const SIGNIFICANT = 1 << 3;
const NEEDS_LAYER_STACK_REBUILD = Self::LAYERS.bits() | Self::OFFSETS.bits();
const NEEDS_RELOCATES_REBUILD = Self::LAYERS.bits() | Self::RELOCATES.bits();
}
}
impl Changes {
pub fn new() -> Self {
Self::default()
}
pub fn did_change(&mut self, cache: &Cache, changes: &[(usize, ChangeList)]) {
for (layer_index, cl) in changes {
for (path, entry) in cl.entries() {
if path.is_abs_root() {
self.classify_root_entry(cache, *layer_index, entry);
} else if path.is_property_path() {
continue;
} else {
self.classify_prim_entry(cache, *layer_index, path, entry);
}
}
}
}
fn classify_prim_entry(&mut self, cache: &Cache, layer: usize, path: &Path, entry: &ChangeEntry) {
let significant = entry.flags.intersects(sdf::ChangeFlags::NON_INERT_PRIM)
|| entry
.info_changed
.iter()
.any(|k| Self::field_promotes_to_significant(k));
if significant {
self.fanout_significant(cache, layer, path);
} else if entry.flags.intersects(sdf::ChangeFlags::INERT_PRIM) {
self.cache.did_change_prims.insert(path.clone());
}
}
fn classify_root_entry(&mut self, _cache: &Cache, _layer: usize, entry: &ChangeEntry) {
let mut significant_at_root = false;
for &key in &entry.info_changed {
if key == FieldKey::SubLayers.as_str() {
self.layer_stack |= LayerStackChanges::LAYERS | LayerStackChanges::SIGNIFICANT;
} else if key == FieldKey::SubLayerOffsets.as_str() {
self.layer_stack |= LayerStackChanges::OFFSETS | LayerStackChanges::SIGNIFICANT;
} else if key == FieldKey::LayerRelocates.as_str() {
self.layer_stack |= LayerStackChanges::RELOCATES | LayerStackChanges::SIGNIFICANT;
} else if key == FieldKey::DefaultPrim.as_str() {
significant_at_root = true;
}
}
if significant_at_root {
self.cache.did_change_significantly.insert(Path::abs_root());
}
}
fn fanout_significant(&mut self, cache: &Cache, layer: usize, path: &Path) {
for dep in cache.dependencies().lookup_with_ancestors(layer, path) {
self.cache.did_change_significantly.insert(dep);
}
for dep in cache.dependencies().subtree_lookup(layer, path) {
self.cache.did_change_significantly.insert(dep);
}
self.cache.did_change_significantly.insert(path.clone());
}
fn field_promotes_to_significant(field: &str) -> bool {
field == FieldKey::References.as_str()
|| field == FieldKey::Payload.as_str()
|| field == FieldKey::InheritPaths.as_str()
|| field == FieldKey::Specializes.as_str()
|| field == FieldKey::VariantSetNames.as_str()
|| field == FieldKey::VariantSelection.as_str()
|| field == FieldKey::Instanceable.as_str()
|| field == FieldKey::Specifier.as_str()
|| field == FieldKey::Active.as_str()
|| field == FieldKey::ApiSchemas.as_str()
|| field == FieldKey::Relocates.as_str()
}
pub fn apply(self, cache: &mut Cache) {
if self.layer_stack.contains(LayerStackChanges::SIGNIFICANT) {
cache.clear_all_indices();
}
if self
.layer_stack
.intersects(LayerStackChanges::NEEDS_LAYER_STACK_REBUILD)
{
cache.recompute_layer_stack();
}
if self.layer_stack.intersects(LayerStackChanges::NEEDS_RELOCATES_REBUILD) {
cache.recompute_relocates();
}
if self.layer_stack.contains(LayerStackChanges::SIGNIFICANT) {
return;
}
for path in &self.cache.did_change_significantly {
cache.drop_index_subtree(path);
}
for path in &self.cache.did_change_prims {
if self.cache.did_change_significantly.iter().any(|p| path.has_prefix(p)) {
continue;
}
cache.drop_index(path);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ar::DefaultResolver;
use crate::pcp::{LayerStack, VariantFallbackMap};
use crate::sdf::{ChangeFlags, ChangeList};
fn p(s: &str) -> Path {
Path::new(s).expect("valid path")
}
fn empty_cache() -> Cache {
let stack = LayerStack::new(Vec::new(), 0, Box::new(DefaultResolver::new()), true);
Cache::new(stack, VariantFallbackMap::new())
}
#[test]
fn references_promotes_to_significant() {
let cache = empty_cache();
let mut cl = ChangeList::new();
cl.entry_mut(&p("/Foo"))
.info_changed
.insert(FieldKey::References.as_str());
let mut changes = Changes::new();
changes.did_change(&cache, &[(0, cl)]);
assert!(changes.cache.did_change_significantly.contains(&p("/Foo")));
}
#[test]
fn variant_selection_promotes_to_significant() {
let cache = empty_cache();
let mut cl = ChangeList::new();
cl.entry_mut(&p("/Foo"))
.info_changed
.insert(FieldKey::VariantSelection.as_str());
let mut changes = Changes::new();
changes.did_change(&cache, &[(0, cl)]);
assert!(changes.cache.did_change_significantly.contains(&p("/Foo")));
}
#[test]
fn inert_add_lands_on_spec_tier() {
let cache = empty_cache();
let mut cl = ChangeList::new();
cl.entry_mut(&p("/Foo")).flags = ChangeFlags::ADD_INERT_PRIM;
let mut changes = Changes::new();
changes.did_change(&cache, &[(0, cl)]);
assert!(!changes.cache.did_change_significantly.contains(&p("/Foo")));
assert!(changes.cache.did_change_specs.is_empty());
}
#[test]
fn non_inert_add_is_significant_with_self_path() {
let cache = empty_cache();
let mut cl = ChangeList::new();
cl.entry_mut(&p("/Foo")).flags = ChangeFlags::ADD_NON_INERT_PRIM;
let mut changes = Changes::new();
changes.did_change(&cache, &[(0, cl)]);
assert!(changes.cache.did_change_significantly.contains(&p("/Foo")));
}
#[test]
fn sublayers_change_is_layer_stack_significant() {
let cache = empty_cache();
let mut cl = ChangeList::new();
cl.entry_mut(&Path::abs_root())
.info_changed
.insert(FieldKey::SubLayers.as_str());
let mut changes = Changes::new();
changes.did_change(&cache, &[(0, cl)]);
assert!(changes.layer_stack.contains(LayerStackChanges::SIGNIFICANT));
assert!(changes.layer_stack.contains(LayerStackChanges::LAYERS));
}
#[test]
fn default_prim_change_is_significant_at_root() {
let cache = empty_cache();
let mut cl = ChangeList::new();
cl.entry_mut(&Path::abs_root())
.info_changed
.insert(FieldKey::DefaultPrim.as_str());
let mut changes = Changes::new();
changes.did_change(&cache, &[(0, cl)]);
assert!(changes.cache.did_change_significantly.contains(&Path::abs_root()));
assert!(!changes.layer_stack.contains(LayerStackChanges::SIGNIFICANT));
}
#[test]
fn layer_relocates_change_flags_relocates() {
let cache = empty_cache();
let mut cl = ChangeList::new();
cl.entry_mut(&Path::abs_root())
.info_changed
.insert(FieldKey::LayerRelocates.as_str());
let mut changes = Changes::new();
changes.did_change(&cache, &[(0, cl)]);
assert!(changes.layer_stack.contains(LayerStackChanges::RELOCATES));
assert!(changes.layer_stack.contains(LayerStackChanges::SIGNIFICANT));
}
#[test]
fn property_changes_no_op() {
let cache = empty_cache();
let mut cl = ChangeList::new();
cl.entry_mut(&p("/Foo.attr")).flags = ChangeFlags::ADD_PROPERTY;
let mut changes = Changes::new();
changes.did_change(&cache, &[(0, cl)]);
assert!(changes.cache.did_change_significantly.is_empty());
assert!(changes.cache.did_change_specs.is_empty());
assert!(!changes.layer_stack.contains(LayerStackChanges::SIGNIFICANT));
}
}