x_bow/lib.rs
1//! # X-Bow: precise state management
2//!
3//! X-Bow is a state management library aimed for use in UI programming.
4//! It let you...
5//! * keep your data in a centralized store.
6//! * build "paths" that point to parts of the store.
7//! * borrow and mutate data at those paths.
8//! * subscribe to mutations at those paths through async API.
9//!
10//! ## Quick Example
11//!
12//! ```
13//! # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
14//! # #[derive(Default, Trackable)]
15//! # struct AnotherStruct {
16//! # asdf: String
17//! # }
18//! // Derive `Trackable` to allow parts of the struct to be tracked.
19//! #[derive(Default, Trackable)]
20//! #[track(deep)] // `deep` option is useful if the fields themselves are structs
21//! struct MyStruct {
22//! field_1: i32,
23//! field_2: u64,
24//! child_struct: AnotherStruct
25//! }
26//!
27//! // Create a centralized store with the data.
28//! let store = Store::new(MyStruct::default());
29//!
30//! // Build a path to the `i32` at `field_1` in the `MyStruct`.
31//! let path = store.build_path().field_1();
32//!
33//! // This implements the `Stream` trait. You can do `stream.next().await`, etc.
34//! let stream = path.until_change();
35//!
36//! // Mutably borrow the `i32` of the path, and increment it.
37//! // This will cause the `stream` we created to fire.
38//! *path.borrow_mut() += 1;
39//! ```
40//!
41//!
42//! ## Concepts
43//!
44//! ### Store
45//! The store is where the application state lives. It is a big RefCell.
46//!
47//! ### Paths
48//! A path identifies a piece of data in your store. It implements [PathExt],
49//! which contains most of the methods you will interact with.
50//!
51//! Paths are usually wrapped in `PathBuilder` objects. These objects each
52//! dereference to the path object it wraps.
53//!
54//! ### Path Builders
55//! A path builder wraps a path object and derefs to the path object.
56//!
57//! It also provides methods that to "continue" the path; if the path points to
58//! `T`, the PathBuilder will let you convert it to a path that points to some
59//! part inside `T`.
60//!
61//! For example: the path builder that wraps a path to `Vec<T>` has an
62//! `index(idx: usize)` method that returns a path to `T`.
63//!
64//! To convert from a PathBuilder to a Path, use [IntoPath::into_path].
65//! To convert from a Path to a PathBuilder, use [PathExt::build_path].
66//!
67//! ### Trackable Types
68//! Types that implements [Trackable] have their corresponding `PathBuilder` type.
69//! To be `Trackable`, types should have `#[derive(Trackable)]` on them.
70//!
71//! ## Usage
72//! ### Steps
73//! 1. Make your structs and enums trackable by putting
74//! `#[derive(Trackable)]` and `[track(deep)]` on them.
75//! See documentation for the [Trackable macro][derive@Trackable].
76//! ```
77//! # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
78//! // 👇 Derive `Trackable` to allow parts of the struct to be tracked.
79//! #[derive(Trackable)]
80//! #[track(deep)]
81//! struct MyStruct {
82//! field_1: i32,
83//! field_2: u64,
84//! child_enum: MyEnum
85//! }
86//! // 👇 Derive `Trackable` to allow parts of the enum to be tracked.
87//! #[derive(Trackable)]
88//! #[track(deep)]
89//! enum MyEnum {
90//! Variant1(i32),
91//! Variant2 {
92//! data: String
93//! }
94//! }
95//! ```
96//! 2. Put your data in a [Store].
97//! ```
98//! # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
99//! # #[derive(Trackable)]
100//! # #[track(deep)]
101//! # struct MyStruct {
102//! # field_1: i32,
103//! # field_2: u64,
104//! # child_enum: MyEnum
105//! # }
106//! #
107//! # #[derive(Trackable)]
108//! # #[track(deep)]
109//! # enum MyEnum {
110//! # Variant1(i32),
111//! # Variant2 {
112//! # data: String
113//! # }
114//! # }
115//! let my_data = MyStruct {
116//! field_1: 42,
117//! field_2: 123,
118//! child_enum: MyEnum::Variant2 { data: "Hello".to_string() }
119//! };
120//! let store = Store::new(my_data);
121//! ```
122//! 3. Make [Path]s.
123//! ```
124//! # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
125//! # #[derive(Trackable)]
126//! # #[track(deep)]
127//! # struct MyStruct {
128//! # field_1: i32,
129//! # field_2: u64,
130//! # child_enum: MyEnum
131//! # }
132//! #
133//! # #[derive(Trackable)]
134//! # #[track(deep)]
135//! # enum MyEnum {
136//! # Variant1(i32),
137//! # Variant2 {
138//! # data: String
139//! # }
140//! # }
141//! let my_data = MyStruct {
142//! field_1: 42,
143//! field_2: 123,
144//! child_enum: MyEnum::Variant2 { data: "Hello".to_string() }
145//! };
146//! # let store = Store::new(my_data);
147//! let path_to_field_1 = store.build_path().field_1();
148//! let path_to_data = store.build_path().child_enum().Variant2_data();
149//! ```
150//! 4. Use the Paths you made.
151//! See [PathExt] and [PathExtGuaranteed] for available APIs.
152//!
153//! ### Tracking through Vec and HashMap
154//! You can track through [Vec] using the `index(_)` method.
155//! ```
156//! # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
157//! let store = Store::new(vec![1, 2, 3]);
158//! let path = store.build_path().index(1); // 👈 path to the second element in the vec
159//! ```
160//! You can track through [HashMap][std::collections::HashMap] using the `key(_)` method.
161//! ```
162//! # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
163//! # use std::collections::HashMap;
164//! let store = Store::new(HashMap::<u32, String>::new());
165//! let path = store.build_path().key(555); // 👈 path to the String at key 555 in the hashmap.
166//! ```
167//!
168//! ## Design
169//!
170//! ### Borrowing and Paths
171//! The design of this library is a hybrid between simple [RefCell][std::cell::RefCell]
172//! and the "lens" concept prevalent in the functional programming world.
173//!
174//! The centralized data store of X-Bow is a RefCell.
175//! The library provides RefCell-like APIs like
176//! [borrow][PathExtGuaranteed::borrow] and [borrow_mut][PathExtGuaranteed::borrow_mut].
177//! Mutation is well supported and immutable data structures are not needed.
178//!
179//! The library uses *Path*s to identify parts of the data in the store.
180//! Paths are made from composing segments. Each segment is like a "lens",
181//! knowing how to project into some substructure of a type.
182//! Composed together, these segments become a path that knows how to project
183//! from the root data in the store to the part that it identifies.
184//!
185//! The difference between Paths and Lens/Optics is just that our paths work
186//! mutably, while Lens/Optics are often associated with immutable/functional design.
187//!
188//! ### Notification and Subscription
189//! Another important aspect of the library design is the notification/subscription
190//! functionality provided through [until_change][PathExt::until_change]
191//! and [until_bubbling_change][PathExt::until_bubbling_change].
192//!
193//! Change listening is done based on Paths' hashes. We have a map associating
194//! each hash to a version number and a list of listening [Waker][std::task::Waker]s.
195//! The [until_change][PathExt::until_change] method registers wakers at the
196//! hash of its target path and all prefix paths (when some piece of data
197//! encompassing the target data is changed, we assume the target data is changed too).
198//!
199//! #### Hash Collision
200//! If two paths end up with the same hash, wake notification to one would wake
201//! listeners to the other too. Thus, the `until_change` stream may fire spuriously.
202//!
203//! Keep in mind that the probability of `u64` hash collision is extremely low;
204//! with 10,000 distinct paths in a store, collision probability can
205//! [be calculated](https://en.wikipedia.org/wiki/Birthday_problem#Probability_of_a_shared_birthday_(collision))
206//! to be less than 1E-11 (0.000000001%).
207//!
208//! To further minimize the impact of hash collisions, X-Bow saves the length
209//! of paths along with their hashes. This increases collision probability, but
210//! it ensures that paths of different lengths never collide; modifying some
211//! data deep in the state tree would never result in the entire tree being
212//! woken.
213
214mod borrow_mut_guard;
215mod guarantee;
216mod hash;
217mod hash_visitor;
218mod impls;
219mod path;
220mod path_ext;
221mod path_impl;
222mod store;
223mod trackable;
224mod until_change;
225mod wakers;
226
227pub use guarantee::PathExtGuaranteed;
228pub use path::Path;
229pub use path_ext::PathExt;
230pub use path_impl::ReferencePath;
231pub use store::{Store, StoreRoot};
232pub use trackable::{IntoPath, Trackable};
233
234pub mod path_ext_wrappers {
235 pub use super::path_ext::{
236 bind_for_each::BindForEach, for_each::ForEach, signal_stream::SignalStream,
237 };
238}
239
240/// Macro to allows building paths to fields inside a struct/enum.
241///
242/// ```
243/// # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
244/// // Derive `Trackable` to allow parts of the struct to be tracked.
245/// #[derive(Default, Trackable)]
246/// struct MyStruct {
247/// field_1: i32,
248/// field_2: u64
249/// }
250///
251/// // Create a centralized store with the data.
252/// let store = Store::new(MyStruct::default());
253///
254/// // Build a path to the `i32` at `field_1` in the `MyStruct`.
255/// let path = store.build_path().field_1();
256/// ```
257///
258/// ### Shallow and Deep Tracking
259///
260/// By default, the `Trackable` derive macro makes tracking "shallow".
261/// This means you can build path into each child field of a struct/enum,
262/// but if the type in a child field is itself a struct/enum, you won't be able
263/// to continue your path into a grandchild field.
264/// ```compile_fail
265/// # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
266/// #[derive(Default, Trackable)]
267/// struct MyStruct {
268/// field_1: ChildStruct,
269/// field_2: u64
270/// }
271/// #[derive(Default)]
272/// struct ChildStruct {
273/// asdf: String
274/// }
275///
276/// let store = Store::new(MyStruct::default());
277///
278/// store.build_path().field_1(); // OK
279/// store.build_path().field_1().asdf(); // cannot do this!!
280/// ```
281///
282/// To allow building paths deep into descendants, enable deep tracking
283/// ```
284/// # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
285/// #[derive(Default, Trackable)]
286/// struct MyStruct {
287/// #[track(deep)] // 👈 enable deep tracking
288/// field_1: ChildStruct,
289/// field_2: u64
290/// }
291/// #[derive(Default, Trackable /* 👈 the subject of deep tracking must be Trackable */)]
292/// struct ChildStruct {
293/// asdf: String
294/// }
295///
296/// let store = Store::new(MyStruct::default());
297///
298/// store.build_path().field_1(); // OK
299/// store.build_path().field_1().asdf(); // OK
300/// ```
301///
302/// Often, you will end up with `#[track(deep)]` on most (if not all) your
303/// fields. In this case, you can apply the attribute on the struct/enum itself.
304/// The attribute `#[track(shallow)]` can then be applied on individual fields
305/// to opt out of deep tracking.
306/// ```
307/// # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
308/// #[derive(Trackable)]
309/// #[track(deep)] // 👈 enable deep tracking
310/// struct MyStruct {
311/// field_1: ChildStruct,
312/// #[track(shallow)] // 👈 don't track this field deeply
313/// field_2: u64
314/// }
315/// # #[derive(Trackable)]
316/// # struct ChildStruct {
317/// # asdf: String
318/// # }
319/// ```
320///
321/// ### Tracking Enums
322///
323/// As implied along this doc, the `Trackable` derive macro works on enums too.
324/// ```
325/// # use x_bow::{Trackable, Store, PathExt, PathExtGuaranteed};
326/// #[derive(Trackable)]
327/// enum MyEnum {
328/// Variant1 (String),
329/// Variant2 {
330/// field_a: i32,
331/// field_b: u64
332/// }
333/// }
334/// let store = Store::new(MyEnum::Variant1 ("Hello".into()));
335///
336/// let path = store.build_path().Variant1_0(); // <- the `String` in Variant1
337/// let path = store.build_path().Variant2_field_a(); // <- the `i32` in Variant2
338/// ```
339pub use x_bow_macros::Trackable;
340
341// TODO: figure out how to make Rust-Analyzer stop suggesting items from this module
342#[doc(hidden)]
343pub mod __private_macro_only {
344 #[doc(hidden)]
345 pub use super::guarantee::PathExtGuaranteed;
346 #[doc(hidden)]
347 pub use super::hash_visitor::HashVisitor;
348 #[doc(hidden)]
349 pub use super::impls::leaf::TrackableLeaf;
350 #[doc(hidden)]
351 pub use super::path::Path;
352 #[doc(hidden)]
353 pub use super::trackable::IntoPath;
354 #[doc(hidden)]
355 pub use super::trackable::Trackable;
356 #[doc(hidden)]
357 pub use super::wakers::StoreWakers;
358 #[doc(hidden)]
359 pub use x_bow_macros::IntoPath;
360}