mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 16:54:41 -05:00
Compare commits
1 Commits
cleanup-te
...
lens
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73db030535 |
158
leptos_reactive/src/lens.rs
Normal file
158
leptos_reactive/src/lens.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// Paths are fn pointers. They can be safely cast to usize but not back.
|
||||
|
||||
use crate::{
|
||||
create_trigger, runtime::FxIndexMap, store_value, Signal, StoredValue,
|
||||
Trigger,
|
||||
};
|
||||
use std::{any::Any, fmt::Debug};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct PathId(usize);
|
||||
|
||||
pub struct StoreInner<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
value: StoredValue<T>,
|
||||
lenses: FxIndexMap<PathId, Trigger>,
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for StoreInner<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StoreInner")
|
||||
.field("value", &self.value)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StoreInner<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
value: store_value(value),
|
||||
lenses: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_update<U, V>(
|
||||
&mut self,
|
||||
lens: fn(&mut T) -> &mut U,
|
||||
setter: impl FnOnce(&mut U) -> V + 'static,
|
||||
) -> Option<V> {
|
||||
// get or create trigger
|
||||
let id = PathId(lens as usize);
|
||||
let trigger = *self.lenses.entry(id).or_default();
|
||||
|
||||
// run update function
|
||||
let result = self.value.try_update_value(|value| {
|
||||
let zone = lens(value);
|
||||
setter(zone)
|
||||
})?;
|
||||
|
||||
// notify trigger
|
||||
if trigger.try_notify() {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_read<U: 'static, V>(
|
||||
&mut self,
|
||||
lens: fn(&mut T) -> &mut U,
|
||||
getter: impl Fn(&U) -> V + 'static,
|
||||
) -> Signal<Option<V>> {
|
||||
// get or create trigger
|
||||
let id = PathId(lens as usize);
|
||||
let trigger = *self.lenses.entry(id).or_default();
|
||||
|
||||
let value = self.value;
|
||||
|
||||
// run update function
|
||||
Signal::derive(move || {
|
||||
trigger.track();
|
||||
value.try_update_value(|value| {
|
||||
let zone = lens(value);
|
||||
getter(&*zone)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::StoreInner;
|
||||
use crate::{
|
||||
create_effect, create_runtime, SignalGet, SignalGetUntracked,
|
||||
SignalWith, SignalWithUntracked,
|
||||
};
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
#[derive(Default)]
|
||||
struct SomeComplexType {
|
||||
a: NonCloneableUsize,
|
||||
b: NonCloneableString,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
struct NonCloneableUsize(usize);
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
struct NonCloneableString(String);
|
||||
|
||||
#[test]
|
||||
pub fn create_lens() {
|
||||
let rt = create_runtime();
|
||||
|
||||
// create the store
|
||||
let mut store = StoreInner::new(SomeComplexType::default());
|
||||
|
||||
// create two signal lenses
|
||||
fn lens_a(store: &mut SomeComplexType) -> &mut NonCloneableUsize {
|
||||
&mut store.a
|
||||
}
|
||||
fn lens_b(store: &mut SomeComplexType) -> &mut NonCloneableString {
|
||||
&mut store.b
|
||||
}
|
||||
let read_a = store.try_read(lens_a, |a| a.0);
|
||||
read_a.with_untracked(|val| assert_eq!(val, &Some(0)));
|
||||
assert_eq!(read_a.get_untracked(), Some(0));
|
||||
let read_b = store.try_read(lens_b, |b| b.0.len());
|
||||
assert_eq!(read_b.get_untracked(), Some(0));
|
||||
|
||||
// track how many times each variable notifies
|
||||
let reads_on_a = Rc::new(Cell::new(0));
|
||||
let reads_on_b = Rc::new(Cell::new(0));
|
||||
create_effect({
|
||||
let reads_on_a = Rc::clone(&reads_on_a);
|
||||
move |_| {
|
||||
read_a.track();
|
||||
reads_on_a.set(reads_on_a.get() + 1);
|
||||
}
|
||||
});
|
||||
create_effect({
|
||||
let reads_on_b = Rc::clone(&reads_on_b);
|
||||
move |_| {
|
||||
read_b.track();
|
||||
reads_on_b.set(reads_on_b.get() + 1);
|
||||
}
|
||||
});
|
||||
assert_eq!(reads_on_a.get(), 1);
|
||||
assert_eq!(reads_on_b.get(), 1);
|
||||
|
||||
// update each one once
|
||||
store.try_update(lens_a, |a| *a = NonCloneableUsize(42));
|
||||
assert_eq!(read_a.get_untracked(), Some(42));
|
||||
|
||||
store.try_update(lens_b, |b| b.0.push_str("hello, world!"));
|
||||
assert_eq!(read_b.get_untracked(), Some(13));
|
||||
|
||||
// each effect has only run once
|
||||
// none of the values has been cloned (they can't)
|
||||
assert_eq!(reads_on_a.get(), 2);
|
||||
assert_eq!(reads_on_b.get(), 2);
|
||||
|
||||
rt.dispose();
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,7 @@ mod context;
|
||||
mod diagnostics;
|
||||
mod effect;
|
||||
mod hydration;
|
||||
mod lens;
|
||||
mod memo;
|
||||
mod node;
|
||||
mod resource;
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
use cfg_if::cfg_if;
|
||||
use core::hash::BuildHasherDefault;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use indexmap::IndexSet;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use rustc_hash::{FxHashMap, FxHasher};
|
||||
use slotmap::{SecondaryMap, SlotMap, SparseSecondaryMap};
|
||||
use std::{
|
||||
@@ -46,7 +46,8 @@ tokio::task_local! {
|
||||
pub(crate) static TASK_RUNTIME: Option<RuntimeId>;
|
||||
}
|
||||
|
||||
type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
|
||||
pub(crate) type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
|
||||
pub(crate) type FxIndexMap<T, U> = IndexMap<T, U, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
// The data structure that owns all the signals, memos, effects,
|
||||
// and other data included in the reactive system.
|
||||
|
||||
@@ -17,6 +17,12 @@ pub struct Trigger {
|
||||
pub(crate) defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
impl Default for Trigger {
|
||||
fn default() -> Self {
|
||||
create_trigger()
|
||||
}
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
/// Notifies any reactive code where this trigger is tracked to rerun.
|
||||
///
|
||||
@@ -29,7 +35,7 @@ impl Trigger {
|
||||
|
||||
/// Attempts to notify any reactive code where this trigger is tracked to rerun.
|
||||
///
|
||||
/// Returns `None` if the runtime has been disposed.
|
||||
/// Returns `false` if the runtime has been disposed.
|
||||
pub fn try_notify(&self) -> bool {
|
||||
with_runtime(|runtime| {
|
||||
runtime.mark_dirty(self.id);
|
||||
|
||||
Reference in New Issue
Block a user