Skip to content

Commit 65b2c67

Browse files
committed
feat: Proxy pattern base api
1 parent 5f105c8 commit 65b2c67

File tree

12 files changed

+320
-3
lines changed

12 files changed

+320
-3
lines changed

Cargo.lock

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

patterns/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ edition = "2021"
66
[dependencies]
77
lazy_static = "1.4.0"
88
once_cell = "1.18.0"
9+
slotmap = "1.0"

patterns/src/lib.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
#![feature(fn_traits)]
2+
#![feature(type_alias_impl_trait)]
3+
4+
#[allow(dead_code)]
5+
mod observer;
6+
#[allow(dead_code)]
17
mod proxy;
2-
mod singleton;
38
#[allow(dead_code)]
4-
mod observer;
9+
mod singleton;

patterns/src/observer/events.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ impl<T> Clone for ListenerCell<T> {
5252
}
5353
}
5454

55-
#[derive(Eq)]
5655
pub struct ListenerRef {
5756
weak: Weak<RefCell<dyn ListenerUpdate>>,
5857
}
@@ -84,6 +83,8 @@ impl ListenerRef {
8483
}
8584
}
8685

86+
impl Eq for ListenerRef {}
87+
8788
impl PartialEq for ListenerRef {
8889
fn eq(&self, other: &Self) -> bool {
8990
self.weak.ptr_eq(&other.weak)

patterns/src/proxy/computed.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use crate::proxy::node::NodeId;
2+
use crate::proxy::runtime::RuntimeContext;
3+
use std::marker::PhantomData;
4+
5+
pub struct Computed<T> {
6+
id: NodeId,
7+
ty: PhantomData<T>,
8+
}
9+
10+
impl<T> Computed<T> {
11+
pub(crate) fn new(id: NodeId) -> Self {
12+
Self {
13+
id,
14+
ty: PhantomData,
15+
}
16+
}
17+
18+
pub fn get() -> T {}
19+
}
20+
21+
pub fn computed<T>(f: impl Fn() -> T) -> Computed<T> {
22+
RuntimeContext.create_computed(f)
23+
}

patterns/src/proxy/effect.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use std::any::Any;
2+
use std::cell::RefCell;
3+
use std::rc::Rc;
4+
5+
pub(crate) trait AnyComputation {
6+
fn run(&self, value: Rc<RefCell<dyn Any>>) -> bool;
7+
}

patterns/src/proxy/memo.rs

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use crate::proxy::effect::AnyComputation;
2+
use crate::proxy::node::NodeId;
3+
use std::any::Any;
4+
use std::cell::RefCell;
5+
use std::marker::PhantomData;
6+
use std::ops::{Deref, DerefMut};
7+
use std::rc::Rc;
8+
9+
pub(crate) type FnMemo<T> = impl Fn(&T) -> T;
10+
11+
pub struct Memo<T> {
12+
id: NodeId,
13+
ty: PhantomData<T>,
14+
}
15+
16+
impl<T> Memo<T> {
17+
pub(crate) fn new(id: NodeId) -> Self {
18+
Self {
19+
id,
20+
ty: PhantomData,
21+
}
22+
}
23+
24+
pub fn as_ref(&self) -> &T {
25+
self.val.borrow().deref()
26+
}
27+
}
28+
29+
impl<T: Copy> Memo<T> {
30+
pub fn get(&self) -> T {
31+
self.val.borrow().to_owned()
32+
}
33+
}
34+
35+
pub struct MemoState<T> {
36+
f: FnMemo<T>,
37+
ty: PhantomData<T>,
38+
}
39+
40+
impl<T> MemoState<T> {
41+
pub(crate) fn new(f: FnMemo<T>) -> Self {
42+
Self { f, ty: PhantomData }
43+
}
44+
}
45+
46+
impl<T> AnyComputation for MemoState<T> {
47+
fn run(&self, value: Rc<RefCell<dyn Any>>) -> bool {
48+
let (new_value, is_different) = {
49+
let curr_value = value.borrow().downcast_ref::<Option<&T>>();
50+
let new_value = (self.f)(curr_value.as_ref());
51+
let is_different = curr_value.as_ref() != Some(&new_value);
52+
(new_value, is_different)
53+
};
54+
if is_different {
55+
let mut curr_value = value.borrow_mut().downcast_mut().unwrap();
56+
*curr_value = Some(new_value);
57+
}
58+
is_different
59+
}
60+
}

patterns/src/proxy/mod.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! This is an implementation of a proxy pattern heavily based on the `leptos_reactive` crate,
2+
//! Part of the `leptos` framework https://crates.io/crates/leptos.
3+
//!
4+
//! This implementation combines the proxy + singleton + observer patterns.
5+
//! For an implementation of the observer pattern, check out the `observer` module in this crate.
6+
//!
7+
//! Many UI frameworks have reactive values that appear in this module.
8+
//! It's a good way to see and understand how values can be proxied using their getters/setters
9+
//! and made reactive. For a good explanation of reactivity in Vue, check out:
10+
//! https://vuejs.org/guide/extras/reactivity-in-depth.html
11+
//!
12+
//! Also, check out the video by the creator of `leptos`:
13+
//! https://youtu.be/UrMHPrumJEs?si=S5LrQNGS-fnNL8WJ
14+
//!
15+
//! Crate Link: https://crates.io/crates/leptos_reactive
16+
//! Github Link: https://github.com/leptos-rs/leptos/blob/main/leptos_reactive
17+
18+
mod effect;
19+
mod memo;
20+
mod node;
21+
mod reference;
22+
mod runtime;
23+
mod watch;
24+
mod computed;
25+
26+
#[cfg(test)]
27+
mod tests {
28+
use crate::proxy::reference::create_ref;
29+
30+
#[test]
31+
fn ref_test() {
32+
// create a ref
33+
let number = reference(0);
34+
35+
// watch value change
36+
let mut number_change_counter = 0;
37+
number.watch(|curr, prev| number_change_counter += 1);
38+
39+
// memoized value also watches for change
40+
let doubled = number.memoized(|x| (*x) * 2);
41+
assert_eq!(number.get(), 0);
42+
43+
// create computed values with multiple dependencies
44+
let counter = create_ref(0);
45+
let computed = computed(|| number.get() * counter.get());
46+
47+
// update the value two times
48+
value.set(1);
49+
value.update(|x| *x = 2);
50+
51+
// at this point there should be two changes to `value`
52+
assert_eq!(change_counter, 2);
53+
54+
// and doubled should be equal to 2
55+
assert_eq!(doubled.get(), 4);
56+
}
57+
}

patterns/src/proxy/node.rs

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::proxy::effect::AnyComputation;
2+
use crate::proxy::runtime::Runtime;
3+
use slotmap::new_key_type;
4+
use std::any::Any;
5+
use std::cell::RefCell;
6+
use std::rc::Rc;
7+
8+
new_key_type! {
9+
pub struct NodeId;
10+
}
11+
12+
pub(crate) enum ReactiveError {
13+
Disposed,
14+
BadCast,
15+
}
16+
17+
impl NodeId {
18+
pub fn try_with<T, U>(
19+
&self,
20+
runtime: &Runtime,
21+
f: impl FnOnce(&T) -> U,
22+
) -> Result<U, ReactiveError> {
23+
}
24+
}
25+
26+
pub(crate) enum ReactiveNodeType {
27+
Reference,
28+
Memo { f: Rc<dyn AnyComputation> },
29+
Computed { f: Rc<dyn AnyComputation> },
30+
Effect { f: Rc<dyn AnyComputation> },
31+
}
32+
33+
pub(crate) struct ReactiveNode {
34+
pub value: Option<Rc<RefCell<dyn Any>>>,
35+
pub node_type: ReactiveNodeType,
36+
}

patterns/src/proxy/reference.rs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use crate::proxy::memo::{FnMemo, Memo};
2+
use crate::proxy::node::NodeId;
3+
use crate::proxy::runtime::{with_runtime, Runtime, RuntimeContext};
4+
use std::any::Any;
5+
use std::cell::RefCell;
6+
use std::marker::PhantomData;
7+
use std::ops::{Deref, DerefMut};
8+
use std::rc::Rc;
9+
10+
pub(crate) type FnUpdate<T> = impl FnOnce(&mut T);
11+
12+
pub(crate) type FnWatch<T> = impl Fn(&Option<T>);
13+
14+
pub struct Reference<T> {
15+
pub(crate) id: NodeId,
16+
pub(crate) ty: PhantomData<T>,
17+
}
18+
19+
impl<T> Reference<T> {
20+
pub(crate) fn new(id: NodeId) -> Self {
21+
Reference {
22+
id,
23+
ty: PhantomData,
24+
}
25+
}
26+
27+
pub fn update(&self, f: FnUpdate<T>) {}
28+
29+
pub fn watch(&self, f: FnWatch<T>) {}
30+
31+
pub fn memo(&self, f: FnMemo<T>) -> Memo<T> {
32+
let memo = RuntimeContext.create_memo(f);
33+
memo
34+
}
35+
36+
pub fn as_ref(&self) -> &T {
37+
self.val.borrow().deref()
38+
}
39+
40+
pub(crate) fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
41+
with_runtime(|runtime| self.id.try_with(runtime, f))
42+
}
43+
}
44+
45+
impl<T: Copy> Reference<T> {
46+
pub fn get(&self) -> T {
47+
self.try_with()
48+
}
49+
}
50+
51+
pub fn reference<T>(value: T) -> Reference<T> {
52+
RuntimeContext.create_ref(value)
53+
}

patterns/src/proxy/runtime.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use crate::proxy::memo::{FnMemo, Memo, MemoState};
2+
use crate::proxy::node::{NodeId, ReactiveNode, ReactiveNodeType};
3+
use crate::proxy::reference::Reference;
4+
use slotmap::{SecondaryMap, SlotMap};
5+
use std::cell::RefCell;
6+
use std::collections::HashSet;
7+
use std::rc::Rc;
8+
9+
thread_local! {
10+
pub(crate) static RUNTIME: Runtime = Runtime::new();
11+
}
12+
13+
pub struct Runtime {
14+
nodes: RefCell<SlotMap<NodeId, ReactiveNode>>,
15+
node_subscribers: RefCell<SecondaryMap<NodeId, RefCell<HashSet<NodeId>>>>,
16+
}
17+
18+
impl Runtime {
19+
pub(crate) fn new() -> Self {
20+
Self {
21+
nodes: RefCell::new(SlotMap::default()),
22+
node_subscribers: RefCell::new(SecondaryMap::default()),
23+
}
24+
}
25+
}
26+
27+
pub(crate) fn with_runtime<T, F>(f: F) -> T
28+
where
29+
F: FnOnce(&Runtime) -> T,
30+
{
31+
RUNTIME.with(|runtime| f(runtime))
32+
}
33+
34+
pub(crate) struct RuntimeContext;
35+
36+
impl RuntimeContext {
37+
pub(crate) fn create_ref<T>(self, value: T) -> Reference<T> {
38+
let id = with_runtime(|runtime| {
39+
runtime.nodes.borrow_mut().insert(ReactiveNode {
40+
value: Some(value),
41+
node_type: ReactiveNodeType::Ref,
42+
})
43+
});
44+
Reference::new(id)
45+
}
46+
47+
pub(crate) fn create_memo<T>(self, f: FnMemo<T>) -> Memo<T> {
48+
let state = Rc::new(MemoState::new(f));
49+
let id = with_runtime(|runtime| {
50+
runtime.nodes.borrow_mut().insert(ReactiveNode {
51+
value: Some(Rc::new(RefCell::new(None::<T>))),
52+
node_type: ReactiveNodeType::Memo { f: state },
53+
})
54+
});
55+
Memo::new(id)
56+
}
57+
}

patterns/src/proxy/watch.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)