use crate::node::{NodeCodec, NodeUiInstance};
use gantz_ca::NodeData;
use gantz_core::data::ReifyNodeError;
use std::collections::HashMap;
pub struct InstanceEntry {
pub src: NodeData,
pub inst: NodeUiInstance,
}
#[derive(Default)]
pub struct NodeInstances {
entries: HashMap<usize, InstanceEntry>,
}
impl NodeInstances {
pub fn take(
&mut self,
codec: &NodeCodec,
n_ix: usize,
data: &NodeData,
) -> Result<InstanceEntry, ReifyNodeError> {
if let Some(entry) = self.entries.remove(&n_ix) {
if entry.src == *data {
return Ok(entry);
}
}
let inst = codec.reify_ui(data)?;
Ok(InstanceEntry {
src: data.clone(),
inst,
})
}
pub fn put(&mut self, n_ix: usize, entry: InstanceEntry) {
self.entries.insert(n_ix, entry);
}
pub fn peek(&self, n_ix: usize, data: &NodeData) -> Option<&NodeUiInstance> {
let entry = self.entries.get(&n_ix)?;
(entry.src == *data).then_some(&entry.inst)
}
pub fn apply_reindex(&mut self, reindex: &crate::ops::Reindex) {
for op in &reindex.0 {
self.entries.remove(&op.removed);
if let Some(from) = op.moved_from {
if let Some(entry) = self.entries.remove(&from) {
self.entries.insert(op.removed, entry);
}
}
}
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ops::{Reindex, RemoveOp};
fn codec() -> NodeCodec {
crate::test_node::codec()
}
fn expr_data(src: &str) -> NodeData {
gantz_core::data::erase_node_typed(&gantz_core::node::Expr::new(src).unwrap()).unwrap()
}
fn inst_ptr(entry: &InstanceEntry) -> *const () {
&*entry.inst.node as *const dyn crate::NodeUi as *const ()
}
#[test]
fn take_hits_on_equal_data() {
let codec = codec();
let data = expr_data("(+ $l $r)");
let mut cache = NodeInstances::default();
let entry = cache.take(&codec, 0, &data).unwrap();
let ptr = inst_ptr(&entry);
cache.put(0, entry);
let entry = cache.take(&codec, 0, &data).unwrap();
assert_eq!(inst_ptr(&entry), ptr);
assert_eq!(entry.inst.erase().unwrap(), data);
}
#[test]
fn take_misses_on_changed_data() {
let codec = codec();
let old = expr_data("(+ $l $r)");
let new = expr_data("(* $l $r)");
let mut cache = NodeInstances::default();
let entry = cache.take(&codec, 0, &old).unwrap();
cache.put(0, entry);
let entry = cache.take(&codec, 0, &new).unwrap();
assert_eq!(entry.inst.erase().unwrap(), new);
assert_eq!(entry.src, new);
cache.put(0, entry);
assert_eq!(cache.len(), 1);
}
#[test]
fn take_err_retains_nothing() {
let codec = codec();
let mut unknown = expr_data("(+ $l $r)");
unknown.tag = "NotInTheManifest".to_string();
let mut cache = NodeInstances::default();
assert!(cache.take(&codec, 0, &unknown).is_err());
assert!(cache.is_empty());
let known = expr_data("(+ $l $r)");
let entry = cache.take(&codec, 0, &known).unwrap();
cache.put(0, entry);
assert_eq!(cache.len(), 1);
}
#[test]
fn take_err_drops_stale_entry() {
let codec = codec();
let known = expr_data("(+ $l $r)");
let mut cache = NodeInstances::default();
let entry = cache.take(&codec, 0, &known).unwrap();
cache.put(0, entry);
let mut unknown = expr_data("(+ $l $r)");
unknown.tag = "NotInTheManifest".to_string();
assert!(cache.take(&codec, 0, &unknown).is_err());
assert!(cache.is_empty());
}
#[test]
fn edit_updates_witness() {
let codec = codec();
let old = expr_data("(+ $l $r)");
let new = expr_data("(* $l $r)");
let mut cache = NodeInstances::default();
let mut entry = cache.take(&codec, 0, &old).unwrap();
entry.inst = codec.reify_ui(&new).unwrap();
entry.src = entry.inst.erase().unwrap();
let ptr = inst_ptr(&entry);
cache.put(0, entry);
let entry = cache.take(&codec, 0, &new).unwrap();
assert_eq!(inst_ptr(&entry), ptr);
cache.put(0, entry);
let entry = cache.take(&codec, 0, &old).unwrap();
assert_eq!(entry.inst.erase().unwrap(), old);
}
#[test]
fn peek_checks_witness() {
let codec = codec();
let data = expr_data("(+ $l $r)");
let other = expr_data("(* $l $r)");
let mut cache = NodeInstances::default();
assert!(cache.peek(0, &data).is_none());
let entry = cache.take(&codec, 0, &data).unwrap();
cache.put(0, entry);
assert!(cache.peek(0, &data).is_some());
assert!(cache.peek(0, &other).is_none());
assert!(cache.peek(1, &data).is_none());
assert_eq!(cache.len(), 1);
}
#[test]
fn apply_reindex_migrates_entries() {
let codec = codec();
let datas: Vec<_> = ["(+ $l $r)", "(* $l $r)", "(- $l $r)"]
.iter()
.map(|s| expr_data(s))
.collect();
let mut cache = NodeInstances::default();
let mut ptrs = vec![];
for (i, d) in datas.iter().enumerate() {
let entry = cache.take(&codec, i, d).unwrap();
ptrs.push(inst_ptr(&entry));
cache.put(i, entry);
}
let reindex = Reindex(vec![RemoveOp {
removed: 1,
moved_from: Some(2),
}]);
cache.apply_reindex(&reindex);
assert_eq!(cache.len(), 2);
assert_eq!(reindex.apply_to_index(2), Some(1));
let entry = cache.take(&codec, 1, &datas[2]).unwrap();
assert_eq!(inst_ptr(&entry), ptrs[2]);
cache.put(1, entry);
assert!(cache.peek(0, &datas[0]).is_some());
assert!(cache.peek(2, &datas[2]).is_none());
}
#[test]
fn clear_empties() {
let codec = codec();
let data = expr_data("(+ $l $r)");
let mut cache = NodeInstances::default();
let entry = cache.take(&codec, 0, &data).unwrap();
cache.put(0, entry);
assert!(!cache.is_empty());
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
}