use crate::runtime::RuntimeInner;
use crate::Effect;
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Clone)]
pub struct Signal<T> {
value: Rc<RefCell<T>>,
id: usize,
runtime: Weak<RefCell<RuntimeInner>>,
}
impl<T: Clone + 'static> Signal<T> {
pub(crate) fn new_in_scope(initial: T, id: usize, runtime: Rc<RefCell<RuntimeInner>>) -> Self {
Self {
value: Rc::new(RefCell::new(initial)),
id,
runtime: Rc::downgrade(&runtime),
}
}
pub fn get(&self) -> T {
if let Some(runtime) = self.runtime.upgrade() {
runtime.borrow().track_read(self.id);
}
self.value.borrow().clone()
}
pub fn set(&self, new_value: T) {
*self.value.borrow_mut() = new_value;
if let Some(runtime) = self.runtime.upgrade() {
runtime.borrow().notify_observers(self.id);
}
}
pub fn update(&self, f: impl FnOnce(&mut T)) {
{
let mut value = self.value.borrow_mut();
f(&mut *value);
}
if let Some(runtime) = self.runtime.upgrade() {
runtime.borrow().notify_observers(self.id);
}
}
pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
if let Some(runtime) = self.runtime.upgrade() {
runtime.borrow().track_read(self.id);
}
let value = self.value.borrow();
f(&*value)
}
pub fn id(&self) -> usize {
self.id
}
pub fn watch<F>(&self, callback: F) -> WatchGuard
where
F: Fn(T) + 'static,
{
if let Some(runtime) = self.runtime.upgrade() {
let observer_id = runtime.borrow().allocate_id();
let value = Rc::clone(&self.value);
let callback = Rc::new(callback);
let callback_clone = Rc::clone(&callback);
runtime.borrow().create_observer(observer_id, move || {
let val = value.borrow().clone();
callback_clone(val);
});
RuntimeInner::with_observer_scoped(&runtime, observer_id, || {
runtime.borrow().track_read(self.id);
});
let val = self.value.borrow().clone();
callback(val);
WatchGuard {
observer_id,
runtime: Rc::downgrade(&runtime),
}
} else {
WatchGuard {
observer_id: 0,
runtime: Weak::new(),
}
}
}
pub fn map<U, F>(&self, f: F) -> Signal<U>
where
U: Clone + 'static,
F: Fn(&T) -> U + 'static,
{
if let Some(runtime) = self.runtime.upgrade() {
let source = self.clone();
let derived_id = runtime.borrow().allocate_id();
let derived = Signal::new_in_scope(f(&self.get()), derived_id, Rc::clone(&runtime));
let derived_clone = derived.clone();
let f = Rc::new(f);
let effect_id = runtime.borrow().allocate_id();
let _effect = Effect::new_in_scope_with_lifecycle(
move || {
let val = source.get();
derived_clone.set(f(&val));
},
effect_id,
Rc::clone(&runtime),
true, );
runtime
.borrow()
.register_derived_effect(derived_id, _effect);
derived
} else {
Signal {
value: Rc::new(RefCell::new(f(&self.value.borrow()))),
id: 0,
runtime: Weak::new(),
}
}
}
pub fn zip<U>(self, other: Signal<U>) -> Signal<(T, U)>
where
U: Clone + 'static,
{
if let Some(runtime) = self.runtime.upgrade() {
let combined_id = runtime.borrow().allocate_id();
let combined =
Signal::new_in_scope((self.get(), other.get()), combined_id, Rc::clone(&runtime));
let self_clone = self.clone();
let other_value = Rc::clone(&other.value);
let combined_clone1 = combined.clone();
let effect1_id = runtime.borrow().allocate_id();
let effect1 = Effect::new_in_scope_with_lifecycle(
move || {
let self_val = self_clone.get();
let other_val = other_value.borrow().clone();
combined_clone1.set((self_val, other_val));
},
effect1_id,
Rc::clone(&runtime),
true, );
let self_value = Rc::clone(&self.value);
let other_clone = other.clone();
let combined_clone2 = combined.clone();
let effect2_id = runtime.borrow().allocate_id();
let effect2 = Effect::new_in_scope_with_lifecycle(
move || {
let self_val = self_value.borrow().clone();
let other_val = other_clone.get();
combined_clone2.set((self_val, other_val));
},
effect2_id,
Rc::clone(&runtime),
true, );
runtime
.borrow()
.register_derived_effect(combined_id, effect1);
runtime
.borrow()
.register_derived_effect(combined_id, effect2);
combined
} else {
Signal {
value: Rc::new(RefCell::new((
self.value.borrow().clone(),
other.value.borrow().clone(),
))),
id: 0,
runtime: Weak::new(),
}
}
}
}
impl<T> Drop for Signal<T> {
fn drop(&mut self) {
if Rc::strong_count(&self.value) == 1 {
if let Some(runtime) = self.runtime.upgrade() {
runtime.borrow().remove_signal(self.id);
}
}
}
}
pub struct WatchGuard {
observer_id: usize,
runtime: Weak<RefCell<RuntimeInner>>,
}
impl Drop for WatchGuard {
fn drop(&mut self) {
if let Some(runtime) = self.runtime.upgrade() {
runtime.borrow().remove_observer(self.observer_id);
}
}
}