Remove Memo

This commit is contained in:
Greg Johnston 2022-08-24 11:19:58 -04:00
parent 7cfebcbfee
commit 41f415160a
13 changed files with 42 additions and 388 deletions

View file

@ -6,8 +6,6 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
wee_alloc = "0.4"
log = "0.4"
console_log = "0.2"
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

View file

@ -2,12 +2,11 @@ use leptos::*;
pub fn simple_counter(cx: Scope) -> web_sys::Element {
let (value, set_value) = create_signal(cx, 0);
log::debug!("ok");
view! {
<div>
<button on:click=move |_| set_value(|value| *value -= 1)>"-1"</button>
<span>"Value: " {move || value().to_string()}</span>
<span>{move || value().to_string()}</span>
<button on:click=move |_| set_value(|value| *value += 1)>"+1"</button>
</div>
}

View file

@ -5,6 +5,5 @@ use leptos::*;
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
pub fn main() {
console_log::init_with_level(log::Level::Debug);
mount_to_body(simple_counter)
}

View file

@ -98,7 +98,7 @@ impl<T> EffectState<T> {
fn cleanup(&self, id: (ScopeId, EffectId)) {
for source in self.sources.borrow().iter() {
source.unsubscribe(self.runtime, Subscriber::Effect(id))
source.unsubscribe(self.runtime, Subscriber(id))
}
}
}
@ -141,7 +141,7 @@ where
// add it to the Scope stack, which means any signals called
// in the effect fn immediately below will add this Effect as a dependency
self.runtime.push_stack(Subscriber::Effect(id));
self.runtime.push_stack(Subscriber(id));
// actually run the effect
if let Some(transition) = self.runtime.running_transition() && self.render_effect {

View file

@ -1,299 +1,29 @@
use serde::{Deserialize, Serialize};
use crate::{Runtime, Scope, ScopeId, Source, Subscriber};
use crate::{
create_effect, create_signal, ReadSignal, Runtime, Scope, ScopeId, Source, Subscriber,
};
use std::{
any::{type_name, Any},
cell::RefCell,
collections::HashSet,
fmt::Debug,
marker::PhantomData,
};
pub fn create_memo<T>(cx: Scope, f: impl FnMut(Option<&T>) -> T + 'static) -> Memo<T>
pub type Memo<T> = ReadSignal<T>;
pub fn create_memo<T>(cx: Scope, mut f: impl FnMut(Option<T>) -> T + 'static) -> Memo<T>
where
T: Debug + 'static,
T: Clone + Debug + 'static,
{
let state = MemoState::new(cx.runtime, f);
let initial = f(None);
let (read, set) = create_signal(cx, initial);
let id = cx.push_memo(state);
create_effect(cx, move |prev| {
let new = f(prev);
set(|n| *n = new.clone());
new
});
let eff = Memo {
runtime: cx.runtime,
scope: cx.id,
id,
ty: PhantomData,
};
cx.runtime
.any_memo((cx.id, id), |memo| memo.run((cx.id, id)));
eff
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Memo<T>
where
T: 'static,
{
runtime: &'static Runtime,
pub(crate) scope: ScopeId,
pub(crate) id: MemoId,
pub(crate) ty: PhantomData<T>,
}
impl<T> Clone for Memo<T> {
fn clone(&self) -> Self {
Self {
runtime: self.runtime,
scope: self.scope,
id: self.id,
ty: PhantomData,
}
}
}
impl<T> Copy for Memo<T> {}
impl<T> Memo<T>
where
T: Debug,
{
pub fn get(&self) -> T
where
T: Clone,
{
self.with(T::clone)
}
pub fn with<U>(&self, f: impl Fn(&T) -> U) -> U {
if let Some(running_subscriber) = self.runtime.running_effect() {
match running_subscriber {
Subscriber::Memo(running_memo_id) => {
self.runtime.any_memo(running_memo_id, |running_memo| {
self.add_subscriber(Subscriber::Memo(running_memo_id));
running_memo.subscribe_to(Source::Memo((self.scope, self.id)));
});
}
Subscriber::Effect(running_effect_id) => {
self.runtime
.any_effect(running_effect_id, |running_effect| {
self.add_subscriber(Subscriber::Effect(running_effect_id));
running_effect.subscribe_to(Source::Memo((self.scope, self.id)));
});
}
}
}
// If transition is running, or contains this as a source, read from t_value
if let Some(transition) = self.runtime.running_transition() {
self.runtime
.memo((self.scope, self.id), |state: &MemoState<T>| {
if transition.running.get()
&& transition.memos.borrow().contains(&(self.scope, self.id))
{
f(state
.t_value
.borrow()
.as_ref()
.expect("read ReadSignal under transition, without any t_value"))
} else {
f(state.value.borrow().as_ref().unwrap())
}
})
} else {
self.runtime.memo(
(self.scope, self.id),
|memo_state: &MemoState<T>| match &*memo_state.value.borrow() {
Some(v) => f(v),
None => {
memo_state.run((self.scope, self.id));
f(memo_state.value.borrow().as_ref().unwrap())
}
},
)
}
}
fn add_subscriber(&self, subscriber: Subscriber) {
self.runtime
.memo((self.scope, self.id), |memo_state: &MemoState<T>| {
memo_state.subscribers.borrow_mut().insert(subscriber);
})
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) struct MemoId(pub(crate) usize);
pub(crate) struct MemoState<T>
where
T: Debug,
{
runtime: &'static Runtime,
f: Box<debug_cell::RefCell<dyn FnMut(Option<&T>) -> T>>,
value: debug_cell::RefCell<Option<T>>,
t_value: debug_cell::RefCell<Option<T>>,
sources: debug_cell::RefCell<HashSet<Source>>,
subscribers: debug_cell::RefCell<HashSet<Subscriber>>,
}
impl<T> Debug for MemoState<T>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoState")
.field(
"f",
&format!(
"FnMut<Option<&{}>> -> {}",
type_name::<T>(),
type_name::<T>()
),
)
.field("value", &*self.value.borrow())
.field("t_value", &*self.t_value.borrow())
.field("sources", &*self.sources.borrow())
.field("subscribers", &*self.subscribers.borrow())
.finish()
}
}
impl<T> MemoState<T>
where
T: Debug,
{
pub fn new(runtime: &'static Runtime, f: impl FnMut(Option<&T>) -> T + 'static) -> Self {
let f = Box::new(debug_cell::RefCell::new(f));
Self {
runtime,
f,
value: debug_cell::RefCell::new(None),
sources: Default::default(),
t_value: Default::default(),
subscribers: Default::default(),
}
}
pub(crate) fn add_source(&self, source: Source) {
self.sources.borrow_mut().insert(source);
}
fn cleanup(&self, id: (ScopeId, MemoId)) {
for source in self.sources.borrow().iter() {
source.unsubscribe(self.runtime, Subscriber::Memo(id))
}
}
}
pub(crate) trait AnyMemo: Debug {
fn run(&self, id: (ScopeId, MemoId));
fn unsubscribe(&self, subscriber: Subscriber);
fn as_any(&self) -> &dyn Any;
fn subscribe_to(&self, source: Source);
fn end_transition(&self, runtime: &'static Runtime);
}
impl<T> AnyMemo for MemoState<T>
where
T: Debug + 'static,
{
fn run(&self, id: (ScopeId, MemoId)) {
// clear previous dependencies
// at this point, Effect dependencies have been added to Signal
// and any Signal changes will call Effect dependency automatically
self.cleanup(id);
// add it to the Scope stack, which means any signals called
// in the effect fn immediately below will add this Effect as a dependency
self.runtime.push_stack(Subscriber::Memo(id));
// actually run the effect
if let Some(transition) = self.runtime.running_transition() {
let mut t_value = self.t_value.borrow_mut();
if let Some(t_value) = &mut *t_value {
let v = { (self.f.borrow_mut())(Some(t_value)) };
*t_value = v;
} else {
// fork reality, using the old value as the basis for the transitional value
let v = { (self.f.borrow_mut())(self.value.borrow().as_ref()) };
*t_value = Some(v);
// track this memo
transition.memos.borrow_mut().insert(id);
}
} else {
let v = { (self.f.borrow_mut())(self.value.borrow().as_ref()) };
*self.value.borrow_mut() = Some(v);
}
// notify subscribers
let subs = { self.subscribers.borrow().clone() };
for subscriber in subs.iter() {
subscriber.run(self.runtime);
}
// pop it back off the stack
self.runtime.pop_stack();
}
fn as_any(&self) -> &dyn Any {
self
}
fn subscribe_to(&self, source: Source) {
self.add_source(source);
}
fn unsubscribe(&self, subscriber: Subscriber) {
self.subscribers.borrow_mut().remove(&subscriber);
}
fn end_transition(&self, runtime: &'static Runtime) {
let t_value = self.t_value.borrow_mut().take();
if let Some(value) = t_value {
*self.value.borrow_mut() = Some(value);
let subs = { self.subscribers.borrow().clone() };
for subscriber in subs.iter() {
subscriber.run(runtime);
}
}
}
}
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()
}
read
}

View file

@ -1,6 +1,6 @@
use crate::{
AnyEffect, AnyMemo, AnySignal, EffectId, MemoId, MemoState, ResourceId, ResourceState, Scope,
ScopeDisposer, ScopeId, ScopeState, SignalId, SignalState, Subscriber, TransitionState,
AnyEffect, AnySignal, EffectId, ResourceId, ResourceState, Scope, ScopeDisposer, ScopeId,
ScopeState, SignalId, SignalState, Subscriber, TransitionState,
};
use slotmap::SlotMap;
use std::cell::RefCell;
@ -43,32 +43,6 @@ impl Runtime {
})
}
pub fn any_memo<T>(&self, id: (ScopeId, MemoId), f: impl FnOnce(&dyn AnyMemo) -> T) -> T {
self.scope(id.0, |scope| {
if let Some(n) = scope.memos.get(id.1 .0) {
(f)(n)
} else {
panic!("couldn't locate {id:?}");
}
})
}
pub fn memo<T, U>(&self, id: (ScopeId, MemoId), f: impl FnOnce(&MemoState<T>) -> U) -> U
where
T: Debug + 'static,
{
self.any_memo(id, |n| {
if let Some(n) = n.as_any().downcast_ref::<MemoState<T>>() {
f(n)
} else {
panic!(
"couldn't convert {id:?} to MemoState<{}>",
std::any::type_name::<T>()
);
}
})
}
pub fn any_signal<T>(&self, id: (ScopeId, SignalId), f: impl FnOnce(&dyn AnySignal) -> T) -> T {
self.scope(id.0, |scope| {
if let Some(n) = scope.signals.get(id.1 .0) {

View file

@ -1,6 +1,6 @@
use crate::{
AnyEffect, AnyMemo, AnySignal, EffectId, EffectState, MemoId, MemoState, ReadSignal,
ResourceId, ResourceState, Runtime, SignalId, SignalState, Transition,
AnyEffect, AnySignal, EffectId, EffectState, ReadSignal, ResourceId, ResourceState, Runtime,
SignalId, SignalState, Transition,
};
use elsa::FrozenVec;
use serde::Serialize;
@ -61,16 +61,6 @@ impl Scope {
})
}
pub(crate) fn push_memo<T>(&self, state: MemoState<T>) -> MemoId
where
T: Debug + 'static,
{
self.runtime.scope(self.id, |scope| {
scope.memos.push(Box::new(state));
MemoId(scope.memos.len() - 1)
})
}
pub(crate) fn push_resource<S, T>(&self, state: Rc<ResourceState<S, T>>) -> ResourceId
where
S: Debug + Clone + 'static,
@ -114,7 +104,6 @@ pub(crate) struct ScopeState {
pub(crate) contexts: debug_cell::RefCell<HashMap<TypeId, Box<dyn Any>>>,
pub(crate) children: debug_cell::RefCell<Vec<ScopeId>>,
pub(crate) signals: FrozenVec<Box<dyn AnySignal>>,
pub(crate) memos: FrozenVec<Box<dyn AnyMemo>>,
pub(crate) effects: FrozenVec<Box<dyn AnyEffect>>,
pub(crate) resources: FrozenVec<Rc<dyn Any>>,
}
@ -132,7 +121,6 @@ impl ScopeState {
contexts: Default::default(),
children: Default::default(),
signals: Default::default(),
memos: Default::default(),
effects: Default::default(),
resources: Default::default(),
}

View file

@ -50,21 +50,11 @@ where
pub fn with<U>(&self, f: impl Fn(&T) -> U) -> U {
if let Some(running_subscriber) = self.runtime.running_effect() {
match running_subscriber {
Subscriber::Memo(running_memo_id) => {
self.runtime.any_memo(running_memo_id, |running_memo| {
self.add_subscriber(Subscriber::Memo(running_memo_id));
running_memo.subscribe_to(Source::Signal((self.scope, self.id)));
});
}
Subscriber::Effect(running_effect_id) => {
self.runtime
.any_effect(running_effect_id, |running_effect| {
self.add_subscriber(Subscriber::Effect(running_effect_id));
running_effect.subscribe_to(Source::Signal((self.scope, self.id)));
});
}
}
self.runtime
.any_effect(running_subscriber.0, |running_effect| {
self.add_subscriber(Subscriber(running_subscriber.0));
running_effect.subscribe_to(Source((self.scope, self.id)));
});
}
// If transition is running, or contains this as a source, take from t_value

View file

@ -1,21 +1,11 @@
use crate::{MemoId, Runtime, ScopeId, SignalId, Subscriber};
use crate::{Runtime, ScopeId, SignalId, Subscriber};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) enum Source {
Signal((ScopeId, SignalId)),
Memo((ScopeId, MemoId)),
}
pub(crate) struct Source(pub(crate) (ScopeId, SignalId));
impl Source {
pub fn unsubscribe(&self, runtime: &'static Runtime, subscriber: Subscriber) {
match self {
Source::Signal(id) => {
runtime.any_signal(*id, |signal_state| signal_state.unsubscribe(subscriber))
}
Source::Memo(id) => {
runtime.any_memo(*id, |memo_state| memo_state.unsubscribe(subscriber))
}
}
runtime.any_signal(self.0, |signal_state| signal_state.unsubscribe(subscriber))
}
}

View file

@ -1,17 +1,11 @@
use crate::{EffectId, MemoId, Runtime, ScopeId};
use crate::{EffectId, Runtime, ScopeId};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) enum Subscriber {
Memo((ScopeId, MemoId)),
Effect((ScopeId, EffectId)),
}
pub(crate) struct Subscriber(pub(crate) (ScopeId, EffectId));
impl Subscriber {
pub fn run(&self, runtime: &'static Runtime) {
match self {
Subscriber::Memo(id) => runtime.any_memo(*id, |memo| memo.run(*id)),
Subscriber::Effect(id) => runtime.any_effect(*id, |effect| effect.run(*id)),
}
runtime.any_effect(self.0, |effect| effect.run(self.0))
}
}

View file

@ -5,8 +5,8 @@ use std::{
};
use crate::{
create_effect, create_signal, runtime::Runtime, spawn::queue_microtask, EffectId, MemoId,
ReadSignal, Scope, ScopeId, SignalId, WriteSignal,
create_effect, create_signal, runtime::Runtime, spawn::queue_microtask, EffectId, ReadSignal,
Scope, ScopeId, SignalId, WriteSignal,
};
pub fn use_transition(cx: Scope) -> Transition {
@ -38,7 +38,6 @@ impl Transition {
running: Cell::new(true),
resources: Default::default(),
signals: Default::default(),
memos: Default::default(),
effects: Default::default(),
}));
}
@ -52,7 +51,6 @@ impl Transition {
let scope = self.scope;
let resources = running_transition.resources.clone();
let signals = running_transition.signals.clone();
let memos = running_transition.memos.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
@ -66,11 +64,6 @@ impl Transition {
signal.end_transition(runtime);
});
}
for memo in memos.borrow().iter() {
runtime.any_memo(*memo, |memo| {
memo.end_transition(runtime);
});
}
for effect in effects.borrow().iter() {
runtime.any_effect(*effect, |any_effect| {
any_effect.run(*effect);
@ -94,6 +87,5 @@ pub(crate) struct TransitionState {
pub running: Cell<bool>,
pub resources: Rc<RefCell<HashSet<ReadSignal<usize>>>>,
pub signals: Rc<RefCell<HashSet<(ScopeId, SignalId)>>>,
pub memos: Rc<RefCell<HashSet<(ScopeId, MemoId)>>>,
pub effects: Rc<RefCell<Vec<(ScopeId, EffectId)>>>,
}

View file

@ -51,10 +51,10 @@ where
// Rebuild the list of nested routes conservatively, and show the root route here
let mut disposers = Vec::<ScopeDisposer>::new();
let route_states: Memo<RouterState> = create_memo(cx, move |prev: Option<&RouterState>| {
let route_states: Memo<RouterState> = create_memo(cx, move |prev: Option<RouterState>| {
let next_matches = matches();
let prev_matches = prev.map(|p| &p.matches);
let prev_routes = prev.map(|p| &p.routes);
let prev_matches = prev.as_ref().map(|p| &p.matches);
let prev_routes = prev.as_ref().map(|p| &p.routes);
// are the new route matches the same as the previous route matches so far?
let mut equal = prev_matches
@ -107,7 +107,7 @@ where
// TODO dispose of extra routes from previous matches if they're longer than new ones
if let Some(prev) = prev && equal {
if let Some(prev) = &prev && equal {
RouterState {
matches: next_matches.to_vec(),
routes: prev_routes.cloned().unwrap_or_default(),
@ -139,7 +139,7 @@ where
view! { <div>{root_outlet}</div> }
}
#[derive(Debug)]
#[derive(Clone, Debug)]
struct RouterState {
matches: Vec<RouteMatch>,
routes: Rc<RefCell<Vec<RouteContext>>>,

View file

@ -5,14 +5,14 @@ use crate::{State, Url};
use super::params::ParamsMap;
pub fn create_location(cx: Scope, path: ReadSignal<String>, state: ReadSignal<State>) -> Location {
let url = create_memo(cx, move |prev: Option<&Url>| {
let url = create_memo(cx, move |prev: Option<Url>| {
path.with(|path| {
log::debug!("create_location with path {path}");
match Url::try_from(path.as_str()) {
Ok(url) => url,
Err(e) => {
log::error!("[Leptos Router] Invalid path {path}\n\n{e:?}");
prev.unwrap().clone()
prev.clone().unwrap()
}
}
})