use std::any::{Any, TypeId};
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::{Rc, Weak};
use slotmap::{SlotMap, new_key_type};
use crate::reactive::Owner;
new_key_type! {
pub(crate) struct NodeId;
}
const MAX_RUNS_PER_FLUSH: u32 = 16;
pub(crate) struct Scope {
pub(crate) wake: RefCell<Option<Rc<dyn Fn()>>>,
woken: Cell<bool>,
pub(crate) quiet: Cell<bool>,
}
impl Scope {
pub(crate) fn new() -> Scope {
Scope {
wake: RefCell::new(None),
woken: Cell::new(false),
quiet: Cell::new(false),
}
}
}
#[derive(Default)]
pub(crate) struct OwnerState {
pub(crate) children: Vec<Weak<OwnerData>>,
pub(crate) cleanups: Vec<Box<dyn FnOnce()>>,
pub(crate) nodes: Vec<NodeId>,
pub(crate) contexts: Vec<(TypeId, Rc<dyn Any>)>,
}
pub(crate) struct OwnerData {
pub(crate) parent: Option<Weak<OwnerData>>,
pub(crate) scope: Rc<Scope>,
pub(crate) cleaned: Cell<bool>,
pub(crate) state: RefCell<OwnerState>,
pub(crate) timer_book: RefCell<Option<Weak<RefCell<crate::timer::TimerBook>>>>,
pub(crate) clipboard: RefCell<Option<crate::clipboard::WeakSlot>>,
}
impl OwnerData {
pub(crate) fn adopt(&self, child: &Rc<OwnerData>) {
let mut state = self.state.borrow_mut();
state.children.retain(|child| child.strong_count() > 0);
state.children.push(Rc::downgrade(child));
}
pub(crate) fn cleanup(self: &Rc<Self>) {
if self.cleaned.get() {
return;
}
self.cleaned.set(true);
let state = std::mem::take(&mut *self.state.borrow_mut());
for child in &state.children {
if let Some(child) = child.upgrade() {
child.cleanup();
}
}
for cleanup in state.cleanups {
cleanup();
}
for node in state.nodes {
dispose(node);
}
}
pub(crate) fn child(self: &Rc<Self>) -> Owner {
let inner = Rc::new(OwnerData {
parent: Some(Rc::downgrade(self)),
scope: Rc::clone(&self.scope),
cleaned: Cell::new(false),
state: RefCell::new(OwnerState::default()),
timer_book: RefCell::new(None),
clipboard: RefCell::new(None),
});
self.adopt(&inner);
Owner { inner }
}
}
pub(crate) struct SignalData<T> {
pub(crate) value: RefCell<T>,
pub(crate) node: Cell<NodeId>,
}
pub(crate) struct MemoData<T> {
pub(crate) value: RefCell<Option<T>>,
pub(crate) node: Cell<NodeId>,
}
pub(crate) struct EffectCell {
pub(crate) run: RefCell<Option<Box<dyn FnMut()>>>,
}
pub(crate) enum Node {
Signal {
subscribers: Vec<NodeId>,
version: u32,
},
Memo {
run: Option<Rc<dyn Fn()>>,
subscribers: Vec<NodeId>,
sources: Vec<(NodeId, u32)>,
state: MemoState,
version: u32,
},
Effect {
cell: Rc<EffectCell>,
sources: Vec<(NodeId, u32)>,
queued: bool,
runs: u32,
generation: u64,
scope: Rc<Scope>,
},
}
impl Node {
fn subscribers_mut(&mut self) -> Option<&mut Vec<NodeId>> {
match self {
Node::Signal { subscribers, .. } | Node::Memo { subscribers, .. } => Some(subscribers),
Node::Effect { .. } => None,
}
}
fn subscribers(&self) -> Option<&Vec<NodeId>> {
match self {
Node::Signal { subscribers, .. } | Node::Memo { subscribers, .. } => Some(subscribers),
Node::Effect { .. } => None,
}
}
fn sources_mut(&mut self) -> Option<&mut Vec<(NodeId, u32)>> {
match self {
Node::Memo { sources, .. } | Node::Effect { sources, .. } => Some(sources),
Node::Signal { .. } => None,
}
}
fn sources(&self) -> Option<&Vec<(NodeId, u32)>> {
match self {
Node::Memo { sources, .. } | Node::Effect { sources, .. } => Some(sources),
Node::Signal { .. } => None,
}
}
fn version(&self) -> Option<u32> {
match self {
Node::Signal { version, .. } | Node::Memo { version, .. } => Some(*version),
Node::Effect { .. } => None,
}
}
fn remove_subscriber(&mut self, id: NodeId) {
if let Some(subscribers) = self.subscribers_mut()
&& let Some(position) = subscribers.iter().position(|sub| *sub == id)
{
subscribers.swap_remove(position);
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) enum MemoState {
Clean,
Dirty,
Check,
}
pub(crate) struct Runtime {
pub(crate) arena: SlotMap<NodeId, Node>,
queue: VecDeque<NodeId>,
flushing: bool,
ran: bool,
woken: Vec<Rc<Scope>>,
generation: u64,
current: Weak<OwnerData>,
default_scope: Rc<Scope>,
}
thread_local! {
static RUNTIME: RefCell<Runtime> = RefCell::new(Runtime::new());
static OBSERVER: Cell<Option<NodeId>> = const { Cell::new(None) };
}
pub(crate) fn with_runtime<R>(f: impl FnOnce(&mut Runtime) -> R) -> R {
RUNTIME.with_borrow_mut(f)
}
impl Runtime {
fn new() -> Runtime {
Runtime {
arena: SlotMap::with_key(),
queue: VecDeque::new(),
flushing: false,
ran: false,
woken: Vec::new(),
generation: 0,
current: Weak::default(),
default_scope: Rc::new(Scope::new()),
}
}
fn node(&self, id: NodeId) -> Option<&Node> {
self.arena.get(id)
}
fn version_of(&self, id: NodeId) -> Option<u32> {
self.node(id).and_then(Node::version)
}
fn is_memo(&self, id: NodeId) -> bool {
matches!(self.node(id), Some(Node::Memo { .. }))
}
pub(crate) fn current_owner(&self) -> Option<Rc<OwnerData>> {
self.current.upgrade()
}
pub(crate) fn set_current(&mut self, owner: &Rc<OwnerData>) {
self.current = Rc::downgrade(owner);
}
pub(crate) fn current_scope(&self) -> Rc<Scope> {
self.current_owner().map_or_else(
|| Rc::clone(&self.default_scope),
|owner| Rc::clone(&owner.scope),
)
}
pub(crate) fn owner_scope(&self, parent: Option<&OwnerData>) -> Rc<Scope> {
match parent {
Some(parent) => Rc::clone(&parent.scope),
None => Rc::new(Scope::new()),
}
}
pub(crate) fn restore_current_owner(&mut self, previous: Option<Weak<OwnerData>>) {
self.current = previous.unwrap_or_default();
}
fn register_node(&mut self, id: NodeId) {
let Some(owner) = self.current_owner() else {
return;
};
owner.state.borrow_mut().nodes.push(id);
}
pub(crate) fn new_signal(&mut self) -> NodeId {
let id = self.arena.insert(Node::Signal {
subscribers: Vec::new(),
version: 0,
});
self.register_node(id);
id
}
pub(crate) fn new_memo(&mut self, run: Rc<dyn Fn()>) -> NodeId {
let id = self.arena.insert(Node::Memo {
run: Some(run),
subscribers: Vec::new(),
sources: Vec::new(),
state: MemoState::Dirty,
version: 0,
});
self.register_node(id);
id
}
pub(crate) fn new_effect(&mut self, cell: Rc<EffectCell>, scope: Rc<Scope>) -> NodeId {
let id = self.arena.insert(Node::Effect {
cell,
sources: Vec::new(),
queued: false,
runs: 0,
generation: u64::MAX,
scope,
});
self.register_node(id);
id
}
fn dispose_node(&mut self, id: NodeId) -> Option<Node> {
let count = self.node(id).and_then(Node::sources).map_or(0, Vec::len);
for index in 0..count {
let Some(source) = self
.node(id)
.and_then(Node::sources)
.and_then(|sources| sources.get(index))
.map(|entry| entry.0)
else {
break;
};
if let Some(node) = self.arena.get_mut(source) {
node.remove_subscriber(id);
}
}
self.arena.remove(id)
}
pub(crate) fn notify(&mut self, id: NodeId) -> Wakes {
let wakes = Wakes(self.woken.len());
let count = {
let Some(node) = self.arena.get_mut(id) else {
return wakes;
};
match node {
Node::Signal { version, .. } | Node::Memo { version, .. } => {
*version = version.wrapping_add(1);
node.subscribers().map_or(0, Vec::len)
}
Node::Effect { .. } => return wakes,
}
};
for index in 0..count {
let subscriber = {
let Some(subscribers) = self.node(id).and_then(Node::subscribers) else {
break;
};
match subscribers.get(index) {
Some(subscriber) => *subscriber,
None => break,
}
};
self.mark_dirty(subscriber);
}
wakes
}
fn mark_dirty(&mut self, id: NodeId) {
let propagated = match self.arena.get_mut(id) {
Some(Node::Memo { state, .. }) => {
let fresh = *state == MemoState::Clean;
*state = MemoState::Dirty;
fresh
}
Some(Node::Effect { .. }) => false,
_ => return,
};
if propagated {
self.mark_subscribers(id);
} else {
self.queue_effect(id);
}
}
fn mark_check(&mut self, id: NodeId) {
let propagated = match self.arena.get_mut(id) {
Some(Node::Memo { state, .. }) => {
let fresh = *state == MemoState::Clean;
if fresh {
*state = MemoState::Check;
}
fresh
}
Some(Node::Effect { .. }) => false,
_ => return,
};
if propagated {
self.mark_subscribers(id);
} else {
self.queue_effect(id);
}
}
fn mark_subscribers(&mut self, id: NodeId) {
let count = self
.node(id)
.and_then(Node::subscribers)
.map_or(0, Vec::len);
for index in 0..count {
let subscriber = {
let Some(subscribers) = self.node(id).and_then(Node::subscribers) else {
break;
};
match subscribers.get(index) {
Some(subscriber) => *subscriber,
None => break,
}
};
self.mark_check(subscriber);
}
}
fn queue_effect(&mut self, id: NodeId) {
let Some(Node::Effect { queued, scope, .. }) = self.arena.get_mut(id) else {
return;
};
let first_wake =
(!scope.quiet.get() && !scope.woken.replace(true)).then(|| Rc::clone(scope));
if !*queued {
*queued = true;
self.queue.push_back(id);
}
if let Some(scope) = first_wake {
self.woken.push(scope);
}
}
fn memo_state(&self, id: NodeId) -> MemoState {
match self.node(id) {
Some(Node::Memo { state, .. }) => *state,
_ => MemoState::Clean,
}
}
fn source_count(&self, id: NodeId) -> usize {
self.node(id).and_then(Node::sources).map_or(0, Vec::len)
}
fn source_at(&self, id: NodeId, index: usize) -> Option<(NodeId, u32)> {
let sources = self.node(id).and_then(Node::sources)?;
sources.get(index).copied()
}
fn memo_set_clean(&mut self, id: NodeId) {
if let Some(Node::Memo { state, .. }) = self.arena.get_mut(id) {
*state = MemoState::Clean;
}
}
fn memo_run(&self, id: NodeId) -> Option<Rc<dyn Fn()>> {
match self.node(id) {
Some(Node::Memo { run, .. }) => run.clone(),
_ => None,
}
}
fn begin_memo(&mut self, id: NodeId) {
if let Some(Node::Memo { state, .. }) = self.arena.get_mut(id) {
*state = MemoState::Clean;
}
self.clear_sources(id);
}
fn begin_effect(&mut self, id: NodeId) -> bool {
let runs = {
let Some(Node::Effect {
queued,
runs,
generation,
..
}) = self.arena.get_mut(id)
else {
return false;
};
if !*queued {
return false;
}
if *generation != self.generation {
*generation = self.generation;
*runs = 1;
} else {
*runs += 1;
}
*runs
};
if runs > MAX_RUNS_PER_FLUSH {
self.release_effect(id);
debug_assert!(
false,
"a binding ran {runs} times in one flush, so it is part of a \
cycle: it writes a signal that wakes it again, directly or \
through another binding"
);
return false;
}
true
}
fn release_effect(&mut self, id: NodeId) {
if let Some(Node::Effect { queued, .. }) = self.arena.get_mut(id) {
*queued = false;
}
}
fn effect_cell(&self, id: NodeId) -> Option<Rc<EffectCell>> {
match self.node(id) {
Some(Node::Effect { cell, .. }) => Some(Rc::clone(cell)),
_ => None,
}
}
fn clear_sources(&mut self, id: NodeId) {
let count = self.source_count(id);
for index in 0..count {
let Some((source, _)) = self.source_at(id, index) else {
break;
};
if let Some(node) = self.arena.get_mut(source) {
node.remove_subscriber(id);
}
}
if let Some(node) = self.arena.get_mut(id)
&& let Some(sources) = node.sources_mut()
{
sources.clear();
}
}
pub(crate) fn track(&mut self, reader: NodeId, source: NodeId, version: u32) {
let already = match self.node(reader) {
Some(node) => node
.sources()
.is_some_and(|sources| sources.iter().any(|(seen, _)| *seen == source)),
None => return,
};
if already {
return;
}
if let Some(node) = self.arena.get_mut(source)
&& let Some(subscribers) = node.subscribers_mut()
{
subscribers.push(reader);
}
if let Some(node) = self.arena.get_mut(reader)
&& let Some(sources) = node.sources_mut()
{
sources.push((source, version));
}
}
fn source_changed(&self, id: NodeId, index: usize) -> bool {
self.source_at(id, index).is_some_and(|(source, seen)| {
self.version_of(source)
.is_some_and(|version| version != seen)
})
}
fn pop_effect(&mut self) -> Option<NodeId> {
self.queue.pop_front()
}
fn set_ran(&mut self) {
self.ran = true;
}
fn begin_flush(&mut self) -> bool {
if self.flushing {
return false;
}
self.flushing = true;
self.ran = false;
self.generation += 1;
true
}
fn flush_ran(&self) -> bool {
self.ran
}
fn end_flush(&mut self) {
self.flushing = false;
for scope in self.woken.drain(..) {
scope.woken.set(false);
}
}
}
struct ObserverGuard {
previous: Option<NodeId>,
}
impl ObserverGuard {
fn enter(observer: Option<NodeId>) -> ObserverGuard {
let previous = OBSERVER.with(|cell| cell.replace(observer));
ObserverGuard { previous }
}
}
impl Drop for ObserverGuard {
fn drop(&mut self) {
OBSERVER.with(|cell| cell.replace(self.previous));
}
}
pub(crate) struct CurrentOwnerGuard {
previous: Option<Weak<OwnerData>>,
}
impl CurrentOwnerGuard {
pub(crate) fn enter(owner: &Rc<OwnerData>) -> CurrentOwnerGuard {
let previous = with_runtime(|runtime| {
let previous = runtime
.current_owner()
.map(|current| Rc::downgrade(¤t));
runtime.current = Rc::downgrade(owner);
previous
});
CurrentOwnerGuard { previous }
}
}
impl Drop for CurrentOwnerGuard {
fn drop(&mut self) {
with_runtime(|runtime| runtime.restore_current_owner(self.previous.take()));
}
}
struct FlushGuard;
impl Drop for FlushGuard {
fn drop(&mut self) {
with_runtime(|runtime| runtime.end_flush());
}
}
pub(crate) struct DisposeOnUnwind {
pub(crate) node: NodeId,
pub(crate) armed: bool,
}
impl Drop for DisposeOnUnwind {
fn drop(&mut self) {
if self.armed {
dispose(self.node);
}
}
}
pub(crate) fn dispose(id: NodeId) {
let node = with_runtime(|runtime| runtime.dispose_node(id));
drop(node);
}
pub(crate) fn track_read(source: NodeId) {
let Some(reader) = OBSERVER.with(Cell::get) else {
return;
};
with_runtime(|runtime| {
if let Some(version) = runtime.version_of(source) {
runtime.track(reader, source, version);
}
});
}
pub(crate) fn flush() -> bool {
if !with_runtime(|runtime| runtime.begin_flush()) {
return false;
}
let _guard = FlushGuard;
while let Some(id) = with_runtime(|runtime| runtime.pop_effect()) {
run_effect(id);
}
with_runtime(|runtime| runtime.flush_ran())
}
fn run_effect(id: NodeId) {
if !with_runtime(|runtime| runtime.begin_effect(id)) {
return;
}
let changed = sources_changed(id);
with_runtime(|runtime| runtime.release_effect(id));
if !changed {
return;
}
let Some(cell) = with_runtime(|runtime| runtime.effect_cell(id)) else {
return;
};
with_runtime(|runtime| {
runtime.clear_sources(id);
runtime.set_ran();
});
run_body(&cell, id);
}
pub(crate) fn run_body(cell: &Rc<EffectCell>, node: NodeId) {
let _observer = ObserverGuard::enter(Some(node));
let mut borrow = cell
.run
.try_borrow_mut()
.expect("an effect is never called while it is running");
if let Some(run) = &mut *borrow {
run();
}
}
fn sources_changed(id: NodeId) -> bool {
let count = with_runtime(|runtime| runtime.source_count(id));
for index in 0..count {
let Some((source, _)) = with_runtime(|runtime| runtime.source_at(id, index)) else {
break;
};
update_source(source);
if with_runtime(|runtime| runtime.source_changed(id, index)) {
return true;
}
}
false
}
fn update_source(source: NodeId) {
if with_runtime(|runtime| runtime.is_memo(source)) {
update_memo(source);
}
}
pub(crate) fn update_memo(id: NodeId) {
match with_runtime(|runtime| runtime.memo_state(id)) {
MemoState::Clean => {}
MemoState::Dirty => recompute_memo(id),
MemoState::Check => {
if sources_changed(id) {
recompute_memo(id);
} else {
with_runtime(|runtime| runtime.memo_set_clean(id));
}
}
}
}
fn recompute_memo(id: NodeId) {
let Some(run) = with_runtime(|runtime| runtime.memo_run(id)) else {
return;
};
with_runtime(|runtime| runtime.begin_memo(id));
let _observer = ObserverGuard::enter(Some(id));
run();
}
pub(crate) fn compute_new_memo(id: NodeId) {
recompute_memo(id);
}
#[must_use = "the wakes must be fired once the runtime is released"]
pub(crate) struct Wakes(usize);
pub(crate) fn fire_wakes(wakes: Wakes) {
let end = with_runtime(|runtime| runtime.woken.len());
for index in wakes.0..end {
let callback = with_runtime(|runtime| {
runtime
.woken
.get(index)
.and_then(|scope| scope.wake.borrow().clone())
});
if let Some(callback) = callback {
callback();
}
}
}