Files
Evan AlmloffandJonathan Kelley 58c41f8561 Stores: Externally tracked state and reactive collections (#4483)
* map signals mutably

* boxed signals

* remove mutable borrow type from boxed writable

* improve map mut example

* restore boxed_mut helper

* make boxed readable/writable types copy

* Move away from the ReadOnlySignal alias in examples

* automatically convert Write

* Fix some tests

* ReadSignal and WriteSignal

* move read only signal alias

* make maps copy

* store prototype

* implement derive macro

* Handle generics in the derive macro

* Make Selector boxable

* derive boxed conversion

* Get rid of store type

* Remove hardcoded type from derive macro

* selector -> store

* Refactor store implementation

* more vec methods

* todo mvc store example

* Implement Storable for HashMap

* fix write

* restore sorting logic in todomvc

* derive readable and writable

* improve todomvc store example

* Result store

* Option store

* collect dead subscribers

* fix clippy and formatting

* recognize some common foreign types automatically

* fix foreign stores in props

* slice and array selectors

* clean up store example

* writable is child owned

* box selector

* read and write to builtins

* re-use the proc macro in the derive macro

* Switch to a extension trait based approach

* switch to extension traits in the derive macro

* transposed method

* derive enums

* Expose Store::new

* remove stores from the prelude

* More consistent naming scheme for SelectorScope methods

* document hashmap methods

* document index extension

* document option ext

* document result ext

* document slice extension

* document the vec extension

* make more methods borrow the store mutably

* Move to impls to have higher priority over the builtin write extension traits

* fix deadlock

* finish stores documentation

* Document the store macro

* cleanup docs and organization

* finish derive docs

* fix typo

* fix clippy

* fix doc tests

* fix derive macro with generics

* fix clippy

* test and fix empty structs

* clean up todomvc store example

* default to a boxed store writer to make it easier to type when providing to the context api

* fix cargo doc

* fix clippy in store example

* relax generational box lifetime bounds

* relax bounds

* relax signal bounds

* fix compilation

* read store

* relax bounds on default store impls

* fix enum derive

* Convert to readsignal and writesignal automatically

* simplify type matching logic

* add write store

* unsized stores

* shrink the path vec type

* use more concrete types

* remove opaque types

* remove copy bounds

* use lens as generic name everywhere

* more consistent borrow semantics

* use a concrete type for the signal metadata and mention references in the map docs

* improve todomvc store docs

* document store derive macro internals

* refactor macro

* improve selector node documentation

* fix clippy

* add btreemap

* fix doc tests

* more option methods

* fix doc

* fix clippy

* add a bunch more result methods

* add a bunch of bounds to the iterators

* fix clippy

* remove option from subscribers

* add store macro to generate extension traits

* fix store example

* fix deadlock

* fix docs

* fix doc tests

* fix clippy

* add stores to public api

* add store macro, and then update todomvc_store example

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
2025-08-05 11:30:21 -07:00

231 lines
6.7 KiB
Rust

use generational_box::{GenerationalBox, Storage, SyncStorage, UnsyncStorage};
/// # Example
///
/// ```compile_fail
/// let data = String::from("hello world");
/// let owner = UnsyncStorage::owner();
/// let key = owner.insert(&data);
/// drop(data);
/// assert_eq!(*key.read(), "hello world");
/// ```
#[allow(unused)]
fn compile_fail() {}
#[test]
fn leaking_is_ok() {
fn leaking_is_ok_test<S: Storage<String> + 'static>() {
let data = String::from("hello world");
let key;
{
// create an owner
let owner = S::owner();
// insert data into the store
key = owner.insert(data);
// don't drop the owner
std::mem::forget(owner);
}
assert_eq!(
key.try_read().as_deref().unwrap(),
&"hello world".to_string()
);
}
leaking_is_ok_test::<UnsyncStorage>();
leaking_is_ok_test::<SyncStorage>();
}
#[test]
fn drops() {
fn drops_test<S: Storage<String> + 'static>() {
let data = String::from("hello world");
let key;
{
// create an owner
let owner = S::owner();
// insert data into the store
key = owner.insert(data);
// drop the owner
}
assert!(key.try_read().is_err());
}
drops_test::<UnsyncStorage>();
drops_test::<SyncStorage>();
}
#[test]
fn works() {
fn works_test<S: Storage<i32> + 'static>() {
let owner = S::owner();
let key = owner.insert(1);
assert_eq!(*key.read(), 1);
}
works_test::<UnsyncStorage>();
works_test::<SyncStorage>();
}
#[test]
fn insert_while_reading() {
fn insert_while_reading_test<S: Storage<String> + Storage<&'static i32> + 'static>() {
let owner = S::owner();
let key;
{
let data: String = "hello world".to_string();
key = owner.insert(data);
}
let value = key.read();
owner.insert(&1);
assert_eq!(*value, "hello world");
}
insert_while_reading_test::<UnsyncStorage>();
insert_while_reading_test::<SyncStorage>();
}
#[test]
#[should_panic]
fn panics() {
fn panics_test<S: Storage<i32> + 'static>() {
let owner = S::owner();
let key = owner.insert(1);
drop(owner);
assert_eq!(*key.read(), 1);
}
panics_test::<UnsyncStorage>();
panics_test::<SyncStorage>();
}
#[test]
fn fuzz() {
fn maybe_owner_scope<S: Storage<String> + 'static>(
valid_keys: &mut Vec<GenerationalBox<String, S>>,
invalid_keys: &mut Vec<GenerationalBox<String, S>>,
path: &mut Vec<u8>,
) {
let branch_cutoff = 5;
let children = if path.len() < branch_cutoff {
rand::random::<u8>() % 4
} else {
rand::random::<u8>() % 2
};
for i in 0..children {
let owner = S::owner();
let value = format!("hello world {path:?}");
let key = owner.insert(value.clone());
println!("created new box {key:?}");
valid_keys.push(key);
path.push(i);
// read all keys
println!("{:?}", path);
for key in valid_keys.iter() {
println!("reading {key:?}");
let value = key.read();
println!("{:?}", &*value);
assert!(value.starts_with("hello world"));
}
for key in invalid_keys.iter() {
println!("reading invalid {key:?}");
assert!(key.try_read().is_err());
}
maybe_owner_scope(valid_keys, invalid_keys, path);
// After all the children run, we should still have our data
let key_value = &*key.read();
println!("{:?}", key_value);
assert_eq!(key_value, &value);
let invalid = valid_keys.pop().unwrap();
println!("popping {invalid:?}");
invalid_keys.push(invalid);
path.pop();
}
}
for _ in 0..10 {
maybe_owner_scope::<UnsyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());
}
for _ in 0..10 {
maybe_owner_scope::<SyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());
}
}
#[test]
fn fuzz_rc() {
fn maybe_owner_scope<S: Storage<String>>(
valid_keys: &mut Vec<Vec<GenerationalBox<String, S>>>,
invalid_keys: &mut Vec<GenerationalBox<String, S>>,
path: &mut Vec<u8>,
) {
let branch_cutoff = 5;
let children = if path.len() < branch_cutoff {
rand::random::<u8>() % 4
} else {
rand::random::<u8>() % 2
};
for i in 0..children {
let owner = S::owner();
let value = format!("hello world {path:?}");
let key = owner.insert_rc(value.clone());
println!("created new box {key:?}");
let mut keys = vec![key];
for _ in 0..rand::random::<u8>() % 10 {
if rand::random::<u8>() % 2 == 0 {
let owner = S::owner();
let key = owner.insert_reference(key).unwrap();
println!("created new reference {key:?}");
invalid_keys.push(key);
}
let key = owner.insert_reference(key).unwrap();
println!("created new reference {key:?}");
keys.push(key);
}
valid_keys.push(keys.clone());
path.push(i);
// read all keys
println!("{:?}", path);
for keys in valid_keys.iter() {
for key in keys {
println!("reading {key:?}");
let value = key.read();
println!("{:?}", &*value);
assert!(value.starts_with("hello world"));
}
}
for key in invalid_keys.iter() {
println!("reading invalid {key:?}");
assert!(key.try_read().is_err());
}
maybe_owner_scope(valid_keys, invalid_keys, path);
// After all the children run, we should still have our data
for key in keys {
let key_value = &*key.read();
println!("{:?}", key_value);
assert_eq!(key_value, &value);
}
let invalid = valid_keys.pop().unwrap();
println!("popping {invalid:?}");
invalid_keys.extend(invalid);
path.pop();
}
}
for _ in 0..10 {
maybe_owner_scope::<UnsyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());
}
for _ in 0..10 {
maybe_owner_scope::<SyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());
}
}