use std::collections::BTreeMap;
use std::sync::Arc;
use parking_lot::RwLock;
use serde::Serialize;
use super::context::{current_context, ClientEffectSpec};
use super::signal::{Signal, SignalId};
fn merge_capture_deps(deps: &mut Vec<SignalId>, captures: &BTreeMap<String, SignalId>) {
for id in captures.values() {
if !deps.contains(id) {
deps.push(*id);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EffectId(pub u32);
fn fallback_effect_id() -> EffectId {
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(2_000_000);
EffectId(COUNTER.fetch_add(1, Ordering::Relaxed))
}
#[derive(Clone)]
pub struct Effect {
pub id: EffectId,
callback: Arc<RwLock<Box<dyn FnMut() + Send + Sync>>>,
}
impl Effect {
pub fn run(&self) {
if let Some(ctx) = current_context() {
ctx.run_effect(self.id.0);
} else {
(self.callback.write())();
}
}
}
pub fn use_effect<F>(callback: F) -> Effect
where
F: FnMut() + Send + Sync + 'static,
{
let id = current_context()
.map(|c| EffectId(c.next_effect_id()))
.unwrap_or_else(fallback_effect_id);
let cb: Arc<RwLock<Box<dyn FnMut() + Send + Sync>>> = Arc::new(RwLock::new(Box::new(callback)));
if let Some(ctx) = current_context() {
let cb_clone = cb.clone();
ctx.register_effect(id, move || {
(cb_clone.write())();
});
}
let eff = Effect { id, callback: cb };
eff.run();
eff
}
pub fn attach_client_effect(
effect: &Effect,
kind: &str,
body: impl Into<String>,
captures: BTreeMap<String, SignalId>,
target: Option<SignalId>,
debounce_ms: Option<u64>,
) {
if let Some(ctx) = current_context() {
let mut deps = ctx.take_effect_deps(effect.id.0);
merge_capture_deps(&mut deps, &captures);
ctx.register_client_effect(ClientEffectSpec {
id: effect.id.0,
deps,
captures,
kind: kind.to_string(),
body: body.into(),
target,
debounce_ms,
});
}
}
pub fn register_client_effect(
kind: &str,
body: impl Into<String>,
captures: BTreeMap<String, SignalId>,
target: Option<SignalId>,
debounce_ms: Option<u64>,
) -> EffectId {
let id = current_context()
.map(|c| EffectId(c.next_effect_id()))
.unwrap_or_else(fallback_effect_id);
if let Some(ctx) = current_context() {
let mut deps = ctx.take_effect_deps(id.0);
merge_capture_deps(&mut deps, &captures);
ctx.register_client_effect(ClientEffectSpec {
id: id.0,
deps,
captures,
kind: kind.to_string(),
body: body.into(),
target,
debounce_ms,
});
}
id
}
#[derive(Clone)]
pub struct Computed<T: Clone + Serialize + Send + Sync + 'static> {
signal: Signal<T>,
#[allow(dead_code)]
effect: Effect,
}
impl<T: Clone + Serialize + Send + Sync + 'static> Computed<T> {
pub fn id(&self) -> SignalId {
self.signal.id()
}
pub fn get(&self) -> T {
self.signal.get()
}
pub fn peek(&self) -> T {
self.signal.peek()
}
}
pub fn use_computed<T, F>(mut compute: F) -> Computed<T>
where
T: Clone + Serialize + Send + Sync + 'static,
F: FnMut() -> T + Send + Sync + 'static,
{
let signal_slot: Arc<RwLock<Option<Signal<T>>>> = Arc::new(RwLock::new(None));
let slot_for_effect = signal_slot.clone();
let id = current_context()
.map(|c| EffectId(c.next_effect_id()))
.unwrap_or_else(fallback_effect_id);
let cb: Arc<RwLock<Box<dyn FnMut() + Send + Sync>>> =
Arc::new(RwLock::new(Box::new(move || {
let next = compute();
let mut guard = slot_for_effect.write();
if let Some(sig) = guard.as_ref() {
sig.set(next);
} else {
*guard = Some(Signal::new(next));
}
})));
if let Some(ctx) = current_context() {
let cb_clone = cb.clone();
ctx.register_effect(id, move || {
(cb_clone.write())();
});
}
let eff = Effect {
id,
callback: cb,
};
eff.run();
let signal = signal_slot
.read()
.clone()
.expect("use_computed: effect must initialize signal");
Computed { signal, effect: eff }
}
pub fn use_computed_with_js<T, F>(
captures: BTreeMap<String, SignalId>,
mut compute: F,
js_body: &str,
) -> Computed<T>
where
T: Clone + Serialize + Send + Sync + 'static,
F: FnMut() -> T + Send + Sync + 'static,
{
let signal_slot: Arc<RwLock<Option<Signal<T>>>> = Arc::new(RwLock::new(None));
let slot_for_effect = signal_slot.clone();
let effect_id = current_context()
.map(|c| EffectId(c.next_effect_id()))
.unwrap_or_else(fallback_effect_id);
let cb: Arc<RwLock<Box<dyn FnMut() + Send + Sync>>> =
Arc::new(RwLock::new(Box::new(move || {
let next = compute();
let mut guard = slot_for_effect.write();
if let Some(sig) = guard.as_ref() {
sig.set(next);
} else {
*guard = Some(Signal::new(next));
}
})));
if let Some(ctx) = current_context() {
let cb_clone = cb.clone();
ctx.register_effect(effect_id, move || {
(cb_clone.write())();
});
}
let eff = Effect {
id: effect_id,
callback: cb,
};
eff.run();
let signal = signal_slot
.read()
.clone()
.expect("use_computed_with_js: effect must initialize signal");
let target = signal.id();
let body = format!(
"(state, __resuma) => {{ state.{target}.set(({js_body})(state, __resuma)); }}",
target = target,
js_body = js_body
);
if let Some(ctx) = current_context() {
let mut deps = ctx.take_effect_deps(effect_id.0);
merge_capture_deps(&mut deps, &captures);
ctx.register_client_effect(ClientEffectSpec {
id: effect_id.0,
deps,
captures,
kind: "computed".into(),
body,
target: Some(target),
debounce_ms: None,
});
}
Computed {
signal,
effect: eff,
}
}