mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
Remove unused reactive system
This commit is contained in:
parent
450f521ad0
commit
24d6965893
15 changed files with 0 additions and 1674 deletions
|
@ -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"]
|
|
@ -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<T>(cx: Scope, f: impl FnMut(Option<T>) -> 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<T>(cx: Scope, f: impl FnMut(Option<T>) -> 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<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
system: &'static System,
|
||||
// function to execute when dependencies change
|
||||
f: Box<RefCell<dyn FnMut(Option<T>) -> T>>,
|
||||
// previous value of the effect
|
||||
value: RefCell<Option<T>>,
|
||||
// internal signal holding value of the function
|
||||
signal: Option<Rc<SignalState<Option<T>>>>,
|
||||
// how many of our dependencies have changed since the last time we ran the function?
|
||||
waiting: Cell<u32>,
|
||||
// did something actually change in one of our dependencies?
|
||||
fresh: Cell<bool>,
|
||||
// all the signals this computation depends on
|
||||
signals: RefCell<HashSet<ObservableLink>>,
|
||||
// custom cleanup functions to call
|
||||
cleanups: RefCell<Vec<Box<dyn FnOnce()>>>,
|
||||
}
|
||||
|
||||
impl<T> Computation<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
system: &'static System,
|
||||
f: impl FnMut(Option<T>) -> T + 'static,
|
||||
signal: Option<Rc<SignalState<Option<T>>>>,
|
||||
) -> 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<T> Observer for Computation<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
fn run(self: Rc<Self>) {
|
||||
// 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<dyn Observer>)),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
fn update(self: Rc<Self>) {
|
||||
// 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<Self>, signal: ObservableLink) {
|
||||
self.signals.borrow_mut().insert(signal);
|
||||
}
|
||||
|
||||
fn is_waiting(&self) -> bool {
|
||||
self.waiting.get() > 0
|
||||
}
|
||||
|
||||
fn stale(self: Rc<Self>, 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<T> Computation<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
fn cleanup(self: Rc<Self>) {
|
||||
for source in self.signals.borrow().iter() {
|
||||
source.unsubscribe(ObserverLink(Rc::downgrade(&self) as Weak<dyn Observer>));
|
||||
}
|
||||
|
||||
for cleanup in self.cleanups.take() {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Observer {
|
||||
fn run(self: Rc<Self>);
|
||||
|
||||
fn add_signal(self: Rc<Self>, signal: ObservableLink);
|
||||
|
||||
fn is_waiting(&self) -> bool;
|
||||
|
||||
fn update(self: Rc<Self>);
|
||||
|
||||
fn stale(self: Rc<Self>, increment: WaitingCount, fresh: bool);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ObserverLink(pub(crate) Weak<dyn Observer>);
|
||||
|
||||
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<H: std::hash::Hasher>(&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,
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
use std::any::{Any, TypeId};
|
||||
|
||||
use crate::Scope;
|
||||
|
||||
pub fn provide_context<T>(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<T>(cx: Scope) -> Option<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
let id = TypeId::of::<T>();
|
||||
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::<T>());
|
||||
match local_value {
|
||||
Some(val) => Some(val.clone()),
|
||||
None => scope_state
|
||||
.parent
|
||||
.and_then(|parent| use_context::<T>(parent)),
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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::<Vec<_>>();
|
||||
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()
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
use std::{fmt::Debug, marker::PhantomData, rc::Rc};
|
||||
|
||||
use crate::{Computation, Observer, ReadSignal, Scope, SignalState};
|
||||
|
||||
pub fn create_memo<T>(cx: Scope, f: impl FnMut(Option<T>) -> T + 'static) -> Memo<T>
|
||||
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<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
inner: ReadSignal<Option<T>>,
|
||||
}
|
||||
|
||||
impl<T> Copy for Memo<T> where T: Clone + 'static {}
|
||||
|
||||
impl<T> Memo<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
pub fn get(self) -> T {
|
||||
self.with(|val| val.clone())
|
||||
}
|
||||
|
||||
pub fn with<U>(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<T> FnOnce<()> for Memo<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FnMut<()> for Memo<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Fn<()> for Memo<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
{
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
}
|
|
@ -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<S, T, Fu>(
|
||||
cx: Scope,
|
||||
source: impl Fn() -> S + 'static,
|
||||
fetcher: impl Fn(S) -> Fu + 'static,
|
||||
) -> Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
create_resource_with_initial_value(cx, source, fetcher, None)
|
||||
}
|
||||
|
||||
pub fn create_resource_with_initial_value<S, T, Fu>(
|
||||
cx: Scope,
|
||||
source: impl Fn() -> S + 'static,
|
||||
fetcher: impl Fn(S) -> Fu + 'static,
|
||||
initial_value: Option<T>,
|
||||
) -> Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
Fu: Future<Output = T> + '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<Box<dyn Future<Output = T>>>);
|
||||
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<S, T> Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
{
|
||||
pub fn read(&self) -> Option<T> {
|
||||
self.system
|
||||
.resource((self.scope, self.id), |resource: &ResourceState<S, T>| {
|
||||
resource.read()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loading(&self) -> bool {
|
||||
self.system
|
||||
.resource((self.scope, self.id), |resource: &ResourceState<S, T>| {
|
||||
resource.loading.get()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn refetch(&self) {
|
||||
self.system
|
||||
.resource((self.scope, self.id), |resource: &ResourceState<S, T>| {
|
||||
resource.refetch()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Resource<S, T>
|
||||
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<S>,
|
||||
pub(crate) out_ty: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<S, T> Clone for Resource<S, T>
|
||||
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<S, T> Copy for Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceState<S, T>
|
||||
where
|
||||
S: Clone + 'static,
|
||||
T: Clone + Debug + 'static,
|
||||
{
|
||||
scope: Scope,
|
||||
value: ReadSignal<Option<T>>,
|
||||
set_value: WriteSignal<Option<T>>,
|
||||
pub loading: ReadSignal<bool>,
|
||||
set_loading: WriteSignal<bool>,
|
||||
track: ReadSignal<usize>,
|
||||
trigger: WriteSignal<usize>,
|
||||
source: Memo<S>,
|
||||
fetcher: Rc<dyn Fn(S) -> Pin<Box<dyn Future<Output = T>>>>,
|
||||
resolved: Rc<Cell<bool>>,
|
||||
scheduled: Rc<Cell<bool>>,
|
||||
suspense_contexts: Rc<RefCell<HashSet<SuspenseContext>>>,
|
||||
}
|
||||
|
||||
impl<S, T> ResourceState<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
{
|
||||
pub fn read(&self) -> Option<T> {
|
||||
let suspense_cx = use_context::<SuspenseContext>(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();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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<T>(&self, f: impl FnOnce() -> T) -> T {
|
||||
self.system.untrack(f)
|
||||
}
|
||||
}
|
||||
|
||||
// Internals
|
||||
impl Scope {
|
||||
pub(crate) fn push_signal<T>(&self, state: Rc<SignalState<T>>) -> 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<T>(&self, state: Rc<Computation<T>>) -> 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<S, T>(&self, state: Rc<ResourceState<S, T>>) -> 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<dyn FnOnce()>);
|
||||
|
||||
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<Scope>,
|
||||
pub(crate) contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
||||
pub(crate) children: RefCell<Vec<ScopeId>>,
|
||||
pub(crate) arena: AppendOnlyVec<Rc<dyn Any>>,
|
||||
}
|
||||
|
||||
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<Scope>) -> Self {
|
||||
Self {
|
||||
parent,
|
||||
contexts: Default::default(),
|
||||
children: Default::default(),
|
||||
arena: AppendOnlyVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T>(cx: Scope, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
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<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub(crate) system: &'static System,
|
||||
pub(crate) scope: ScopeId,
|
||||
pub(crate) id: SignalId,
|
||||
pub(crate) ty: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> ReadSignal<T> {
|
||||
pub fn get(self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.with(|val| val.clone())
|
||||
}
|
||||
|
||||
pub fn with<U>(self, f: impl Fn(&T) -> U) -> U {
|
||||
self.system
|
||||
.signal((self.scope, self.id), |state| state.with(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for ReadSignal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
system: self.system,
|
||||
scope: self.scope,
|
||||
id: self.id,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for ReadSignal<T> {}
|
||||
|
||||
impl<T> FnOnce<()> for ReadSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FnMut<()> for ReadSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Fn<()> for ReadSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
{
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct WriteSignal<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
system: &'static System,
|
||||
pub(crate) scope: ScopeId,
|
||||
pub(crate) id: SignalId,
|
||||
pub(crate) ty: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> WriteSignal<T>
|
||||
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<T> Clone for WriteSignal<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
system: self.system,
|
||||
scope: self.scope,
|
||||
id: self.id,
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for WriteSignal<T> where T: Clone {}
|
||||
|
||||
impl<T, F> FnOnce<(F,)> for WriteSignal<T>
|
||||
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<T, F> FnMut<(F,)> for WriteSignal<T>
|
||||
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<T, F> Fn<(F,)> for WriteSignal<T>
|
||||
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<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
system: &'static System,
|
||||
parent: RefCell<Option<ObserverLink>>,
|
||||
value: RefCell<T>,
|
||||
t_value: RefCell<Option<T>>,
|
||||
subscribers: RefCell<HashSet<ObserverLink>>,
|
||||
}
|
||||
|
||||
impl<T> SignalState<T>
|
||||
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<U>(self: Rc<Self>, 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<Self>, 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<Self>) -> ObservableLink {
|
||||
ObservableLink(Rc::downgrade(&self) as Weak<dyn Observable>)
|
||||
}
|
||||
|
||||
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<T> Observable for SignalState<T> {
|
||||
fn unsubscribe(&self, observer: ObserverLink) {
|
||||
self.subscribers.borrow_mut().remove(&observer);
|
||||
}
|
||||
}
|
||||
pub struct ObservableLink(pub(crate) Weak<dyn Observable>);
|
||||
|
||||
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<H: std::hash::Hasher>(&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 {}
|
|
@ -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<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(fut)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn spawn_local<F>(_fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
// noop for now; useful for ignoring any async tasks on the server side
|
||||
// could be replaced with a Tokio dependency
|
||||
}
|
|
@ -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<usize>,
|
||||
set_pending_resources: WriteSignal<usize>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for SuspenseContext {
|
||||
fn hash<H: std::hash::Hasher>(&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
|
||||
}
|
||||
}
|
|
@ -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<Option<ObserverLink>>,
|
||||
tracking: Cell<bool>,
|
||||
batch: RefCell<Vec<Box<dyn FnOnce()>>>,
|
||||
pub(crate) scopes: RefCell<SlotMap<ScopeId, Rc<ScopeState>>>,
|
||||
}
|
||||
|
||||
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<Scope>,
|
||||
) -> 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<T>(
|
||||
&self,
|
||||
f: impl FnOnce() -> T,
|
||||
observer: Option<ObserverLink>,
|
||||
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<T>(&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<ObserverLink> {
|
||||
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<dyn FnOnce()> = Box::new(deferred_fn);
|
||||
// TODO safety
|
||||
let deferred: Box<dyn FnOnce() + 'static> = unsafe { std::mem::transmute(deferred) };
|
||||
|
||||
self.batch.borrow_mut().push(deferred);
|
||||
}
|
||||
|
||||
pub(crate) fn scope<T>(&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<T, U>(
|
||||
&self,
|
||||
id: (ScopeId, SignalId),
|
||||
f: impl FnOnce(Rc<SignalState<T>>) -> U,
|
||||
) -> U
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.scope(id.0, |scope| {
|
||||
if let Ok(n) = scope.arena[id.1 .0].clone().downcast::<SignalState<T>>() {
|
||||
(f)(n)
|
||||
} else {
|
||||
panic!(
|
||||
"couldn't convert {id:?} to SignalState<{}>",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn resource<S, T, U>(
|
||||
&self,
|
||||
id: (ScopeId, ResourceId),
|
||||
f: impl FnOnce(&ResourceState<S, T>) -> 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::<ResourceState<S, T>>()
|
||||
{
|
||||
(f)(&n)
|
||||
} else {
|
||||
panic!(
|
||||
"couldn't convert {id:?} to SignalState<{}>",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn running_transition(&self) -> Option<TransitionState> {
|
||||
None
|
||||
// TODO transition
|
||||
}
|
||||
|
||||
pub(crate) fn transition(&self) -> Option<TransitionState> {
|
||||
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<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
std::ptr::hash(&self, state);
|
||||
}
|
||||
}
|
|
@ -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<bool>,
|
||||
set_pending: WriteSignal<bool>,
|
||||
}
|
||||
|
||||
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::<usize>();
|
||||
|
||||
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<bool>,
|
||||
pub resources: Rc<RefCell<HashSet<ReadSignal<usize>>>>,
|
||||
pub signals: Rc<RefCell<HashSet<(ScopeId, SignalId)>>>,
|
||||
pub computation: Rc<RefCell<Vec<ObserverLink>>>,
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
Loading…
Reference in a new issue