use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::marker::PhantomData;
use std::rc::Rc;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) struct NodeId {
index: u32,
generation: u32,
}
enum Kind {
Signal,
Memo(Rc<dyn Fn() -> bool>),
Effect(Rc<dyn Fn()>),
Owner,
}
struct Node {
generation: u32,
alive: bool,
kind: Kind,
value: Option<Rc<dyn Any>>,
subscribers: Vec<NodeId>,
sources: Vec<NodeId>,
dirty: bool,
owner: Option<NodeId>,
owned: Vec<NodeId>,
cleanups: Vec<Box<dyn FnOnce()>>,
}
#[derive(Default)]
struct Runtime {
nodes: RefCell<Vec<Node>>,
free: RefCell<Vec<u32>>,
observer: Cell<Option<NodeId>>,
owner: Cell<Option<NodeId>>,
queue: RefCell<VecDeque<NodeId>>,
batch_depth: Cell<u32>,
flushing: Cell<bool>,
}
ferroui_base::ferro_thread_local! {
static RUNTIME: Runtime = Runtime::default();
}
fn with_runtime<R>(f: impl FnOnce(&Runtime) -> R) -> R {
RUNTIME.with(f)
}
impl Runtime {
fn create(&self, kind: Kind, value: Option<Rc<dyn Any>>) -> NodeId {
let owner = self.owner.get();
let node = Node {
generation: 0,
alive: true,
kind,
value,
subscribers: Vec::new(),
sources: Vec::new(),
dirty: false,
owner,
owned: Vec::new(),
cleanups: Vec::new(),
};
let mut nodes = self.nodes.borrow_mut();
let id = match self.free.borrow_mut().pop() {
Some(index) => {
let slot = &mut nodes[index as usize];
let generation = slot.generation + 1;
*slot = Node { generation, ..node };
NodeId { index, generation }
}
None => {
nodes.push(node);
NodeId { index: (nodes.len() - 1) as u32, generation: 0 }
}
};
if let Some(owner) = owner {
if let Some(owner) = Self::node_mut(&mut nodes, owner) {
owner.owned.push(id);
}
}
id
}
fn node_mut(nodes: &mut [Node], id: NodeId) -> Option<&mut Node> {
nodes.get_mut(id.index as usize).filter(|node| node.alive && node.generation == id.generation)
}
fn is_alive(&self, id: NodeId) -> bool {
let nodes = self.nodes.borrow();
nodes.get(id.index as usize).is_some_and(|node| node.alive && node.generation == id.generation)
}
fn value(&self, id: NodeId) -> Rc<dyn Any> {
let nodes = self.nodes.borrow();
match nodes.get(id.index as usize) {
Some(node) if node.alive && node.generation == id.generation => {
node.value.clone().expect("the node holds a value")
}
_ => panic!("a signal or memo was used after its owner was disposed"),
}
}
fn track(&self, source: NodeId) {
let Some(observer) = self.observer.get() else { return };
let mut nodes = self.nodes.borrow_mut();
let Some(node) = Self::node_mut(&mut nodes, source) else { return };
if !node.subscribers.contains(&observer) {
node.subscribers.push(observer);
}
if let Some(observer) = Self::node_mut(&mut nodes, observer) {
if !observer.sources.contains(&source) {
observer.sources.push(source);
}
}
}
fn notify(&self, id: NodeId) {
let subscribers = {
let mut nodes = self.nodes.borrow_mut();
Self::node_mut(&mut nodes, id).map(|node| node.subscribers.clone()).unwrap_or_default()
};
for subscriber in subscribers {
self.mark_dirty(subscriber);
}
self.flush();
}
fn mark_dirty(&self, id: NodeId) {
let (is_effect, subscribers) = {
let mut nodes = self.nodes.borrow_mut();
let Some(node) = Self::node_mut(&mut nodes, id) else { return };
if node.dirty {
return;
}
node.dirty = true;
match node.kind {
Kind::Effect(_) => (true, Vec::new()),
Kind::Memo(_) => (false, node.subscribers.clone()),
_ => (false, Vec::new()),
}
};
if is_effect {
self.queue.borrow_mut().push_back(id);
}
for subscriber in subscribers {
self.mark_dirty(subscriber);
}
}
fn flush(&self) {
if self.batch_depth.get() > 0 || self.flushing.get() {
return;
}
self.flushing.set(true);
loop {
let next = self.queue.borrow_mut().pop_front();
let Some(id) = next else { break };
let dirty = {
let mut nodes = self.nodes.borrow_mut();
Self::node_mut(&mut nodes, id).is_some_and(|node| node.dirty)
};
if dirty {
self.run(id);
}
}
self.flushing.set(false);
}
fn run(&self, id: NodeId) -> bool {
self.dispose_owned(id);
self.clear_sources(id);
let kind = {
let mut nodes = self.nodes.borrow_mut();
let Some(node) = Self::node_mut(&mut nodes, id) else { return false };
node.dirty = false;
match &node.kind {
Kind::Memo(compute) => Some(Ok(compute.clone())),
Kind::Effect(run) => Some(Err(run.clone())),
_ => None,
}
};
let previous = (self.observer.replace(Some(id)), self.owner.replace(Some(id)));
let changed = match kind {
Some(Ok(compute)) => compute(),
Some(Err(run)) => {
run();
true
}
None => false,
};
self.observer.set(previous.0);
self.owner.set(previous.1);
changed
}
fn clear_sources(&self, id: NodeId) {
let mut nodes = self.nodes.borrow_mut();
let sources = match Self::node_mut(&mut nodes, id) {
Some(node) => std::mem::take(&mut node.sources),
None => return,
};
for source in sources {
if let Some(source) = Self::node_mut(&mut nodes, source) {
source.subscribers.retain(|subscriber| *subscriber != id);
}
}
}
fn dispose_owned(&self, id: NodeId) {
let (owned, cleanups) = {
let mut nodes = self.nodes.borrow_mut();
match Self::node_mut(&mut nodes, id) {
Some(node) => (std::mem::take(&mut node.owned), std::mem::take(&mut node.cleanups)),
None => return,
}
};
for cleanup in cleanups.into_iter().rev() {
cleanup();
}
for child in owned.into_iter().rev() {
self.dispose(child);
}
}
fn dispose(&self, id: NodeId) {
if !self.is_alive(id) {
return;
}
self.dispose_owned(id);
self.clear_sources(id);
let (subscribers, value, owner) = {
let mut nodes = self.nodes.borrow_mut();
let Some(node) = Self::node_mut(&mut nodes, id) else { return };
node.alive = false;
node.kind = Kind::Owner;
(std::mem::take(&mut node.subscribers), node.value.take(), node.owner.take())
};
{
let mut nodes = self.nodes.borrow_mut();
if let Some(owner) = owner.and_then(|owner| Self::node_mut(&mut nodes, owner)) {
owner.owned.retain(|owned| *owned != id);
}
for subscriber in subscribers {
if let Some(subscriber) = Self::node_mut(&mut nodes, subscriber) {
subscriber.sources.retain(|source| *source != id);
}
}
}
drop(value);
self.free.borrow_mut().push(id.index);
}
}
pub struct Signal<T: 'static> {
id: NodeId,
_marker: PhantomData<fn() -> T>,
}
impl<T: 'static> Clone for Signal<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: 'static> Copy for Signal<T> {}
impl<T: 'static> PartialEq for Signal<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T: 'static> std::fmt::Debug for Signal<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Signal({:?})", self.id)
}
}
pub fn signal<T: 'static>(value: T) -> Signal<T> {
let id = with_runtime(|runtime| runtime.create(Kind::Signal, Some(Rc::new(RefCell::new(value)))));
Signal { id, _marker: PhantomData }
}
impl<T: 'static> Signal<T> {
fn cell(&self) -> Rc<RefCell<T>> {
with_runtime(|runtime| runtime.value(self.id))
.downcast::<RefCell<T>>()
.unwrap_or_else(|_| panic!("a signal holds a value of its type"))
}
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
with_runtime(|runtime| runtime.track(self.id));
self.with_untracked(f)
}
pub fn with_untracked<R>(&self, f: impl FnOnce(&T) -> R) -> R {
let cell = self.cell();
let value = cell.borrow();
f(&value)
}
pub fn get(&self) -> T
where
T: Clone,
{
self.with(T::clone)
}
pub fn get_untracked(&self) -> T
where
T: Clone,
{
self.with_untracked(T::clone)
}
pub fn set(&self, value: T) {
*self.cell().borrow_mut() = value;
with_runtime(|runtime| runtime.notify(self.id));
}
pub fn set_if_changed(&self, value: T)
where
T: PartialEq,
{
let changed = self.with_untracked(|current| *current != value);
if changed {
self.set(value);
}
}
pub fn update(&self, f: impl FnOnce(&mut T)) {
f(&mut self.cell().borrow_mut());
with_runtime(|runtime| runtime.notify(self.id));
}
pub fn take(&self) -> T
where
T: Default,
{
let value = std::mem::take(&mut *self.cell().borrow_mut());
with_runtime(|runtime| runtime.notify(self.id));
value
}
pub fn two_way(self) -> TwoWay<T> {
TwoWay(self)
}
pub fn is_alive(&self) -> bool {
with_runtime(|runtime| runtime.is_alive(self.id))
}
}
pub struct TwoWay<T: 'static>(pub(crate) Signal<T>);
pub struct Memo<T: 'static> {
id: NodeId,
_marker: PhantomData<fn() -> T>,
}
impl<T: 'static> Clone for Memo<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: 'static> Copy for Memo<T> {}
pub fn memo<T: PartialEq + 'static>(compute: impl Fn(Option<&T>) -> T + 'static) -> Memo<T> {
let cell: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None));
let target = cell.clone();
let recompute = move || {
let next = {
let previous = target.borrow();
compute(previous.as_ref())
};
let changed = target.borrow().as_ref() != Some(&next);
if changed {
*target.borrow_mut() = Some(next);
}
changed
};
let id = with_runtime(|runtime| {
let id = runtime.create(Kind::Memo(Rc::new(recompute)), Some(cell));
let mut nodes = runtime.nodes.borrow_mut();
if let Some(node) = Runtime::node_mut(&mut nodes, id) {
node.dirty = true;
}
id
});
Memo { id, _marker: PhantomData }
}
impl<T: 'static> Memo<T> {
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
let dirty = with_runtime(|runtime| {
runtime.track(self.id);
let mut nodes = runtime.nodes.borrow_mut();
Runtime::node_mut(&mut nodes, self.id).is_some_and(|node| node.dirty)
});
if dirty {
with_runtime(|runtime| runtime.run(self.id));
}
let cell = with_runtime(|runtime| runtime.value(self.id))
.downcast::<RefCell<Option<T>>>()
.unwrap_or_else(|_| panic!("a memo holds a value of its type"));
let value = cell.borrow();
f(value.as_ref().expect("a memo that was read is computed"))
}
pub fn get(&self) -> T
where
T: Clone,
{
self.with(T::clone)
}
pub fn get_untracked(&self) -> T
where
T: Clone,
{
untrack(|| self.get())
}
}
pub fn effect(f: impl Fn() + 'static) {
with_runtime(|runtime| {
let id = runtime.create(Kind::Effect(Rc::new(f)), None);
runtime.run(id);
});
}
pub fn on_cleanup(f: impl FnOnce() + 'static) {
with_runtime(|runtime| {
let Some(owner) = runtime.owner.get() else { return };
let mut nodes = runtime.nodes.borrow_mut();
if let Some(node) = Runtime::node_mut(&mut nodes, owner) {
node.cleanups.push(Box::new(f));
}
});
}
pub fn untrack<R>(f: impl FnOnce() -> R) -> R {
let previous = with_runtime(|runtime| runtime.observer.replace(None));
let result = f();
with_runtime(|runtime| runtime.observer.set(previous));
result
}
pub fn batch<R>(f: impl FnOnce() -> R) -> R {
with_runtime(|runtime| runtime.batch_depth.set(runtime.batch_depth.get() + 1));
let result = f();
with_runtime(|runtime| {
runtime.batch_depth.set(runtime.batch_depth.get() - 1);
runtime.flush();
});
result
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Owner {
id: NodeId,
}
impl Owner {
pub fn new() -> Self {
Owner { id: with_runtime(|runtime| runtime.create(Kind::Owner, None)) }
}
pub fn new_root() -> Self {
let previous = with_runtime(|runtime| runtime.owner.replace(None));
let owner = Self::new();
with_runtime(|runtime| runtime.owner.set(previous));
owner
}
pub fn current() -> Option<Self> {
with_runtime(|runtime| runtime.owner.get()).map(|id| Owner { id })
}
pub fn with<R>(&self, f: impl FnOnce() -> R) -> R {
let previous = with_runtime(|runtime| (runtime.owner.replace(Some(self.id)), runtime.observer.replace(None)));
let result = f();
with_runtime(|runtime| {
runtime.owner.set(previous.0);
runtime.observer.set(previous.1);
});
result
}
pub fn dispose(self) {
with_runtime(|runtime| runtime.dispose(self.id));
}
pub fn is_alive(&self) -> bool {
with_runtime(|runtime| runtime.is_alive(self.id))
}
}
impl Default for Owner {
fn default() -> Self {
Self::new()
}
}