From 24d6965893f566774e239434ab8610bc2327c511 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 7 Oct 2022 06:41:34 -0400 Subject: [PATCH] Remove unused reactive system --- reactive/Cargo.toml | 18 --- reactive/src/computation.rs | 234 ------------------------------ reactive/src/context.rs | 33 ----- reactive/src/lib.rs | 61 -------- reactive/src/memo.rs | 79 ---------- reactive/src/resource.rs | 263 --------------------------------- reactive/src/scope.rs | 130 ----------------- reactive/src/signal.rs | 279 ------------------------------------ reactive/src/spawn.rs | 38 ----- reactive/src/suspense.rs | 53 ------- reactive/src/system.rs | 191 ------------------------ reactive/src/transition.rs | 92 ------------ reactive/tests/effect.rs | 89 ------------ reactive/tests/memo.rs | 87 ----------- reactive/tests/signal.rs | 27 ---- 15 files changed, 1674 deletions(-) delete mode 100644 reactive/Cargo.toml delete mode 100644 reactive/src/computation.rs delete mode 100644 reactive/src/context.rs delete mode 100644 reactive/src/lib.rs delete mode 100644 reactive/src/memo.rs delete mode 100644 reactive/src/resource.rs delete mode 100644 reactive/src/scope.rs delete mode 100644 reactive/src/signal.rs delete mode 100644 reactive/src/spawn.rs delete mode 100644 reactive/src/suspense.rs delete mode 100644 reactive/src/system.rs delete mode 100644 reactive/src/transition.rs delete mode 100644 reactive/tests/effect.rs delete mode 100644 reactive/tests/memo.rs delete mode 100644 reactive/tests/signal.rs diff --git a/reactive/Cargo.toml b/reactive/Cargo.toml deleted file mode 100644 index e16484313..000000000 --- a/reactive/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "leptos_reactive_new" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -append-only-vec = "0.1" -log = "0.4" -slotmap = "1" -serde = { version = "1", features = ["derive"] } -wasm-bindgen = { version = "0.2", optional = true } -wasm-bindgen-futures = { version = "0.4", optional = true } - -[features] -default = [] -browser = ["dep:wasm-bindgen", "dep:wasm-bindgen-futures"] \ No newline at end of file diff --git a/reactive/src/computation.rs b/reactive/src/computation.rs deleted file mode 100644 index a41a84ac3..000000000 --- a/reactive/src/computation.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::{ - cell::{Cell, RefCell}, - collections::HashSet, - fmt::Debug, - rc::{Rc, Weak}, -}; - -use crate::{ObservableLink, Scope, SignalState, System}; - -pub fn create_render_effect(cx: Scope, f: impl FnMut(Option) -> T + 'static) -where - T: Debug + Clone + 'static, -{ - let c = Rc::new(Computation::new(cx.system, f, None)); - Rc::clone(&c).run(); - cx.push_computation(c); -} - -pub fn create_effect(cx: Scope, f: impl FnMut(Option) -> T + 'static) -where - T: Debug + Clone + 'static, -{ - let c = Rc::new(Computation::new(cx.system, f, None)); - Rc::clone(&c).run(); - cx.push_computation(c); -} - -pub struct Computation -where - T: Clone + 'static, -{ - system: &'static System, - // function to execute when dependencies change - f: Box) -> T>>, - // previous value of the effect - value: RefCell>, - // internal signal holding value of the function - signal: Option>>>, - // how many of our dependencies have changed since the last time we ran the function? - waiting: Cell, - // did something actually change in one of our dependencies? - fresh: Cell, - // all the signals this computation depends on - signals: RefCell>, - // custom cleanup functions to call - cleanups: RefCell>>, -} - -impl Computation -where - T: Clone, -{ - pub(crate) fn new( - system: &'static System, - f: impl FnMut(Option) -> T + 'static, - signal: Option>>>, - ) -> Self { - Self { - system, - f: Box::new(RefCell::new(f)), - value: Default::default(), - signal, - waiting: Cell::new(0), - fresh: Cell::new(false), - signals: Default::default(), - cleanups: Default::default(), - } - } -} - -impl Observer for Computation -where - T: Clone + 'static, -{ - fn run(self: Rc) { - // clean up dependencies and cleanups - (Rc::clone(&self)).cleanup(); - - // run the computation - self.system.wrap( - { - let this = self.clone(); - move || { - let curr = { this.value.borrow_mut().take() }; - let v = { (this.f.borrow_mut())(curr) }; - *this.value.borrow_mut() = Some(v); - } - }, - Some(ObserverLink(Rc::downgrade(&self) as Weak)), - true, - ) - } - - fn update(self: Rc) { - // reset waiting, in case this is a force-refresh - self.waiting.set(0); - - // run the effect - Rc::clone(&self).run(); - - // set the signal, if there is one - if let Some(signal) = &self.signal { - Rc::clone(signal).update(move |n| *n = self.value.borrow().clone()); - } - } - - fn add_signal(self: Rc, signal: ObservableLink) { - self.signals.borrow_mut().insert(signal); - } - - fn is_waiting(&self) -> bool { - self.waiting.get() > 0 - } - - fn stale(self: Rc, increment: WaitingCount, fresh: bool) { - let waiting = self.waiting.get(); - - // If waiting is already 0 but change is -1, the computation has been force-refreshed - if waiting == 0 && increment == WaitingCount::Decrement { - return; - } - // mark computations that depend on the internal signal stale - if waiting == 0 && increment == WaitingCount::Increment && let Some(signal) = &self.signal { - // we don't mark it fresh, because we don't know for a fact that something has changed yet - signal.stale(WaitingCount::Increment, false); - } - - match increment { - WaitingCount::Decrement => { - if waiting > 0 { - self.waiting.set(waiting - 1); - } - } - WaitingCount::Unchanged => {} - WaitingCount::Increment => { - self.waiting.set(waiting + 1); - } - } - - self.fresh.set(self.fresh.get() || fresh); - - // are we still waiting? - let waiting = self.waiting.get(); - if waiting == 0 { - if self.fresh.get() { - Rc::clone(&self).update(); - } - - // mark any computations that depend on us as not stale - if let Some(signal) = &self.signal { - signal.stale(WaitingCount::Decrement, false); - } - } - } -} - -impl Computation -where - T: Clone + 'static, -{ - fn cleanup(self: Rc) { - for source in self.signals.borrow().iter() { - source.unsubscribe(ObserverLink(Rc::downgrade(&self) as Weak)); - } - - for cleanup in self.cleanups.take() { - cleanup(); - } - } -} - -pub trait Observer { - fn run(self: Rc); - - fn add_signal(self: Rc, signal: ObservableLink); - - fn is_waiting(&self) -> bool; - - fn update(self: Rc); - - fn stale(self: Rc, increment: WaitingCount, fresh: bool); -} - -#[derive(Clone)] -pub struct ObserverLink(pub(crate) Weak); - -impl std::fmt::Debug for ObserverLink { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("ObserverLink").finish() - } -} - -impl ObserverLink { - pub(crate) fn is_waiting(&self) -> bool { - if let Some(c) = self.0.upgrade() { - c.is_waiting() - } else { - false - } - } - - pub(crate) fn update(&self) { - if let Some(c) = self.0.upgrade() { - c.update() - } - } - - pub(crate) fn stale(&self, increment: WaitingCount, fresh: bool) { - if let Some(c) = self.0.upgrade() { - c.stale(increment, fresh); - } - } -} - -impl std::hash::Hash for ObserverLink { - fn hash(&self, state: &mut H) { - std::ptr::hash(&self.0, state); - } -} - -impl PartialEq for ObserverLink { - fn eq(&self, other: &Self) -> bool { - Weak::ptr_eq(&self.0, &other.0) - } -} - -impl Eq for ObserverLink {} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum WaitingCount { - Decrement, - Unchanged, - Increment, -} diff --git a/reactive/src/context.rs b/reactive/src/context.rs deleted file mode 100644 index fc4b49f76..000000000 --- a/reactive/src/context.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::any::{Any, TypeId}; - -use crate::Scope; - -pub fn provide_context(cx: Scope, value: T) -where - T: Clone + 'static, -{ - let id = value.type_id(); - cx.system.scope(cx.id, |scope_state| { - scope_state - .contexts - .borrow_mut() - .insert(id, Box::new(value)); - }) -} - -pub fn use_context(cx: Scope) -> Option -where - T: Clone + 'static, -{ - let id = TypeId::of::(); - cx.system.scope(cx.id, |scope_state| { - let contexts = scope_state.contexts.borrow(); - let local_value = contexts.get(&id).and_then(|val| val.downcast_ref::()); - match local_value { - Some(val) => Some(val.clone()), - None => scope_state - .parent - .and_then(|parent| use_context::(parent)), - } - }) -} diff --git a/reactive/src/lib.rs b/reactive/src/lib.rs deleted file mode 100644 index 84559ee07..000000000 --- a/reactive/src/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -#![feature(fn_traits)] -#![feature(let_chains)] -#![feature(unboxed_closures)] -#![feature(test)] - -// The implementation of this reactive system is largely a Rust port of [Flimsy](https://github.com/fabiospampinato/flimsy/blob/master/src/flimsy.annotated.ts), -// which is itself a simplified and annotated version of SolidJS reactivity. - -mod computation; -mod context; -mod memo; -mod resource; -mod scope; -mod signal; -mod spawn; -mod suspense; -mod system; -mod transition; - -pub use computation::*; -pub use context::*; -pub use memo::*; -pub use resource::*; -pub use scope::*; -pub use signal::*; -pub use spawn::*; -pub use suspense::*; -pub use system::*; -pub use transition::*; - -extern crate test; - -#[cfg(test)] -mod tests { - use test::Bencher; - - use std::{cell::Cell, rc::Rc}; - - use crate::{create_effect, create_scope, create_signal}; - - #[bench] - fn create_and_update_1000_signals(b: &mut Bencher) { - b.iter(|| { - create_scope(|cx| { - let acc = Rc::new(Cell::new(0)); - let sigs = (0..1000).map(|n| create_signal(cx, n)).collect::>(); - assert_eq!(sigs.len(), 1000); - /* create_effect(cx, { - let acc = Rc::clone(&acc); - move |_| { - for sig in &sigs { - acc.set(acc.get() + (sig.0)()) - } - } - }); - assert_eq!(acc.get(), 499500) */ - }) - .dispose() - }); - } -} diff --git a/reactive/src/memo.rs b/reactive/src/memo.rs deleted file mode 100644 index febad61b3..000000000 --- a/reactive/src/memo.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::{fmt::Debug, marker::PhantomData, rc::Rc}; - -use crate::{Computation, Observer, ReadSignal, Scope, SignalState}; - -pub fn create_memo(cx: Scope, f: impl FnMut(Option) -> T + 'static) -> Memo -where - T: Debug + Clone + 'static, -{ - // create the computation - let sig = Rc::new(SignalState::new(cx.system, None)); - let c = Rc::new(Computation::new(cx.system, f, Some(Rc::clone(&sig)))); - Rc::clone(&c).update(); - cx.push_computation(c); - - // generate ReadSignal handle for the memo - let id = cx.push_signal(sig); - Memo { - inner: ReadSignal { - system: cx.system, - scope: cx.id, - id, - ty: PhantomData, - }, - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Memo -where - T: Clone + 'static, -{ - inner: ReadSignal>, -} - -impl Copy for Memo where T: Clone + 'static {} - -impl Memo -where - T: Clone + 'static, -{ - pub fn get(self) -> T { - self.with(|val| val.clone()) - } - - pub fn with(self, f: impl Fn(&T) -> U) -> U { - // unwrap because the effect runs while the memo is being created - // so there will always be a value here - self.inner.with(|n| f(n.as_ref().unwrap())) - } -} - -impl FnOnce<()> for Memo -where - T: Debug + Clone, -{ - type Output = T; - - extern "rust-call" fn call_once(self, _args: ()) -> Self::Output { - self.get() - } -} - -impl FnMut<()> for Memo -where - T: Debug + Clone, -{ - extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output { - self.get() - } -} - -impl Fn<()> for Memo -where - T: Debug + Clone, -{ - extern "rust-call" fn call(&self, _args: ()) -> Self::Output { - self.get() - } -} diff --git a/reactive/src/resource.rs b/reactive/src/resource.rs deleted file mode 100644 index c6d5cf44c..000000000 --- a/reactive/src/resource.rs +++ /dev/null @@ -1,263 +0,0 @@ -use std::{ - cell::{Cell, RefCell}, - collections::HashSet, - fmt::Debug, - future::Future, - marker::PhantomData, - pin::Pin, - rc::Rc, -}; - -use serde::{Deserialize, Serialize}; - -use crate::{ - create_effect, create_memo, create_signal, queue_microtask, spawn::spawn_local, use_context, - Memo, ReadSignal, Scope, ScopeId, SuspenseContext, System, WriteSignal, ResourceId, -}; - -pub fn create_resource( - cx: Scope, - source: impl Fn() -> S + 'static, - fetcher: impl Fn(S) -> Fu + 'static, -) -> Resource -where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, - Fu: Future + 'static, -{ - create_resource_with_initial_value(cx, source, fetcher, None) -} - -pub fn create_resource_with_initial_value( - cx: Scope, - source: impl Fn() -> S + 'static, - fetcher: impl Fn(S) -> Fu + 'static, - initial_value: Option, -) -> Resource -where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, - Fu: Future + 'static, -{ - let resolved = initial_value.is_some(); - let (value, set_value) = create_signal(cx, initial_value); - let (loading, set_loading) = create_signal(cx, false); - let (track, trigger) = create_signal(cx, 0); - let fetcher = Rc::new(move |s| Box::pin(fetcher(s)) as Pin>>); - let source = create_memo(cx, move |_| source()); - - // TODO hydration/streaming logic - - let r = Rc::new(ResourceState { - scope: cx, - value, - set_value, - loading, - set_loading, - track, - trigger, - source, - fetcher, - resolved: Rc::new(Cell::new(resolved)), - scheduled: Rc::new(Cell::new(false)), - suspense_contexts: Default::default(), - }); - - // initial load fires immediately - create_effect(cx, { - let r = Rc::clone(&r); - move |_| r.load(false) - }); - - let id = cx.push_resource(r); - - Resource { - system: cx.system, - scope: cx.id, - id, - source_ty: PhantomData, - out_ty: PhantomData, - } -} - -impl Resource -where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, -{ - pub fn read(&self) -> Option { - self.system - .resource((self.scope, self.id), |resource: &ResourceState| { - resource.read() - }) - } - - pub fn loading(&self) -> bool { - self.system - .resource((self.scope, self.id), |resource: &ResourceState| { - resource.loading.get() - }) - } - - pub fn refetch(&self) { - self.system - .resource((self.scope, self.id), |resource: &ResourceState| { - resource.refetch() - }) - } -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct Resource -where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, -{ - system: &'static System, - pub(crate) scope: ScopeId, - pub(crate) id: ResourceId, - pub(crate) source_ty: PhantomData, - pub(crate) out_ty: PhantomData, -} - -impl Clone for Resource -where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, -{ - fn clone(&self) -> Self { - Self { - system: self.system, - scope: self.scope, - id: self.id, - source_ty: PhantomData, - out_ty: PhantomData, - } - } -} - -impl Copy for Resource -where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, -{ -} - -#[derive(Clone)] -pub struct ResourceState -where - S: Clone + 'static, - T: Clone + Debug + 'static, -{ - scope: Scope, - value: ReadSignal>, - set_value: WriteSignal>, - pub loading: ReadSignal, - set_loading: WriteSignal, - track: ReadSignal, - trigger: WriteSignal, - source: Memo, - fetcher: Rc Pin>>>, - resolved: Rc>, - scheduled: Rc>, - suspense_contexts: Rc>>, -} - -impl ResourceState -where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, -{ - pub fn read(&self) -> Option { - let suspense_cx = use_context::(self.scope); - - let v = self.value.get(); - - let suspense_contexts = self.suspense_contexts.clone(); - let has_value = v.is_some(); - create_effect(self.scope, move |_| { - if let Some(s) = &suspense_cx { - let mut contexts = suspense_contexts.borrow_mut(); - if !contexts.contains(s) { - contexts.insert(*s); - - // on subsequent reads, increment will be triggered in load() - // because the context has been tracked here - // on the first read, resource is already loading without having incremented - if !has_value { - s.increment(); - } - } - } - }); - - v - } - - pub fn refetch(&self) { - self.load(true); - } - - fn load(&self, refetching: bool) { - // doesn't refetch if already refetching - if refetching && self.scheduled.get() { - return; - } - - self.scheduled.set(false); - - let loaded_under_transition = self.scope.system.running_transition().is_some(); - - let fut = (self.fetcher)(self.source.get()); - - // `scheduled` is true for the rest of this code only - self.scheduled.set(true); - queue_microtask({ - let scheduled = Rc::clone(&self.scheduled); - move || { - scheduled.set(false); - } - }); - - self.set_loading.update(|n| *n = true); - self.trigger.update(|n| *n += 1); - - // increment counter everywhere it's read - let suspense_contexts = self.suspense_contexts.clone(); - let running_transition = self.scope.system.running_transition(); - for suspense_context in suspense_contexts.borrow().iter() { - suspense_context.increment(); - if let Some(transition) = &running_transition { - transition - .resources - .borrow_mut() - .insert(suspense_context.pending_resources); - } - } - - // run the Future - spawn_local({ - let resolved = self.resolved.clone(); - let scope = self.scope; - let set_value = self.set_value; - let set_loading = self.set_loading; - async move { - let res = fut.await; - resolved.set(true); - - // TODO hydration - - if let Some(transition) = scope.system.transition() { - // TODO transition - } - - set_value.update(|n| *n = Some(res)); - set_loading.update(|n| *n = false); - - for suspense_context in suspense_contexts.borrow().iter() { - suspense_context.decrement(); - } - } - }) - } -} diff --git a/reactive/src/scope.rs b/reactive/src/scope.rs deleted file mode 100644 index 41c3f352a..000000000 --- a/reactive/src/scope.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::{Computation, ResourceState, SignalState, System}; -use append_only_vec::AppendOnlyVec; -use serde::{Deserialize, Serialize}; -use std::{ - any::{Any, TypeId}, - cell::RefCell, - collections::HashMap, - fmt::Debug, - rc::Rc, -}; - -#[must_use = "Scope will leak memory if the disposer function is never called"] -pub fn create_scope(f: impl FnOnce(Scope) + 'static) -> ScopeDisposer { - let runtime = Box::leak(Box::new(System::new())); - runtime.create_scope(f, None) -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct Scope { - pub(crate) system: &'static System, - pub(crate) id: ScopeId, -} - -impl Scope { - pub fn child_scope(self, f: impl FnOnce(Scope)) -> ScopeDisposer { - self.system.create_scope(f, Some(self)) - } - - pub fn transition_pending(&self) -> bool { - // TODO transition self.system.transition().is_some() - false - } - - pub fn untrack(&self, f: impl FnOnce() -> T) -> T { - self.system.untrack(f) - } -} - -// Internals -impl Scope { - pub(crate) fn push_signal(&self, state: Rc>) -> SignalId - where - T: Debug + 'static, - { - self.system.scope(self.id, |scope| { - scope.arena.push(state); - SignalId(scope.arena.len() - 1) - }) - } - - pub(crate) fn push_computation(&self, state: Rc>) -> ComputationId - where - T: Clone + Debug + 'static, - { - self.system.scope(self.id, |scope| { - scope.arena.push(state); - ComputationId(scope.arena.len() - 1) - }) - } - - pub(crate) fn push_resource(&self, state: Rc>) -> ResourceId - where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, - { - self.system.scope(self.id, |scope| { - scope.arena.push(state); - ResourceId(scope.arena.len() - 1) - }) - } - - pub fn dispose(self) { - // first, drop child scopes - self.system.scope(self.id, |scope| { - for id in scope.children.borrow().iter() { - self.system.remove_scope(id) - } - }) - // removing from the runtime will drop this Scope, and all its Signals/Effects/Memos - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub(crate) struct SignalId(pub(crate) usize); - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub(crate) struct ComputationId(pub(crate) usize); - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub(crate) struct ResourceId(pub(crate) usize); - -pub struct ScopeDisposer(pub(crate) Box); - -impl ScopeDisposer { - pub fn dispose(self) { - (self.0)() - } -} - -impl Debug for ScopeDisposer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("ScopeDisposer").finish() - } -} - -slotmap::new_key_type! { pub(crate) struct ScopeId; } - -pub(crate) struct ScopeState { - pub(crate) parent: Option, - pub(crate) contexts: RefCell>>, - pub(crate) children: RefCell>, - pub(crate) arena: AppendOnlyVec>, -} - -impl Debug for ScopeState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ScopeState").finish() - } -} - -impl ScopeState { - pub(crate) fn new(parent: Option) -> Self { - Self { - parent, - contexts: Default::default(), - children: Default::default(), - arena: AppendOnlyVec::new(), - } - } -} diff --git a/reactive/src/signal.rs b/reactive/src/signal.rs deleted file mode 100644 index e6eacdd31..000000000 --- a/reactive/src/signal.rs +++ /dev/null @@ -1,279 +0,0 @@ -use std::{ - cell::RefCell, - collections::HashSet, - fmt::Debug, - marker::PhantomData, - rc::{Rc, Weak}, -}; - -use crate::{ObserverLink, Scope, ScopeId, SignalId, System, WaitingCount}; - -pub fn create_signal(cx: Scope, value: T) -> (ReadSignal, WriteSignal) -where - T: Clone + Debug, -{ - let state = Rc::new(SignalState::new(cx.system, value)); - let id = cx.push_signal(state); - - let read = ReadSignal { - system: cx.system, - scope: cx.id, - id, - ty: PhantomData, - }; - - let write = WriteSignal { - system: cx.system, - scope: cx.id, - id, - ty: PhantomData, - }; - - (read, write) -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct ReadSignal -where - T: 'static, -{ - pub(crate) system: &'static System, - pub(crate) scope: ScopeId, - pub(crate) id: SignalId, - pub(crate) ty: PhantomData, -} - -impl ReadSignal { - pub fn get(self) -> T - where - T: Clone, - { - self.with(|val| val.clone()) - } - - pub fn with(self, f: impl Fn(&T) -> U) -> U { - self.system - .signal((self.scope, self.id), |state| state.with(f)) - } -} - -impl Clone for ReadSignal { - fn clone(&self) -> Self { - Self { - system: self.system, - scope: self.scope, - id: self.id, - ty: PhantomData, - } - } -} - -impl Copy for ReadSignal {} - -impl FnOnce<()> for ReadSignal -where - T: Debug + Clone, -{ - type Output = T; - - extern "rust-call" fn call_once(self, _args: ()) -> Self::Output { - self.get() - } -} - -impl FnMut<()> for ReadSignal -where - T: Debug + Clone, -{ - extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output { - self.get() - } -} - -impl Fn<()> for ReadSignal -where - T: Debug + Clone, -{ - extern "rust-call" fn call(&self, _args: ()) -> Self::Output { - self.get() - } -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct WriteSignal -where - T: Clone + 'static, -{ - system: &'static System, - pub(crate) scope: ScopeId, - pub(crate) id: SignalId, - pub(crate) ty: PhantomData, -} - -impl WriteSignal -where - T: Clone, -{ - pub fn update(self, f: impl FnOnce(&mut T)) { - self.system - .signal((self.scope, self.id), move |state| state.update(f)) - } -} - -impl Clone for WriteSignal -where - T: Clone, -{ - fn clone(&self) -> Self { - Self { - system: self.system, - scope: self.scope, - id: self.id, - ty: PhantomData, - } - } -} - -impl Copy for WriteSignal where T: Clone {} - -impl FnOnce<(F,)> for WriteSignal -where - F: Fn(&mut T) + 'static, - T: Clone + 'static, -{ - type Output = (); - - extern "rust-call" fn call_once(self, args: (F,)) -> Self::Output { - self.update(args.0) - } -} - -impl FnMut<(F,)> for WriteSignal -where - F: Fn(&mut T) + 'static, - T: Clone + 'static, -{ - extern "rust-call" fn call_mut(&mut self, args: (F,)) -> Self::Output { - self.update(args.0) - } -} - -impl Fn<(F,)> for WriteSignal -where - F: Fn(&mut T) + 'static, - T: Clone + 'static, -{ - extern "rust-call" fn call(&self, args: (F,)) -> Self::Output { - self.update(args.0) - } -} - -pub(crate) struct SignalState -where - T: 'static, -{ - system: &'static System, - parent: RefCell>, - value: RefCell, - t_value: RefCell>, - subscribers: RefCell>, -} - -impl SignalState -where - T: 'static, -{ - pub fn new(system: &'static System, value: T) -> Self { - Self { - system, - parent: Default::default(), - value: RefCell::new(value), - t_value: Default::default(), - subscribers: Default::default(), - } - } - - pub fn with(self: Rc, f: impl Fn(&T) -> U) -> U { - let observer = self.system.observer(); - - if let Some(observer) = observer.and_then(|o| o.0.upgrade()) { - // register the signal as a dependency, if we are tracking and the parent is a Observer - // (rather than, for example, a root, which does not track) - if self.system.tracking() { - self.add_observer(ObserverLink(Rc::downgrade(&observer))); - observer.add_signal(Rc::clone(&self).as_observable()); - } - } - - // if there's a stale parent, it needs to be refreshed - // this may cause other upstream Observers to refresh - if let Some(parent) = self.parent.borrow().clone() && parent.is_waiting() { - parent.update(); - } - - f(&self.value.borrow()) - } - - pub fn update(self: Rc, f: impl FnOnce(&mut T)) { - if self.system.batching() { - let this = Rc::clone(&self); - // TODO transition - self.system - .add_to_batch(move || (f)(&mut *this.value.borrow_mut())); - } else { - // TODO transition - (f)(&mut *self.value.borrow_mut()); - - // notify observers that there's a new value - self.stale(WaitingCount::Unchanged, true); - } - } - - pub fn add_observer(&self, observer: ObserverLink) { - self.subscribers.borrow_mut().insert(observer); - } - - pub fn as_observable(self: Rc) -> ObservableLink { - ObservableLink(Rc::downgrade(&self) as Weak) - } - - pub fn stale(&self, delta: WaitingCount, fresh: bool) { - let subs = { self.subscribers.borrow().clone() }; - for observer in subs { - observer.stale(delta, fresh); - } - } -} - -pub(crate) trait Observable { - fn unsubscribe(&self, observer: ObserverLink); -} - -impl Observable for SignalState { - fn unsubscribe(&self, observer: ObserverLink) { - self.subscribers.borrow_mut().remove(&observer); - } -} -pub struct ObservableLink(pub(crate) Weak); - -impl ObservableLink { - pub(crate) fn unsubscribe(&self, observer: ObserverLink) { - if let Some(observable) = self.0.upgrade() { - observable.unsubscribe(observer); - } - } -} - -impl std::hash::Hash for ObservableLink { - fn hash(&self, state: &mut H) { - std::ptr::hash(&self.0, state); - } -} - -impl PartialEq for ObservableLink { - fn eq(&self, other: &Self) -> bool { - Weak::ptr_eq(&self.0, &other.0) - } -} - -impl Eq for ObservableLink {} diff --git a/reactive/src/spawn.rs b/reactive/src/spawn.rs deleted file mode 100644 index ec642f2f1..000000000 --- a/reactive/src/spawn.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::future::Future; - -// run immediately on server -#[cfg(feature = "ssr")] -pub fn queue_microtask(task: impl FnOnce()) { - task(); -} - -// run immediately on server -#[cfg(any(feature = "csr", feature = "hydrate"))] -pub fn queue_microtask(task: impl FnOnce() + 'static) { - microtask(wasm_bindgen::closure::Closure::once_into_js(task)); -} - -#[cfg(any(feature = "csr", feature = "hydrate"))] -#[wasm_bindgen::prelude::wasm_bindgen( - inline_js = "export function microtask(f) { queueMicrotask(f); }" -)] -extern "C" { - fn microtask(task: wasm_bindgen::JsValue); -} - -#[cfg(any(feature = "csr", feature = "hydrate"))] -pub fn spawn_local(fut: F) -where - F: Future + 'static, -{ - wasm_bindgen_futures::spawn_local(fut) -} - -#[cfg(feature = "ssr")] -pub fn spawn_local(_fut: F) -where - F: Future + 'static, -{ - // noop for now; useful for ignoring any async tasks on the server side - // could be replaced with a Tokio dependency -} diff --git a/reactive/src/suspense.rs b/reactive/src/suspense.rs deleted file mode 100644 index 657dbdd2e..000000000 --- a/reactive/src/suspense.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{create_signal, spawn::queue_microtask, ReadSignal, Scope, WriteSignal}; - -#[derive(Copy, Clone, Debug)] -pub struct SuspenseContext { - pub pending_resources: ReadSignal, - set_pending_resources: WriteSignal, -} - -impl std::hash::Hash for SuspenseContext { - fn hash(&self, state: &mut H) { - self.pending_resources.scope.hash(state); - self.pending_resources.id.hash(state); - } -} - -impl PartialEq for SuspenseContext { - fn eq(&self, other: &Self) -> bool { - self.pending_resources.scope == other.pending_resources.scope - && self.pending_resources.id == other.pending_resources.id - } -} - -impl Eq for SuspenseContext {} - -impl SuspenseContext { - pub fn new(cx: Scope) -> Self { - let (pending_resources, set_pending_resources) = create_signal(cx, 0); - Self { - pending_resources, - set_pending_resources, - } - } - - pub fn increment(&self) { - let setter = self.set_pending_resources; - queue_microtask(move || setter.update(|n| *n += 1)); - } - - pub fn decrement(&self) { - let setter = self.set_pending_resources; - queue_microtask(move || { - setter.update(|n| { - if *n > 0 { - *n -= 1 - } - }) - }); - } - - pub fn ready(&self) -> bool { - self.pending_resources.get() == 0 - } -} diff --git a/reactive/src/system.rs b/reactive/src/system.rs deleted file mode 100644 index 0d1d6fec4..000000000 --- a/reactive/src/system.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::{ - cell::{Cell, RefCell}, - fmt::Debug, - rc::Rc, -}; - -use slotmap::SlotMap; - -use crate::{ - ObserverLink, ResourceId, ResourceState, Scope, ScopeDisposer, ScopeId, ScopeState, SignalId, - SignalState, TransitionState, -}; - -pub struct System { - observer: RefCell>, - tracking: Cell, - batch: RefCell>>, - pub(crate) scopes: RefCell>>, -} - -impl System { - pub fn new() -> Self { - Self { - observer: Default::default(), - tracking: Default::default(), - batch: Default::default(), - scopes: Default::default(), - } - } - - pub fn create_scope( - &'static self, - f: impl FnOnce(Scope), - parent: Option, - ) -> ScopeDisposer { - let id = { - self.scopes - .borrow_mut() - .insert(Rc::new(ScopeState::new(parent))) - }; - let scope = Scope { system: self, id }; - f(scope); - - ScopeDisposer(Box::new(move || scope.dispose())) - } - - pub(crate) fn wrap( - &self, - f: impl FnOnce() -> T, - observer: Option, - tracking: bool, - ) -> T { - let prev_observer = self.observer.replace(observer); - let prev_tracking = self.tracking.get(); - - self.tracking.set(tracking); - - let value = f(); - - *self.observer.borrow_mut() = prev_observer; - self.tracking.set(prev_tracking); - - value - } - - pub(crate) fn untrack(&self, f: impl FnOnce() -> T) -> T { - self.wrap(f, self.observer(), false) - } - - pub(crate) fn tracking(&self) -> bool { - self.tracking.get() - } - - pub(crate) fn observer(&self) -> Option { - self.observer.borrow().clone() - } - - pub(crate) fn batching(&self) -> bool { - !self.batch.borrow().is_empty() - } - - pub(crate) fn add_to_batch(&self, deferred_fn: impl FnOnce()) { - let deferred: Box = Box::new(deferred_fn); - // TODO safety - let deferred: Box = unsafe { std::mem::transmute(deferred) }; - - self.batch.borrow_mut().push(deferred); - } - - pub(crate) fn scope(&self, id: ScopeId, f: impl FnOnce(&ScopeState) -> T) -> T { - let scope = { self.scopes.borrow().get(id).cloned() }; - if let Some(scope) = scope { - (f)(&scope) - } else { - log::error!( - "couldn't locate {id:?} in scopes {:#?}", - self.scopes.borrow() - ); - panic!("couldn't locate {id:?}"); - } - } - - pub(crate) fn remove_scope(&self, scope: &ScopeId) { - self.scopes.borrow_mut().remove(*scope); - } - - pub(crate) fn signal( - &self, - id: (ScopeId, SignalId), - f: impl FnOnce(Rc>) -> U, - ) -> U - where - T: 'static, - { - self.scope(id.0, |scope| { - if let Ok(n) = scope.arena[id.1 .0].clone().downcast::>() { - (f)(n) - } else { - panic!( - "couldn't convert {id:?} to SignalState<{}>", - std::any::type_name::() - ); - } - }) - } - - pub(crate) fn resource( - &self, - id: (ScopeId, ResourceId), - f: impl FnOnce(&ResourceState) -> U, - ) -> U - where - S: Debug + Clone + 'static, - T: Debug + Clone + 'static, - { - self.scope(id.0, |scope| { - if let Ok(n) = scope.arena[id.1 .0] - .clone() - .downcast::>() - { - (f)(&n) - } else { - panic!( - "couldn't convert {id:?} to SignalState<{}>", - std::any::type_name::() - ); - } - }) - } - - pub(crate) fn running_transition(&self) -> Option { - None - // TODO transition - } - - pub(crate) fn transition(&self) -> Option { - None - // TODO transition - } -} - -impl Default for System { - fn default() -> Self { - Self::new() - } -} - -impl std::fmt::Debug for System { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("System") - .field("observer", &self.observer) - .field("tracking", &self.tracking) - .field("batch", &self.batch.borrow().len()) - .field("scopes", &self.scopes) - .finish() - } -} - -impl PartialEq for System { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } -} - -impl Eq for System {} - -impl std::hash::Hash for System { - fn hash(&self, state: &mut H) { - std::ptr::hash(&self, state); - } -} diff --git a/reactive/src/transition.rs b/reactive/src/transition.rs deleted file mode 100644 index 10d9b4214..000000000 --- a/reactive/src/transition.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::{ - cell::{Cell, RefCell}, - collections::HashSet, - rc::Rc, -}; - -use crate::{ - create_effect, create_signal, spawn::queue_microtask, system::System, ObserverLink, ReadSignal, - Scope, ScopeId, SignalId, WriteSignal, -}; - -pub fn use_transition(cx: Scope) -> Transition { - let (pending, set_pending) = create_signal(cx, false); - Transition { - system: cx.system, - scope: cx, - pending, - set_pending, - } -} - -#[derive(Copy, Clone)] -pub struct Transition { - system: &'static System, - scope: Scope, - pending: ReadSignal, - set_pending: WriteSignal, -} - -impl Transition { - pub fn start(&self, f: impl FnOnce()) { - /* if self.system.running_transition().is_some() { - f(); - } else { - { - self.set_pending.update(|n| *n = true); - *self.system.transition.borrow_mut() = Some(Rc::new(TransitionState { - running: Cell::new(true), - resources: Default::default(), - signals: Default::default(), - effects: Default::default(), - })); - } - - f(); - - if let Some(running_transition) = self.system.running_transition() { - running_transition.running.set(false); - - let system = self.system; - let scope = self.scope; - let resources = running_transition.resources.clone(); - let signals = running_transition.signals.clone(); - let effects = running_transition.effects.clone(); - let set_pending = self.set_pending; - // place this at end of task queue so it doesn't start at 0 - queue_microtask(move || { - create_effect(scope, move |_| { - let pending = resources.borrow().iter().map(|p| p.get()).sum::(); - - if pending == 0 { - for signal in signals.borrow().iter() { - system.any_signal(*signal, |signal| { - signal.end_transition(system); - }); - } - for effect in effects.borrow().iter() { - system.any_effect(*effect, |any_effect| { - any_effect.run(*effect); - }); - } - set_pending.update(|n| *n = false); - } - }); - }); - } - } */ - todo!() - } - - pub fn pending(&self) -> bool { - self.pending.get() - } -} - -#[derive(Debug)] -pub(crate) struct TransitionState { - pub running: Cell, - pub resources: Rc>>>, - pub signals: Rc>>, - pub computation: Rc>>, -} diff --git a/reactive/tests/effect.rs b/reactive/tests/effect.rs deleted file mode 100644 index 23934d78d..000000000 --- a/reactive/tests/effect.rs +++ /dev/null @@ -1,89 +0,0 @@ -use leptos_reactive::{create_effect, create_memo, create_scope, create_signal}; - -#[test] -fn effect_runs() { - use std::cell::RefCell; - use std::rc::Rc; - - create_scope(|cx| { - let (a, set_a) = create_signal(cx, -1); - - // simulate an arbitrary side effect - let b = Rc::new(RefCell::new(String::new())); - - create_effect(cx, { - let b = b.clone(); - move |_| { - let formatted = format!("Value is {}", a()); - *b.borrow_mut() = formatted; - } - }); - - assert_eq!(b.borrow().as_str(), "Value is -1"); - - set_a(|a| *a = 1); - - assert_eq!(b.borrow().as_str(), "Value is 1"); - }) - .dispose() -} - -#[test] -fn effect_tracks_memo() { - use std::cell::RefCell; - use std::rc::Rc; - - create_scope(|cx| { - let (a, set_a) = create_signal(cx, -1); - let b = create_memo(cx, move |_| format!("Value is {}", a())); - - // simulate an arbitrary side effect - let c = Rc::new(RefCell::new(String::new())); - - create_effect(cx, { - let c = c.clone(); - move |_| { - *c.borrow_mut() = b(); - } - }); - - assert_eq!(b().as_str(), "Value is -1"); - assert_eq!(c.borrow().as_str(), "Value is -1"); - - set_a(|a| *a = 1); - - assert_eq!(b().as_str(), "Value is 1"); - assert_eq!(c.borrow().as_str(), "Value is 1"); - }) - .dispose() -} - -#[test] -fn untrack_mutes_effect() { - use std::cell::RefCell; - use std::rc::Rc; - - create_scope(|cx| { - let (a, set_a) = create_signal(cx, -1); - - // simulate an arbitrary side effect - let b = Rc::new(RefCell::new(String::new())); - - create_effect(cx, { - let b = b.clone(); - move |_| { - let formatted = format!("Value is {}", cx.untrack(a)); - *b.borrow_mut() = formatted; - } - }); - - assert_eq!(a(), -1); - assert_eq!(b.borrow().as_str(), "Value is -1"); - - set_a(|a| *a = 1); - - assert_eq!(a(), 1); - assert_eq!(b.borrow().as_str(), "Value is -1"); - }) - .dispose() -} diff --git a/reactive/tests/memo.rs b/reactive/tests/memo.rs deleted file mode 100644 index cdbd9dc76..000000000 --- a/reactive/tests/memo.rs +++ /dev/null @@ -1,87 +0,0 @@ -use leptos_reactive::{create_memo, create_scope, create_signal}; - -#[test] -fn basic_memo() { - create_scope(|cx| { - let a = create_memo(cx, |_| 5); - assert_eq!(a(), 5); - }) - .dispose() -} - -#[test] -fn memo_with_computed_value() { - create_scope(|cx| { - let (a, set_a) = create_signal(cx, 0); - let (b, set_b) = create_signal(cx, 0); - let c = create_memo(cx, move |_| a() + b()); - assert_eq!(c(), 0); - set_a(|a| *a = 5); - assert_eq!(c(), 5); - set_b(|b| *b = 1); - assert_eq!(c(), 6); - }) - .dispose() -} - -#[test] -fn nested_memos() { - create_scope(|cx| { - let (a, set_a) = create_signal(cx, 0); - let (b, set_b) = create_signal(cx, 0); - let c = create_memo(cx, move |_| a() + b()); - let d = create_memo(cx, move |_| c() * 2); - let e = create_memo(cx, move |_| d() + 1); - assert_eq!(d(), 0); - set_a(|a| *a = 5); - assert_eq!(c(), 5); - assert_eq!(d(), 10); - assert_eq!(e(), 11); - set_b(|b| *b = 1); - assert_eq!(c(), 6); - assert_eq!(d(), 12); - assert_eq!(e(), 13); - }) - .dispose() -} - -#[test] -fn memo_runs_only_when_inputs_change() { - use std::{cell::Cell, rc::Rc}; - - create_scope(|cx| { - let call_count = Rc::new(Cell::new(0)); - let (a, set_a) = create_signal(cx, 0); - let (b, _) = create_signal(cx, 0); - let (c, _) = create_signal(cx, 0); - - // pretend that this is some kind of expensive Observer and we need to access its its value often - // we could do this with a derived signal, but that would re-run the Observer - // memos should only run when their inputs actually change: this is the only point - let c = create_memo(cx, { - let call_count = call_count.clone(); - move |_| { - call_count.set(call_count.get() + 1); - a() + b() + c() - } - }); - - assert_eq!(call_count.get(), 1); - - // here we access the value a bunch of times - assert_eq!(c(), 0); - assert_eq!(c(), 0); - assert_eq!(c(), 0); - assert_eq!(c(), 0); - assert_eq!(c(), 0); - - // we've still only called the memo calculation once - assert_eq!(call_count.get(), 1); - - // and we only call it again when an input changes - set_a(|n| *n = 1); - assert_eq!(c(), 1); - assert_eq!(call_count.get(), 2); - }) - .dispose() -} diff --git a/reactive/tests/signal.rs b/reactive/tests/signal.rs deleted file mode 100644 index 957991136..000000000 --- a/reactive/tests/signal.rs +++ /dev/null @@ -1,27 +0,0 @@ -use leptos_reactive::{create_scope, create_signal}; - -#[test] -fn basic_signal() { - create_scope(|cx| { - let (a, set_a) = create_signal(cx, 0); - assert_eq!(a(), 0); - set_a(|a| *a = 5); - assert_eq!(a(), 5); - }) - .dispose() -} - -#[test] -fn derived_signals() { - create_scope(|cx| { - let (a, set_a) = create_signal(cx, 0); - let (b, set_b) = create_signal(cx, 0); - let c = move || a() + b(); - assert_eq!(c(), 0); - set_a(|a| *a = 5); - assert_eq!(c(), 5); - set_b(|b| *b = 1); - assert_eq!(c(), 6); - }) - .dispose() -}