implement readable and callable for all structs in the hooks crate
This commit is contained in:
1 parent
98158bdc6f
commit
ffc49530f6
13 files changed
+167
-491
No files matched your search
Generated
+1
@@ -2492,6 +2492,7 @@ dependencies = [
|
|||||||
"dioxus-signals",
|
"dioxus-signals",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"generational-box",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ thiserror = { workspace = true }
|
|||||||
slab = { workspace = true }
|
slab = { workspace = true }
|
||||||
dioxus-debug-cell = "0.1.1"
|
dioxus-debug-cell = "0.1.1"
|
||||||
futures-util = { workspace = true}
|
futures-util = { workspace = true}
|
||||||
|
generational-box.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures-util = { workspace = true, default-features = false }
|
futures-util = { workspace = true, default-features = false }
|
||||||
|
|||||||
@@ -49,7 +49,43 @@ impl<O: 'static> Copy for UseCallback<O> {}
|
|||||||
|
|
||||||
impl<O> UseCallback<O> {
|
impl<O> UseCallback<O> {
|
||||||
/// Call the callback
|
/// Call the callback
|
||||||
pub fn call(&mut self) -> O {
|
pub fn call(&self) -> O {
|
||||||
self.inner.with_mut(|f| f())
|
(self.inner.write_unchecked())()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This makes UseCallback callable like a normal function
|
||||||
|
impl<O> std::ops::Deref for UseCallback<O> {
|
||||||
|
type Target = dyn Fn() -> O;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
|
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
||||||
|
|
||||||
|
// First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
|
||||||
|
let uninit_callable = MaybeUninit::<Self>::uninit();
|
||||||
|
// Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
|
||||||
|
let uninit_closure = move || Self::call(unsafe { &*uninit_callable.as_ptr() });
|
||||||
|
|
||||||
|
// Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
|
||||||
|
let size_of_closure = std::mem::size_of_val(&uninit_closure);
|
||||||
|
assert_eq!(size_of_closure, std::mem::size_of::<Self>());
|
||||||
|
|
||||||
|
// Then cast the lifetime of the closure to the lifetime of &self.
|
||||||
|
fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
let reference_to_closure = cast_lifetime(
|
||||||
|
{
|
||||||
|
// The real closure that we will never use.
|
||||||
|
&uninit_closure
|
||||||
|
},
|
||||||
|
// We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
|
||||||
|
unsafe { std::mem::transmute(self) },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cast the closure to a trait object.
|
||||||
|
reference_to_closure as &_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ use dioxus_core::{prelude::*, Task};
|
|||||||
use dioxus_signals::*;
|
use dioxus_signals::*;
|
||||||
use dioxus_signals::{Readable, Writable};
|
use dioxus_signals::{Readable, Writable};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
/// A hook that allows you to spawn a future.
|
/// A hook that allows you to spawn a future.
|
||||||
/// This future will **not** run on the server
|
/// This future will **not** run on the server
|
||||||
@@ -39,7 +40,7 @@ where
|
|||||||
{
|
{
|
||||||
let mut state = use_signal(|| UseFutureState::Pending);
|
let mut state = use_signal(|| UseFutureState::Pending);
|
||||||
|
|
||||||
let mut callback = use_callback(move || {
|
let callback = use_callback(move || {
|
||||||
let fut = future();
|
let fut = future();
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
state.set(UseFutureState::Pending);
|
state.set(UseFutureState::Pending);
|
||||||
@@ -147,3 +148,37 @@ impl UseFuture {
|
|||||||
self.state.into()
|
self.state.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<UseFuture> for ReadOnlySignal<UseFutureState> {
|
||||||
|
fn from(val: UseFuture) -> Self {
|
||||||
|
val.state.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Readable for UseFuture {
|
||||||
|
type Target = UseFutureState;
|
||||||
|
type Storage = UnsyncStorage;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn try_read_unchecked(
|
||||||
|
&self,
|
||||||
|
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||||
|
self.state.try_read_unchecked()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn peek_unchecked(&self) -> ReadableRef<'static, Self> {
|
||||||
|
self.state.peek_unchecked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow calling a signal with signal() syntax
|
||||||
|
///
|
||||||
|
/// Currently only limited to copy types, though could probably specialize for string/arc/rc
|
||||||
|
impl Deref for UseFuture {
|
||||||
|
type Target = dyn Fn() -> UseFutureState;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
Readable::deref_impl(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,8 +25,9 @@ use futures_util::StreamExt;
|
|||||||
/// ```
|
/// ```
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn use_memo<R: PartialEq>(f: impl FnMut() -> R + 'static) -> Memo<R> {
|
pub fn use_memo<R: PartialEq>(f: impl FnMut() -> R + 'static) -> Memo<R> {
|
||||||
let mut callback = use_callback(f);
|
let callback = use_callback(f);
|
||||||
use_hook(|| Signal::memo(move || callback.call()))
|
#[allow(clippy::redundant_closure)]
|
||||||
|
use_hook(|| Signal::memo(move || callback()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes
|
/// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use crate::{use_callback, use_signal, UseCallback};
|
use crate::{use_callback, use_signal, UseCallback};
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core::{
|
use dioxus_core::{
|
||||||
prelude::{spawn, use_hook},
|
prelude::{spawn, use_hook},
|
||||||
Task,
|
Task,
|
||||||
};
|
};
|
||||||
use dioxus_signals::*;
|
use dioxus_signals::*;
|
||||||
use futures_util::{future, pin_mut, FutureExt, StreamExt};
|
use futures_util::{future, pin_mut, FutureExt, StreamExt};
|
||||||
|
use std::ops::Deref;
|
||||||
use std::{cell::Cell, future::Future, rc::Rc};
|
use std::{cell::Cell, future::Future, rc::Rc};
|
||||||
|
|
||||||
/// A memo that resolve to a value asynchronously.
|
/// A memo that resolve to a value asynchronously.
|
||||||
@@ -49,7 +51,7 @@ where
|
|||||||
(rc, Rc::new(Cell::new(Some(changed))))
|
(rc, Rc::new(Cell::new(Some(changed))))
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut cb = use_callback(move || {
|
let cb = use_callback(move || {
|
||||||
// Create the user's task
|
// Create the user's task
|
||||||
#[allow(clippy::redundant_closure)]
|
#[allow(clippy::redundant_closure)]
|
||||||
let fut = rc.run_in(|| future());
|
let fut = rc.run_in(|| future());
|
||||||
@@ -70,7 +72,7 @@ where
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut task = use_hook(|| Signal::new(cb.call()));
|
let mut task = use_hook(|| Signal::new(cb()));
|
||||||
|
|
||||||
use_hook(|| {
|
use_hook(|| {
|
||||||
let mut changed = changed.take().unwrap();
|
let mut changed = changed.take().unwrap();
|
||||||
@@ -83,7 +85,7 @@ where
|
|||||||
task.write().cancel();
|
task.write().cancel();
|
||||||
|
|
||||||
// Start a new task
|
// Start a new task
|
||||||
task.set(cb.call());
|
task.set(cb());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -181,10 +183,54 @@ impl<T> Resource<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> std::ops::Deref for Resource<T> {
|
impl<T> From<Resource<T>> for ReadOnlySignal<Option<T>> {
|
||||||
type Target = Signal<Option<T>>;
|
fn from(val: Resource<T>) -> Self {
|
||||||
|
val.value.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Readable for Resource<T> {
|
||||||
|
type Target = Option<T>;
|
||||||
|
type Storage = UnsyncStorage;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn try_read_unchecked(
|
||||||
|
&self,
|
||||||
|
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||||
|
self.value.try_read_unchecked()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn peek_unchecked(&self) -> ReadableRef<'static, Self> {
|
||||||
|
self.value.peek_unchecked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoAttributeValue for Resource<T>
|
||||||
|
where
|
||||||
|
T: Clone + IntoAttributeValue,
|
||||||
|
{
|
||||||
|
fn into_value(self) -> dioxus_core::AttributeValue {
|
||||||
|
self.with(|f| f.clone().into_value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoDynNode for Resource<T>
|
||||||
|
where
|
||||||
|
T: Clone + IntoDynNode,
|
||||||
|
{
|
||||||
|
fn into_dyn_node(self) -> dioxus_core::DynamicNode {
|
||||||
|
self().into_dyn_node()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow calling a signal with signal() syntax
|
||||||
|
///
|
||||||
|
/// Currently only limited to copy types, though could probably specialize for string/arc/rc
|
||||||
|
impl<T: Clone> Deref for Resource<T> {
|
||||||
|
type Target = dyn Fn() -> Option<T>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.value
|
Readable::deref_impl(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
use self::error::{UseSharedStateError, UseSharedStateResult};
|
|
||||||
use dioxus_core::ScopeId;
|
|
||||||
use std::{collections::HashSet, rc::Rc, sync::Arc};
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
pub use dioxus_debug_cell::{
|
|
||||||
error::{BorrowError, BorrowMutError},
|
|
||||||
Ref, RefCell, RefMut,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
pub use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut};
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! debug_location {
|
|
||||||
() => {{
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
std::panic::Location::caller()
|
|
||||||
}
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
{
|
|
||||||
()
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod error {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String {
|
|
||||||
locations
|
|
||||||
.iter()
|
|
||||||
.map(|location| format!(" - {location}"))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
}
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum UseSharedStateError {
|
|
||||||
#[cfg_attr(
|
|
||||||
debug_assertions,
|
|
||||||
error(
|
|
||||||
"[{0}] {1} is already borrowed at, so it cannot be borrowed mutably. Previous borrows:\n[{2}]\n\n",
|
|
||||||
.source.attempted_at,
|
|
||||||
.type_name,
|
|
||||||
locations_display(&.source.already_borrowed_at)
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[cfg_attr(
|
|
||||||
not(debug_assertions),
|
|
||||||
error("{type_name} is already borrowed, so it cannot be borrowed mutably. (More detail available in debug mode)")
|
|
||||||
)]
|
|
||||||
AlreadyBorrowed {
|
|
||||||
source: super::BorrowMutError,
|
|
||||||
type_name: &'static str,
|
|
||||||
},
|
|
||||||
#[cfg_attr(
|
|
||||||
debug_assertions,
|
|
||||||
error(
|
|
||||||
"[{0}] {1} is already borrowed mutably at [{2}], so it cannot be borrowed anymore.",
|
|
||||||
.source.attempted_at,
|
|
||||||
.type_name,
|
|
||||||
locations_display(&.source.already_borrowed_at)
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[cfg_attr(
|
|
||||||
not(debug_assertions),
|
|
||||||
error("{type_name} is already borrowed mutably, so it cannot be borrowed anymore. (More detail available in debug mode)")
|
|
||||||
)]
|
|
||||||
AlreadyBorrowedMutably {
|
|
||||||
source: super::BorrowError,
|
|
||||||
type_name: &'static str,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type UseSharedStateResult<T> = Result<T, UseSharedStateError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProvidedState<T> = Rc<RefCell<ProvidedStateInner<T>>>;
|
|
||||||
|
|
||||||
// Tracks all the subscribers to a shared State
|
|
||||||
pub(crate) struct ProvidedStateInner<T> {
|
|
||||||
value: T,
|
|
||||||
notify_any: Arc<dyn Fn(ScopeId)>,
|
|
||||||
consumers: HashSet<ScopeId>,
|
|
||||||
gen: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ProvidedStateInner<T> {
|
|
||||||
pub(crate) fn notify_consumers(&mut self) {
|
|
||||||
self.gen += 1;
|
|
||||||
for consumer in self.consumers.iter() {
|
|
||||||
(self.notify_any)(*consumer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This hook provides some relatively light ergonomics around shared state.
|
|
||||||
///
|
|
||||||
/// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
|
|
||||||
/// ergonomics in a pinch, with zero cost.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use dioxus::prelude::*;
|
|
||||||
/// #
|
|
||||||
/// # fn app() -> Element {
|
|
||||||
/// # rsx! {
|
|
||||||
/// # Parent{}
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
///
|
|
||||||
/// #[derive(Clone, Copy)]
|
|
||||||
/// enum Theme {
|
|
||||||
/// Light,
|
|
||||||
/// Dark,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Provider
|
|
||||||
/// fn Parent<'a>(cx: Scope<'a>) -> Element {
|
|
||||||
/// use_shared_state_provider(|| Theme::Dark);
|
|
||||||
/// let theme = use_shared_state::<Theme>().unwrap();
|
|
||||||
///
|
|
||||||
/// rsx! {
|
|
||||||
/// button{
|
|
||||||
/// onclick: move |_| {
|
|
||||||
/// let current_theme = *theme.read();
|
|
||||||
/// *theme.write() = match current_theme {
|
|
||||||
/// Theme::Dark => Theme::Light,
|
|
||||||
/// Theme::Light => Theme::Dark,
|
|
||||||
/// };
|
|
||||||
/// },
|
|
||||||
/// "Change theme"
|
|
||||||
/// }
|
|
||||||
/// Child{}
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Consumer
|
|
||||||
/// fn Child<'a>(cx: Scope<'a>) -> Element {
|
|
||||||
/// let theme = use_shared_state::<Theme>().unwrap();
|
|
||||||
/// let current_theme = *theme.read();
|
|
||||||
///
|
|
||||||
/// rsx! {
|
|
||||||
/// match current_theme {
|
|
||||||
/// Theme::Dark => {
|
|
||||||
/// "Dark mode"
|
|
||||||
/// }
|
|
||||||
/// Theme::Light => {
|
|
||||||
/// "Light mode"
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # How it works
|
|
||||||
///
|
|
||||||
/// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider.
|
|
||||||
///
|
|
||||||
/// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified.
|
|
||||||
#[must_use]
|
|
||||||
pub fn use_shared_state<T: 'static>() -> Option<&UseSharedState<T>> {
|
|
||||||
let state_owner: &mut Option<UseSharedStateOwner<T>> = &mut *cx.use_hook(move || {
|
|
||||||
let scope_id = cx.scope_id();
|
|
||||||
let root = cx.consume_context::<ProvidedState<T>>()?;
|
|
||||||
|
|
||||||
root.borrow_mut().consumers.insert(scope_id);
|
|
||||||
|
|
||||||
let state = UseSharedState::new(root);
|
|
||||||
let owner = UseSharedStateOwner { state, scope_id };
|
|
||||||
Some(owner)
|
|
||||||
});
|
|
||||||
state_owner.as_mut().map(|s| {
|
|
||||||
s.state.gen = s.state.inner.borrow().gen;
|
|
||||||
&s.state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This wrapper detects when the hook is dropped and will unsubscribe when the component is unmounted
|
|
||||||
struct UseSharedStateOwner<T> {
|
|
||||||
state: UseSharedState<T>,
|
|
||||||
scope_id: ScopeId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for UseSharedStateOwner<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// we need to unsubscribe when our component is unmounted
|
|
||||||
let mut root = self.state.inner.borrow_mut();
|
|
||||||
root.consumers.remove(&self.scope_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State that is shared between components through the context system
|
|
||||||
pub struct UseSharedState<T> {
|
|
||||||
pub(crate) inner: Rc<RefCell<ProvidedStateInner<T>>>,
|
|
||||||
gen: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> UseSharedState<T> {
|
|
||||||
fn new(inner: Rc<RefCell<ProvidedStateInner<T>>>) -> Self {
|
|
||||||
let gen = inner.borrow().gen;
|
|
||||||
Self { inner, gen }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notify all consumers of the state that it has changed. (This is called automatically when you call "write")
|
|
||||||
pub fn notify_consumers(&self) {
|
|
||||||
self.inner.borrow_mut().notify_consumers();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try reading the shared state
|
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
|
||||||
#[cfg_attr(debug_assertions, inline(never))]
|
|
||||||
pub fn try_read(&self) -> UseSharedStateResult<Ref<'_, T>> {
|
|
||||||
match self.inner.try_borrow() {
|
|
||||||
Ok(value) => Ok(Ref::map(value, |inner| &inner.value)),
|
|
||||||
Err(source) => Err(UseSharedStateError::AlreadyBorrowedMutably {
|
|
||||||
source,
|
|
||||||
type_name: std::any::type_name::<Self>(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the shared value
|
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
|
||||||
#[cfg_attr(debug_assertions, inline(never))]
|
|
||||||
pub fn read(&self) -> Ref<'_, T> {
|
|
||||||
match self.try_read() {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(message) => panic!(
|
|
||||||
"Reading the shared state failed: {}\n({:?})",
|
|
||||||
message, message
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try writing the shared state
|
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
|
||||||
#[cfg_attr(debug_assertions, inline(never))]
|
|
||||||
pub fn try_write(&self) -> UseSharedStateResult<RefMut<'_, T>> {
|
|
||||||
match self.inner.try_borrow_mut() {
|
|
||||||
Ok(mut value) => {
|
|
||||||
value.notify_consumers();
|
|
||||||
Ok(RefMut::map(value, |inner| &mut inner.value))
|
|
||||||
}
|
|
||||||
Err(source) => Err(UseSharedStateError::AlreadyBorrowed {
|
|
||||||
source,
|
|
||||||
type_name: std::any::type_name::<Self>(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calling "write" will force the component to re-render
|
|
||||||
///
|
|
||||||
///
|
|
||||||
// TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock
|
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
|
||||||
#[cfg_attr(debug_assertions, inline(never))]
|
|
||||||
pub fn write(&self) -> RefMut<'_, T> {
|
|
||||||
match self.try_write() {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(message) => panic!(
|
|
||||||
"Writing to shared state failed: {}\n({:?})",
|
|
||||||
message, message
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries writing the value without forcing a re-render
|
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
|
||||||
#[cfg_attr(debug_assertions, inline(never))]
|
|
||||||
pub fn try_write_silent(&self) -> UseSharedStateResult<RefMut<'_, T>> {
|
|
||||||
match self.inner.try_borrow_mut() {
|
|
||||||
Ok(value) => Ok(RefMut::map(value, |inner| &mut inner.value)),
|
|
||||||
Err(source) => Err(UseSharedStateError::AlreadyBorrowed {
|
|
||||||
source,
|
|
||||||
type_name: std::any::type_name::<Self>(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes the value without forcing a re-render
|
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
|
||||||
#[cfg_attr(debug_assertions, inline(never))]
|
|
||||||
pub fn write_silent(&self) -> RefMut<'_, T> {
|
|
||||||
match self.try_write_silent() {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(message) => panic!(
|
|
||||||
"Writing to shared state silently failed: {}\n({:?})",
|
|
||||||
message, message
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take a reference to the inner value temporarily and produce a new value
|
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
|
||||||
#[cfg_attr(debug_assertions, inline(never))]
|
|
||||||
pub fn with<O>(&self, immutable_callback: impl FnOnce(&T) -> O) -> O {
|
|
||||||
immutable_callback(&*self.read())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take a mutable reference to the inner value temporarily and produce a new value
|
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
|
||||||
#[cfg_attr(debug_assertions, inline(never))]
|
|
||||||
pub fn with_mut<O>(&self, mutable_callback: impl FnOnce(&mut T) -> O) -> O {
|
|
||||||
mutable_callback(&mut *self.write())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Clone for UseSharedState<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: self.inner.clone(),
|
|
||||||
gen: self.gen,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PartialEq for UseSharedState<T> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.gen == other.gen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provide some state for components down the hierarchy to consume without having to drill props. See [`use_shared_state`] to consume the state
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use dioxus::prelude::*;
|
|
||||||
/// #
|
|
||||||
/// # fn app() -> Element {
|
|
||||||
/// # rsx! {
|
|
||||||
/// # Parent{}
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
///
|
|
||||||
/// #[derive(Clone, Copy)]
|
|
||||||
/// enum Theme {
|
|
||||||
/// Light,
|
|
||||||
/// Dark,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Provider
|
|
||||||
/// fn Parent<'a>(cx: Scope<'a>) -> Element {
|
|
||||||
/// use_shared_state_provider(|| Theme::Dark);
|
|
||||||
/// let theme = use_shared_state::<Theme>().unwrap();
|
|
||||||
///
|
|
||||||
/// rsx! {
|
|
||||||
/// button{
|
|
||||||
/// onclick: move |_| {
|
|
||||||
/// let current_theme = *theme.read();
|
|
||||||
/// *theme.write() = match current_theme {
|
|
||||||
/// Theme::Dark => Theme::Light,
|
|
||||||
/// Theme::Light => Theme::Dark,
|
|
||||||
/// };
|
|
||||||
/// },
|
|
||||||
/// "Change theme"
|
|
||||||
/// }
|
|
||||||
/// // Children components that consume the state...
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn use_shared_state_provider<T: 'static>(f: impl FnOnce() -> T) {
|
|
||||||
cx.use_hook(|| {
|
|
||||||
let state: ProvidedState<T> = Rc::new(RefCell::new(ProvidedStateInner {
|
|
||||||
value: f(),
|
|
||||||
notify_any: cx.schedule_update_any(),
|
|
||||||
consumers: HashSet::new(),
|
|
||||||
gen: 0,
|
|
||||||
}));
|
|
||||||
|
|
||||||
cx.provide_context(state);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ use generational_box::UnsyncStorage;
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::mem::MaybeUninit;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
@@ -268,31 +267,6 @@ impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
|
|||||||
type Target = dyn Fn() -> T;
|
type Target = dyn Fn() -> T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
Readable::deref_impl(self)
|
||||||
|
|
||||||
// First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
|
|
||||||
let uninit_callable = MaybeUninit::<Self>::uninit();
|
|
||||||
// Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
|
|
||||||
let uninit_closure = move || *Self::read(unsafe { &*uninit_callable.as_ptr() });
|
|
||||||
|
|
||||||
// Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
|
|
||||||
let size_of_closure = std::mem::size_of_val(&uninit_closure);
|
|
||||||
assert_eq!(size_of_closure, std::mem::size_of::<Self>());
|
|
||||||
|
|
||||||
// Then cast the lifetime of the closure to the lifetime of &self.
|
|
||||||
fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
|
|
||||||
b
|
|
||||||
}
|
|
||||||
let reference_to_closure = cast_lifetime(
|
|
||||||
{
|
|
||||||
// The real closure that we will never use.
|
|
||||||
&uninit_closure
|
|
||||||
},
|
|
||||||
// We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
|
|
||||||
unsafe { std::mem::transmute(self) },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cast the closure to a trait object.
|
|
||||||
reference_to_closure as &Self::Target
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{read::Readable, Memo, ReadableRef};
|
use crate::{read::Readable, Memo, ReadableRef};
|
||||||
use dioxus_core::prelude::{IntoAttributeValue, ScopeId};
|
use dioxus_core::prelude::ScopeId;
|
||||||
use generational_box::UnsyncStorage;
|
use generational_box::UnsyncStorage;
|
||||||
use std::{mem::MaybeUninit, ops::Deref};
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::Signal;
|
use crate::Signal;
|
||||||
|
|
||||||
@@ -68,15 +68,6 @@ impl<T: PartialEq + 'static> Readable for GlobalMemo<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PartialEq + 'static> IntoAttributeValue for GlobalMemo<T>
|
|
||||||
where
|
|
||||||
T: Clone + IntoAttributeValue,
|
|
||||||
{
|
|
||||||
fn into_value(self) -> dioxus_core::AttributeValue {
|
|
||||||
self.memo().into_value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq + 'static> PartialEq for GlobalMemo<T> {
|
impl<T: PartialEq + 'static> PartialEq for GlobalMemo<T> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
std::ptr::eq(self, other)
|
std::ptr::eq(self, other)
|
||||||
@@ -90,31 +81,6 @@ impl<T: PartialEq + Clone + 'static> Deref for GlobalMemo<T> {
|
|||||||
type Target = dyn Fn() -> T;
|
type Target = dyn Fn() -> T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
Readable::deref_impl(self)
|
||||||
|
|
||||||
// First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
|
|
||||||
let uninit_callable = MaybeUninit::<Self>::uninit();
|
|
||||||
// Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
|
|
||||||
let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone();
|
|
||||||
|
|
||||||
// Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
|
|
||||||
let size_of_closure = std::mem::size_of_val(&uninit_closure);
|
|
||||||
assert_eq!(size_of_closure, std::mem::size_of::<Self>());
|
|
||||||
|
|
||||||
// Then cast the lifetime of the closure to the lifetime of &self.
|
|
||||||
fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
|
|
||||||
b
|
|
||||||
}
|
|
||||||
let reference_to_closure = cast_lifetime(
|
|
||||||
{
|
|
||||||
// The real closure that we will never use.
|
|
||||||
&uninit_closure
|
|
||||||
},
|
|
||||||
// We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self.
|
|
||||||
unsafe { std::mem::transmute(self) },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cast the closure to a trait object.
|
|
||||||
reference_to_closure as &Self::Target
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::write::Writable;
|
use crate::write::Writable;
|
||||||
use crate::{read::Readable, ReadableRef};
|
use crate::{read::Readable, ReadableRef};
|
||||||
use crate::{WritableRef, Write};
|
use crate::{WritableRef, Write};
|
||||||
use dioxus_core::prelude::{IntoAttributeValue, ScopeId};
|
use dioxus_core::prelude::ScopeId;
|
||||||
use generational_box::UnsyncStorage;
|
use generational_box::UnsyncStorage;
|
||||||
use std::{mem::MaybeUninit, ops::Deref};
|
use std::ops::Deref;
|
||||||
|
|
||||||
use super::get_global_context;
|
use super::get_global_context;
|
||||||
use crate::Signal;
|
use crate::Signal;
|
||||||
@@ -118,15 +118,6 @@ impl<T: 'static> Writable for GlobalSignal<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> IntoAttributeValue for GlobalSignal<T>
|
|
||||||
where
|
|
||||||
T: Clone + IntoAttributeValue,
|
|
||||||
{
|
|
||||||
fn into_value(self) -> dioxus_core::AttributeValue {
|
|
||||||
self.signal().into_value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> PartialEq for GlobalSignal<T> {
|
impl<T: 'static> PartialEq for GlobalSignal<T> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
std::ptr::eq(self, other)
|
std::ptr::eq(self, other)
|
||||||
@@ -140,34 +131,6 @@ impl<T: Clone + 'static> Deref for GlobalSignal<T> {
|
|||||||
type Target = dyn Fn() -> T;
|
type Target = dyn Fn() -> T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
Readable::deref_impl(self)
|
||||||
|
|
||||||
// First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
|
|
||||||
let uninit_callable = MaybeUninit::<Self>::uninit();
|
|
||||||
|
|
||||||
// Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
|
|
||||||
let uninit_closure = move || {
|
|
||||||
<GlobalSignal<T> as Readable>::read(unsafe { &*uninit_callable.as_ptr() }).clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
|
|
||||||
let size_of_closure = std::mem::size_of_val(&uninit_closure);
|
|
||||||
assert_eq!(size_of_closure, std::mem::size_of::<Self>());
|
|
||||||
|
|
||||||
// Then cast the lifetime of the closure to the lifetime of &self.
|
|
||||||
fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
|
|
||||||
b
|
|
||||||
}
|
|
||||||
let reference_to_closure = cast_lifetime(
|
|
||||||
{
|
|
||||||
// The real closure that we will never use.
|
|
||||||
&uninit_closure
|
|
||||||
},
|
|
||||||
// We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self.
|
|
||||||
unsafe { std::mem::transmute(self) },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cast the closure to a trait object.
|
|
||||||
reference_to_closure as &Self::Target
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,6 +204,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> IntoDynNode for Memo<T>
|
||||||
|
where
|
||||||
|
T: Clone + IntoDynNode + PartialEq,
|
||||||
|
{
|
||||||
|
fn into_dyn_node(self) -> dioxus_core::DynamicNode {
|
||||||
|
self().into_dyn_node()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: 'static> PartialEq for Memo<T> {
|
impl<T: 'static> PartialEq for Memo<T> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.inner == other.inner
|
self.inner == other.inner
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{read::Readable, ReadableRef, Signal, SignalData};
|
use crate::{read::Readable, ReadableRef, Signal, SignalData};
|
||||||
|
use dioxus_core::IntoDynNode;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use dioxus_core::{prelude::IntoAttributeValue, ScopeId};
|
use dioxus_core::{prelude::IntoAttributeValue, ScopeId};
|
||||||
@@ -105,6 +106,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
impl<T: 'static, S: Storage<SignalData<T>>> PartialEq for ReadOnlySignal<T, S> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.inner == other.inner
|
self.inner == other.inner
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::{
|
|||||||
ReadableRef,
|
ReadableRef,
|
||||||
};
|
};
|
||||||
use crate::{Memo, WritableRef};
|
use crate::{Memo, WritableRef};
|
||||||
|
use dioxus_core::IntoDynNode;
|
||||||
use dioxus_core::{prelude::IntoAttributeValue, ScopeId};
|
use dioxus_core::{prelude::IntoAttributeValue, ScopeId};
|
||||||
use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage};
|
use generational_box::{AnyStorage, Storage, SyncStorage, UnsyncStorage};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -245,6 +246,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> IntoDynNode for Signal<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 Signal<T, S> {
|
impl<T: 'static, S: Storage<SignalData<T>>> PartialEq for Signal<T, S> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.inner == other.inner
|
self.inner == other.inner
|
||||||
|
|||||||
Reference in new issue
Block a user