Tracing
This commit is contained in:
@@ -5,3 +5,7 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ruin_runtime = { package = "ruin-runtime", path = "../runtime" }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "std"] }
|
||||
|
||||
101
lib/reactivity/examples/tracing_subscriber_showcase.rs
Normal file
101
lib/reactivity/examples/tracing_subscriber_showcase.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
//! Example tracing setup for RUIN runtime + reactivity.
|
||||
//!
|
||||
//! Try:
|
||||
//!
|
||||
//! - `cargo run -p ruin_reactivity --example tracing_subscriber_showcase`
|
||||
//! - `RUST_LOG=info,ruin_runtime::runtime=debug,ruin_reactivity::graph=debug cargo run -p ruin_reactivity --example tracing_subscriber_showcase`
|
||||
//! - `RUST_LOG=info,ruin_runtime::scheduler=trace,ruin_reactivity::event=trace,ruin_reactivity::effect=debug cargo run -p ruin_reactivity --example tracing_subscriber_showcase`
|
||||
|
||||
use ruin_reactivity::{cell, effect, event, on, thunk};
|
||||
use ruin_runtime::time::sleep;
|
||||
use std::time::Duration;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{EnvFilter, fmt};
|
||||
|
||||
fn install_tracing() {
|
||||
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
EnvFilter::new(
|
||||
"info,\
|
||||
ruin_runtime::runtime=debug,\
|
||||
ruin_runtime::scheduler=debug,\
|
||||
ruin_reactivity::graph=debug,\
|
||||
ruin_reactivity::effect=debug,\
|
||||
ruin_reactivity::event=debug",
|
||||
)
|
||||
});
|
||||
|
||||
let fmt_layer = fmt::layer()
|
||||
.with_target(true)
|
||||
.with_thread_ids(true)
|
||||
.with_thread_names(true)
|
||||
.compact();
|
||||
|
||||
let _ = tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(fmt_layer)
|
||||
.try_init();
|
||||
}
|
||||
|
||||
#[ruin_runtime::async_main]
|
||||
async fn main() {
|
||||
install_tracing();
|
||||
|
||||
tracing::info!(
|
||||
event = "showcase_start",
|
||||
note = "override RUST_LOG to see more or less detail",
|
||||
"starting tracing subscriber showcase"
|
||||
);
|
||||
|
||||
let count = cell(0usize);
|
||||
let doubled = thunk({
|
||||
let count = count.clone();
|
||||
move || count.get() * 2
|
||||
});
|
||||
let clicks = event::<usize>();
|
||||
|
||||
let _drain = on(&clicks, {
|
||||
let count = count.clone();
|
||||
move |delta| {
|
||||
tracing::info!(
|
||||
event = "apply_click_delta",
|
||||
delta = *delta,
|
||||
"draining queued event into cell update"
|
||||
);
|
||||
count.update(|value| *value += *delta);
|
||||
}
|
||||
});
|
||||
|
||||
let _view = effect({
|
||||
let count = count.clone();
|
||||
let doubled = doubled.clone();
|
||||
move || {
|
||||
tracing::info!(
|
||||
target: "demo::view",
|
||||
count = count.get(),
|
||||
doubled = doubled.get(),
|
||||
"derived view state updated"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
tracing::info!(
|
||||
event = "emit_clicks",
|
||||
count = 3,
|
||||
"emitting queued click deltas"
|
||||
);
|
||||
clicks.emit(1);
|
||||
clicks.emit(2);
|
||||
clicks.emit(0);
|
||||
|
||||
tracing::info!(event = "manual_set", value = 10, "setting count directly");
|
||||
let _ = count.set(10);
|
||||
|
||||
tracing::info!(
|
||||
event = "showcase_done",
|
||||
hint = "see ruin_runtime::* and ruin_reactivity::* targets in the filter",
|
||||
"example body completed; awaiting once so scheduled microtasks can flush"
|
||||
);
|
||||
|
||||
sleep(Duration::from_millis(1)).await;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{NodeId, Reactor, current};
|
||||
use crate::{NodeId, Reactor, current, trace_targets};
|
||||
|
||||
/// Creates a [`Cell`] in the current thread's default reactor.
|
||||
pub fn cell<T: 'static>(initial: T) -> Cell<T> {
|
||||
@@ -29,6 +29,12 @@ impl Reactor {
|
||||
impl<T: 'static> Cell<T> {
|
||||
fn new(reactor: Reactor, initial: T) -> Self {
|
||||
let id = reactor.allocate_node();
|
||||
tracing::debug!(
|
||||
target: trace_targets::CELL,
|
||||
event = "create_cell",
|
||||
node_id = id.0,
|
||||
"created reactive cell"
|
||||
);
|
||||
Self {
|
||||
inner: Rc::new(CellInner {
|
||||
reactor,
|
||||
@@ -40,6 +46,13 @@ impl<T: 'static> Cell<T> {
|
||||
|
||||
/// Runs `f` with a shared reference to the current value.
|
||||
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::CELL,
|
||||
event = "read_cell",
|
||||
node_id = self.inner.id.0,
|
||||
"reading reactive cell"
|
||||
);
|
||||
self.inner.reactor.observe(self.inner.id);
|
||||
let value = self.inner.value.borrow();
|
||||
f(&value)
|
||||
@@ -48,6 +61,12 @@ impl<T: 'static> Cell<T> {
|
||||
/// Replaces the current value and notifies dependents.
|
||||
pub fn replace(&self, value: T) -> T {
|
||||
let previous = self.inner.value.replace(value);
|
||||
tracing::debug!(
|
||||
target: trace_targets::CELL,
|
||||
event = "replace_cell",
|
||||
node_id = self.inner.id.0,
|
||||
"replaced cell value"
|
||||
);
|
||||
self.inner.reactor.trigger(self.inner.id);
|
||||
previous
|
||||
}
|
||||
@@ -58,6 +77,12 @@ impl<T: 'static> Cell<T> {
|
||||
let mut value = self.inner.value.borrow_mut();
|
||||
f(&mut value)
|
||||
};
|
||||
tracing::debug!(
|
||||
target: trace_targets::CELL,
|
||||
event = "update_cell",
|
||||
node_id = self.inner.id.0,
|
||||
"updated cell value in place"
|
||||
);
|
||||
self.inner.reactor.trigger(self.inner.id);
|
||||
output
|
||||
}
|
||||
@@ -78,11 +103,26 @@ impl<T: PartialEq + 'static> Cell<T> {
|
||||
pub fn set(&self, value: T) -> Option<T> {
|
||||
let mut current = self.inner.value.borrow_mut();
|
||||
if *current == value {
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::CELL,
|
||||
event = "set_cell",
|
||||
node_id = self.inner.id.0,
|
||||
changed = false,
|
||||
"suppressed unchanged cell write"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let previous = std::mem::replace(&mut *current, value);
|
||||
drop(current);
|
||||
tracing::debug!(
|
||||
target: trace_targets::CELL,
|
||||
event = "set_cell",
|
||||
node_id = self.inner.id.0,
|
||||
changed = true,
|
||||
"set cell value"
|
||||
);
|
||||
self.inner.reactor.trigger(self.inner.id);
|
||||
Some(previous)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::cell::{Cell, RefCell};
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use crate::reactor::ObserverHook;
|
||||
use crate::{NodeId, Reactor, current};
|
||||
use crate::{NodeId, Reactor, current, trace_targets};
|
||||
|
||||
/// Creates an effect in the current thread's default reactor.
|
||||
pub fn effect(f: impl Fn() + 'static) -> EffectHandle {
|
||||
@@ -42,6 +42,12 @@ impl EffectHandle {
|
||||
self_ref: RefCell::new(Weak::new()),
|
||||
});
|
||||
*inner.self_ref.borrow_mut() = Rc::downgrade(&inner);
|
||||
tracing::debug!(
|
||||
target: trace_targets::EFFECT,
|
||||
event = "create_effect",
|
||||
node_id = id.0,
|
||||
"created reactive effect"
|
||||
);
|
||||
|
||||
let observer: Rc<dyn ObserverHook> = inner.clone();
|
||||
reactor.register_observer(id, observer);
|
||||
@@ -72,9 +78,27 @@ struct EffectInner {
|
||||
impl EffectInner {
|
||||
fn schedule(&self) {
|
||||
if self.disposed.get() || self.scheduled.replace(true) {
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::EFFECT,
|
||||
event = "schedule_effect",
|
||||
node_id = self.id.0,
|
||||
queued = false,
|
||||
disposed = self.disposed.get(),
|
||||
already_scheduled = self.scheduled.get(),
|
||||
"effect scheduling skipped"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::EFFECT,
|
||||
event = "schedule_effect",
|
||||
node_id = self.id.0,
|
||||
queued = true,
|
||||
"queued effect for microtask flush"
|
||||
);
|
||||
let weak = self.self_ref.borrow().clone();
|
||||
let reactor = self.reactor.clone();
|
||||
reactor.schedule(move || {
|
||||
@@ -87,6 +111,12 @@ impl EffectInner {
|
||||
}
|
||||
|
||||
inner.scheduled.set(false);
|
||||
let _span = tracing::debug_span!(
|
||||
target: trace_targets::EFFECT,
|
||||
"effect.run",
|
||||
node_id = inner.id.0
|
||||
)
|
||||
.entered();
|
||||
inner.reactor.run_in_context(inner.id, || (inner.effect)());
|
||||
});
|
||||
}
|
||||
@@ -96,6 +126,12 @@ impl EffectInner {
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
target: trace_targets::EFFECT,
|
||||
event = "dispose_effect",
|
||||
node_id = self.id.0,
|
||||
"disposed reactive effect"
|
||||
);
|
||||
self.reactor.unregister_observer(self.id);
|
||||
self.reactor.dispose(self.id);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::cell::{Cell, RefCell};
|
||||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{NodeId, Reactor, current};
|
||||
use crate::{NodeId, Reactor, current, trace_targets};
|
||||
|
||||
type SubscriberFn<T> = dyn Fn(&T) + 'static;
|
||||
|
||||
@@ -68,6 +68,14 @@ impl Reactor {
|
||||
let mut queued = queue.borrow_mut();
|
||||
queued.drain(..).collect::<Vec<_>>()
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::EVENT,
|
||||
event = "drain_event_queue",
|
||||
event_id = event.inner.id.0,
|
||||
drained = drained.len(),
|
||||
"draining queued event values reactively"
|
||||
);
|
||||
for value in &drained {
|
||||
handler(value);
|
||||
}
|
||||
@@ -84,6 +92,12 @@ impl Reactor {
|
||||
impl<T: 'static> Event<T> {
|
||||
fn new(reactor: Reactor) -> Self {
|
||||
let id = reactor.allocate_node();
|
||||
tracing::debug!(
|
||||
target: trace_targets::EVENT,
|
||||
event = "create_event",
|
||||
node_id = id.0,
|
||||
"created reactive event"
|
||||
);
|
||||
Self {
|
||||
inner: Rc::new(EventInner {
|
||||
reactor,
|
||||
@@ -95,6 +109,13 @@ impl<T: 'static> Event<T> {
|
||||
}
|
||||
|
||||
fn observe(&self) {
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::EVENT,
|
||||
event = "observe_event",
|
||||
event_id = self.inner.id.0,
|
||||
"observing event reactively"
|
||||
);
|
||||
self.inner.reactor.observe(self.inner.id);
|
||||
}
|
||||
|
||||
@@ -107,6 +128,13 @@ impl<T: 'static> Event<T> {
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!(
|
||||
target: trace_targets::EVENT,
|
||||
event = "emit_event",
|
||||
event_id = self.inner.id.0,
|
||||
subscriber_count = subscribers.len(),
|
||||
"emitting event value"
|
||||
);
|
||||
for subscriber in subscribers {
|
||||
subscriber(&value);
|
||||
}
|
||||
@@ -121,9 +149,25 @@ impl<T: 'static> Event<T> {
|
||||
.subscribers
|
||||
.borrow_mut()
|
||||
.insert(id, Rc::new(handler) as Rc<SubscriberFn<T>>);
|
||||
tracing::debug!(
|
||||
target: trace_targets::EVENT,
|
||||
event = "subscribe_event",
|
||||
event_id = self.inner.id.0,
|
||||
subscription_id = id,
|
||||
subscriber_count = self.inner.subscribers.borrow().len(),
|
||||
"added event subscriber"
|
||||
);
|
||||
|
||||
let inner = Rc::clone(&self.inner);
|
||||
Subscription::new(move || {
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::EVENT,
|
||||
event = "unsubscribe_event",
|
||||
event_id = inner.id.0,
|
||||
subscription_id = id,
|
||||
"removing event subscriber"
|
||||
);
|
||||
inner.subscribers.borrow_mut().remove(&id);
|
||||
})
|
||||
}
|
||||
@@ -173,6 +217,12 @@ impl SubscriptionInner {
|
||||
if !self.active.replace(false) {
|
||||
return;
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::EVENT,
|
||||
event = "unsubscribe",
|
||||
"cancelling subscription"
|
||||
);
|
||||
(self.cancel)();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
//! single-threaded and designed to live on a runtime-managed thread, while async work feeds it
|
||||
//! from the edges by updating state or emitting events.
|
||||
|
||||
pub(crate) mod trace_targets {
|
||||
pub const GRAPH: &str = "ruin_reactivity::graph";
|
||||
pub const CELL: &str = "ruin_reactivity::cell";
|
||||
pub const THUNK: &str = "ruin_reactivity::thunk";
|
||||
pub const MEMO: &str = "ruin_reactivity::memo";
|
||||
pub const EFFECT: &str = "ruin_reactivity::effect";
|
||||
pub const EVENT: &str = "ruin_reactivity::event";
|
||||
}
|
||||
|
||||
mod cell;
|
||||
mod effect;
|
||||
mod event;
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::rc::{Rc, Weak};
|
||||
|
||||
use ruin_runtime::queue_microtask;
|
||||
|
||||
use crate::NodeId;
|
||||
use crate::{NodeId, trace_targets};
|
||||
|
||||
type Job = Box<dyn FnOnce() + 'static>;
|
||||
|
||||
@@ -72,20 +72,37 @@ pub struct Reactor {
|
||||
impl Reactor {
|
||||
/// Creates a new empty reactor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
let reactor = Self {
|
||||
inner: Rc::new(ReactorInner::new()),
|
||||
}
|
||||
};
|
||||
tracing::debug!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "reactor_new",
|
||||
"created reactive reactor"
|
||||
);
|
||||
reactor
|
||||
}
|
||||
|
||||
/// Returns the current thread's default reactor.
|
||||
pub fn current() -> Self {
|
||||
CURRENT_REACTOR.with(|slot| {
|
||||
if let Some(inner) = slot.borrow().upgrade() {
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "current_reactor_reuse",
|
||||
"reusing current thread default reactor"
|
||||
);
|
||||
return Self { inner };
|
||||
}
|
||||
|
||||
let reactor = Self::new();
|
||||
*slot.borrow_mut() = Rc::downgrade(&reactor.inner);
|
||||
tracing::debug!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "current_reactor_install",
|
||||
"installed current thread default reactor"
|
||||
);
|
||||
reactor
|
||||
})
|
||||
}
|
||||
@@ -96,6 +113,12 @@ impl Reactor {
|
||||
/// [`observe`](Self::observe) made while `f` executes will become the observer's new
|
||||
/// dependencies.
|
||||
pub fn run_in_context<T>(&self, observer: NodeId, f: impl FnOnce() -> T) -> T {
|
||||
let _span = tracing::debug_span!(
|
||||
target: trace_targets::GRAPH,
|
||||
"reactor.run_in_context",
|
||||
observer_id = observer.0
|
||||
)
|
||||
.entered();
|
||||
self.clear_observer_dependencies(observer);
|
||||
self.inner.stack.borrow_mut().push(observer);
|
||||
let inserted = self.inner.active_computations.borrow_mut().insert(observer);
|
||||
@@ -140,6 +163,13 @@ impl Reactor {
|
||||
.expect("active computation should appear in observer stack");
|
||||
let mut cycle = stack[start..].to_vec();
|
||||
cycle.push(observable);
|
||||
tracing::debug!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "cycle_detected",
|
||||
observable_id = observable.0,
|
||||
cycle_len = cycle.len(),
|
||||
"reactive cycle detected"
|
||||
);
|
||||
panic_any(ReactCycleError::new(cycle));
|
||||
}
|
||||
|
||||
@@ -148,6 +178,15 @@ impl Reactor {
|
||||
return;
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "observe",
|
||||
observer_id = observer.0,
|
||||
observable_id = observable.0,
|
||||
"recording reactive dependency"
|
||||
);
|
||||
|
||||
self.inner
|
||||
.dependencies
|
||||
.borrow_mut()
|
||||
@@ -172,6 +211,15 @@ impl Reactor {
|
||||
.map(|nodes| nodes.iter().copied().collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "trigger",
|
||||
observable_id = observable.0,
|
||||
dependent_count = dependents.len(),
|
||||
"triggering reactive dependents"
|
||||
);
|
||||
|
||||
for dependent in dependents {
|
||||
let hook = self
|
||||
.inner
|
||||
@@ -190,6 +238,12 @@ impl Reactor {
|
||||
|
||||
/// Disposes all graph bookkeeping for `node`.
|
||||
pub fn dispose(&self, node: NodeId) {
|
||||
tracing::debug!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "dispose_node",
|
||||
node_id = node.0,
|
||||
"disposing reactive node bookkeeping"
|
||||
);
|
||||
self.clear_observer_dependencies(node);
|
||||
|
||||
let incoming = self
|
||||
@@ -218,13 +272,28 @@ impl Reactor {
|
||||
.pending_jobs
|
||||
.borrow_mut()
|
||||
.push_back(Box::new(job));
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "schedule_job",
|
||||
pending_jobs = self.inner.pending_jobs.borrow().len(),
|
||||
"queued reactive job for microtask flush"
|
||||
);
|
||||
self.inner.ensure_flush_scheduled();
|
||||
}
|
||||
|
||||
pub(crate) fn allocate_node(&self) -> NodeId {
|
||||
let raw = self.inner.next_node.get();
|
||||
self.inner.next_node.set(raw.wrapping_add(1));
|
||||
NodeId::new(raw)
|
||||
let id = NodeId::new(raw);
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "allocate_node",
|
||||
node_id = id.0,
|
||||
"allocated reactive node id"
|
||||
);
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn register_observer(&self, id: NodeId, observer: Rc<dyn ObserverHook>) {
|
||||
@@ -303,6 +372,13 @@ impl ReactorInner {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "schedule_flush",
|
||||
pending_jobs = self.pending_jobs.borrow().len(),
|
||||
"scheduling reactive microtask flush"
|
||||
);
|
||||
let reactor = Rc::clone(self);
|
||||
queue_microtask(move || {
|
||||
reactor.flush_jobs();
|
||||
@@ -310,11 +386,23 @@ impl ReactorInner {
|
||||
}
|
||||
|
||||
fn flush_jobs(self: Rc<Self>) {
|
||||
let _span = tracing::debug_span!(
|
||||
target: trace_targets::GRAPH,
|
||||
"reactor.flush_jobs"
|
||||
)
|
||||
.entered();
|
||||
loop {
|
||||
let job = self.pending_jobs.borrow_mut().pop_front();
|
||||
let Some(job) = job else {
|
||||
break;
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::GRAPH,
|
||||
event = "run_job",
|
||||
remaining_jobs = self.pending_jobs.borrow().len(),
|
||||
"running reactive scheduled job"
|
||||
);
|
||||
job();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::reactor::ObserverHook;
|
||||
use crate::{NodeId, Reactor, current};
|
||||
use crate::{NodeId, Reactor, current, trace_targets};
|
||||
|
||||
type ComputeFn<T> = dyn Fn() -> T + 'static;
|
||||
type EqualsFn<T> = dyn Fn(&T, &T) -> bool + 'static;
|
||||
@@ -93,11 +93,24 @@ impl<T: 'static> Thunk<T> {
|
||||
|
||||
let observer: Rc<dyn ObserverHook> = inner.clone();
|
||||
reactor.register_observer(id, observer);
|
||||
tracing::debug!(
|
||||
target: trace_targets::THUNK,
|
||||
event = "create_thunk",
|
||||
node_id = id.0,
|
||||
"created reactive thunk"
|
||||
);
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// Runs `f` with a shared reference to the current computed value.
|
||||
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::THUNK,
|
||||
event = "read_thunk",
|
||||
node_id = self.inner.id.0,
|
||||
"reading thunk value"
|
||||
);
|
||||
self.inner.reactor.observe(self.inner.id);
|
||||
self.inner.ensure_value();
|
||||
let value = self.inner.value.borrow();
|
||||
@@ -132,11 +145,24 @@ impl<T: 'static> Memo<T> {
|
||||
|
||||
let observer: Rc<dyn ObserverHook> = inner.clone();
|
||||
reactor.register_observer(id, observer);
|
||||
tracing::debug!(
|
||||
target: trace_targets::MEMO,
|
||||
event = "create_memo",
|
||||
node_id = id.0,
|
||||
"created reactive memo"
|
||||
);
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// Runs `f` with a shared reference to the current computed value.
|
||||
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::MEMO,
|
||||
event = "read_memo",
|
||||
node_id = self.inner.id.0,
|
||||
"reading memo value"
|
||||
);
|
||||
self.inner.reactor.observe(self.inner.id);
|
||||
self.inner.ensure_value();
|
||||
let value = self.inner.value.borrow();
|
||||
@@ -167,6 +193,12 @@ impl<T> ThunkInner<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
let _span = tracing::debug_span!(
|
||||
target: trace_targets::THUNK,
|
||||
"thunk.recompute",
|
||||
node_id = self.id.0
|
||||
)
|
||||
.entered();
|
||||
let next = self.reactor.run_in_context(self.id, || (self.compute)());
|
||||
*self.value.borrow_mut() = Some(next);
|
||||
self.dirty.set(false);
|
||||
@@ -191,6 +223,12 @@ impl<T> MemoInner<T> {
|
||||
}
|
||||
|
||||
fn recompute(&self) -> bool {
|
||||
let _span = tracing::debug_span!(
|
||||
target: trace_targets::MEMO,
|
||||
"memo.recompute",
|
||||
node_id = self.id.0
|
||||
)
|
||||
.entered();
|
||||
let next = self.reactor.run_in_context(self.id, || (self.compute)());
|
||||
let mut value = self.value.borrow_mut();
|
||||
let changed = match value.as_ref() {
|
||||
@@ -199,13 +237,29 @@ impl<T> MemoInner<T> {
|
||||
};
|
||||
*value = Some(next);
|
||||
self.dirty.set(false);
|
||||
tracing::debug!(
|
||||
target: trace_targets::MEMO,
|
||||
event = "memo_recompute",
|
||||
node_id = self.id.0,
|
||||
changed,
|
||||
"recomputed memo"
|
||||
);
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ObserverHook for ThunkInner<T> {
|
||||
fn notify(&self) {
|
||||
if self.dirty.replace(true) {
|
||||
let already_dirty = self.dirty.replace(true);
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::THUNK,
|
||||
event = "invalidate_thunk",
|
||||
node_id = self.id.0,
|
||||
already_dirty,
|
||||
"invalidating thunk"
|
||||
);
|
||||
if already_dirty {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -225,11 +279,29 @@ impl<T: 'static> ObserverHook for MemoInner<T> {
|
||||
fn notify(&self) {
|
||||
if self.value.borrow().is_none() {
|
||||
self.dirty.set(true);
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::MEMO,
|
||||
event = "invalidate_memo",
|
||||
node_id = self.id.0,
|
||||
eagerly_recomputed = false,
|
||||
"marked uninitialized memo dirty"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
self.dirty.set(true);
|
||||
if self.recompute() {
|
||||
let changed = self.recompute();
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!(
|
||||
target: trace_targets::MEMO,
|
||||
event = "invalidate_memo",
|
||||
node_id = self.id.0,
|
||||
eagerly_recomputed = true,
|
||||
changed,
|
||||
"invalidated memo"
|
||||
);
|
||||
if changed {
|
||||
self.reactor.trigger(self.id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user