use std::any::Any;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use crate::cell::CellHandle;
use crate::slot::SlotHandle;
type ComputeFn = dyn Fn(&Context) -> Box<dyn Any>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SlotId(u64);
thread_local! {
static TRACKING_STACK: RefCell<Vec<SlotId>> = const { RefCell::new(Vec::new()) };
}
pub(crate) fn push_tracking_frame(id: SlotId) {
TRACKING_STACK.with(|stack| stack.borrow_mut().push(id));
}
pub(crate) fn pop_tracking_frame() {
TRACKING_STACK.with(|stack| stack.borrow_mut().pop());
}
pub(crate) fn current_tracking_frame() -> Option<SlotId> {
TRACKING_STACK.with(|stack| stack.borrow().last().copied())
}
pub(crate) struct SlotNode {
pub(crate) value: Option<Box<dyn Any>>,
pub(crate) compute: Box<ComputeFn>,
pub(crate) dependencies: HashSet<SlotId>,
pub(crate) dependents: HashSet<SlotId>,
}
pub(crate) struct CellNode {
pub(crate) value: Box<dyn Any>,
pub(crate) dependents: HashSet<SlotId>,
}
pub(crate) enum Node {
Slot(SlotNode),
Cell(CellNode),
}
pub struct Context {
pub(crate) nodes: RefCell<HashMap<SlotId, Node>>,
pub(crate) next_id: RefCell<u64>,
}
impl Context {
pub fn new() -> Self {
Self {
nodes: RefCell::new(HashMap::new()),
next_id: RefCell::new(0),
}
}
pub(crate) fn alloc_id(&self) -> SlotId {
let mut id = self.next_id.borrow_mut();
let slot_id = SlotId(*id);
*id += 1;
slot_id
}
pub fn slot<T, F>(&self, compute: F) -> SlotHandle<T>
where
T: 'static,
F: Fn(&Context) -> T + 'static,
{
let id = self.alloc_id();
let node = SlotNode {
value: None,
compute: Box::new(move |ctx| Box::new(compute(ctx))),
dependencies: HashSet::new(),
dependents: HashSet::new(),
};
self.nodes.borrow_mut().insert(id, Node::Slot(node));
SlotHandle::new(id)
}
pub fn get<T: Clone + 'static>(&self, handle: &SlotHandle<T>) -> T {
self.get_slot(handle.id)
}
fn get_slot<T: Clone + 'static>(&self, id: SlotId) -> T {
if let Some(parent_id) = current_tracking_frame()
&& parent_id != id
{
let mut nodes = self.nodes.borrow_mut();
if let Some(node) = nodes.get_mut(&id) {
match node {
Node::Slot(s) => {
s.dependents.insert(parent_id);
}
Node::Cell(c) => {
c.dependents.insert(parent_id);
}
}
}
if let Some(Node::Slot(parent)) = nodes.get_mut(&parent_id) {
parent.dependencies.insert(id);
}
}
{
let nodes = self.nodes.borrow();
if let Some(Node::Slot(slot)) = nodes.get(&id)
&& let Some(ref val) = slot.value
{
return val
.downcast_ref::<T>()
.expect("type mismatch in slot")
.clone();
}
}
let compute: Box<ComputeFn>;
let old_deps: Vec<SlotId>;
{
let mut nodes = self.nodes.borrow_mut();
let slot = match nodes.get_mut(&id) {
Some(Node::Slot(s)) => s,
_ => panic!("get_slot called on non-slot id"),
};
old_deps = slot.dependencies.drain().collect();
compute = unsafe {
let ptr = &*slot.compute as *const ComputeFn;
Box::new(move |ctx| (*ptr)(ctx))
};
}
{
let mut nodes = self.nodes.borrow_mut();
for dep_id in old_deps {
if let Some(dep_node) = nodes.get_mut(&dep_id) {
match dep_node {
Node::Slot(s) => {
s.dependents.remove(&id);
}
Node::Cell(c) => {
c.dependents.remove(&id);
}
}
}
}
}
push_tracking_frame(id);
let result = compute(self);
pop_tracking_frame();
let cloned = result
.downcast_ref::<T>()
.expect("type mismatch in slot compute")
.clone();
{
let mut nodes = self.nodes.borrow_mut();
if let Some(Node::Slot(slot)) = nodes.get_mut(&id) {
slot.value = Some(result);
}
}
cloned
}
pub fn get_cell<T: Clone + 'static>(&self, handle: &CellHandle<T>) -> T {
if let Some(parent_id) = current_tracking_frame() {
let mut nodes = self.nodes.borrow_mut();
if let Some(Node::Cell(c)) = nodes.get_mut(&handle.id) {
c.dependents.insert(parent_id);
}
if let Some(Node::Slot(parent)) = nodes.get_mut(&parent_id) {
parent.dependencies.insert(handle.id);
}
}
let nodes = self.nodes.borrow();
if let Some(Node::Cell(c)) = nodes.get(&handle.id) {
c.value
.downcast_ref::<T>()
.expect("type mismatch in cell")
.clone()
} else {
panic!("get_cell called on non-cell id");
}
}
pub fn cell<T: PartialEq + 'static>(&self, value: T) -> CellHandle<T> {
let id = self.alloc_id();
let node = CellNode {
value: Box::new(value),
dependents: HashSet::new(),
};
self.nodes.borrow_mut().insert(id, Node::Cell(node));
CellHandle::new(id)
}
pub fn set_cell<T: PartialEq + 'static>(&self, handle: &CellHandle<T>, new_value: T) {
let changed = {
let nodes = self.nodes.borrow();
if let Some(Node::Cell(c)) = nodes.get(&handle.id) {
let old = c
.value
.downcast_ref::<T>()
.expect("type mismatch in cell set");
*old != new_value
} else {
panic!("set_cell on non-cell id");
}
};
if changed {
{
let mut nodes = self.nodes.borrow_mut();
if let Some(Node::Cell(c)) = nodes.get_mut(&handle.id) {
c.value = Box::new(new_value);
}
}
let dependents: Vec<SlotId> = {
let nodes = self.nodes.borrow();
if let Some(Node::Cell(c)) = nodes.get(&handle.id) {
c.dependents.iter().copied().collect()
} else {
vec![]
}
};
for dep_id in dependents {
self.clear_slot(dep_id);
}
}
}
fn clear_slot(&self, id: SlotId) {
let dependents: Vec<SlotId>;
{
let mut nodes = self.nodes.borrow_mut();
if let Some(Node::Slot(slot)) = nodes.get_mut(&id) {
if slot.value.is_none() {
return; }
slot.value = None;
dependents = slot.dependents.iter().copied().collect();
} else {
return;
}
}
for dep_id in dependents {
self.clear_slot(dep_id);
}
}
pub fn is_set<T: 'static>(&self, handle: &SlotHandle<T>) -> bool {
let nodes = self.nodes.borrow();
if let Some(Node::Slot(slot)) = nodes.get(&handle.id) {
slot.value.is_some()
} else {
false
}
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}