use std::cmp::Ordering;
use std::collections::{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)
}
fn idx(self) -> usize {
self.0 as usize
}
}
#[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)>>,
}
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);
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 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>,
}
#[derive(Default)]
pub(crate) struct LayerStackRegistry {
instances: Vec<LayerStackInstance>,
by_key: HashMap<LayerStackKey, LayerStackId>,
contexts: ExprVarInterner,
}
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.idx()].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.instances.len() as u32);
if let LayerStackKey::Target { source, .. } = key {
debug_assert!(
source.referent().idx() < id.idx(),
"a key's source referent must precede its owner",
);
debug_assert_eq!(
self.instances[source.referent().idx()].vars_source,
source,
"a minted key's source must be canonical",
);
}
let vars_source = self.derive_vars_source(id, key, expr_id);
self.instances.push(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().idx()];
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.idx()).map_or(&[], |inst| inst.members.as_slice())
}
pub(crate) fn member_set(&self, id: LayerStackId) -> Option<&HashSet<LayerId>> {
self.instances.get(id.idx()).map(|inst| &inst.member_set)
}
pub(crate) fn member_layers(&self) -> HashSet<LayerId> {
self.instances
.iter()
.flat_map(|inst| inst.member_set.iter().copied())
.collect()
}
pub(crate) fn target_key(&self, id: LayerStackId) -> Option<(LayerId, VarsSource)> {
match self.instances[id.idx()].key {
LayerStackKey::Root => None,
LayerStackKey::Target { root, source } => Some((root, source)),
}
}
pub(crate) fn targets(&self) -> impl Iterator<Item = (LayerStackId, LayerId, VarsSource)> + '_ {
self.instances
.iter()
.enumerate()
.filter_map(|(i, inst)| match inst.key {
LayerStackKey::Root => None,
LayerStackKey::Target { root, source } => Some((LayerStackId(i as u32), root, source)),
})
}
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.idx()].key, expr_id);
let instance = &mut self.instances[id.idx()];
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.idx()].expr_vars
}
pub(crate) fn vars_source(&self, id: LayerStackId) -> VarsSource {
self.instances[id.idx()].vars_source
}
pub(crate) fn sublayer_var_deps(&self, id: LayerStackId) -> &HashSet<String> {
&self.instances[id.idx()].sublayer_var_deps
}
pub(crate) fn set_sublayer_var_deps(&mut self, id: LayerStackId, deps: HashSet<String>) {
self.instances[id.idx()].sublayer_var_deps = deps;
}
pub(crate) fn changed_var_names(&self, old: ExprVarId, new: ExprVarId) -> HashSet<String> {
self.contexts.changed_names(old, new)
}
}
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");
}
}