use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap, HashSet};
use crate::sdf::{LayerOffset, Value};
use super::layer_graph::LayerId;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub(crate) struct LayerStackId(u32);
impl LayerStackId {
pub(crate) const ROOT: LayerStackId = LayerStackId(0);
#[cfg(test)]
pub(crate) const fn from_raw(raw: u32) -> Self {
Self(raw)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) enum VarsSource {
Root,
Instance(LayerStackId),
}
impl VarsSource {
pub(crate) fn referent(self) -> LayerStackId {
match self {
VarsSource::Root => LayerStackId::ROOT,
VarsSource::Instance(id) => id,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct ExprVarId(u32);
impl ExprVarId {
fn idx(self) -> usize {
self.0 as usize
}
}
#[derive(Default)]
pub(crate) struct ExprVarInterner {
contexts: Vec<Vec<(String, Value)>>,
fresh_since_sweep: usize,
}
impl ExprVarInterner {
pub(crate) fn intern(&mut self, vars: &HashMap<String, Value>) -> ExprVarId {
let canon = canonical_context(vars);
if let Some(id) = self.find_canonical(&canon) {
return id;
}
let id = ExprVarId(self.contexts.len() as u32);
self.contexts.push(canon);
self.fresh_since_sweep += 1;
id
}
fn find_canonical(&self, canon: &[(String, Value)]) -> Option<ExprVarId> {
self.contexts
.iter()
.position(|context| {
context.len() == canon.len()
&& context
.iter()
.zip(canon)
.all(|((cn, cv), (n, v))| cn == n && value_eq(cv, v))
})
.map(|i| ExprVarId(i as u32))
}
fn vars(&self, id: ExprVarId) -> &[(String, Value)] {
&self.contexts[id.idx()]
}
fn compact(&mut self, used: &HashSet<usize>) -> Vec<Option<ExprVarId>> {
let mut remap = vec![None; self.contexts.len()];
let mut kept = Vec::with_capacity(used.len());
for (i, context) in self.contexts.drain(..).enumerate() {
if used.contains(&i) {
remap[i] = Some(ExprVarId(kept.len() as u32));
kept.push(context);
}
}
self.contexts = kept;
remap
}
fn changed_names(&self, old: ExprVarId, new: ExprVarId) -> HashSet<String> {
let old = self.vars(old);
let new = self.vars(new);
let mut changed = HashSet::new();
let (mut i, mut j) = (0, 0);
while i < old.len() && j < new.len() {
match old[i].0.cmp(&new[j].0) {
Ordering::Less => {
changed.insert(old[i].0.clone());
i += 1;
}
Ordering::Greater => {
changed.insert(new[j].0.clone());
j += 1;
}
Ordering::Equal => {
if !value_eq(&old[i].1, &new[j].1) {
changed.insert(old[i].0.clone());
}
i += 1;
j += 1;
}
}
}
changed.extend(old[i..].iter().map(|(name, _)| name.clone()));
changed.extend(new[j..].iter().map(|(name, _)| name.clone()));
changed
}
}
pub(crate) struct StackVarsDelta {
pub(crate) stack: LayerStackId,
pub(crate) old_expr: ExprVarId,
pub(crate) new_expr: ExprVarId,
pub(crate) old_source: VarsSource,
pub(crate) new_source: VarsSource,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum LayerStackKey {
Root,
Target { root: LayerId, source: VarsSource },
}
struct LayerStackInstance {
key: LayerStackKey,
members: Vec<(LayerId, LayerOffset)>,
member_set: HashSet<LayerId>,
expr_vars: HashMap<String, Value>,
expr_id: ExprVarId,
vars_source: VarsSource,
sublayer_var_deps: HashSet<String>,
}
const SWEEP_MINT_THRESHOLD: usize = 32;
#[derive(Default)]
pub(crate) struct StackMarks {
marked: HashSet<LayerStackId>,
}
impl StackMarks {
pub(crate) fn mark(&mut self, id: LayerStackId) {
self.marked.insert(id);
}
fn is_marked(&self, id: LayerStackId) -> bool {
self.marked.contains(&id)
}
}
#[derive(Default)]
pub(crate) struct LayerStackRegistry {
instances: BTreeMap<LayerStackId, LayerStackInstance>,
by_key: HashMap<LayerStackKey, LayerStackId>,
contexts: ExprVarInterner,
next_id: u32,
minted_since_sweep: usize,
}
impl LayerStackRegistry {
pub(crate) fn lookup_target(&self, root: LayerId, source: VarsSource) -> Option<LayerStackId> {
self.by_key.get(&LayerStackKey::Target { root, source }).copied()
}
pub(crate) fn set_root(
&mut self,
members: Vec<(LayerId, LayerOffset)>,
expr_vars: HashMap<String, Value>,
) -> Option<StackVarsDelta> {
if self.instances.is_empty() {
let id = self.insert(LayerStackKey::Root, members, expr_vars);
debug_assert_eq!(id, LayerStackId::ROOT, "the root stack must be instance 0");
None
} else {
debug_assert!(
matches!(self.instances[&LayerStackId::ROOT].key, LayerStackKey::Root),
"instance 0 must be the root stack",
);
self.set_composed(LayerStackId::ROOT, members, expr_vars)
}
}
pub(crate) fn intern_target(
&mut self,
root: LayerId,
source: VarsSource,
members: Vec<(LayerId, LayerOffset)>,
expr_vars: HashMap<String, Value>,
) -> LayerStackId {
self.insert(LayerStackKey::Target { root, source }, members, expr_vars)
}
fn insert(
&mut self,
key: LayerStackKey,
members: Vec<(LayerId, LayerOffset)>,
expr_vars: HashMap<String, Value>,
) -> LayerStackId {
let member_set = members.iter().map(|&(id, _)| id).collect();
let expr_id = self.contexts.intern(&expr_vars);
let id = LayerStackId(self.next_id);
self.next_id = self.next_id.checked_add(1).expect("layer-stack id space exhausted");
if let LayerStackKey::Target { source, .. } = key {
debug_assert!(source.referent() < id, "a key's source referent must precede its owner",);
debug_assert_eq!(
self.instances[&source.referent()].vars_source,
source,
"a minted key's source must be canonical",
);
}
let vars_source = self.derive_vars_source(id, key, expr_id);
self.minted_since_sweep += 1;
self.instances.insert(
id,
LayerStackInstance {
key,
members,
member_set,
expr_vars,
expr_id,
vars_source,
sublayer_var_deps: HashSet::new(),
},
);
self.by_key.insert(key, id);
id
}
fn derive_vars_source(&self, id: LayerStackId, key: LayerStackKey, expr_id: ExprVarId) -> VarsSource {
match key {
LayerStackKey::Root => VarsSource::Root,
LayerStackKey::Target { source, .. } => {
let referent = &self.instances[&source.referent()];
if expr_id == referent.expr_id {
referent.vars_source
} else {
VarsSource::Instance(id)
}
}
}
}
pub(crate) fn members(&self, id: LayerStackId) -> &[(LayerId, LayerOffset)] {
self.instances
.get(&id)
.map_or(&[], |instance| instance.members.as_slice())
}
pub(crate) fn member_set(&self, id: LayerStackId) -> Option<&HashSet<LayerId>> {
self.instances.get(&id).map(|instance| &instance.member_set)
}
pub(crate) fn try_members(&self, id: LayerStackId) -> Option<&[(LayerId, LayerOffset)]> {
self.instances.get(&id).map(|instance| instance.members.as_slice())
}
pub(crate) fn member_layers(&self) -> HashSet<LayerId> {
self.instances
.values()
.flat_map(|instance| instance.member_set.iter().copied())
.collect()
}
pub(crate) fn target_key(&self, id: LayerStackId) -> Option<(LayerId, VarsSource)> {
match self.instances[&id].key {
LayerStackKey::Root => None,
LayerStackKey::Target { root, source } => Some((root, source)),
}
}
pub(crate) fn targets(&self) -> Vec<(LayerStackId, LayerId, VarsSource)> {
self.instances
.iter()
.filter_map(|(&id, instance)| match instance.key {
LayerStackKey::Root => None,
LayerStackKey::Target { root, source } => Some((id, root, source)),
})
.collect()
}
pub(crate) fn set_composed(
&mut self,
id: LayerStackId,
members: Vec<(LayerId, LayerOffset)>,
expr_vars: HashMap<String, Value>,
) -> Option<StackVarsDelta> {
let expr_id = self.contexts.intern(&expr_vars);
let vars_source = self.derive_vars_source(id, self.instances[&id].key, expr_id);
let instance = self.instances.get_mut(&id).expect("a recomposed stack is interned");
let delta = (instance.expr_id != expr_id || instance.vars_source != vars_source).then_some(StackVarsDelta {
stack: id,
old_expr: instance.expr_id,
new_expr: expr_id,
old_source: instance.vars_source,
new_source: vars_source,
});
instance.member_set = members.iter().map(|&(id, _)| id).collect();
instance.members = members;
instance.expr_vars = expr_vars;
instance.expr_id = expr_id;
instance.vars_source = vars_source;
delta
}
pub(crate) fn expression_variables(&self, id: LayerStackId) -> &HashMap<String, Value> {
&self.instances[&id].expr_vars
}
pub(crate) fn vars_source(&self, id: LayerStackId) -> VarsSource {
self.instances[&id].vars_source
}
pub(crate) fn sublayer_var_deps(&self, id: LayerStackId) -> &HashSet<String> {
&self.instances[&id].sublayer_var_deps
}
pub(crate) fn set_sublayer_var_deps(&mut self, id: LayerStackId, deps: HashSet<String>) {
self.instances
.get_mut(&id)
.expect("a recomposed stack is interned")
.sublayer_var_deps = deps;
}
pub(crate) fn changed_var_names(&self, old: ExprVarId, new: ExprVarId) -> HashSet<String> {
self.contexts.changed_names(old, new)
}
fn close_over_referents(&self, marks: &mut StackMarks) {
for (&id, instance) in self.instances.iter().rev() {
if !marks.is_marked(id) {
continue;
}
if let LayerStackKey::Target { source, .. } = instance.key {
marks.mark(source.referent());
}
marks.mark(instance.vars_source.referent());
}
}
pub(crate) fn sweep(&mut self, mut marks: StackMarks) -> Vec<LayerStackId> {
self.minted_since_sweep = 0;
self.contexts.fresh_since_sweep = 0;
self.close_over_referents(&mut marks);
let removed: Vec<LayerStackId> = self
.instances
.keys()
.copied()
.filter(|&id| id != LayerStackId::ROOT && !marks.is_marked(id))
.collect();
for &id in &removed {
let instance = self.instances.remove(&id).expect("removal ids were just enumerated");
self.by_key.remove(&instance.key);
}
let used: HashSet<usize> = self.instances.values().map(|instance| instance.expr_id.idx()).collect();
if used.len() < self.contexts.contexts.len() {
let remap = self.contexts.compact(&used);
for instance in self.instances.values_mut() {
instance.expr_id = remap[instance.expr_id.idx()].expect("a survivor's context outlives compaction");
}
}
removed
}
pub(crate) fn ripe_for_sweep(&self) -> bool {
self.minted_since_sweep + self.contexts.fresh_since_sweep >= SWEEP_MINT_THRESHOLD.max(self.instances.len() / 4)
}
}
#[cfg(test)]
impl LayerStackRegistry {
pub(crate) fn instance_count(&self) -> usize {
self.instances.len()
}
}
fn canonical_context(vars: &HashMap<String, Value>) -> Vec<(String, Value)> {
let mut canon: Vec<(String, Value)> = vars.iter().map(|(name, value)| (name.clone(), value.clone())).collect();
canon.sort_by(|a, b| a.0.cmp(&b.0));
canon
}
fn value_eq(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Half(a), Value::Half(b)) => a.to_bits() == b.to_bits(),
(Value::Float(a), Value::Float(b)) => a.to_bits() == b.to_bits(),
(Value::Double(a), Value::Double(b)) => a.to_bits() == b.to_bits(),
(Value::Dictionary(a), Value::Dictionary(b)) => {
a.len() == b.len() && a.iter().all(|(key, av)| b.get(key).is_some_and(|bv| value_eq(av, bv)))
}
_ => a == b,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nan_seed_dedups() {
let mut interner = ExprVarInterner::default();
let vars = || HashMap::from([("V".to_string(), Value::Double(f64::NAN))]);
let first = interner.intern(&vars());
let second = interner.intern(&vars());
assert_eq!(first, second, "a NaN-valued context must intern to a single id");
}
fn registry_with_targets(targets: u32) -> (LayerStackRegistry, Vec<LayerStackId>) {
let mut registry = LayerStackRegistry::default();
registry.set_root(vec![(LayerId::from_raw(0), LayerOffset::default())], HashMap::new());
let ids = (1..=targets)
.map(|i| {
let layer = LayerId::from_raw(i);
registry.intern_target(
layer,
VarsSource::Root,
vec![(layer, LayerOffset::default())],
HashMap::new(),
)
})
.collect();
(registry, ids)
}
#[test]
fn sweep_removes_unmarked() {
let (mut registry, ids) = registry_with_targets(2);
let (a, b) = (ids[0], ids[1]);
let mut marks = StackMarks::default();
marks.mark(b);
let swept = registry.sweep(marks);
assert_eq!(swept, vec![a], "only the unmarked instance is removed");
assert!(registry.member_set(a).is_none());
assert!(registry.members(a).is_empty(), "a reclaimed handle reads as empty");
assert_eq!(
registry.lookup_target(LayerId::from_raw(1), VarsSource::Root),
None,
"the key mapping is erased with the instance",
);
assert!(registry.member_set(b).is_some(), "a marked instance survives");
assert!(
registry.member_set(LayerStackId::ROOT).is_some(),
"the root stack is never removed"
);
}
#[test]
fn closure_keeps_source_chain() {
let mut registry = LayerStackRegistry::default();
registry.set_root(vec![], HashMap::new());
let vars = HashMap::from([("V".to_string(), Value::String("x".to_string()))]);
let authoring = registry.intern_target(LayerId::from_raw(1), VarsSource::Root, vec![], vars.clone());
let dependent = registry.intern_target(LayerId::from_raw(2), VarsSource::Instance(authoring), vec![], vars);
let mut marks = StackMarks::default();
marks.mark(dependent);
let swept = registry.sweep(marks);
assert!(swept.is_empty(), "a live stack's source ancestry survives the sweep");
assert!(registry.member_set(authoring).is_some() && registry.member_set(dependent).is_some());
}
#[test]
fn reclaimed_id_not_reused() {
let (mut registry, ids) = registry_with_targets(1);
let reclaimed = ids[0];
registry.sweep(StackMarks::default());
assert!(registry.member_set(reclaimed).is_none());
let fresh = registry.intern_target(LayerId::from_raw(9), VarsSource::Root, vec![], HashMap::new());
assert_ne!(fresh, reclaimed, "a reclaimed id is never reused for a new key");
let reminted = registry.intern_target(
LayerId::from_raw(1),
VarsSource::Root,
vec![(LayerId::from_raw(1), LayerOffset::default())],
HashMap::new(),
);
assert_ne!(reminted, reclaimed, "the reclaimed key mints a fresh instance");
assert_eq!(
registry.lookup_target(LayerId::from_raw(1), VarsSource::Root),
Some(reminted),
);
}
#[test]
fn ripe_threshold() {
let (mut registry, _) = registry_with_targets(SWEEP_MINT_THRESHOLD as u32 - 1);
assert!(
registry.ripe_for_sweep(),
"the root mint plus {} target mints reach the threshold",
SWEEP_MINT_THRESHOLD - 1,
);
let swept = registry.sweep(StackMarks::default());
assert_eq!(swept.len(), SWEEP_MINT_THRESHOLD - 1);
assert!(!registry.ripe_for_sweep(), "a sweep resets the trigger");
registry.intern_target(LayerId::from_raw(1), VarsSource::Root, vec![], HashMap::new());
assert_eq!(registry.minted_since_sweep, 1, "a re-mint counts toward the next sweep");
}
#[test]
fn value_churn_compacts() {
let mut registry = LayerStackRegistry::default();
registry.set_root(vec![], HashMap::new());
let target = registry.intern_target(LayerId::from_raw(1), VarsSource::Root, vec![], HashMap::new());
let vars = |v: &str| HashMap::from([("V".to_string(), Value::String(v.to_string()))]);
for i in 0..SWEEP_MINT_THRESHOLD {
registry.set_composed(target, vec![], vars(&i.to_string()));
}
assert!(registry.ripe_for_sweep(), "context churn alone ripens the trigger");
let mut marks = StackMarks::default();
marks.mark(target);
let swept = registry.sweep(marks);
assert!(swept.is_empty(), "everything marked survives");
assert_eq!(
registry.contexts.contexts.len(),
2,
"the churned contexts compact away, keeping the empty seed and the last value",
);
assert!(!registry.ripe_for_sweep(), "the sweep resets the churn counter");
}
#[test]
fn sparse_ids_survive() {
let (mut registry, ids) = registry_with_targets(3);
let keeper = ids[0];
let mut marks = StackMarks::default();
marks.mark(keeper);
registry.sweep(marks);
assert_eq!(registry.instance_count(), 2, "the root and the keeper remain");
let late = registry.intern_target(LayerId::from_raw(9), VarsSource::Root, vec![], HashMap::new());
let mut marks = StackMarks::default();
marks.mark(keeper);
marks.mark(late);
assert_eq!(marks.marked.len(), 2, "mark storage tracks only the marked ids");
registry.sweep(marks);
assert!(registry.member_set(keeper).is_some(), "the low survivor persists");
assert!(registry.member_set(late).is_some(), "the high survivor persists");
assert_eq!(registry.instance_count(), 3);
}
#[test]
fn sweep_compacts_interner() {
let mut registry = LayerStackRegistry::default();
registry.set_root(vec![], HashMap::new());
let vars = |v: &str| HashMap::from([("V".to_string(), Value::String(v.to_string()))]);
let kept = registry.intern_target(LayerId::from_raw(1), VarsSource::Root, vec![], vars("keep"));
let churn = registry.intern_target(LayerId::from_raw(2), VarsSource::Root, vec![], vars("a"));
registry.set_composed(churn, vec![], vars("b"));
registry.set_composed(churn, vec![], vars("c"));
assert_eq!(
registry.contexts.contexts.len(),
5,
"the empty seed, the survivor's context, and three churned values",
);
let mut marks = StackMarks::default();
marks.mark(kept);
registry.sweep(marks);
assert_eq!(
registry.contexts.contexts.len(),
2,
"only the contexts the survivors reference remain",
);
assert_eq!(
registry.expression_variables(kept),
&vars("keep"),
"a survivor's context id remaps in place",
);
assert_eq!(registry.expression_variables(LayerStackId::ROOT), &HashMap::new());
}
}