use core::cell::RefCell;
use core::sync::atomic::{AtomicUsize, Ordering};
extern crate alloc;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(usize);
impl NodeId {
pub fn new() -> Self {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
Self(COUNTER.fetch_add(1, Ordering::Relaxed))
}
}
impl Default for NodeId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeType {
Signal,
Effect,
Memo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EffectTiming {
Layout,
#[default]
Passive,
}
pub struct Observer {
pub id: NodeId,
pub node_type: NodeType,
pub timing: EffectTiming,
pub cleanup: Option<()>,
}
impl Clone for Observer {
fn clone(&self) -> Self {
Self {
id: self.id,
node_type: self.node_type,
timing: self.timing,
cleanup: None, }
}
}
#[derive(Debug, Default)]
pub(crate) struct DependencyNode {
pub(crate) subscribers: Vec<NodeId>,
pub(crate) dependencies: Vec<NodeId>,
}
type SchedulerFn = Box<dyn Fn(Box<dyn FnOnce() + Send>) + Send + Sync>;
static SCHEDULER: std::sync::OnceLock<SchedulerFn> = std::sync::OnceLock::new();
pub fn set_scheduler<F>(scheduler: F)
where
F: Fn(Box<dyn FnOnce() + Send>) + Send + Sync + 'static,
{
let _ = SCHEDULER.set(Box::new(scheduler));
}
pub struct Runtime {
observer_stack: RefCell<Vec<Observer>>,
pub(crate) dependency_graph: RefCell<BTreeMap<NodeId, DependencyNode>>,
pub(crate) pending_updates: RefCell<Vec<NodeId>>,
pub(crate) update_scheduled: RefCell<bool>,
}
impl Runtime {
pub fn new() -> Self {
Self {
observer_stack: RefCell::new(Vec::new()),
dependency_graph: RefCell::new(BTreeMap::new()),
pending_updates: RefCell::new(Vec::new()),
update_scheduled: RefCell::new(false),
}
}
pub fn current_observer(&self) -> Option<NodeId> {
self.observer_stack
.borrow()
.last()
.map(|observer| observer.id)
}
pub fn push_observer(&self, observer: Observer) {
self.observer_stack.borrow_mut().push(observer);
}
pub fn pop_observer(&self) -> Option<Observer> {
self.observer_stack.borrow_mut().pop()
}
pub fn track_dependency(&self, signal_id: NodeId) {
if let Some(observer_id) = self.current_observer() {
let mut graph = self.dependency_graph.borrow_mut();
let signal_node = graph.entry(signal_id).or_default();
if !signal_node.subscribers.contains(&observer_id) {
signal_node.subscribers.push(observer_id);
}
let observer_node = graph.entry(observer_id).or_default();
if !observer_node.dependencies.contains(&signal_id) {
observer_node.dependencies.push(signal_id);
}
}
}
pub fn notify_signal_change(&self, signal_id: NodeId) {
let graph = self.dependency_graph.borrow();
if let Some(node) = graph.get(&signal_id) {
let mut layout_effects = Vec::new();
let mut passive_effects = Vec::new();
for &subscriber_id in &node.subscribers {
if let Some(timing) = super::effect::get_effect_timing(subscriber_id) {
match timing {
EffectTiming::Layout => layout_effects.push(subscriber_id),
EffectTiming::Passive => passive_effects.push(subscriber_id),
}
} else {
passive_effects.push(subscriber_id);
}
}
drop(graph);
for effect_id in layout_effects {
super::effect::Effect::execute_effect(effect_id);
}
for effect_id in passive_effects {
self.schedule_update(effect_id);
}
}
}
pub fn schedule_update(&self, node_id: NodeId) {
let mut pending = self.pending_updates.borrow_mut();
if !pending.contains(&node_id) {
pending.push(node_id);
}
if !*self.update_scheduled.borrow() {
*self.update_scheduled.borrow_mut() = true;
if let Some(scheduler) = SCHEDULER.get() {
scheduler(Box::new(|| {
RUNTIME.with(|rt| rt.flush_updates());
}));
}
}
}
pub fn clear_dependencies(&self, node_id: NodeId) {
let mut graph = self.dependency_graph.borrow_mut();
if let Some(node) = graph.get(&node_id) {
let dependencies = node.dependencies.clone();
for &dep_id in &dependencies {
if let Some(dep_node) = graph.get_mut(&dep_id) {
dep_node.subscribers.retain(|&id| id != node_id);
}
}
}
if let Some(node) = graph.get_mut(&node_id) {
node.dependencies.clear();
}
}
pub fn remove_node(&self, node_id: NodeId) {
self.clear_dependencies(node_id);
self.dependency_graph.borrow_mut().remove(&node_id);
self.pending_updates
.borrow_mut()
.retain(|&id| id != node_id);
}
pub fn has_node(&self, node_id: NodeId) -> bool {
self.dependency_graph.borrow().contains_key(&node_id)
}
pub fn subscriber_count(&self, node_id: NodeId) -> usize {
self.dependency_graph
.borrow()
.get(&node_id)
.map(|node| node.subscribers.len())
.unwrap_or(0)
}
#[doc(hidden)]
pub fn debug_subscribers(&self, node_id: NodeId) -> alloc::vec::Vec<NodeId> {
self.dependency_graph
.borrow()
.get(&node_id)
.map(|n| n.subscribers.clone())
.unwrap_or_default()
}
#[doc(hidden)]
pub fn debug_dependencies(&self, node_id: NodeId) -> alloc::vec::Vec<NodeId> {
self.dependency_graph
.borrow()
.get(&node_id)
.map(|n| n.dependencies.clone())
.unwrap_or_default()
}
#[doc(hidden)]
pub fn debug_observer_stack(&self) -> alloc::vec::Vec<NodeId> {
self.observer_stack.borrow().iter().map(|o| o.id).collect()
}
#[doc(hidden)]
pub fn debug_pending_updates(&self) -> alloc::vec::Vec<NodeId> {
self.pending_updates.borrow().clone()
}
}
impl Default for Runtime {
fn default() -> Self {
Self::new()
}
}
thread_local! {
static RUNTIME: Runtime = Runtime::new();
}
pub fn with_runtime<F, R>(f: F) -> R
where
F: FnOnce(&Runtime) -> R,
{
RUNTIME.with(f)
}
pub(crate) fn try_with_runtime<F, R>(f: F) -> Option<R>
where
F: FnOnce(&Runtime) -> R,
{
RUNTIME.try_with(f).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[test]
#[serial]
fn test_node_id_uniqueness() {
let id1 = NodeId::new();
let id2 = NodeId::new();
let id3 = NodeId::new();
assert_ne!(id1, id2);
assert_ne!(id2, id3);
assert_ne!(id1, id3);
}
#[test]
#[serial]
fn test_runtime_observer_stack() {
let runtime = Runtime::new();
assert!(runtime.current_observer().is_none());
let observer1 = Observer {
id: NodeId::new(),
node_type: NodeType::Effect,
timing: EffectTiming::default(),
cleanup: None,
};
let id1 = observer1.id;
runtime.push_observer(observer1);
assert_eq!(runtime.current_observer(), Some(id1));
let observer2 = Observer {
id: NodeId::new(),
node_type: NodeType::Effect,
timing: EffectTiming::default(),
cleanup: None,
};
let id2 = observer2.id;
runtime.push_observer(observer2);
assert_eq!(runtime.current_observer(), Some(id2));
runtime.pop_observer();
assert_eq!(runtime.current_observer(), Some(id1));
runtime.pop_observer();
assert!(runtime.current_observer().is_none());
}
#[test]
#[serial]
fn test_dependency_tracking() {
let runtime = Runtime::new();
let signal_id = NodeId::new();
let effect_id = NodeId::new();
runtime.push_observer(Observer {
id: effect_id,
node_type: NodeType::Effect,
timing: EffectTiming::default(),
cleanup: None,
});
runtime.track_dependency(signal_id);
let graph = runtime.dependency_graph.borrow();
let signal_node = graph.get(&signal_id).unwrap();
assert!(signal_node.subscribers.contains(&effect_id));
let effect_node = graph.get(&effect_id).unwrap();
assert!(effect_node.dependencies.contains(&signal_id));
}
#[test]
#[serial]
fn test_notify_signal_change() {
let runtime = Runtime::new();
let signal_id = NodeId::new();
let effect_id = NodeId::new();
{
let mut graph = runtime.dependency_graph.borrow_mut();
graph
.entry(signal_id)
.or_default()
.subscribers
.push(effect_id);
}
runtime.notify_signal_change(signal_id);
let pending = runtime.pending_updates.borrow();
assert!(pending.contains(&effect_id));
}
#[test]
#[serial]
fn test_clear_dependencies() {
let runtime = Runtime::new();
let signal_id = NodeId::new();
let effect_id = NodeId::new();
{
let mut graph = runtime.dependency_graph.borrow_mut();
graph
.entry(signal_id)
.or_default()
.subscribers
.push(effect_id);
graph
.entry(effect_id)
.or_default()
.dependencies
.push(signal_id);
}
runtime.clear_dependencies(effect_id);
let graph = runtime.dependency_graph.borrow();
let signal_node = graph.get(&signal_id).unwrap();
assert!(!signal_node.subscribers.contains(&effect_id));
let effect_node = graph.get(&effect_id).unwrap();
assert!(effect_node.dependencies.is_empty());
}
#[test]
#[serial]
fn debug_subscribers_returns_registered_observers_in_insertion_order() {
let runtime = Runtime::new();
let signal_id = NodeId::new();
let effect_id_a = NodeId::new();
let effect_id_b = NodeId::new();
{
let mut graph = runtime.dependency_graph.borrow_mut();
let node = graph.entry(signal_id).or_default();
node.subscribers.push(effect_id_a);
node.subscribers.push(effect_id_b);
}
let subs = runtime.debug_subscribers(signal_id);
assert_eq!(subs, alloc::vec![effect_id_a, effect_id_b]);
}
#[test]
#[serial]
fn debug_dependencies_returns_observer_dependency_list() {
let runtime = Runtime::new();
let observer_id = NodeId::new();
let signal_a = NodeId::new();
let signal_b = NodeId::new();
{
let mut graph = runtime.dependency_graph.borrow_mut();
let node = graph.entry(observer_id).or_default();
node.dependencies.push(signal_a);
node.dependencies.push(signal_b);
}
let deps = runtime.debug_dependencies(observer_id);
assert_eq!(deps, alloc::vec![signal_a, signal_b]);
}
#[test]
#[serial]
fn debug_observer_stack_returns_pushed_observers_bottom_to_top() {
let runtime = Runtime::new();
let outer_id = NodeId::new();
let inner_id = NodeId::new();
runtime.push_observer(Observer {
id: outer_id,
node_type: NodeType::Effect,
timing: EffectTiming::default(),
cleanup: None,
});
runtime.push_observer(Observer {
id: inner_id,
node_type: NodeType::Effect,
timing: EffectTiming::default(),
cleanup: None,
});
let stack = runtime.debug_observer_stack();
assert_eq!(stack, alloc::vec![outer_id, inner_id]);
}
#[test]
#[serial]
fn debug_pending_updates_returns_scheduled_node_ids_snapshot() {
let runtime = Runtime::new();
let pending_a = NodeId::new();
let pending_b = NodeId::new();
{
let mut p = runtime.pending_updates.borrow_mut();
p.push(pending_a);
p.push(pending_b);
}
let snapshot = runtime.debug_pending_updates();
assert_eq!(snapshot, alloc::vec![pending_a, pending_b]);
assert_eq!(runtime.pending_updates.borrow().len(), 2);
}
}