Boxed and mapped signals preparing for stores (#4413)
This PR contains just the breaking changes required for the upcoming store implementation. It adds two new primitives: Mutable mapped signals (without a different subscriber list). This lets you scope a writable value and pass it to a child component. It requires two closures to map the immutable and mutable references to the inner signal. With stores, these closures will be derived Boxed readable and writable values (ReadSignal, and WriteSignal). Any readable or writable types can be converted to a read signal or write signal with the boxed method or built in types are automatically converted into the boxed variants when passing into props * 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 * remove stores * writable is child owned * fix typo * fix clippy * fix doc tests
This commit is contained in:
1 parent
6919f72abc
commit
d8d11db403
64 files changed
+1303
-722
No files matched your search
+1
-1
@@ -129,7 +129,7 @@ members = [
|
||||
"packages/playwright-tests/nested-suspense",
|
||||
"packages/playwright-tests/cli-optimization",
|
||||
"packages/playwright-tests/wasm-split-harness",
|
||||
"packages/playwright-tests/default-features-disabled",
|
||||
"packages/playwright-tests/default-features-disabled"
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
||||
@@ -36,7 +36,7 @@ impl FromStr for Size {
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn product_page(product_id: ReadOnlySignal<usize>) -> Element {
|
||||
pub fn product_page(product_id: ReadSignal<usize>) -> Element {
|
||||
let mut quantity = use_signal(|| 1);
|
||||
let mut size = use_signal(Size::default);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn App() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Homepage(story: ReadOnlySignal<PreviewState>) -> Element {
|
||||
fn Homepage(story: ReadSignal<PreviewState>) -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: asset!("/assets/hackernews.css") }
|
||||
div { display: "flex", flex_direction: "row", width: "100%",
|
||||
@@ -84,7 +84,7 @@ fn Stories() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn StoryListing(story: ReadOnlySignal<i64>) -> Element {
|
||||
fn StoryListing(story: ReadSignal<i64>) -> Element {
|
||||
let story = use_server_future(move || get_story(story()))?;
|
||||
|
||||
let StoryItem {
|
||||
@@ -167,7 +167,7 @@ impl Display for PreviewState {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Preview(story: ReadOnlySignal<PreviewState>) -> Element {
|
||||
fn Preview(story: ReadSignal<PreviewState>) -> Element {
|
||||
let PreviewState {
|
||||
active_story: Some(id),
|
||||
} = story()
|
||||
|
||||
@@ -36,7 +36,7 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(count: Signal<i32>) -> Element {
|
||||
fn Child(count: WriteSignal<i32>) -> Element {
|
||||
let mut early_return = use_signal(|| false);
|
||||
|
||||
let early = rsx! {
|
||||
|
||||
@@ -75,7 +75,7 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ColorControl(label: &'static str, color_str: Signal<String>) -> Element {
|
||||
fn ColorControl(label: &'static str, color_str: WriteSignal<String>) -> Element {
|
||||
rsx!(div {
|
||||
class: "color-control",
|
||||
{ label },
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BreedPic(breed: Signal<String>) -> Element {
|
||||
fn BreedPic(breed: WriteSignal<String>) -> Element {
|
||||
// This resource will restart whenever the breed changes
|
||||
let mut fut = use_resource(move || async move {
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
|
||||
@@ -81,7 +81,7 @@ impl FromStr for State {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Home(url_hash: ReadOnlySignal<State>) -> Element {
|
||||
fn Home(url_hash: ReadSignal<State>) -> Element {
|
||||
// The initial state of the state comes from the url hash
|
||||
let mut state = use_signal(&*url_hash);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(state: Memo<isize>, items: Memo<Vec<isize>>, depth: ReadOnlySignal<usize>) -> Element {
|
||||
fn Child(state: Memo<isize>, items: Memo<Vec<isize>>, depth: ReadSignal<usize>) -> Element {
|
||||
// These memos don't get re-computed when early returns happen
|
||||
let state = use_memo(move || state() + 1);
|
||||
let item = use_memo(move || items()[depth() - 1]);
|
||||
|
||||
@@ -63,9 +63,9 @@ fn Home() -> Element {
|
||||
}
|
||||
}
|
||||
|
||||
// Instead of accepting String and usize directly, we use ReadOnlySignal to make the parameters `Copy` and let us subscribe to them automatically inside the meme
|
||||
// Instead of accepting String and usize directly, we use ReadSignal to make the parameters `Copy` and let us subscribe to them automatically inside the meme
|
||||
#[component]
|
||||
fn Search(query: ReadOnlySignal<String>, word_count: ReadOnlySignal<usize>) -> Element {
|
||||
fn Search(query: ReadSignal<String>, word_count: ReadSignal<usize>) -> Element {
|
||||
const ITEMS: &[&str] = &[
|
||||
"hello",
|
||||
"world",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Example: Updating components with use_resource
|
||||
//! -----------------
|
||||
//!
|
||||
//! This example shows how to use ReadOnlySignal to make props reactive
|
||||
//! This example shows how to use ReadSignal to make props reactive
|
||||
//! when linking to it from the same component, when using use_resource
|
||||
|
||||
use dioxus::prelude::*;
|
||||
@@ -25,15 +25,15 @@ fn App() -> Element {
|
||||
}
|
||||
}
|
||||
|
||||
// We use id: ReadOnlySignal<i32> instead of id: i32 to make id work with reactive hooks
|
||||
// Any i32 we pass in will automatically be converted into a ReadOnlySignal<i32>
|
||||
// We use id: ReadSignal<i32> instead of id: i32 to make id work with reactive hooks
|
||||
// Any i32 we pass in will automatically be converted into a ReadSignal<i32>
|
||||
#[component]
|
||||
fn Blog(id: ReadOnlySignal<i32>) -> Element {
|
||||
fn Blog(id: ReadSignal<i32>) -> Element {
|
||||
async fn future(n: i32) -> i32 {
|
||||
n
|
||||
}
|
||||
|
||||
// Because we accept ReadOnlySignal<i32> instead of i32, the resource will automatically subscribe to the id when we read it
|
||||
// Because we accept ReadSignal<i32> instead of i32, the resource will automatically subscribe to the id when we read it
|
||||
let res = use_resource(move || future(id()));
|
||||
|
||||
match res() {
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
//! This simple example implements a counter that can be incremented, decremented, and paused. It also demonstrates
|
||||
//! that background tasks in use_futures can modify the value as well.
|
||||
//!
|
||||
//! Most signals implement Into<ReadOnlySignal<T>>, making ReadOnlySignal a good default type when building new
|
||||
//! Most signals implement Into<ReadSignal<T>>, making ReadSignal a good default type when building new
|
||||
//! library components that don't need to modify their values.
|
||||
|
||||
use async_std::task::sleep;
|
||||
@@ -81,7 +81,7 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(mut count: ReadOnlySignal<i32>) -> Element {
|
||||
fn Child(mut count: ReadSignal<i32>) -> Element {
|
||||
println!("rendering child with count {count}");
|
||||
|
||||
rsx! {
|
||||
|
||||
+5
-5
@@ -110,7 +110,7 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TodoHeader(mut todos: Signal<HashMap<u32, TodoItem>>) -> Element {
|
||||
fn TodoHeader(mut todos: WriteSignal<HashMap<u32, TodoItem>>) -> Element {
|
||||
let mut draft = use_signal(|| "".to_string());
|
||||
let mut todo_id = use_signal(|| 0);
|
||||
|
||||
@@ -146,7 +146,7 @@ fn TodoHeader(mut todos: Signal<HashMap<u32, TodoItem>>) -> Element {
|
||||
/// This takes the ID of the todo and the todos signal as props
|
||||
/// We can use these together to memoize the todo contents and checked state
|
||||
#[component]
|
||||
fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
|
||||
fn TodoEntry(mut todos: WriteSignal<HashMap<u32, TodoItem>>, id: u32) -> Element {
|
||||
let mut is_editing = use_signal(|| false);
|
||||
|
||||
// To avoid re-rendering this component when the todo list changes, we isolate our reads to memos
|
||||
@@ -208,9 +208,9 @@ fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
|
||||
|
||||
#[component]
|
||||
fn ListFooter(
|
||||
mut todos: Signal<HashMap<u32, TodoItem>>,
|
||||
active_todo_count: ReadOnlySignal<usize>,
|
||||
mut filter: Signal<FilterState>,
|
||||
mut todos: WriteSignal<HashMap<u32, TodoItem>>,
|
||||
active_todo_count: ReadSignal<usize>,
|
||||
mut filter: WriteSignal<FilterState>,
|
||||
) -> Element {
|
||||
// We use a memoized signal to calculate whether we should show the "Clear completed" button.
|
||||
// This will recompute whenever the todos change, and if the value is true, the button will be shown.
|
||||
|
||||
@@ -108,7 +108,7 @@ fn Forecast(weather: WeatherResponse) -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn SearchBox(mut country: Signal<WeatherLocation>) -> Element {
|
||||
fn SearchBox(mut country: WriteSignal<WeatherLocation>) -> Element {
|
||||
let mut input = use_signal(|| "".to_string());
|
||||
|
||||
let locations = use_resource(move || async move { get_locations(&input()).await });
|
||||
|
||||
@@ -87,7 +87,7 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ColorControl(label: &'static str, color_str: Signal<String>) -> Element {
|
||||
fn ColorControl(label: &'static str, color_str: WriteSignal<String>) -> Element {
|
||||
rsx!(div {
|
||||
class: "color-control",
|
||||
{ label },
|
||||
|
||||
@@ -51,7 +51,7 @@ You can use the `#[props()]` attribute to modify the behavior of the props the c
|
||||
Props also act slightly differently when used with:
|
||||
|
||||
- [`Option<T>`](#optional-props) - The field is automatically optional with a default value of `None`.
|
||||
- [`ReadOnlySignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadOnlySignal<T>` when it is passed as a prop.
|
||||
- [`ReadSignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadSignal<T>` when it is passed as a prop.
|
||||
- [`String`](#formatted-props) - The props macro will accept formatted strings for any prop field with the type `String`.
|
||||
- [`children`](#children-props) - The props macro will accept child elements if you include the `children` prop.
|
||||
|
||||
@@ -257,14 +257,14 @@ fn Counter(count: i32) -> Element {
|
||||
|
||||
To fix this issue you can either:
|
||||
|
||||
1. Make the prop reactive by wrapping it in `ReadOnlySignal` (recommended):
|
||||
1. Make the prop reactive by wrapping it in `ReadSignal` (recommended):
|
||||
|
||||
`ReadOnlySignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadOnlySignal` when it is passed as a prop.
|
||||
`ReadSignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadSignal` when it is passed as a prop.
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
#[component]
|
||||
fn Counter(count: ReadOnlySignal<i32>) -> Element {
|
||||
fn Counter(count: ReadSignal<i32>) -> Element {
|
||||
// Since we made count reactive, the memo will automatically rerun when count changes.
|
||||
let doubled_count = use_memo(move || count() * 2);
|
||||
rsx! {
|
||||
|
||||
@@ -47,7 +47,7 @@ You can use the `#[props()]` attribute to modify the behavior of the props deriv
|
||||
Props also act slightly differently when used with:
|
||||
|
||||
- [`Option<T>`](#optional-props) - The field is automatically optional with a default value of `None`.
|
||||
- [`ReadOnlySignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadOnlySignal<T>` when it is passed as a prop.
|
||||
- [`ReadSignal<T>`](#reactive-props) - The props macro will automatically convert `T` into `ReadSignal<T>` when it is passed as a prop.
|
||||
- [`String`](#formatted-props) - The props macro will accept formatted strings for any prop field with the type `String`.
|
||||
- [`children`](#children-props) - The props macro will accept child elements if you include the `children` prop.
|
||||
|
||||
@@ -270,14 +270,14 @@ fn Counter(count: i32) -> Element {
|
||||
|
||||
To fix this issue you can either:
|
||||
|
||||
1. Make the prop reactive by wrapping it in `ReadOnlySignal` (recommended):
|
||||
1. Make the prop reactive by wrapping it in `ReadSignal` (recommended):
|
||||
|
||||
`ReadOnlySignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadOnlySignal` when it is passed as a prop.
|
||||
`ReadSignal` is a `Copy` reactive value. Dioxus will automatically convert any value into a `ReadSignal` when it is passed as a prop.
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
#[component]
|
||||
fn Counter(count: ReadOnlySignal<i32>) -> Element {
|
||||
fn Counter(count: ReadSignal<i32>) -> Element {
|
||||
// Since we made count reactive, the memo will automatically rerun when count changes.
|
||||
let doubled_count = use_memo(move || count() * 2);
|
||||
rsx! {
|
||||
|
||||
@@ -172,7 +172,7 @@ mod util {
|
||||
}
|
||||
|
||||
mod field_info {
|
||||
use crate::props::type_from_inside_option;
|
||||
use crate::props::{looks_like_write_type, type_from_inside_option};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::spanned::Spanned;
|
||||
@@ -221,6 +221,11 @@ mod field_info {
|
||||
builder_attr.auto_into = false;
|
||||
}
|
||||
|
||||
// Write fields automatically use impl Into
|
||||
if looks_like_write_type(&field.ty) {
|
||||
builder_attr.auto_into = true;
|
||||
}
|
||||
|
||||
// extended field is automatically empty
|
||||
if !builder_attr.extends.is_empty() {
|
||||
builder_attr.default = Some(
|
||||
@@ -503,9 +508,9 @@ fn type_from_inside_option(ty: &Type) -> Option<&Type> {
|
||||
let seg = path.segments.last()?;
|
||||
|
||||
// If the segment is a supported optional type, provide the inner type.
|
||||
// Return the inner type if the pattern is `Option<T>` or `ReadOnlySignal<Option<T>>``
|
||||
if seg.ident == "ReadOnlySignal" {
|
||||
// Get the inner type. E.g. the `u16` in `ReadOnlySignal<u16>` or `Option` in `ReadOnlySignal<Option<bool>>`
|
||||
// Return the inner type if the pattern is `Option<T>` or `ReadSignal<Option<T>>``
|
||||
if seg.ident == "ReadOnlySignal" || seg.ident == "ReadSignal" {
|
||||
// Get the inner type. E.g. the `u16` in `ReadSignal<u16>` or `Option` in `ReadSignal<Option<bool>>`
|
||||
let inner_type = extract_inner_type_from_segment(seg)?;
|
||||
let Type::Path(inner_path) = inner_type else {
|
||||
// If it isn't a path, the inner type isn't option
|
||||
@@ -614,13 +619,13 @@ mod struct_info {
|
||||
generics
|
||||
}
|
||||
|
||||
/// Checks if the props have any fields that should be owned by the child. For example, when converting T to `ReadOnlySignal<T>`, the new signal should be owned by the child
|
||||
/// Checks if the props have any fields that should be owned by the child. For example, when converting T to `ReadSignal<T>`, the new signal should be owned by the child
|
||||
fn has_child_owned_fields(&self) -> bool {
|
||||
self.fields.iter().any(|f| child_owned_type(f.ty))
|
||||
}
|
||||
|
||||
fn memoize_impl(&self) -> Result<TokenStream, Error> {
|
||||
// First check if there are any ReadOnlySignal fields, if there are not, we can just use the partialEq impl
|
||||
// First check if there are any ReadSignal fields, if there are not, we can just use the partialEq impl
|
||||
let signal_fields: Vec<_> = self
|
||||
.included_fields()
|
||||
.filter(|f| looks_like_signal_type(f.ty))
|
||||
@@ -1736,7 +1741,7 @@ fn remove_option_wrapper(type_: Type) -> Type {
|
||||
|
||||
/// Check if a type should be owned by the child component after conversion
|
||||
fn child_owned_type(ty: &Type) -> bool {
|
||||
looks_like_signal_type(ty) || looks_like_callback_type(ty)
|
||||
looks_like_signal_type(ty) || looks_like_write_type(ty) || looks_like_callback_type(ty)
|
||||
}
|
||||
|
||||
fn looks_like_signal_type(ty: &Type) -> bool {
|
||||
@@ -1745,6 +1750,20 @@ fn looks_like_signal_type(ty: &Type) -> bool {
|
||||
path_without_generics == parse_quote!(dioxus_core::ReadOnlySignal)
|
||||
|| path_without_generics == parse_quote!(prelude::ReadOnlySignal)
|
||||
|| path_without_generics == parse_quote!(ReadOnlySignal)
|
||||
|| path_without_generics == parse_quote!(dioxus_core::prelude::ReadSignal)
|
||||
|| path_without_generics == parse_quote!(prelude::ReadSignal)
|
||||
|| path_without_generics == parse_quote!(ReadSignal)
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn looks_like_write_type(ty: &Type) -> bool {
|
||||
match extract_base_type_without_generics(ty) {
|
||||
Some(path_without_generics) => {
|
||||
path_without_generics == parse_quote!(dioxus_core::prelude::WriteSignal)
|
||||
|| path_without_generics == parse_quote!(prelude::WriteSignal)
|
||||
|| path_without_generics == parse_quote!(WriteSignal)
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
@@ -1778,6 +1797,17 @@ fn test_looks_like_type() {
|
||||
ReadOnlySignal<Option<i32>, UnsyncStorage>
|
||||
)));
|
||||
|
||||
assert!(!looks_like_signal_type(&parse_quote!(
|
||||
Option<ReadSignal<i32>>
|
||||
)));
|
||||
assert!(looks_like_signal_type(&parse_quote!(ReadSignal<i32>)));
|
||||
assert!(looks_like_signal_type(
|
||||
&parse_quote!(ReadSignal<i32, SyncStorage>)
|
||||
));
|
||||
assert!(looks_like_signal_type(&parse_quote!(
|
||||
ReadSignal<Option<i32>, UnsyncStorage>
|
||||
)));
|
||||
|
||||
assert!(looks_like_callback_type(&parse_quote!(
|
||||
Option<EventHandler>
|
||||
)));
|
||||
|
||||
@@ -52,13 +52,13 @@ mod test_default_into {
|
||||
|
||||
// Test default values for signals
|
||||
#[props(default)]
|
||||
read_only_w_default: ReadOnlySignal<bool>,
|
||||
read_only_w_default: ReadSignal<bool>,
|
||||
|
||||
#[props(default = true)]
|
||||
read_only_w_default_val: ReadOnlySignal<bool>,
|
||||
read_only_w_default_val: ReadSignal<bool>,
|
||||
|
||||
#[props(default = ReadOnlySignal::new(Signal::new(true)))]
|
||||
read_only_w_default_val_explicit: ReadOnlySignal<bool>,
|
||||
#[props(default = ReadSignal::new(Signal::new(true)))]
|
||||
read_only_w_default_val_explicit: ReadSignal<bool>,
|
||||
|
||||
// Test default values for callbacks/event handlers
|
||||
#[props(default)]
|
||||
@@ -108,7 +108,7 @@ mod test_optional_signals {
|
||||
fn UsesComponents() -> Element {
|
||||
rsx! {
|
||||
PropsStruct {
|
||||
regular_read_signal: ReadOnlySignal::new(Signal::new(1234)),
|
||||
regular_read_signal: ReadSignal::new(Signal::new(1234)),
|
||||
}
|
||||
PropsStruct {
|
||||
optional_read_signal: 1234,
|
||||
@@ -126,8 +126,8 @@ mod test_optional_signals {
|
||||
// Test props as struct param.
|
||||
#[derive(Props, Clone, PartialEq)]
|
||||
struct MyTestProps {
|
||||
pub optional_read_signal: ReadOnlySignal<Option<u16>>,
|
||||
pub regular_read_signal: ReadOnlySignal<u16>,
|
||||
pub optional_read_signal: ReadSignal<Option<u16>>,
|
||||
pub regular_read_signal: ReadSignal<u16>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
@@ -137,7 +137,7 @@ mod test_optional_signals {
|
||||
|
||||
// Test props as params.
|
||||
#[component]
|
||||
fn PropParams(opt_read_sig: ReadOnlySignal<Option<u16>>) -> Element {
|
||||
fn PropParams(opt_read_sig: ReadSignal<Option<u16>>) -> Element {
|
||||
rsx! { "hi!" }
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ fn TakesEventHandler(click: EventHandler<usize>, number: usize) -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TakesSignal(sig: ReadOnlySignal<usize>, number: usize) -> Element {
|
||||
fn TakesSignal(sig: ReadSignal<usize>, number: usize) -> Element {
|
||||
let first_render_sig = use_hook(move || sig);
|
||||
if generation() > 0 {
|
||||
// Make sure the signal is memoized in place and never gets dropped
|
||||
@@ -190,12 +190,12 @@ fn cloning_read_only_signal_components_work() {
|
||||
struct NonCloneable<T>(T);
|
||||
|
||||
#[component]
|
||||
fn TakesReadOnlySignalNum(sig: ReadOnlySignal<i32>) -> Element {
|
||||
fn TakesReadOnlySignalNum(sig: ReadSignal<i32>) -> Element {
|
||||
rsx! {}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TakesReadOnlySignalNonClone(sig: ReadOnlySignal<NonCloneable<i32>>) -> Element {
|
||||
fn TakesReadOnlySignalNonClone(sig: ReadSignal<NonCloneable<i32>>) -> Element {
|
||||
rsx! {}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,13 +74,13 @@ fn MyComponent(string: String) -> Element {
|
||||
|
||||
You can fix this issue by either:
|
||||
|
||||
- Making your data `Copy` with `ReadOnlySignal`:
|
||||
- Making your data `Copy` with `ReadSignal`:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// `MyComponent` accepts `ReadOnlySignal<String>` which implements `Copy`
|
||||
// `MyComponent` accepts `ReadSignal<String>` which implements `Copy`
|
||||
#[component]
|
||||
fn MyComponent(string: ReadOnlySignal<String>) -> Element {
|
||||
fn MyComponent(string: ReadSignal<String>) -> Element {
|
||||
use_hook(move || {
|
||||
// ✅ Because the `string` signal is `Copy`, we can copy it into the async task while still having access to it elsewhere
|
||||
spawn(async move {
|
||||
|
||||
@@ -92,9 +92,9 @@ fn MyComponent(state: i32) -> Element {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ✅ Wrap your props in ReadOnlySignal to make them reactive
|
||||
// ✅ Wrap your props in ReadSignal to make them reactive
|
||||
#[component]
|
||||
fn MyReactiveComponent(state: ReadOnlySignal<i32>) -> Element {
|
||||
fn MyReactiveComponent(state: ReadSignal<i32>) -> Element {
|
||||
let doubled = use_memo(move || state() * 2);
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -730,7 +730,7 @@ impl<
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Counter(multiplier: ReadOnlySignal<String>) -> Element {
|
||||
/// fn Counter(multiplier: ReadSignal<String>) -> Element {
|
||||
/// let multiplier_parsed = multiplier().parse::<usize>()?;
|
||||
/// let mut count = use_signal(|| multiplier_parsed);
|
||||
/// rsx! {
|
||||
@@ -783,7 +783,7 @@ impl<
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Counter(multiplier: ReadOnlySignal<String>) -> Element {
|
||||
/// fn Counter(multiplier: ReadSignal<String>) -> Element {
|
||||
/// let multiplier_parsed = multiplier().parse::<usize>()?;
|
||||
/// let mut count = use_signal(|| multiplier_parsed);
|
||||
/// rsx! {
|
||||
|
||||
@@ -87,10 +87,10 @@ pub use crate::innerlude::{
|
||||
Element, ElementId, ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes,
|
||||
IntoAttributeValue, IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation,
|
||||
Mutations, NoOpMutations, Ok, OptionStringFromMarker, Properties, ReactiveContext, RenderError,
|
||||
Result, Runtime, RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SuperFrom, SuperInto,
|
||||
SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps, SuspenseContext, SuspenseExtension,
|
||||
Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner, VPlaceholder,
|
||||
VText, VirtualDom, WriteMutations,
|
||||
Result, Runtime, RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers,
|
||||
SuperFrom, SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps,
|
||||
SuspenseContext, SuspenseExtension, Task, Template, TemplateAttribute, TemplateNode,
|
||||
VComponent, VNode, VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
|
||||
};
|
||||
|
||||
pub use const_format;
|
||||
@@ -141,7 +141,7 @@ impl ReactiveContext {
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let old_subscribers = std::mem::take(&mut self.inner.write().subscribers);
|
||||
for subscriber in old_subscribers {
|
||||
subscriber.0.lock().unwrap().remove(self);
|
||||
subscriber.0.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ impl ReactiveContext {
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let subscribers = &self.inner.read().subscribers;
|
||||
for subscriber in subscribers.iter() {
|
||||
subscriber.0.lock().unwrap().insert(*self);
|
||||
subscriber.0.add(*self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,11 +238,14 @@ impl ReactiveContext {
|
||||
}
|
||||
|
||||
/// Subscribe to this context. The reactive context will automatically remove itself from the subscriptions when it is reset.
|
||||
pub fn subscribe(&self, subscriptions: Arc<Mutex<HashSet<ReactiveContext>>>) {
|
||||
pub fn subscribe(&self, subscriptions: impl Into<Subscribers>) {
|
||||
match self.inner.try_write() {
|
||||
Ok(mut inner) => {
|
||||
subscriptions.lock().unwrap().insert(*self);
|
||||
inner.subscribers.insert(PointerHash(subscriptions));
|
||||
let subscriptions = subscriptions.into();
|
||||
subscriptions.add(*self);
|
||||
inner
|
||||
.subscribers
|
||||
.insert(PointerHash(subscriptions.inner.clone()));
|
||||
}
|
||||
// If the context was dropped, we don't need to subscribe to it anymore
|
||||
Err(BorrowMutError::Dropped(_)) => {}
|
||||
@@ -266,30 +269,28 @@ impl Hash for ReactiveContext {
|
||||
}
|
||||
}
|
||||
|
||||
struct PointerHash<T>(Arc<T>);
|
||||
struct PointerHash<T: ?Sized>(Arc<T>);
|
||||
|
||||
impl<T> Hash for PointerHash<T> {
|
||||
impl<T: ?Sized> Hash for PointerHash<T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
std::sync::Arc::<T>::as_ptr(&self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for PointerHash<T> {
|
||||
impl<T: ?Sized> PartialEq for PointerHash<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::sync::Arc::<T>::as_ptr(&self.0) == std::sync::Arc::<T>::as_ptr(&other.0)
|
||||
std::sync::Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for PointerHash<T> {}
|
||||
impl<T: ?Sized> Eq for PointerHash<T> {}
|
||||
|
||||
impl<T> Clone for PointerHash<T> {
|
||||
impl<T: ?Sized> Clone for PointerHash<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
type SubscriberMap = Mutex<HashSet<ReactiveContext>>;
|
||||
|
||||
struct Inner {
|
||||
self_: Option<ReactiveContext>,
|
||||
|
||||
@@ -297,7 +298,7 @@ struct Inner {
|
||||
update: Box<dyn FnMut() + Send + Sync>,
|
||||
|
||||
// Subscribers to this context
|
||||
subscribers: HashSet<PointerHash<SubscriberMap>>,
|
||||
subscribers: HashSet<PointerHash<dyn SubscriberList + Send + Sync>>,
|
||||
|
||||
// Debug information for signal subscriptions
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -315,9 +316,88 @@ impl Drop for Inner {
|
||||
};
|
||||
|
||||
for subscriber in std::mem::take(&mut self.subscribers) {
|
||||
if let Ok(mut subscriber) = subscriber.0.lock() {
|
||||
subscriber.remove(&self_);
|
||||
subscriber.0.remove(&self_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of [ReactiveContext]s that are subscribed. This is used to notify subscribers when the value changes.
|
||||
#[derive(Clone)]
|
||||
pub struct Subscribers {
|
||||
/// The list of subscribers.
|
||||
pub(crate) inner: Arc<dyn SubscriberList + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Default for Subscribers {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Subscribers {
|
||||
/// Create a new list of subscribers.
|
||||
pub fn new() -> Self {
|
||||
Subscribers {
|
||||
inner: Arc::new(Mutex::new(HashSet::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a subscriber to the list.
|
||||
pub fn add(&self, subscriber: ReactiveContext) {
|
||||
self.inner.add(subscriber);
|
||||
}
|
||||
|
||||
/// Remove a subscriber from the list.
|
||||
pub fn remove(&self, subscriber: &ReactiveContext) {
|
||||
self.inner.remove(subscriber);
|
||||
}
|
||||
|
||||
/// Visit all subscribers in the list.
|
||||
pub fn visit(&self, mut f: impl FnMut(&ReactiveContext)) {
|
||||
self.inner.visit(&mut f);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: SubscriberList + Send + Sync + 'static> From<Arc<S>> for Subscribers {
|
||||
fn from(inner: Arc<S>) -> Self {
|
||||
Subscribers { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of subscribers that can be notified when the value changes. This is used to track when the value changes and notify subscribers.
|
||||
pub trait SubscriberList: Send + Sync {
|
||||
/// Add a subscriber to the list.
|
||||
fn add(&self, subscriber: ReactiveContext);
|
||||
|
||||
/// Remove a subscriber from the list.
|
||||
fn remove(&self, subscriber: &ReactiveContext);
|
||||
|
||||
/// Visit all subscribers in the list.
|
||||
fn visit(&self, f: &mut dyn FnMut(&ReactiveContext));
|
||||
}
|
||||
|
||||
impl SubscriberList for Mutex<HashSet<ReactiveContext>> {
|
||||
fn add(&self, subscriber: ReactiveContext) {
|
||||
if let Ok(mut lock) = self.lock() {
|
||||
lock.insert(subscriber);
|
||||
} else {
|
||||
tracing::warn!("Failed to lock subscriber list to add subscriber: {subscriber}");
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, subscriber: &ReactiveContext) {
|
||||
if let Ok(mut lock) = self.lock() {
|
||||
lock.remove(subscriber);
|
||||
} else {
|
||||
tracing::warn!("Failed to lock subscriber list to remove subscriber: {subscriber}");
|
||||
}
|
||||
}
|
||||
|
||||
fn visit(&self, f: &mut dyn FnMut(&ReactiveContext)) {
|
||||
if let Ok(lock) = self.lock() {
|
||||
lock.iter().for_each(f);
|
||||
} else {
|
||||
tracing::warn!("Failed to lock subscriber list to visit subscribers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn ChildComponent(signal: Signal<Option<i32>>) -> Element {
|
||||
//! fn ChildComponent(signal: WriteSignal<Option<i32>>) -> Element {
|
||||
//! // It feels safe to assume that signal is some because the parent component checked that it was some
|
||||
//! rsx! { "{signal.read().unwrap()}" }
|
||||
//! }
|
||||
|
||||
@@ -44,7 +44,7 @@ async fn test_for_memory_leaks() {
|
||||
#[component]
|
||||
fn AcceptsEventHandlerAndReadOnlySignal(
|
||||
event_handler: EventHandler<MouseEvent>,
|
||||
signal: ReadOnlySignal<i32>,
|
||||
signal: ReadSignal<i32>,
|
||||
) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use dioxus_core::internal::HotReloadedTemplate;
|
||||
use dioxus_core::{ScopeId, VirtualDom};
|
||||
use dioxus_signals::{GlobalKey, Signal, Writable};
|
||||
use dioxus_signals::{GlobalKey, Signal, WritableExt};
|
||||
|
||||
pub use dioxus_devtools_types::*;
|
||||
pub use subsecond;
|
||||
|
||||
@@ -32,6 +32,7 @@ Dioxus is crossplatform app framework that empowers developer to build beautiful
|
||||
## Quick start
|
||||
|
||||
To get started with Dioxus, you'll want to grab the dioxus-cli tool: `dx`. We distribute `dx` with `cargo-binstall` - if you already have binstall skip this step.
|
||||
|
||||
```shell
|
||||
# skip if you already have cargo-binstall
|
||||
cargo install cargo-binstall
|
||||
@@ -123,7 +124,7 @@ fn App() -> Element {
|
||||
|
||||
We can compose these function components to build a complex app. Each new component takes some Properties. For components with no explicit properties, we can omit the type altogether.
|
||||
|
||||
In Dioxus, all properties are memoized by default with `Clone` and `PartialEq`. For props you can't clone, simply wrap the fields in a [`ReadOnlySignal`](dioxus_signals::ReadOnlySignal) and Dioxus will handle converting types for you.
|
||||
In Dioxus, all properties are memoized by default with `Clone` and `PartialEq`. For props you can't clone, simply wrap the fields in a [`ReadSignal`](dioxus_signals::ReadSignal) and Dioxus will handle converting types for you.
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use dioxus_core::{suspend, use_hook, RenderError};
|
||||
use dioxus_hooks::*;
|
||||
use dioxus_signals::Readable;
|
||||
use dioxus_signals::ReadableExt;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::future::Future;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use dioxus_core::try_consume_context;
|
||||
use dioxus_signals::{Readable, Signal, Writable};
|
||||
use dioxus_signals::{ReadableExt, Signal, WritableExt};
|
||||
|
||||
/// The status of the streaming response
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
||||
@@ -111,7 +111,7 @@ println!("{:?}", double_count.read()); // Prints "Some(8)"
|
||||
|
||||
## With non-reactive dependencies
|
||||
|
||||
`use_resource` can determine dependencies automatically with any reactive value ([`Signal`]s, [`ReadOnlySignal`]s, [`Memo`]s, [`Resource`]s, etc). If you need to rerun the future when a normal rust value changes, you can add it as a dependency with the [`crate::use_reactive()`] hook:
|
||||
`use_resource` can determine dependencies automatically with any reactive value ([`Signal`]s, [`ReadSignal`]s, [`Memo`]s, [`Resource`]s, etc). If you need to rerun the future when a normal rust value changes, you can add it as a dependency with the [`crate::use_reactive()`] hook:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
@@ -128,9 +128,9 @@ fn Comp(count: u32) -> Element {
|
||||
}
|
||||
|
||||
// If your value is already reactive, you never need to call `use_reactive` manually
|
||||
// Instead of manually adding count to the dependencies list, you can make your prop reactive by wrapping it in `ReadOnlySignal`
|
||||
// Instead of manually adding count to the dependencies list, you can make your prop reactive by wrapping it in `ReadSignal`
|
||||
#[component]
|
||||
fn ReactiveComp(count: ReadOnlySignal<u32>) -> Element {
|
||||
fn ReactiveComp(count: ReadSignal<u32>) -> Element {
|
||||
// Because `count` is reactive, the resource knows to rerun when `count` changes automatically
|
||||
let new_count = use_resource(move || async move {
|
||||
sleep(100).await;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::{use_callback, use_hook_did_run, use_signal};
|
||||
use dioxus_core::{use_hook, Callback, Task};
|
||||
use dioxus_core::{use_hook, Callback, Subscribers, Task};
|
||||
use dioxus_signals::*;
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
@@ -152,12 +152,12 @@ impl UseFuture {
|
||||
}
|
||||
|
||||
/// Get the current state of the future.
|
||||
pub fn state(&self) -> ReadOnlySignal<UseFutureState> {
|
||||
pub fn state(&self) -> ReadSignal<UseFutureState> {
|
||||
self.state.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UseFuture> for ReadOnlySignal<UseFutureState> {
|
||||
impl From<UseFuture> for ReadSignal<UseFutureState> {
|
||||
fn from(val: UseFuture) -> Self {
|
||||
val.state.into()
|
||||
}
|
||||
@@ -180,6 +180,10 @@ impl Readable for UseFuture {
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
self.state.try_peek_unchecked()
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.state.subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow calling a signal with signal() syntax
|
||||
@@ -189,6 +193,6 @@ impl Deref for UseFuture {
|
||||
type Target = dyn Fn() -> UseFutureState;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { Readable::deref_impl(self) }
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use dioxus_core::{use_after_render, use_before_render, use_hook};
|
||||
use dioxus_signals::{CopyValue, Writable};
|
||||
use dioxus_signals::{CopyValue, WritableExt};
|
||||
|
||||
/// A hook that uses before/after lifecycle hooks to determine if the hook was run
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use dioxus_signals::{Readable, Writable};
|
||||
use dioxus_signals::{ReadableExt, WritableExt};
|
||||
|
||||
use crate::use_signal;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{use_callback, use_signal};
|
||||
|
||||
use dioxus_core::{
|
||||
spawn, use_hook, Callback, IntoAttributeValue, IntoDynNode, ReactiveContext, RenderError,
|
||||
SuspendedFuture, Task,
|
||||
Subscribers, SuspendedFuture, Task,
|
||||
};
|
||||
use dioxus_signals::*;
|
||||
use futures_util::{future, pin_mut, FutureExt, StreamExt};
|
||||
@@ -349,7 +349,7 @@ impl<T> Resource<T> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the current state of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current state of the resource or passed to other hooks and components.
|
||||
/// Get the current state of the resource's future. This method returns a [`ReadSignal`] which can be read to get the current state of the resource or passed to other hooks and components.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
@@ -379,11 +379,11 @@ impl<T> Resource<T> {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn state(&self) -> ReadOnlySignal<UseResourceState> {
|
||||
pub fn state(&self) -> ReadSignal<UseResourceState> {
|
||||
self.state.into()
|
||||
}
|
||||
|
||||
/// Get the current value of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current value of the resource or passed to other hooks and components.
|
||||
/// Get the current value of the resource's future. This method returns a [`ReadSignal`] which can be read to get the current value of the resource or passed to other hooks and components.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@@ -410,12 +410,12 @@ impl<T> Resource<T> {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn value(&self) -> ReadOnlySignal<Option<T>> {
|
||||
pub fn value(&self) -> ReadSignal<Option<T>> {
|
||||
self.value.into()
|
||||
}
|
||||
|
||||
/// Suspend the resource's future and only continue rendering when the future is ready
|
||||
pub fn suspend(&self) -> std::result::Result<MappedSignal<T>, RenderError> {
|
||||
pub fn suspend(&self) -> std::result::Result<MappedSignal<T, Signal<Option<T>>>, RenderError> {
|
||||
match self.state.cloned() {
|
||||
UseResourceState::Stopped | UseResourceState::Paused | UseResourceState::Pending => {
|
||||
let task = self.task();
|
||||
@@ -430,7 +430,7 @@ impl<T> Resource<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Resource<T>> for ReadOnlySignal<Option<T>> {
|
||||
impl<T> From<Resource<T>> for ReadSignal<Option<T>> {
|
||||
fn from(val: Resource<T>) -> Self {
|
||||
val.value.into()
|
||||
}
|
||||
@@ -453,6 +453,10 @@ impl<T> Readable for Resource<T> {
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
self.value.try_peek_unchecked()
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.value.subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoAttributeValue for Resource<T>
|
||||
@@ -480,6 +484,6 @@ impl<T: Clone> Deref for Resource<T> {
|
||||
type Target = dyn Fn() -> Option<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { Readable::deref_impl(self) }
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use dioxus_core::use_hook;
|
||||
use dioxus_signals::{ReadOnlySignal, SetCompare};
|
||||
use dioxus_signals::{ReadSignal, SetCompare};
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Creates a new SetCompare which efficiently tracks when a value changes to check if it is equal to a set of values.
|
||||
@@ -51,6 +51,6 @@ pub fn use_set_compare<R: Eq + Hash>(f: impl FnMut() -> R + 'static) -> SetCompa
|
||||
pub fn use_set_compare_equal<R: Eq + Hash>(
|
||||
value: R,
|
||||
mut compare: SetCompare<R>,
|
||||
) -> ReadOnlySignal<bool> {
|
||||
) -> ReadSignal<bool> {
|
||||
use_hook(move || compare.equal(value))
|
||||
}
|
||||
@@ -2,12 +2,12 @@ use std::cmp::Ordering;
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use crate::use_memo;
|
||||
use dioxus_signals::{ReadOnlySignal, Signal};
|
||||
use dioxus_signals::{ReadSignal, Signal};
|
||||
|
||||
pub fn use_sorted<V: 'static, T: PartialEq>(
|
||||
collection: impl FnMut() -> Signal<V>,
|
||||
) -> ReadOnlySignal<Vec<T>>
|
||||
// pub fn use_sorted<S, I, T>(iterable: impl FnMut() -> Signal<V>) -> ReadOnlySignal<T>
|
||||
) -> ReadSignal<Vec<T>>
|
||||
// pub fn use_sorted<S, I, T>(iterable: impl FnMut() -> Signal<V>) -> ReadSignal<T>
|
||||
// where
|
||||
// S: Into<MaybeSignal<I>>,
|
||||
// T: Ord,
|
||||
|
||||
@@ -81,13 +81,13 @@ fn MyComponent(string: String) -> Element {
|
||||
|
||||
You can fix this issue by either:
|
||||
|
||||
- Making your data `Copy` with `ReadOnlySignal`:
|
||||
- Making your data `Copy` with `ReadSignal`:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// `MyComponent` accepts `ReadOnlySignal<String>` which implements `Copy`
|
||||
// `MyComponent` accepts `ReadSignal<String>` which implements `Copy`
|
||||
#[component]
|
||||
fn MyComponent(string: ReadOnlySignal<String>) -> Element {
|
||||
fn MyComponent(string: ReadSignal<String>) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
// ✅ Because the `string` signal is `Copy`, we can copy it into the closure while still having access to it elsewhere
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use dioxus_core::Element;
|
||||
use dioxus_core_macro::rsx;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_signals::{GlobalSignal, Readable};
|
||||
use dioxus_signals::{GlobalSignal, ReadableExt};
|
||||
|
||||
#[allow(deprecated)]
|
||||
use crate::hooks::use_router;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use dioxus_core::{Element, VNode};
|
||||
use dioxus_core_macro::{rsx, Props};
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_signals::{GlobalSignal, Readable};
|
||||
use dioxus_signals::{GlobalSignal, ReadableExt};
|
||||
|
||||
use tracing::error;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use dioxus_core_macro::{rsx, Props};
|
||||
use dioxus_html::{
|
||||
self as dioxus_elements, ModifiersInteraction, MountedEvent, MouseEvent, PointerInteraction,
|
||||
};
|
||||
use dioxus_signals::{GlobalSignal, Owner, Readable};
|
||||
use dioxus_signals::{GlobalSignal, Owner, ReadableExt};
|
||||
|
||||
use tracing::error;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{provide_router_context, routable::Routable, router_cfg::RouterConfig, Outlet};
|
||||
use dioxus_core::{provide_context, use_hook, Callback, Element};
|
||||
use dioxus_core_macro::{rsx, Props};
|
||||
use dioxus_signals::{GlobalSignal, Owner, Readable};
|
||||
use dioxus_signals::{GlobalSignal, Owner, ReadableExt};
|
||||
|
||||
/// The props for [`Router`].
|
||||
#[derive(Props)]
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{
|
||||
|
||||
use dioxus_core::{provide_context, Element, ReactiveContext, ScopeId};
|
||||
use dioxus_history::history;
|
||||
use dioxus_signals::{CopyValue, Readable, Signal, Writable};
|
||||
use dioxus_signals::{CopyValue, ReadableExt, Signal, Writable};
|
||||
|
||||
use crate::{
|
||||
components::child_router::consume_child_route_mapping, navigation::NavigationTarget,
|
||||
|
||||
@@ -10,7 +10,7 @@ use dioxus_document::{
|
||||
Document, Eval, LinkProps, MetaProps, NoOpDocument, ScriptProps, StyleProps,
|
||||
};
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_signals::{GlobalSignal, Readable};
|
||||
use dioxus_signals::{GlobalSignal, ReadableExt};
|
||||
use dioxus_ssr::Renderer;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
@@ -119,7 +119,7 @@ fn App() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(signal: ReadOnlySignal<usize>) -> Element {
|
||||
fn Child(signal: ReadSignal<usize>) -> Element {
|
||||
rsx! {
|
||||
"{signal}"
|
||||
}
|
||||
|
||||
@@ -27,24 +27,16 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
for i in 0..vec.len() {
|
||||
Child { count: vec.map(move |v| &v[i]) }
|
||||
Child { count: vec.map_mut(move |v| &v[i], move |v| &mut v[i]) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(count: MappedSignal<i32>) -> Element {
|
||||
use_memo({
|
||||
to_owned![count];
|
||||
move || {
|
||||
let value = count.read();
|
||||
println!("Child value: {value}");
|
||||
}
|
||||
});
|
||||
|
||||
fn Child(count: WriteSignal<i32>) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
"Child: {count}"
|
||||
"{count}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Signals can degrade into a ReadOnlySignal variant automatically
|
||||
//! Signals can degrade into a Read variant automatically
|
||||
//! This is done thanks to a conversion by the #[component] macro
|
||||
|
||||
use dioxus::prelude::*;
|
||||
@@ -22,7 +22,7 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(count: ReadOnlySignal<i32>, children: Element) -> Element {
|
||||
fn Child(count: ReadSignal<i32>, children: Element) -> Element {
|
||||
rsx! {
|
||||
div { "Count: {count}" }
|
||||
{children}
|
||||
|
||||
@@ -18,6 +18,6 @@ fn app() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(signal: ReadOnlySignal<usize>) -> Element {
|
||||
fn Child(signal: ReadSignal<usize>) -> Element {
|
||||
rsx! { "{signal}" }
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
use std::{any::Any, ops::Deref};
|
||||
|
||||
use dioxus_core::{IntoAttributeValue, IntoDynNode, Subscribers};
|
||||
use generational_box::{BorrowResult, UnsyncStorage};
|
||||
|
||||
use crate::{
|
||||
read_impls, write_impls, CopyValue, Global, InitializeFromFunction, MappedMutSignal,
|
||||
MappedSignal, Memo, Readable, ReadableExt, ReadableRef, Signal, Writable, WritableExt,
|
||||
};
|
||||
|
||||
/// A signal that can only be read from.
|
||||
pub type ReadOnlySignal<T> = ReadSignal<T>;
|
||||
|
||||
/// A boxed version of [Readable] that can be used to store any readable type.
|
||||
pub struct ReadSignal<T: ?Sized + 'static> {
|
||||
value: CopyValue<Box<dyn Readable<Target = T, Storage = UnsyncStorage>>>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static> ReadSignal<T> {
|
||||
/// Create a new boxed readable value.
|
||||
pub fn new(value: impl Readable<Target = T, Storage = UnsyncStorage> + 'static) -> Self {
|
||||
Self {
|
||||
value: CopyValue::new(Box::new(value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Point to another [ReadSignal]. This will subscribe the other [ReadSignal] to all subscribers of this [ReadSignal].
|
||||
pub fn point_to(&self, other: Self) -> BorrowResult
|
||||
where
|
||||
T: Sized + 'static,
|
||||
{
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let this_subscribers = self.subscribers();
|
||||
let other_subscribers = other.subscribers();
|
||||
if let (Some(this_subscribers), Some(other_subscribers)) =
|
||||
(this_subscribers, other_subscribers)
|
||||
{
|
||||
this_subscribers.visit(|subscriber| {
|
||||
subscriber.subscribe(other_subscribers.clone());
|
||||
});
|
||||
}
|
||||
self.value.point_to(other.value)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// This is only used by the `props` macro.
|
||||
/// Mark any readers of the signal as dirty
|
||||
pub fn mark_dirty(&mut self) {
|
||||
let subscribers = self.value.subscribers();
|
||||
if let Some(subscribers) = subscribers {
|
||||
subscribers.visit(|subscriber| {
|
||||
subscriber.mark_dirty();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static> Clone for ReadSignal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static> Copy for ReadSignal<T> {}
|
||||
|
||||
impl<T: ?Sized + 'static> PartialEq for ReadSignal<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + 'static> Default for ReadSignal<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(Signal::new(T::default()))
|
||||
}
|
||||
}
|
||||
|
||||
read_impls!(ReadSignal<T>);
|
||||
|
||||
impl<T> IntoAttributeValue for ReadSignal<T>
|
||||
where
|
||||
T: Clone + IntoAttributeValue + 'static,
|
||||
{
|
||||
fn into_value(self) -> dioxus_core::AttributeValue {
|
||||
self.with(|f| f.clone().into_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoDynNode for ReadSignal<T>
|
||||
where
|
||||
T: Clone + IntoDynNode + 'static,
|
||||
{
|
||||
fn into_dyn_node(self) -> dioxus_core::DynamicNode {
|
||||
self.with(|f| f.clone().into_dyn_node())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static> Deref for ReadSignal<T> {
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Readable for ReadSignal<T> {
|
||||
type Target = T;
|
||||
type Storage = UnsyncStorage;
|
||||
|
||||
#[track_caller]
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
self.value
|
||||
.try_peek_unchecked()
|
||||
.unwrap()
|
||||
.try_read_unchecked()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
||||
self.value
|
||||
.try_peek_unchecked()
|
||||
.unwrap()
|
||||
.try_peek_unchecked()
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.value.subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
// We can't implement From<impl Readable<Target = T, Storage = S> + 'static> for ReadSignal<T, S>
|
||||
// because it would conflict with the From<T> for T implementation, but we can implement it for
|
||||
// all specific readable types
|
||||
impl<T> From<Signal<T>> for ReadSignal<T> {
|
||||
fn from(value: Signal<T>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl<T: PartialEq> From<Memo<T>> for ReadSignal<T> {
|
||||
fn from(value: Memo<T>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl<T> From<CopyValue<T>> for ReadSignal<T> {
|
||||
fn from(value: CopyValue<T>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl<T: Clone + 'static, R: 'static> From<Global<T, R>> for ReadSignal<R>
|
||||
where
|
||||
T: Readable<Target = R, Storage = UnsyncStorage> + InitializeFromFunction<R>,
|
||||
{
|
||||
fn from(value: Global<T, R>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl<V, O, F> From<MappedSignal<O, V, F>> for ReadSignal<O>
|
||||
where
|
||||
O: ?Sized,
|
||||
V: Readable<Storage = UnsyncStorage> + 'static,
|
||||
F: Fn(&V::Target) -> &O + 'static,
|
||||
{
|
||||
fn from(value: MappedSignal<O, V, F>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl<V, O, F, FMut> From<MappedMutSignal<O, V, F, FMut>> for ReadSignal<O>
|
||||
where
|
||||
O: ?Sized,
|
||||
V: Readable<Storage = UnsyncStorage> + 'static,
|
||||
F: Fn(&V::Target) -> &O + 'static,
|
||||
FMut: 'static,
|
||||
{
|
||||
fn from(value: MappedMutSignal<O, V, F, FMut>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A boxed version of [Writable] that can be used to store any writable type.
|
||||
pub struct WriteSignal<T: ?Sized + 'static> {
|
||||
value: CopyValue<
|
||||
Box<dyn Writable<Target = T, Storage = UnsyncStorage, WriteMetadata = Box<dyn Any>>>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> WriteSignal<T> {
|
||||
/// Create a new boxed writable value.
|
||||
pub fn new<M>(
|
||||
value: impl Writable<Target = T, Storage = UnsyncStorage, WriteMetadata = M> + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
value: CopyValue::new(Box::new(BoxWriteMetadata::new(value))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BoxWriteMetadata<W> {
|
||||
value: W,
|
||||
}
|
||||
|
||||
impl<W: Writable> BoxWriteMetadata<W> {
|
||||
fn new(value: W) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Readable> Readable for BoxWriteMetadata<W> {
|
||||
type Target = W::Target;
|
||||
|
||||
type Storage = W::Storage;
|
||||
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
self.value.try_read_unchecked()
|
||||
}
|
||||
|
||||
fn try_peek_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
self.value.try_peek_unchecked()
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.value.subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Writable> Writable for BoxWriteMetadata<W> {
|
||||
type WriteMetadata = Box<dyn Any>;
|
||||
|
||||
fn try_write_unchecked(
|
||||
&self,
|
||||
) -> Result<crate::WritableRef<'static, Self>, generational_box::BorrowMutError> {
|
||||
self.value
|
||||
.try_write_unchecked()
|
||||
.map(|w| w.map_metadata(|data| Box::new(data) as Box<dyn Any>))
|
||||
}
|
||||
|
||||
fn write(&mut self) -> crate::WritableRef<'_, Self> {
|
||||
self.value
|
||||
.write()
|
||||
.map_metadata(|data| Box::new(data) as Box<dyn Any>)
|
||||
}
|
||||
|
||||
fn try_write(
|
||||
&mut self,
|
||||
) -> Result<crate::WritableRef<'_, Self>, generational_box::BorrowMutError> {
|
||||
self.value
|
||||
.try_write()
|
||||
.map(|w| w.map_metadata(|data| Box::new(data) as Box<dyn Any>))
|
||||
}
|
||||
|
||||
fn write_unchecked(&self) -> crate::WritableRef<'static, Self> {
|
||||
self.value
|
||||
.write_unchecked()
|
||||
.map_metadata(|data| Box::new(data) as Box<dyn Any>)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for WriteSignal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Copy for WriteSignal<T> {}
|
||||
|
||||
impl<T: ?Sized> PartialEq for WriteSignal<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
read_impls!(WriteSignal<T>);
|
||||
write_impls!(WriteSignal<T>);
|
||||
|
||||
impl<T> IntoAttributeValue for WriteSignal<T>
|
||||
where
|
||||
T: Clone + IntoAttributeValue + 'static,
|
||||
{
|
||||
fn into_value(self) -> dioxus_core::AttributeValue {
|
||||
self.with(|f| f.clone().into_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoDynNode for WriteSignal<T>
|
||||
where
|
||||
T: Clone + IntoDynNode + 'static,
|
||||
{
|
||||
fn into_dyn_node(self) -> dioxus_core::DynamicNode {
|
||||
self.with(|f| f.clone().into_dyn_node())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static> Deref for WriteSignal<T> {
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Readable for WriteSignal<T> {
|
||||
type Target = T;
|
||||
type Storage = UnsyncStorage;
|
||||
|
||||
#[track_caller]
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
self.value
|
||||
.try_peek_unchecked()
|
||||
.unwrap()
|
||||
.try_read_unchecked()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
||||
self.value
|
||||
.try_peek_unchecked()
|
||||
.unwrap()
|
||||
.try_peek_unchecked()
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.value.subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Writable for WriteSignal<T> {
|
||||
type WriteMetadata = Box<dyn Any>;
|
||||
|
||||
fn write_unchecked(&self) -> crate::WritableRef<'static, Self> {
|
||||
self.value.try_peek_unchecked().unwrap().write_unchecked()
|
||||
}
|
||||
|
||||
fn try_write_unchecked(
|
||||
&self,
|
||||
) -> Result<crate::WritableRef<'static, Self>, generational_box::BorrowMutError> {
|
||||
self.value
|
||||
.try_peek_unchecked()
|
||||
.unwrap()
|
||||
.try_write_unchecked()
|
||||
}
|
||||
}
|
||||
|
||||
// We can't implement From<impl Writable<Target = T, Storage = S> + 'static> for Write<T, S>
|
||||
// because it would conflict with the From<T> for T implementation, but we can implement it for
|
||||
// all specific readable types
|
||||
impl<T> From<Signal<T>> for WriteSignal<T> {
|
||||
fn from(value: Signal<T>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl<T> From<CopyValue<T>> for WriteSignal<T> {
|
||||
fn from(value: CopyValue<T>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl<T: Clone + 'static, R: 'static> From<Global<T, R>> for WriteSignal<R>
|
||||
where
|
||||
T: Writable<Target = R, Storage = UnsyncStorage> + InitializeFromFunction<R>,
|
||||
{
|
||||
fn from(value: Global<T, R>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
impl<V, O, F, FMut> From<MappedMutSignal<O, V, F, FMut>> for WriteSignal<O>
|
||||
where
|
||||
O: ?Sized,
|
||||
V: Writable<Storage = UnsyncStorage> + 'static,
|
||||
F: Fn(&V::Target) -> &O + 'static,
|
||||
FMut: Fn(&mut V::Target) -> &mut O + 'static,
|
||||
{
|
||||
fn from(value: MappedMutSignal<O, V, F, FMut>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
#![allow(clippy::unnecessary_operation)]
|
||||
#![allow(clippy::no_effect)]
|
||||
|
||||
use dioxus_core::Subscribers;
|
||||
use dioxus_core::{current_owner, current_scope_id, ScopeId};
|
||||
use generational_box::{BorrowResult, GenerationalBox, GenerationalBoxId, Storage, UnsyncStorage};
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::read_impls;
|
||||
use crate::Readable;
|
||||
use crate::ReadableExt;
|
||||
use crate::ReadableRef;
|
||||
use crate::Writable;
|
||||
use crate::WritableRef;
|
||||
use crate::{default_impl, write_impls};
|
||||
use crate::WriteLock;
|
||||
use crate::{default_impl, write_impls, WritableExt};
|
||||
|
||||
/// CopyValue is a wrapper around a value to make the value mutable and Copy.
|
||||
///
|
||||
@@ -151,43 +154,21 @@ impl<T: 'static, S: Storage<T>> Readable for CopyValue<T, S> {
|
||||
crate::warnings::copy_value_hoisted(self, std::panic::Location::caller());
|
||||
self.value.try_read()
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: Storage<T>> Writable for CopyValue<T, S> {
|
||||
type Mut<'a, R: ?Sized + 'static> = S::Mut<'a, R>;
|
||||
|
||||
fn map_mut<I: ?Sized, U: ?Sized, F: FnOnce(&mut I) -> &mut U>(
|
||||
mut_: Self::Mut<'_, I>,
|
||||
f: F,
|
||||
) -> Self::Mut<'_, U> {
|
||||
S::map_mut(mut_, f)
|
||||
}
|
||||
|
||||
fn try_map_mut<I: ?Sized, U: ?Sized, F: FnOnce(&mut I) -> Option<&mut U>>(
|
||||
mut_: Self::Mut<'_, I>,
|
||||
f: F,
|
||||
) -> Option<Self::Mut<'_, U>> {
|
||||
S::try_map_mut(mut_, f)
|
||||
}
|
||||
|
||||
fn downcast_lifetime_mut<'a: 'b, 'b, R: ?Sized + 'static>(
|
||||
mut_: Self::Mut<'a, R>,
|
||||
) -> Self::Mut<'b, R> {
|
||||
S::downcast_lifetime_mut(mut_)
|
||||
}
|
||||
type WriteMetadata = ();
|
||||
|
||||
#[track_caller]
|
||||
fn try_write_unchecked(
|
||||
&self,
|
||||
) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {
|
||||
crate::warnings::copy_value_hoisted(self, std::panic::Location::caller());
|
||||
self.value.try_write()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn set(&mut self, value: T) {
|
||||
crate::warnings::copy_value_hoisted(self, std::panic::Location::caller());
|
||||
self.value.set(value);
|
||||
self.value.try_write().map(WriteLock::new)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +183,7 @@ impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { Readable::deref_impl(self) }
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{Global, InitializeFromFunction};
|
||||
use crate::read::Readable;
|
||||
use crate::read::ReadableExt;
|
||||
use crate::read_impls;
|
||||
use crate::Memo;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use dioxus_core::ScopeId;
|
||||
use dioxus_core::{ScopeId, Subscribers};
|
||||
use generational_box::BorrowResult;
|
||||
use std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc};
|
||||
|
||||
@@ -8,7 +8,7 @@ pub use memo::*;
|
||||
mod signal;
|
||||
pub use signal::*;
|
||||
|
||||
use crate::{Readable, ReadableRef, Signal, Writable, WritableRef};
|
||||
use crate::{Readable, ReadableExt, ReadableRef, Signal, Writable, WritableExt, WritableRef};
|
||||
|
||||
/// A trait for an item that can be constructed from an initialization function
|
||||
pub trait InitializeFromFunction<T> {
|
||||
@@ -39,7 +39,7 @@ where
|
||||
type Target = dyn Fn() -> R;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { Readable::deref_impl(self) }
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,37 +61,17 @@ where
|
||||
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
||||
self.resolve().try_peek_unchecked()
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.resolve().subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static, R: 'static> Writable for Global<T, R>
|
||||
where
|
||||
T: Writable<Target = R> + InitializeFromFunction<R>,
|
||||
{
|
||||
type Mut<'a, Read: ?Sized + 'static> = T::Mut<'a, Read>;
|
||||
|
||||
fn map_mut<I: ?Sized, U: ?Sized + 'static, F: FnOnce(&mut I) -> &mut U>(
|
||||
ref_: Self::Mut<'_, I>,
|
||||
f: F,
|
||||
) -> Self::Mut<'_, U> {
|
||||
T::map_mut(ref_, f)
|
||||
}
|
||||
|
||||
fn try_map_mut<
|
||||
I: ?Sized + 'static,
|
||||
U: ?Sized + 'static,
|
||||
F: FnOnce(&mut I) -> Option<&mut U>,
|
||||
>(
|
||||
ref_: Self::Mut<'_, I>,
|
||||
f: F,
|
||||
) -> Option<Self::Mut<'_, U>> {
|
||||
T::try_map_mut(ref_, f)
|
||||
}
|
||||
|
||||
fn downcast_lifetime_mut<'a: 'b, 'b, Read: ?Sized + 'static>(
|
||||
mut_: Self::Mut<'a, Read>,
|
||||
) -> Self::Mut<'b, Read> {
|
||||
T::downcast_lifetime_mut(mut_)
|
||||
}
|
||||
type WriteMetadata = T::WriteMetadata;
|
||||
|
||||
#[track_caller]
|
||||
fn try_write_unchecked(
|
||||
@@ -106,7 +86,7 @@ where
|
||||
T: Writable<Target = R> + InitializeFromFunction<R>,
|
||||
{
|
||||
/// Write this value
|
||||
pub fn write(&self) -> T::Mut<'static, R> {
|
||||
pub fn write(&self) -> WritableRef<'static, T, R> {
|
||||
self.resolve().try_write_unchecked().unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{Global, InitializeFromFunction};
|
||||
use crate::read::Readable;
|
||||
use crate::read::ReadableExt;
|
||||
use crate::read_impls;
|
||||
use crate::Signal;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
/// ```rust
|
||||
/// use generational_box::*;
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_core::Subscribers;
|
||||
///
|
||||
/// struct MyCopyValue<T: 'static, S: Storage<T>> {
|
||||
/// value: CopyValue<T, S>,
|
||||
@@ -30,6 +31,10 @@
|
||||
/// ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
/// self.value.try_read_unchecked()
|
||||
/// }
|
||||
///
|
||||
/// fn subscribers(&self) -> Option<Subscribers> {
|
||||
/// self.value.subscribers()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// default_impl!(MyCopyValue<T, S: Storage<T>>);
|
||||
@@ -72,6 +77,7 @@ macro_rules! default_impl {
|
||||
/// ```rust
|
||||
/// use generational_box::*;
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_core::Subscribers;
|
||||
///
|
||||
/// struct MyCopyValue<T: 'static, S: Storage<T>> {
|
||||
/// value: CopyValue<T, S>,
|
||||
@@ -92,6 +98,10 @@ macro_rules! default_impl {
|
||||
/// ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
/// self.value.try_read_unchecked()
|
||||
/// }
|
||||
///
|
||||
/// fn subscribers(&self) -> Option<Subscribers> {
|
||||
/// self.value.subscribers()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// read_impls!(MyCopyValue<T, S: Storage<T>>);
|
||||
@@ -145,6 +155,7 @@ macro_rules! read_impls {
|
||||
/// ```rust
|
||||
/// use generational_box::*;
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_core::Subscribers;
|
||||
///
|
||||
/// struct MyCopyValue<T: 'static, S: Storage<T>> {
|
||||
/// value: CopyValue<T, S>,
|
||||
@@ -165,6 +176,10 @@ macro_rules! read_impls {
|
||||
/// ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
/// self.value.try_read_unchecked()
|
||||
/// }
|
||||
///
|
||||
/// fn subscribers(&self) -> Option<Subscribers> {
|
||||
/// self.value.subscribers()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fmt_impls!(MyCopyValue<T, S: Storage<T>>);
|
||||
@@ -221,6 +236,7 @@ macro_rules! fmt_impls {
|
||||
/// ```rust
|
||||
/// use generational_box::*;
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_core::Subscribers;
|
||||
///
|
||||
/// struct MyCopyValue<T: 'static, S: Storage<T>> {
|
||||
/// value: CopyValue<T, S>,
|
||||
@@ -241,6 +257,10 @@ macro_rules! fmt_impls {
|
||||
/// ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
/// self.value.try_read_unchecked()
|
||||
/// }
|
||||
///
|
||||
/// fn subscribers(&self) -> Option<Subscribers> {
|
||||
/// self.value.subscribers()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// eq_impls!(MyCopyValue<T, S: Storage<T>>);
|
||||
@@ -307,7 +327,6 @@ macro_rules! eq_impls {
|
||||
/// &self,
|
||||
/// ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {
|
||||
/// self.value.try_write_unchecked()
|
||||
///
|
||||
/// }
|
||||
///
|
||||
/// //...
|
||||
@@ -332,6 +351,10 @@ macro_rules! write_impls {
|
||||
$(, $gen $(: $gen_bound)?)*
|
||||
> std::ops::Add<T>
|
||||
for $ty<T $(, $gen)*>
|
||||
$(
|
||||
where
|
||||
$($extra_bound_ty: $extra_bound,)*
|
||||
)?
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
@@ -345,6 +368,10 @@ macro_rules! write_impls {
|
||||
$(, $gen $(: $gen_bound)?)*
|
||||
> std::ops::AddAssign<T>
|
||||
for $ty<T $(, $gen)*>
|
||||
$(
|
||||
where
|
||||
$($extra_bound_ty: $extra_bound,)*
|
||||
)?
|
||||
{
|
||||
#[track_caller]
|
||||
fn add_assign(&mut self, rhs: T) {
|
||||
@@ -356,6 +383,10 @@ macro_rules! write_impls {
|
||||
$(, $gen $(: $gen_bound)?)*
|
||||
> std::ops::SubAssign<T>
|
||||
for $ty<T $(, $gen)*>
|
||||
$(
|
||||
where
|
||||
$($extra_bound_ty: $extra_bound,)*
|
||||
)?
|
||||
{
|
||||
#[track_caller]
|
||||
fn sub_assign(&mut self, rhs: T) {
|
||||
@@ -367,6 +398,10 @@ macro_rules! write_impls {
|
||||
$(, $gen $(: $gen_bound)?)*
|
||||
> std::ops::Sub<T>
|
||||
for $ty<T $(, $gen)*>
|
||||
$(
|
||||
where
|
||||
$($extra_bound_ty: $extra_bound,)*
|
||||
)?
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
@@ -380,6 +415,10 @@ macro_rules! write_impls {
|
||||
$(, $gen $(: $gen_bound)?)*
|
||||
> std::ops::MulAssign<T>
|
||||
for $ty<T $(, $gen)*>
|
||||
$(
|
||||
where
|
||||
$($extra_bound_ty: $extra_bound,)*
|
||||
)?
|
||||
{
|
||||
#[track_caller]
|
||||
fn mul_assign(&mut self, rhs: T) {
|
||||
@@ -391,6 +430,10 @@ macro_rules! write_impls {
|
||||
$(, $gen $(: $gen_bound)?)*
|
||||
> std::ops::Mul<T>
|
||||
for $ty<T $(, $gen)*>
|
||||
$(
|
||||
where
|
||||
$($extra_bound_ty: $extra_bound,)*
|
||||
)?
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
@@ -404,6 +447,10 @@ macro_rules! write_impls {
|
||||
$(, $gen $(: $gen_bound)?)*
|
||||
> std::ops::DivAssign<T>
|
||||
for $ty<T $(, $gen)*>
|
||||
$(
|
||||
where
|
||||
$($extra_bound_ty: $extra_bound,)*
|
||||
)?
|
||||
{
|
||||
#[track_caller]
|
||||
fn div_assign(&mut self, rhs: T) {
|
||||
@@ -415,6 +462,10 @@ macro_rules! write_impls {
|
||||
$(, $gen $(: $gen_bound)?)*
|
||||
> std::ops::Div<T>
|
||||
for $ty<T $(, $gen)*>
|
||||
$(
|
||||
where
|
||||
$($extra_bound_ty: $extra_bound,)*
|
||||
)?
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ pub use copy_value::*;
|
||||
pub(crate) mod signal;
|
||||
pub use signal::*;
|
||||
|
||||
mod read_only_signal;
|
||||
pub use read_only_signal::*;
|
||||
|
||||
mod map;
|
||||
pub use map::*;
|
||||
|
||||
mod map_mut;
|
||||
pub use map_mut::*;
|
||||
|
||||
mod set_compare;
|
||||
pub use set_compare::*;
|
||||
|
||||
@@ -41,3 +41,6 @@ mod props;
|
||||
pub use props::*;
|
||||
|
||||
pub mod warnings;
|
||||
|
||||
mod boxed;
|
||||
pub use boxed::*;
|
||||
+57
-37
@@ -1,94 +1,114 @@
|
||||
use crate::{read::Readable, read_impls, ReadableRef};
|
||||
use dioxus_core::IntoAttributeValue;
|
||||
use generational_box::{AnyStorage, BorrowResult, UnsyncStorage};
|
||||
use std::{ops::Deref, rc::Rc};
|
||||
use crate::{read::Readable, read_impls, ReadableExt, ReadableRef};
|
||||
use dioxus_core::{IntoAttributeValue, Subscribers};
|
||||
use generational_box::{AnyStorage, BorrowResult};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A read only signal that has been mapped to a new type.
|
||||
pub struct MappedSignal<O: ?Sized + 'static, S: AnyStorage = UnsyncStorage> {
|
||||
try_read: Rc<dyn Fn() -> Result<S::Ref<'static, O>, generational_box::BorrowError> + 'static>,
|
||||
try_peek: Rc<dyn Fn() -> Result<S::Ref<'static, O>, generational_box::BorrowError> + 'static>,
|
||||
pub struct MappedSignal<O: ?Sized + 'static, V: Readable, F = fn(&<V as Readable>::Target) -> &O> {
|
||||
value: V,
|
||||
map_fn: F,
|
||||
_marker: std::marker::PhantomData<O>,
|
||||
}
|
||||
|
||||
impl<O: ?Sized, S: AnyStorage> Clone for MappedSignal<O, S> {
|
||||
impl<V, O, F> Clone for MappedSignal<O, V, F>
|
||||
where
|
||||
V: Readable + Clone,
|
||||
F: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
MappedSignal {
|
||||
try_read: self.try_read.clone(),
|
||||
try_peek: self.try_peek.clone(),
|
||||
value: self.value.clone(),
|
||||
map_fn: self.map_fn.clone(),
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, S> MappedSignal<O, S>
|
||||
impl<V, O, F> Copy for MappedSignal<O, V, F>
|
||||
where
|
||||
V: Readable + Copy,
|
||||
F: Copy,
|
||||
{
|
||||
}
|
||||
|
||||
impl<V, O, F> MappedSignal<O, V, F>
|
||||
where
|
||||
O: ?Sized,
|
||||
S: AnyStorage,
|
||||
V: Readable,
|
||||
F: Fn(&V::Target) -> &O,
|
||||
{
|
||||
/// Create a new mapped signal.
|
||||
pub(crate) fn new(
|
||||
try_read: Rc<
|
||||
dyn Fn() -> Result<S::Ref<'static, O>, generational_box::BorrowError> + 'static,
|
||||
>,
|
||||
try_peek: Rc<
|
||||
dyn Fn() -> Result<S::Ref<'static, O>, generational_box::BorrowError> + 'static,
|
||||
>,
|
||||
) -> Self {
|
||||
MappedSignal { try_read, try_peek }
|
||||
pub(crate) fn new(value: V, map_fn: F) -> Self {
|
||||
MappedSignal {
|
||||
value,
|
||||
map_fn,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, S> Readable for MappedSignal<O, S>
|
||||
impl<V, O, F> Readable for MappedSignal<O, V, F>
|
||||
where
|
||||
O: ?Sized,
|
||||
S: AnyStorage,
|
||||
V: Readable,
|
||||
F: Fn(&V::Target) -> &O,
|
||||
{
|
||||
type Target = O;
|
||||
type Storage = S;
|
||||
type Storage = V::Storage;
|
||||
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
(self.try_read)()
|
||||
let value = self.value.try_read_unchecked()?;
|
||||
Ok(V::Storage::map(value, |v| (self.map_fn)(v)))
|
||||
}
|
||||
|
||||
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
||||
(self.try_peek)()
|
||||
let value = self.value.try_peek_unchecked()?;
|
||||
Ok(V::Storage::map(value, |v| (self.map_fn)(v)))
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.value.subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, S> IntoAttributeValue for MappedSignal<O, S>
|
||||
impl<V, O, F> IntoAttributeValue for MappedSignal<O, V, F>
|
||||
where
|
||||
O: Clone + IntoAttributeValue,
|
||||
S: AnyStorage,
|
||||
V: Readable,
|
||||
F: Fn(&V::Target) -> &O,
|
||||
{
|
||||
fn into_value(self) -> dioxus_core::AttributeValue {
|
||||
self.with(|f| f.clone().into_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, S> PartialEq for MappedSignal<O, S>
|
||||
impl<V, O, F> PartialEq for MappedSignal<O, V, F>
|
||||
where
|
||||
O: ?Sized,
|
||||
S: AnyStorage,
|
||||
V: Readable + PartialEq,
|
||||
F: PartialEq,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(&self.try_peek, &other.try_peek)
|
||||
&& std::ptr::eq(&self.try_read, &other.try_read)
|
||||
self.value == other.value && self.map_fn == other.map_fn
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow calling a signal with signal() syntax
|
||||
///
|
||||
/// Currently only limited to copy types, though could probably specialize for string/arc/rc
|
||||
impl<O, S> Deref for MappedSignal<O, S>
|
||||
/// Currently only limited to clone types, though could probably specialize for string/arc/rc
|
||||
impl<V, O, F> Deref for MappedSignal<O, V, F>
|
||||
where
|
||||
O: Clone,
|
||||
S: AnyStorage + 'static,
|
||||
V: Readable + 'static,
|
||||
F: Fn(&V::Target) -> &O + 'static,
|
||||
{
|
||||
type Target = dyn Fn() -> O;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { Readable::deref_impl(self) }
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
read_impls!(MappedSignal<T, S: AnyStorage>);
|
||||
read_impls!(MappedSignal<T, V, F> where V: Readable, F: Fn(&V::Target) -> &T);
|
||||
@@ -0,0 +1,150 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::{
|
||||
read_impls, write_impls, Readable, ReadableExt, ReadableRef, Writable, WritableExt,
|
||||
WritableRef, WriteLock,
|
||||
};
|
||||
use dioxus_core::{IntoAttributeValue, Subscribers};
|
||||
use generational_box::{AnyStorage, BorrowResult};
|
||||
|
||||
/// A read only signal that has been mapped to a new type.
|
||||
pub struct MappedMutSignal<
|
||||
O: ?Sized + 'static,
|
||||
V: Readable,
|
||||
F = fn(&<V as Readable>::Target) -> &O,
|
||||
FMut = fn(&mut <V as Readable>::Target) -> &mut O,
|
||||
> {
|
||||
value: V,
|
||||
map_fn: F,
|
||||
map_fn_mut: FMut,
|
||||
_marker: std::marker::PhantomData<O>,
|
||||
}
|
||||
|
||||
impl<V, O, F, FMut> Clone for MappedMutSignal<O, V, F, FMut>
|
||||
where
|
||||
V: Readable + Clone,
|
||||
F: Clone,
|
||||
FMut: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
MappedMutSignal {
|
||||
value: self.value.clone(),
|
||||
map_fn: self.map_fn.clone(),
|
||||
map_fn_mut: self.map_fn_mut.clone(),
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, O, F, FMut> Copy for MappedMutSignal<O, V, F, FMut>
|
||||
where
|
||||
V: Readable + Copy,
|
||||
F: Copy,
|
||||
FMut: Copy,
|
||||
{
|
||||
}
|
||||
|
||||
impl<V, O, F, FMut> MappedMutSignal<O, V, F, FMut>
|
||||
where
|
||||
O: ?Sized,
|
||||
V: Readable,
|
||||
F: Fn(&V::Target) -> &O,
|
||||
{
|
||||
/// Create a new mapped signal.
|
||||
pub(crate) fn new(value: V, map_fn: F, map_fn_mut: FMut) -> Self {
|
||||
MappedMutSignal {
|
||||
value,
|
||||
map_fn,
|
||||
map_fn_mut,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, O, F, FMut> Readable for MappedMutSignal<O, V, F, FMut>
|
||||
where
|
||||
O: ?Sized,
|
||||
V: Readable,
|
||||
F: Fn(&V::Target) -> &O,
|
||||
{
|
||||
type Target = O;
|
||||
type Storage = V::Storage;
|
||||
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
let value = self.value.try_read_unchecked()?;
|
||||
Ok(V::Storage::map(value, |v| (self.map_fn)(v)))
|
||||
}
|
||||
|
||||
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
||||
let value = self.value.try_peek_unchecked()?;
|
||||
Ok(V::Storage::map(value, |v| (self.map_fn)(v)))
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.value.subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, O, F, FMut> Writable for MappedMutSignal<O, V, F, FMut>
|
||||
where
|
||||
O: ?Sized,
|
||||
V: Writable,
|
||||
F: Fn(&V::Target) -> &O,
|
||||
FMut: Fn(&mut V::Target) -> &mut O,
|
||||
{
|
||||
type WriteMetadata = V::WriteMetadata;
|
||||
|
||||
fn try_write_unchecked(
|
||||
&self,
|
||||
) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {
|
||||
let value = self.value.try_write_unchecked()?;
|
||||
Ok(WriteLock::map(value, |v| (self.map_fn_mut)(v)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, O, F, FMut> IntoAttributeValue for MappedMutSignal<O, V, F, FMut>
|
||||
where
|
||||
O: Clone + IntoAttributeValue,
|
||||
V: Readable,
|
||||
F: Fn(&V::Target) -> &O,
|
||||
{
|
||||
fn into_value(self) -> dioxus_core::AttributeValue {
|
||||
self.with(|f| f.clone().into_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, O, F, FMut> PartialEq for MappedMutSignal<O, V, F, FMut>
|
||||
where
|
||||
O: ?Sized,
|
||||
V: Readable + PartialEq,
|
||||
F: PartialEq,
|
||||
FMut: PartialEq,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
&& self.map_fn == other.map_fn
|
||||
&& self.map_fn_mut == other.map_fn_mut
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow calling a signal with signal() syntax
|
||||
///
|
||||
/// Currently only limited to clone types, though could probably specialize for string/arc/rc
|
||||
impl<V, O, F, FMut> Deref for MappedMutSignal<O, V, F, FMut>
|
||||
where
|
||||
O: Clone,
|
||||
V: Readable + 'static,
|
||||
F: Fn(&V::Target) -> &O + 'static,
|
||||
FMut: 'static,
|
||||
{
|
||||
type Target = dyn Fn() -> O;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
read_impls!(MappedMutSignal<T, V, F, FMut> where V: Readable, F: Fn(&V::Target) -> &T);
|
||||
write_impls!(MappedMutSignal<T, V, F, FMut> where V: Writable, F: Fn(&V::Target) -> &T, FMut: Fn(&mut V::Target) -> &mut T);
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::write::Writable;
|
||||
use crate::CopyValue;
|
||||
use crate::{read::Readable, ReadableRef, Signal};
|
||||
use crate::{read_impls, GlobalMemo};
|
||||
use crate::{CopyValue, ReadOnlySignal};
|
||||
use crate::{read_impls, GlobalMemo, ReadableExt, WritableExt};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ops::Deref,
|
||||
@@ -10,6 +10,7 @@ use std::{
|
||||
|
||||
use dioxus_core::{
|
||||
current_scope_id, spawn_isomorphic, IntoAttributeValue, IntoDynNode, ReactiveContext, ScopeId,
|
||||
Subscribers,
|
||||
};
|
||||
use futures_util::StreamExt;
|
||||
use generational_box::{AnyStorage, BorrowResult, UnsyncStorage};
|
||||
@@ -28,15 +29,6 @@ pub struct Memo<T: 'static> {
|
||||
update: CopyValue<UpdateInformation<T>>,
|
||||
}
|
||||
|
||||
impl<T> From<Memo<T>> for ReadOnlySignal<T>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
fn from(val: Memo<T>) -> Self {
|
||||
ReadOnlySignal::new(val.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Memo<T> {
|
||||
/// Create a new memo
|
||||
#[track_caller]
|
||||
@@ -203,6 +195,10 @@ where
|
||||
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
||||
self.inner.try_peek_unchecked()
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
self.inner.subscribers()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoAttributeValue for Memo<T>
|
||||
@@ -236,7 +232,7 @@ where
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { Readable::deref_impl(self) }
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::{ReadOnlySignal, Signal};
|
||||
use crate::{ReadSignal, Signal};
|
||||
use dioxus_core::SuperFrom;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct SignalFromMarker<M>(std::marker::PhantomData<M>);
|
||||
pub struct ReadFromMarker<M>(std::marker::PhantomData<M>);
|
||||
|
||||
impl<T, O, M> SuperFrom<T, SignalFromMarker<M>> for ReadOnlySignal<O>
|
||||
impl<T, O, M> SuperFrom<T, ReadFromMarker<M>> for ReadSignal<O>
|
||||
where
|
||||
O: SuperFrom<T, M>,
|
||||
O: SuperFrom<T, M> + 'static,
|
||||
{
|
||||
fn super_from(input: T) -> Self {
|
||||
ReadOnlySignal::new(Signal::new(O::super_from(input)))
|
||||
ReadSignal::new(Signal::new(O::super_from(input)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ where
|
||||
#[allow(unused)]
|
||||
fn into_signal_compiles() {
|
||||
use dioxus_core::SuperInto;
|
||||
fn takes_signal_string<M>(_: impl SuperInto<ReadOnlySignal<String>, M>) {}
|
||||
fn takes_signal_string<M>(_: impl SuperInto<ReadSignal<String>, M>) {}
|
||||
|
||||
fn takes_option_signal_string<M>(_: impl SuperInto<ReadOnlySignal<Option<String>>, M>) {}
|
||||
fn takes_option_signal_string<M>(_: impl SuperInto<ReadSignal<Option<String>>, M>) {}
|
||||
|
||||
fn don_t_run() {
|
||||
takes_signal_string("hello world");
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use std::{mem::MaybeUninit, ops::Index, rc::Rc};
|
||||
use std::{mem::MaybeUninit, ops::Index};
|
||||
|
||||
use generational_box::AnyStorage;
|
||||
use dioxus_core::Subscribers;
|
||||
use generational_box::{AnyStorage, UnsyncStorage};
|
||||
|
||||
use crate::MappedSignal;
|
||||
use crate::{MappedSignal, ReadSignal};
|
||||
|
||||
/// A reference to a value that can be read from.
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type ReadableRef<'a, T: Readable, O = <T as Readable>::Target> =
|
||||
<T::Storage as AnyStorage>::Ref<'a, O>;
|
||||
|
||||
/// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadOnlySignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Readable`] type.
|
||||
/// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadSignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Readable`] type.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
@@ -21,7 +22,7 @@ pub type ReadableRef<'a, T: Readable, O = <T as Readable>::Target> =
|
||||
/// static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
|
||||
///
|
||||
/// fn MyComponent(count: Signal<i32>) -> Element {
|
||||
/// // Since we defined the function in terms of the readable trait, we can use it with any readable type (Signal, GlobalSignal, ReadOnlySignal, etc)
|
||||
/// // Since we defined the function in terms of the readable trait, we can use it with any readable type (Signal, GlobalSignal, ReadSignal, etc)
|
||||
/// let doubled = use_memo(move || double(&count));
|
||||
/// let global_count_doubled = use_memo(|| double(&COUNT));
|
||||
/// rsx! {
|
||||
@@ -41,64 +42,27 @@ pub trait Readable {
|
||||
/// The type of the storage this readable uses.
|
||||
type Storage: AnyStorage;
|
||||
|
||||
/// Map the readable type to a new type. This lets you provide a view into a readable type without needing to clone the inner value.
|
||||
/// Try to get a reference to the value without checking the lifetime. This will subscribe the current scope to the signal.
|
||||
///
|
||||
/// Anything that subscribes to the readable value will be rerun whenever the original value changes, even if the view does not change. If you want to memorize the view, you can use a [`crate::Memo`] instead.
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>;
|
||||
|
||||
/// Try to peek the current value of the signal without subscribing to updates. If the value has
|
||||
/// been dropped, this will return an error.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn List(list: Signal<Vec<i32>>) -> Element {
|
||||
/// rsx! {
|
||||
/// for index in 0..list.len() {
|
||||
/// // We can use the `map` method to provide a view into the single item in the list that the child component will render
|
||||
/// Item { item: list.map(move |v| &v[index]) }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // The child component doesn't need to know that the mapped value is coming from a list
|
||||
/// #[component]
|
||||
/// fn Item(item: MappedSignal<i32>) -> Element {
|
||||
/// rsx! {
|
||||
/// div { "Item: {item}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn map<O>(self, f: impl Fn(&Self::Target) -> &O + 'static) -> MappedSignal<O, Self::Storage>
|
||||
where
|
||||
Self: Clone + Sized + 'static,
|
||||
{
|
||||
let mapping = Rc::new(f);
|
||||
let try_read = Rc::new({
|
||||
let self_ = self.clone();
|
||||
let mapping = mapping.clone();
|
||||
move || {
|
||||
self_
|
||||
.try_read_unchecked()
|
||||
.map(|ref_| <Self::Storage as AnyStorage>::map(ref_, |r| mapping(r)))
|
||||
}
|
||||
})
|
||||
as Rc<
|
||||
dyn Fn() -> Result<ReadableRef<'static, Self, O>, generational_box::BorrowError>
|
||||
+ 'static,
|
||||
>;
|
||||
let try_peek = Rc::new({
|
||||
let self_ = self.clone();
|
||||
let mapping = mapping.clone();
|
||||
move || {
|
||||
self_
|
||||
.try_peek_unchecked()
|
||||
.map(|ref_| <Self::Storage as AnyStorage>::map(ref_, |r| mapping(r)))
|
||||
}
|
||||
})
|
||||
as Rc<
|
||||
dyn Fn() -> Result<ReadableRef<'static, Self, O>, generational_box::BorrowError>
|
||||
+ 'static,
|
||||
>;
|
||||
MappedSignal::new(try_read, try_peek)
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
fn try_peek_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>;
|
||||
|
||||
/// Get the underlying subscriber list for this readable. This is used to track when the value changes and notify subscribers.
|
||||
fn subscribers(&self) -> Option<Subscribers>;
|
||||
}
|
||||
|
||||
/// An extension trait for `Readable` types that provides some convenience methods.
|
||||
pub trait ReadableExt: Readable {
|
||||
/// Get the current value of the state. If this is a signal, this will subscribe the current scope to the signal.
|
||||
/// If the value has been dropped, this will panic. Calling this on a Signal is the same as
|
||||
/// using the signal() syntax to read and subscribe to its value
|
||||
@@ -122,13 +86,6 @@ pub trait Readable {
|
||||
self.try_read_unchecked().unwrap()
|
||||
}
|
||||
|
||||
/// Try to get a reference to the value without checking the lifetime. This will subscribe the current scope to the signal.
|
||||
///
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>;
|
||||
|
||||
/// Get the current value of the state without subscribing to updates. If the value has been dropped, this will panic.
|
||||
///
|
||||
/// # Example
|
||||
@@ -184,13 +141,37 @@ pub trait Readable {
|
||||
self.try_peek_unchecked().unwrap()
|
||||
}
|
||||
|
||||
/// Try to peek the current value of the signal without subscribing to updates. If the value has
|
||||
/// been dropped, this will return an error.
|
||||
/// Map the readable type to a new type. This lets you provide a view into a readable type without needing to clone the inner value.
|
||||
///
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
fn try_peek_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>;
|
||||
/// Anything that subscribes to the readable value will be rerun whenever the original value changes, even if the view does not change. If you want to memorize the view, you can use a [`crate::Memo`] instead.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn List(list: Signal<Vec<i32>>) -> Element {
|
||||
/// rsx! {
|
||||
/// for index in 0..list.len() {
|
||||
/// // We can use the `map` method to provide a view into the single item in the list that the child component will render
|
||||
/// Item { item: list.map(move |v| &v[index]) }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // The child component doesn't need to know that the mapped value is coming from a list
|
||||
/// #[component]
|
||||
/// fn Item(item: ReadSignal<i32>) -> Element {
|
||||
/// rsx! {
|
||||
/// div { "Item: {item}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn map<F, O>(self, f: F) -> MappedSignal<O, Self, F>
|
||||
where
|
||||
Self: Clone + Sized + 'static,
|
||||
F: Fn(&Self::Target) -> &O + 'static,
|
||||
{
|
||||
MappedSignal::new(self, f)
|
||||
}
|
||||
|
||||
/// Clone the inner value and return it. If the value has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
@@ -263,6 +244,19 @@ pub trait Readable {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Readable + ?Sized> ReadableExt for R {}
|
||||
|
||||
/// An extension trait for `Readable` types that can be boxed into a trait object.
|
||||
pub trait ReadableBoxExt: Readable<Storage = UnsyncStorage> {
|
||||
/// Box the readable value into a trait object. This is useful for passing around readable values without knowing their concrete type.
|
||||
fn boxed(self) -> ReadSignal<Self::Target>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
ReadSignal::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// An extension trait for `Readable<Vec<T>>` that provides some convenience methods.
|
||||
pub trait ReadableVecExt<T: 'static>: Readable<Target = Vec<T>> {
|
||||
/// Returns the length of the inner vector.
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
use crate::{read::Readable, ReadableRef, Signal, SignalData};
|
||||
use dioxus_core::IntoDynNode;
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::{default_impl, read_impls};
|
||||
use dioxus_core::{IntoAttributeValue, ScopeId};
|
||||
use generational_box::{BorrowResult, Storage, UnsyncStorage};
|
||||
|
||||
/// A signal that can only be read from.
|
||||
pub struct ReadOnlySignal<T: 'static, S: Storage<SignalData<T>> = UnsyncStorage> {
|
||||
inner: Signal<T, S>,
|
||||
}
|
||||
|
||||
/// A signal that can only be read from.
|
||||
pub type ReadSignal<T, S = UnsyncStorage> = ReadOnlySignal<T, S>;
|
||||
|
||||
impl<T: 'static, S: Storage<SignalData<T>>> From<Signal<T, S>> for ReadOnlySignal<T, S> {
|
||||
fn from(inner: Signal<T, S>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> ReadOnlySignal<T> {
|
||||
/// Create a new read-only signal.
|
||||
#[track_caller]
|
||||
pub fn new(signal: Signal<T>) -> Self {
|
||||
Self::new_maybe_sync(signal)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: Storage<SignalData<T>>> ReadOnlySignal<T, S> {
|
||||
/// Create a new read-only signal that is maybe sync.
|
||||
#[track_caller]
|
||||
pub fn new_maybe_sync(signal: Signal<T, S>) -> Self {
|
||||
Self { inner: signal }
|
||||
}
|
||||
|
||||
/// Get the scope that the signal was created in.
|
||||
pub fn origin_scope(&self) -> ScopeId {
|
||||
self.inner.origin_scope()
|
||||
}
|
||||
|
||||
/// Get the id of the signal.
|
||||
pub fn id(&self) -> generational_box::GenerationalBoxId {
|
||||
self.inner.id()
|
||||
}
|
||||
|
||||
/// Point to another signal
|
||||
pub fn point_to(&self, other: Self) -> BorrowResult {
|
||||
self.inner.point_to(other.inner)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// This is only used by the `props` macro.
|
||||
/// Mark any readers of the signal as dirty
|
||||
pub fn mark_dirty(&mut self) {
|
||||
use crate::write::Writable;
|
||||
_ = self.inner.try_write();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Storage<SignalData<T>>> Readable for ReadOnlySignal<T, S> {
|
||||
type Target = T;
|
||||
type Storage = S;
|
||||
|
||||
#[track_caller]
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||
self.inner.try_read_unchecked()
|
||||
}
|
||||
|
||||
/// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.**
|
||||
///
|
||||
/// If the signal has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
fn try_peek_unchecked(&self) -> BorrowResult<S::Ref<'static, T>> {
|
||||
self.inner.try_peek_unchecked()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<T: serde::Serialize + 'static, Store: Storage<SignalData<T>>> serde::Serialize
|
||||
for ReadOnlySignal<T, Store>
|
||||
{
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
self.read().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage<SignalData<T>>>
|
||||
serde::Deserialize<'de> for ReadOnlySignal<T, Store>
|
||||
{
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Ok(Self::new_maybe_sync(Signal::new_maybe_sync(
|
||||
T::deserialize(deserializer)?,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoAttributeValue for ReadOnlySignal<T>
|
||||
where
|
||||
T: Clone + IntoAttributeValue,
|
||||
{
|
||||
fn into_value(self) -> dioxus_core::AttributeValue {
|
||||
self.with(|f| f.clone().into_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoDynNode for ReadOnlySignal<T>
|
||||
where
|
||||
T: Clone + IntoDynNode,
|
||||
{
|
||||
fn into_dyn_node(self) -> dioxus_core::DynamicNode {
|
||||
self().into_dyn_node()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: Storage<SignalData<T>>> PartialEq for ReadOnlySignal<T, S> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for ReadOnlySignal<T, S> {
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { Readable::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
read_impls!(
|
||||
ReadOnlySignal<T, S> where
|
||||
S: Storage<SignalData<T>>
|
||||
);
|
||||
default_impl!(
|
||||
ReadOnlySignal<T, S> where
|
||||
S: Storage<SignalData<T>>
|
||||
);
|
||||
|
||||
impl<T: 'static, S: Storage<SignalData<T>>> Clone for ReadOnlySignal<T, S> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: Storage<SignalData<T>>> Copy for ReadOnlySignal<T, S> {}
|
||||
@@ -1,12 +1,11 @@
|
||||
use crate::write::Writable;
|
||||
use crate::{write::Writable, ReadableExt};
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::read::Readable;
|
||||
use dioxus_core::ReactiveContext;
|
||||
use futures_util::StreamExt;
|
||||
use generational_box::{Storage, UnsyncStorage};
|
||||
|
||||
use crate::{CopyValue, ReadOnlySignal, Signal, SignalData};
|
||||
use crate::{CopyValue, ReadSignal, Signal, SignalData};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// An object that can efficiently compare a value to a set of values.
|
||||
@@ -66,9 +65,11 @@ impl<R: Eq + Hash, S: Storage<SignalData<bool>>> SetCompare<R, S> {
|
||||
|
||||
SetCompare { subscribers }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Eq + Hash> SetCompare<R> {
|
||||
/// Returns a signal which is true when the value is equal to the value passed to this function.
|
||||
pub fn equal(&mut self, value: R) -> ReadOnlySignal<bool, S> {
|
||||
pub fn equal(&mut self, value: R) -> ReadSignal<bool> {
|
||||
let subscribers = self.subscribers.write();
|
||||
|
||||
match subscribers.get(&value) {
|
||||
|
||||
+22
-212
@@ -2,15 +2,9 @@ use crate::{
|
||||
default_impl, fmt_impls, read::*, write::*, write_impls, CopyValue, Global, GlobalMemo,
|
||||
GlobalSignal, Memo, ReadableRef, WritableRef,
|
||||
};
|
||||
use dioxus_core::{IntoAttributeValue, IntoDynNode, ReactiveContext, ScopeId};
|
||||
use generational_box::{AnyStorage, BorrowResult, Storage, SyncStorage, UnsyncStorage};
|
||||
use std::{
|
||||
any::Any,
|
||||
collections::HashSet,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
sync::Mutex,
|
||||
};
|
||||
use dioxus_core::{IntoAttributeValue, IntoDynNode, ReactiveContext, ScopeId, Subscribers};
|
||||
use generational_box::{BorrowResult, Storage, SyncStorage, UnsyncStorage};
|
||||
use std::{any::Any, collections::HashSet, ops::Deref, sync::Arc, sync::Mutex};
|
||||
|
||||
#[doc = include_str!("../docs/signals.md")]
|
||||
#[doc(alias = "State")]
|
||||
@@ -261,7 +255,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
||||
self.inner.id()
|
||||
}
|
||||
|
||||
/// **This pattern is no longer recommended. Prefer [`peek`](Signal::peek) or creating new signals instead.**
|
||||
/// **This pattern is no longer recommended. Prefer [`peek`](ReadableExt::peek) or creating new signals instead.**
|
||||
///
|
||||
/// This function is the equivalent of the [write_silent](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseRef.html#method.write_silent) method on use_ref.
|
||||
///
|
||||
@@ -289,7 +283,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
||||
/// *signal.write_silent() += 1;
|
||||
/// ```
|
||||
///
|
||||
/// Instead you can use the [`peek`](Signal::peek) and [`write`](Signal::write) methods instead. The peek method will not subscribe to the current scope which will avoid an infinite loop if you are reading and writing to the same signal in the same scope.
|
||||
/// Instead you can use the [`peek`](ReadableExt::peek) and [`write`](Signal::write) methods instead. The peek method will not subscribe to the current scope which will avoid an infinite loop if you are reading and writing to the same signal in the same scope.
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// let mut signal = use_signal(|| 0);
|
||||
@@ -342,7 +336,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Instead [`peek`](Signal::peek) locally opts out of automatic state updates explicitly for a specific read which is easier to reason about.
|
||||
/// Instead [`peek`](ReadableExt::peek) locally opts out of automatic state updates explicitly for a specific read which is easier to reason about.
|
||||
///
|
||||
/// Here is the same example using peek:
|
||||
/// main.rs:
|
||||
@@ -378,8 +372,10 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[deprecated = "This pattern is no longer recommended. Prefer `peek` or creating new signals instead."]
|
||||
pub fn write_silent(&self) -> S::Mut<'static, T> {
|
||||
S::map_mut(self.inner.write_unchecked(), |inner| &mut inner.value)
|
||||
pub fn write_silent(&self) -> WriteLock<'static, T, S> {
|
||||
WriteLock::map(self.inner.write_unchecked(), |inner: &mut SignalData<T>| {
|
||||
&mut inner.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,34 +404,14 @@ impl<T, S: Storage<SignalData<T>>> Readable for Signal<T, S> {
|
||||
.try_read_unchecked()
|
||||
.map(|inner| S::map(inner, |v| &v.value))
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> Option<Subscribers> {
|
||||
Some(self.inner.read().subscribers.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: Storage<SignalData<T>>> Writable for Signal<T, S> {
|
||||
type Mut<'a, R: ?Sized + 'static> = Write<'a, R, S>;
|
||||
|
||||
fn map_mut<I: ?Sized, U: ?Sized + 'static, F: FnOnce(&mut I) -> &mut U>(
|
||||
ref_: Self::Mut<'_, I>,
|
||||
f: F,
|
||||
) -> Self::Mut<'_, U> {
|
||||
Write::map(ref_, f)
|
||||
}
|
||||
|
||||
fn try_map_mut<
|
||||
I: ?Sized + 'static,
|
||||
U: ?Sized + 'static,
|
||||
F: FnOnce(&mut I) -> Option<&mut U>,
|
||||
>(
|
||||
ref_: Self::Mut<'_, I>,
|
||||
f: F,
|
||||
) -> Option<Self::Mut<'_, U>> {
|
||||
Write::filter_map(ref_, f)
|
||||
}
|
||||
|
||||
fn downcast_lifetime_mut<'a: 'b, 'b, R: ?Sized + 'static>(
|
||||
mut_: Self::Mut<'a, R>,
|
||||
) -> Self::Mut<'b, R> {
|
||||
Write::downcast_lifetime(mut_)
|
||||
}
|
||||
type WriteMetadata = Box<dyn Any>;
|
||||
|
||||
#[track_caller]
|
||||
fn try_write_unchecked(
|
||||
@@ -444,15 +420,15 @@ impl<T: 'static, S: Storage<SignalData<T>>> Writable for Signal<T, S> {
|
||||
#[cfg(debug_assertions)]
|
||||
let origin = std::panic::Location::caller();
|
||||
self.inner.try_write_unchecked().map(|inner| {
|
||||
let borrow = S::map_mut(inner, |v| &mut v.value);
|
||||
Write {
|
||||
write: borrow,
|
||||
drop_signal: Box::new(SignalSubscriberDrop {
|
||||
let borrow = S::map_mut(inner.into_inner(), |v| &mut v.value);
|
||||
WriteLock::new_with_metadata(
|
||||
borrow,
|
||||
Box::new(SignalSubscriberDrop {
|
||||
signal: *self,
|
||||
#[cfg(debug_assertions)]
|
||||
origin,
|
||||
}),
|
||||
}
|
||||
}) as Box<dyn Any>,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -490,7 +466,7 @@ impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for Signal<T, S> {
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { Readable::deref_impl(self) }
|
||||
unsafe { ReadableExt::deref_impl(self) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,172 +488,6 @@ impl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage<SignalData<T>>>
|
||||
}
|
||||
}
|
||||
|
||||
/// A mutable reference to a signal's value. This reference acts similarly to [`std::cell::RefMut`], but it has extra debug information
|
||||
/// and integrates with the reactive system to automatically update dependents.
|
||||
///
|
||||
/// [`Write`] implements [`DerefMut`] which means you can call methods on the inner value just like you would on a mutable reference
|
||||
/// to the inner value. If you need to get the inner reference directly, you can call [`Write::deref_mut`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn app() -> Element {
|
||||
/// let mut value = use_signal(|| String::from("hello"));
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// let mut mutable_reference = value.write();
|
||||
///
|
||||
/// // You call methods like `push_str` on the reference just like you would with the inner String
|
||||
/// mutable_reference.push_str("world");
|
||||
/// },
|
||||
/// "Click to add world to the string"
|
||||
/// }
|
||||
/// div { "{value}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Matching on Write
|
||||
///
|
||||
/// You need to get the inner mutable reference with [`Write::deref_mut`] before you match the inner value. If you try to match
|
||||
/// without calling [`Write::deref_mut`], you will get an error like this:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// enum Colors {
|
||||
/// Red(u32),
|
||||
/// Green
|
||||
/// }
|
||||
/// fn app() -> Element {
|
||||
/// let mut value = use_signal(|| Colors::Red(0));
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// let mut mutable_reference = value.write();
|
||||
///
|
||||
/// match mutable_reference {
|
||||
/// // Since we are matching on the `Write` type instead of &mut Colors, we can't match on the enum directly
|
||||
/// Colors::Red(brightness) => *brightness += 1,
|
||||
/// Colors::Green => {}
|
||||
/// }
|
||||
/// },
|
||||
/// "Click to add brightness to the red color"
|
||||
/// }
|
||||
/// div { "{value:?}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```text
|
||||
/// error[E0308]: mismatched types
|
||||
/// --> src/main.rs:18:21
|
||||
/// |
|
||||
/// 16 | match mutable_reference {
|
||||
/// | ----------------- this expression has type `dioxus::prelude::Write<'_, Colors>`
|
||||
/// 17 | // Since we are matching on the `Write` t...
|
||||
/// 18 | Colors::Red(brightness) => *brightness += 1,
|
||||
/// | ^^^^^^^^^^^^^^^^^^^^^^^ expected `Write<'_, Colors>`, found `Colors`
|
||||
/// |
|
||||
/// = note: expected struct `dioxus::prelude::Write<'_, Colors, >`
|
||||
/// found enum `Colors`
|
||||
/// ```
|
||||
///
|
||||
/// Instead, you need to call deref mut on the reference to get the inner value **before** you match on it:
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::ops::DerefMut;
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// enum Colors {
|
||||
/// Red(u32),
|
||||
/// Green
|
||||
/// }
|
||||
/// fn app() -> Element {
|
||||
/// let mut value = use_signal(|| Colors::Red(0));
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// let mut mutable_reference = value.write();
|
||||
///
|
||||
/// // DerefMut converts the `Write` into a `&mut Colors`
|
||||
/// match mutable_reference.deref_mut() {
|
||||
/// // Now we can match on the inner value
|
||||
/// Colors::Red(brightness) => *brightness += 1,
|
||||
/// Colors::Green => {}
|
||||
/// }
|
||||
/// },
|
||||
/// "Click to add brightness to the red color"
|
||||
/// }
|
||||
/// div { "{value:?}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Generics
|
||||
/// - T is the current type of the write
|
||||
/// - S is the storage type of the signal. This type determines if the signal is local to the current thread, or it can be shared across threads.
|
||||
pub struct Write<'a, T: ?Sized + 'static, S: AnyStorage = UnsyncStorage> {
|
||||
write: S::Mut<'a, T>,
|
||||
drop_signal: Box<dyn Any>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + 'static, S: AnyStorage> Write<'a, T, S> {
|
||||
/// Map the mutable reference to the signal's value to a new type.
|
||||
pub fn map<O: ?Sized>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, S> {
|
||||
let Self {
|
||||
write, drop_signal, ..
|
||||
} = myself;
|
||||
Write {
|
||||
write: S::map_mut(write, f),
|
||||
drop_signal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to map the mutable reference to the signal's value to a new type
|
||||
pub fn filter_map<O: ?Sized>(
|
||||
myself: Self,
|
||||
f: impl FnOnce(&mut T) -> Option<&mut O>,
|
||||
) -> Option<Write<'a, O, S>> {
|
||||
let Self {
|
||||
write, drop_signal, ..
|
||||
} = myself;
|
||||
let write = S::try_map_mut(write, f);
|
||||
write.map(|write| Write { write, drop_signal })
|
||||
}
|
||||
|
||||
/// Downcast the lifetime of the mutable reference to the signal's value.
|
||||
///
|
||||
/// This function enforces the variance of the lifetime parameter `'a` in Mut. Rust will typically infer this cast with a concrete type, but it cannot with a generic type.
|
||||
pub fn downcast_lifetime<'b>(mut_: Self) -> Write<'b, T, S>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
Write {
|
||||
write: S::downcast_lifetime_mut(mut_.write),
|
||||
drop_signal: mut_.drop_signal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static, S: AnyStorage> Deref for Write<'_, T, S> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.write
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, S: AnyStorage> DerefMut for Write<'_, T, S> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.write
|
||||
}
|
||||
}
|
||||
|
||||
struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
|
||||
signal: Signal<T, S>,
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
+285
-33
@@ -1,10 +1,13 @@
|
||||
use std::ops::{DerefMut, IndexMut};
|
||||
use std::ops::{Deref, DerefMut, IndexMut};
|
||||
|
||||
use crate::read::Readable;
|
||||
use generational_box::{AnyStorage, UnsyncStorage};
|
||||
|
||||
/// A reference to a value that can be read from.
|
||||
use crate::{read::Readable, read::ReadableExt, MappedMutSignal, WriteSignal};
|
||||
|
||||
/// A reference to a value that can be written to.
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type WritableRef<'a, T: Writable, O = <T as Readable>::Target> = T::Mut<'a, O>;
|
||||
pub type WritableRef<'a, T: Writable, O = <T as Readable>::Target> =
|
||||
WriteLock<'a, O, <T as Readable>::Storage, <T as Writable>::WriteMetadata>;
|
||||
|
||||
/// A trait for states that can be written to like [`crate::Signal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API.
|
||||
///
|
||||
@@ -32,27 +35,8 @@ pub type WritableRef<'a, T: Writable, O = <T as Readable>::Target> = T::Mut<'a,
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Writable: Readable {
|
||||
/// The type of the reference.
|
||||
type Mut<'a, R: ?Sized + 'static>: DerefMut<Target = R>;
|
||||
|
||||
/// Map the reference to a new type.
|
||||
fn map_mut<I: ?Sized, U: ?Sized, F: FnOnce(&mut I) -> &mut U>(
|
||||
ref_: Self::Mut<'_, I>,
|
||||
f: F,
|
||||
) -> Self::Mut<'_, U>;
|
||||
|
||||
/// Try to map the reference to a new type.
|
||||
fn try_map_mut<I: ?Sized, U: ?Sized, F: FnOnce(&mut I) -> Option<&mut U>>(
|
||||
ref_: Self::Mut<'_, I>,
|
||||
f: F,
|
||||
) -> Option<Self::Mut<'_, U>>;
|
||||
|
||||
/// Downcast a mutable reference in a RefMut to a more specific lifetime
|
||||
///
|
||||
/// This function enforces the variance of the lifetime parameter `'a` in Ref.
|
||||
fn downcast_lifetime_mut<'a: 'b, 'b, T: ?Sized + 'static>(
|
||||
mut_: Self::Mut<'a, T>,
|
||||
) -> Self::Mut<'b, T>;
|
||||
/// Additional data associated with the write reference.
|
||||
type WriteMetadata: 'static;
|
||||
|
||||
/// Get a mutable reference to the value. If the value has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
@@ -63,7 +47,7 @@ pub trait Writable: Readable {
|
||||
/// Try to get a mutable reference to the value.
|
||||
#[track_caller]
|
||||
fn try_write(&mut self) -> Result<WritableRef<'_, Self>, generational_box::BorrowMutError> {
|
||||
self.try_write_unchecked().map(Self::downcast_lifetime_mut)
|
||||
self.try_write_unchecked().map(WriteLock::downcast_lifetime)
|
||||
}
|
||||
|
||||
/// Try to get a mutable reference to the value without checking the lifetime. This will update any subscribers.
|
||||
@@ -80,6 +64,255 @@ pub trait Writable: Readable {
|
||||
fn write_unchecked(&self) -> WritableRef<'static, Self> {
|
||||
self.try_write_unchecked().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// A mutable reference to a writable value. This reference acts similarly to [`std::cell::RefMut`], but it has extra debug information
|
||||
/// and integrates with the reactive system to automatically update dependents.
|
||||
///
|
||||
/// [`WriteLock`] implements [`DerefMut`] which means you can call methods on the inner value just like you would on a mutable reference
|
||||
/// to the inner value. If you need to get the inner reference directly, you can call [`WriteLock::deref_mut`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn app() -> Element {
|
||||
/// let mut value = use_signal(|| String::from("hello"));
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// let mut mutable_reference = value.write();
|
||||
///
|
||||
/// // You call methods like `push_str` on the reference just like you would with the inner String
|
||||
/// mutable_reference.push_str("world");
|
||||
/// },
|
||||
/// "Click to add world to the string"
|
||||
/// }
|
||||
/// div { "{value}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Matching on WriteLock
|
||||
///
|
||||
/// You need to get the inner mutable reference with [`WriteLock::deref_mut`] before you match the inner value. If you try to match
|
||||
/// without calling [`WriteLock::deref_mut`], you will get an error like this:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// enum Colors {
|
||||
/// Red(u32),
|
||||
/// Green
|
||||
/// }
|
||||
/// fn app() -> Element {
|
||||
/// let mut value = use_signal(|| Colors::Red(0));
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// let mut mutable_reference = value.write();
|
||||
///
|
||||
/// match mutable_reference {
|
||||
/// // Since we are matching on the `Write` type instead of &mut Colors, we can't match on the enum directly
|
||||
/// Colors::Red(brightness) => *brightness += 1,
|
||||
/// Colors::Green => {}
|
||||
/// }
|
||||
/// },
|
||||
/// "Click to add brightness to the red color"
|
||||
/// }
|
||||
/// div { "{value:?}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```text
|
||||
/// error[E0308]: mismatched types
|
||||
/// --> src/main.rs:18:21
|
||||
/// |
|
||||
/// 16 | match mutable_reference {
|
||||
/// | ----------------- this expression has type `dioxus::prelude::Write<'_, Colors>`
|
||||
/// 17 | // Since we are matching on the `Write` t...
|
||||
/// 18 | Colors::Red(brightness) => *brightness += 1,
|
||||
/// | ^^^^^^^^^^^^^^^^^^^^^^^ expected `Write<'_, Colors>`, found `Colors`
|
||||
/// |
|
||||
/// = note: expected struct `dioxus::prelude::Write<'_, Colors, >`
|
||||
/// found enum `Colors`
|
||||
/// ```
|
||||
///
|
||||
/// Instead, you need to call deref mut on the reference to get the inner value **before** you match on it:
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::ops::DerefMut;
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// enum Colors {
|
||||
/// Red(u32),
|
||||
/// Green
|
||||
/// }
|
||||
/// fn app() -> Element {
|
||||
/// let mut value = use_signal(|| Colors::Red(0));
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// let mut mutable_reference = value.write();
|
||||
///
|
||||
/// // DerefMut converts the `Write` into a `&mut Colors`
|
||||
/// match mutable_reference.deref_mut() {
|
||||
/// // Now we can match on the inner value
|
||||
/// Colors::Red(brightness) => *brightness += 1,
|
||||
/// Colors::Green => {}
|
||||
/// }
|
||||
/// },
|
||||
/// "Click to add brightness to the red color"
|
||||
/// }
|
||||
/// div { "{value:?}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Generics
|
||||
/// - T is the current type of the write
|
||||
/// - S is the storage type of the signal. This type determines if the signal is local to the current thread, or it can be shared across threads.
|
||||
/// - D is the additional data associated with the write reference. This is used by signals to track when the write is dropped
|
||||
pub struct WriteLock<'a, T: ?Sized + 'static, S: AnyStorage = UnsyncStorage, D = ()> {
|
||||
write: S::Mut<'a, T>,
|
||||
data: D,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, S: AnyStorage> WriteLock<'a, T, S> {
|
||||
/// Create a new write reference
|
||||
pub fn new(write: S::Mut<'a, T>) -> Self {
|
||||
Self { write, data: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, S: AnyStorage, D> WriteLock<'a, T, S, D> {
|
||||
/// Create a new write reference with additional data.
|
||||
pub fn new_with_metadata(write: S::Mut<'a, T>, data: D) -> Self {
|
||||
Self { write, data }
|
||||
}
|
||||
|
||||
/// Get the inner value of the write reference.
|
||||
pub fn into_inner(self) -> S::Mut<'a, T> {
|
||||
self.write
|
||||
}
|
||||
|
||||
/// Get the additional data associated with the write reference.
|
||||
pub fn data(&self) -> &D {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Split into the inner value and the additional data.
|
||||
pub fn into_parts(self) -> (S::Mut<'a, T>, D) {
|
||||
(self.write, self.data)
|
||||
}
|
||||
|
||||
/// Map the metadata of the write reference to a new type.
|
||||
pub fn map_metadata<O>(self, f: impl FnOnce(D) -> O) -> WriteLock<'a, T, S, O> {
|
||||
WriteLock {
|
||||
write: self.write,
|
||||
data: f(self.data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Map the mutable reference to the signal's value to a new type.
|
||||
pub fn map<O: ?Sized>(
|
||||
myself: Self,
|
||||
f: impl FnOnce(&mut T) -> &mut O,
|
||||
) -> WriteLock<'a, O, S, D> {
|
||||
let Self { write, data, .. } = myself;
|
||||
WriteLock {
|
||||
write: S::map_mut(write, f),
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to map the mutable reference to the signal's value to a new type
|
||||
pub fn filter_map<O: ?Sized>(
|
||||
myself: Self,
|
||||
f: impl FnOnce(&mut T) -> Option<&mut O>,
|
||||
) -> Option<WriteLock<'a, O, S, D>> {
|
||||
let Self { write, data, .. } = myself;
|
||||
let write = S::try_map_mut(write, f);
|
||||
write.map(|write| WriteLock { write, data })
|
||||
}
|
||||
|
||||
/// Downcast the lifetime of the mutable reference to the signal's value.
|
||||
///
|
||||
/// This function enforces the variance of the lifetime parameter `'a` in Mut. Rust will typically infer this cast with a concrete type, but it cannot with a generic type.
|
||||
pub fn downcast_lifetime<'b>(mut_: Self) -> WriteLock<'b, T, S, D>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
WriteLock {
|
||||
write: S::downcast_lifetime_mut(mut_.write),
|
||||
data: mut_.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, D> Deref for WriteLock<'_, T, S, D>
|
||||
where
|
||||
S: AnyStorage,
|
||||
T: ?Sized + 'static,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.write
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, D> DerefMut for WriteLock<'_, T, S, D>
|
||||
where
|
||||
S: AnyStorage,
|
||||
T: ?Sized + 'static,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.write
|
||||
}
|
||||
}
|
||||
|
||||
/// An extension trait for [`Writable`] that provides some convenience methods.
|
||||
pub trait WritableExt: Writable {
|
||||
/// Map the readable type to a new type. This lets you provide a view into a readable type without needing to clone the inner value.
|
||||
///
|
||||
/// Anything that subscribes to the readable value will be rerun whenever the original value changes, even if the view does not change. If you want to memorize the view, you can use a [`crate::Memo`] instead.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn List(list: Signal<Vec<i32>>) -> Element {
|
||||
/// rsx! {
|
||||
/// for index in 0..list.len() {
|
||||
/// // We can use the `map` method to provide a view into the single item in the list that the child component will render
|
||||
/// Item { item: list.map_mut(move |v| &v[index], move |v| &mut v[index]) }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // The child component doesn't need to know that the mapped value is coming from a list
|
||||
/// #[component]
|
||||
/// fn Item(item: WriteSignal<i32>) -> Element {
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| *item.write() += 1,
|
||||
/// "{item}"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn map_mut<O, F, FMut>(self, f: F, f_mut: FMut) -> MappedMutSignal<O, Self, F, FMut>
|
||||
where
|
||||
Self: Clone + Sized + 'static,
|
||||
O: ?Sized + 'static,
|
||||
F: Fn(&Self::Target) -> &O + 'static,
|
||||
FMut: Fn(&mut Self::Target) -> &mut O + 'static,
|
||||
{
|
||||
MappedMutSignal::new(self, f, f_mut)
|
||||
}
|
||||
|
||||
/// Run a function with a mutable reference to the value. If the value has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
@@ -115,7 +348,7 @@ pub trait Writable: Readable {
|
||||
where
|
||||
Self::Target: std::ops::IndexMut<I>,
|
||||
{
|
||||
Self::map_mut(self.write(), |v| v.index_mut(index))
|
||||
WriteLock::map(self.write(), |v| v.index_mut(index))
|
||||
}
|
||||
|
||||
/// Takes the value out of the Signal, leaving a Default in its place.
|
||||
@@ -137,6 +370,25 @@ pub trait Writable: Readable {
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Writable + ?Sized> WritableExt for W {}
|
||||
|
||||
/// An extension trait for [`Writable`] values that can be boxed into a trait object.
|
||||
pub trait WritableBoxedExt: Writable<Storage = UnsyncStorage> {
|
||||
/// Box the writable value into a trait object. This is useful for passing around writable values without knowing their concrete type.
|
||||
fn boxed_mut(self) -> WriteSignal<Self::Target>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
WriteSignal::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Writable<Storage = UnsyncStorage> + 'static> WritableBoxedExt for T {
|
||||
fn boxed_mut(self) -> WriteSignal<Self::Target> {
|
||||
WriteSignal::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// An extension trait for [`Writable<Option<T>>`]` that provides some convenience methods.
|
||||
pub trait WritableOptionExt<T: 'static>: Writable<Target = Option<T>> {
|
||||
/// Gets the value out of the Option, or inserts the given value if the Option is empty.
|
||||
@@ -151,16 +403,16 @@ pub trait WritableOptionExt<T: 'static>: Writable<Target = Option<T>> {
|
||||
let is_none = self.read().is_none();
|
||||
if is_none {
|
||||
self.with_mut(|v| *v = Some(default()));
|
||||
Self::map_mut(self.write(), |v| v.as_mut().unwrap())
|
||||
WriteLock::map(self.write(), |v| v.as_mut().unwrap())
|
||||
} else {
|
||||
Self::map_mut(self.write(), |v| v.as_mut().unwrap())
|
||||
WriteLock::map(self.write(), |v| v.as_mut().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to write the inner value of the Option.
|
||||
#[track_caller]
|
||||
fn as_mut(&mut self) -> Option<WritableRef<'_, Self, T>> {
|
||||
Self::try_map_mut(self.write(), |v: &mut Option<T>| v.as_mut())
|
||||
WriteLock::filter_map(self.write(), |v: &mut Option<T>| v.as_mut())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +488,7 @@ pub trait WritableVecExt<T: 'static>: Writable<Target = Vec<T>> {
|
||||
/// Try to mutably get an element from the vector.
|
||||
#[track_caller]
|
||||
fn get_mut(&mut self, index: usize) -> Option<WritableRef<'_, Self, T>> {
|
||||
Self::try_map_mut(self.write(), |v: &mut Vec<T>| v.get_mut(index))
|
||||
WriteLock::filter_map(self.write(), |v: &mut Vec<T>| v.get_mut(index))
|
||||
}
|
||||
|
||||
/// Gets an iterator over the values of the vector.
|
||||
@@ -264,11 +516,11 @@ impl<'a, T: 'static, R: Writable<Target = Vec<T>>> Iterator for WritableValueIte
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let index = self.index;
|
||||
self.index += 1;
|
||||
R::try_map_mut(
|
||||
WriteLock::filter_map(
|
||||
self.value.try_write_unchecked().unwrap(),
|
||||
|v: &mut Vec<T>| v.get_mut(index),
|
||||
)
|
||||
.map(R::downcast_lifetime_mut)
|
||||
.map(WriteLock::downcast_lifetime)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user