mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-14 00:17:17 +00:00
Move to a generic GlobalLazy<T> (#2851)
* Expose a generic lazy type * Switch to generic lazy globals * simplify global lazy context a bit * rename LazyGlobal to Global * use Memo::global in more examples * Fix soundness issue with deref_impl. It relies on the size of self, so it cannot be safe * add a comment about safety * Make clippy happy * fix formatting * Restore changes to signal impl * Add helper methods for global signal and global memo to make getting the inner value easier
This commit is contained in:
parent
668d5415c8
commit
b47a6cf83e
13 changed files with 281 additions and 289 deletions
|
@ -10,7 +10,7 @@ use dioxus::prelude::*;
|
||||||
const STYLE: &str = asset!("./examples/assets/counter.css");
|
const STYLE: &str = asset!("./examples/assets/counter.css");
|
||||||
|
|
||||||
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
|
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
|
||||||
static DOUBLED_COUNT: GlobalMemo<i32> = Signal::global_memo(|| COUNT() * 2);
|
static DOUBLED_COUNT: GlobalMemo<i32> = Memo::global(|| COUNT() * 2);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
launch(app);
|
launch(app);
|
||||||
|
@ -52,7 +52,7 @@ fn Display() -> Element {
|
||||||
fn Reset() -> Element {
|
fn Reset() -> Element {
|
||||||
// Not all write methods are available on global signals since `write` requires a mutable reference. In these cases,
|
// Not all write methods are available on global signals since `write` requires a mutable reference. In these cases,
|
||||||
// We can simply pull out the actual signal using the signal() method.
|
// We can simply pull out the actual signal using the signal() method.
|
||||||
let mut as_signal = use_hook(|| COUNT.signal());
|
let mut as_signal = use_hook(|| COUNT.resolve());
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
button { onclick: move |_| as_signal.set(0), "Reset" }
|
button { onclick: move |_| as_signal.set(0), "Reset" }
|
||||||
|
|
|
@ -189,6 +189,6 @@ impl Deref for UseFuture {
|
||||||
type Target = dyn Fn() -> UseFutureState;
|
type Target = dyn Fn() -> UseFutureState;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
Readable::deref_impl(self)
|
unsafe { Readable::deref_impl(self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -473,6 +473,6 @@ impl<T: Clone> Deref for Resource<T> {
|
||||||
type Target = dyn Fn() -> Option<T>;
|
type Target = dyn Fn() -> Option<T>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
Readable::deref_impl(self)
|
unsafe { Readable::deref_impl(self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,13 +167,13 @@ impl ToTokens for TemplateBody {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
__template.maybe_with_rt(|__template_read| {
|
|
||||||
// If the template has not been hot reloaded, we always use the original template
|
// If the template has not been hot reloaded, we always use the original template
|
||||||
// Templates nested within macros may be merged because they have the same file-line-column-index
|
// Templates nested within macros may be merged because they have the same file-line-column-index
|
||||||
// They cannot be hot reloaded, so this prevents incorrect rendering
|
// They cannot be hot reloaded, so this prevents incorrect rendering
|
||||||
let __template_read = match __template_read.as_ref() {
|
let __template_read = dioxus_core::Runtime::current().ok().map(|_| __template.read());
|
||||||
Some(__template_read) => __template_read,
|
let __template_read = match __template_read.as_ref().map(|__template_read| __template_read.as_ref()) {
|
||||||
None => __original_template(),
|
Some(Some(__template_read)) => &__template_read,
|
||||||
|
_ => __original_template(),
|
||||||
};
|
};
|
||||||
let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(
|
let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(
|
||||||
vec![ #( #dynamic_text.to_string() ),* ],
|
vec![ #( #dynamic_text.to_string() ),* ],
|
||||||
|
@ -184,7 +184,6 @@ impl ToTokens for TemplateBody {
|
||||||
__dynamic_literal_pool
|
__dynamic_literal_pool
|
||||||
);
|
);
|
||||||
__dynamic_value_pool.render_with(__template_read)
|
__dynamic_value_pool.render_with(__template_read)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
{
|
{
|
||||||
|
|
|
@ -193,7 +193,7 @@ 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 {
|
||||||
Readable::deref_impl(self)
|
unsafe { Readable::deref_impl(self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,93 +1,26 @@
|
||||||
use crate::{read::Readable, Memo, ReadableRef};
|
use super::{Global, InitializeFromFunction};
|
||||||
use crate::{read_impls, GlobalKey};
|
use crate::read::Readable;
|
||||||
use dioxus_core::prelude::ScopeId;
|
use crate::read_impls;
|
||||||
use generational_box::{BorrowResult, UnsyncStorage};
|
use crate::Memo;
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use crate::Signal;
|
impl<T: PartialEq> InitializeFromFunction<T> for Memo<T> {
|
||||||
|
fn initialize_from_function(f: fn() -> T) -> Self {
|
||||||
use super::get_global_context;
|
Memo::new(f)
|
||||||
|
|
||||||
/// A signal that can be accessed from anywhere in the application and created in a static
|
|
||||||
pub struct GlobalMemo<T: 'static> {
|
|
||||||
selector: fn() -> T,
|
|
||||||
key: GlobalKey<'static>,
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A memo that can be accessed from anywhere in the application and created in a static
|
||||||
|
pub type GlobalMemo<T> = Global<Memo<T>, T>;
|
||||||
|
|
||||||
impl<T: PartialEq + 'static> GlobalMemo<T> {
|
impl<T: PartialEq + 'static> GlobalMemo<T> {
|
||||||
#[track_caller]
|
|
||||||
/// Create a new global signal
|
|
||||||
pub const fn new(selector: fn() -> T) -> GlobalMemo<T>
|
|
||||||
where
|
|
||||||
T: PartialEq,
|
|
||||||
{
|
|
||||||
let key = std::panic::Location::caller();
|
|
||||||
GlobalMemo {
|
|
||||||
selector,
|
|
||||||
key: GlobalKey::new(key),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the key for this global
|
|
||||||
pub fn key(&self) -> GlobalKey<'static> {
|
|
||||||
self.key.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the signal that backs this global.
|
|
||||||
pub fn memo(&self) -> Memo<T> {
|
|
||||||
let key = self.key();
|
|
||||||
|
|
||||||
let context = get_global_context();
|
|
||||||
|
|
||||||
let read = context.signal.borrow();
|
|
||||||
match read.get(&key) {
|
|
||||||
Some(signal) => *signal.downcast_ref::<Memo<T>>().unwrap(),
|
|
||||||
None => {
|
|
||||||
drop(read);
|
|
||||||
// Constructors are always run in the root scope
|
|
||||||
let signal = ScopeId::ROOT.in_runtime(|| Signal::memo(self.selector));
|
|
||||||
context.signal.borrow_mut().insert(key, Box::new(signal));
|
|
||||||
signal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the scope the signal was created in.
|
|
||||||
pub fn origin_scope(&self) -> ScopeId {
|
|
||||||
ScopeId::ROOT
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the generational id of the signal.
|
/// Get the generational id of the signal.
|
||||||
pub fn id(&self) -> generational_box::GenerationalBoxId {
|
pub fn id(&self) -> generational_box::GenerationalBoxId {
|
||||||
self.memo().id()
|
self.resolve().id()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PartialEq + 'static> Readable for GlobalMemo<T> {
|
/// Resolve the global memo. This will try to get the existing value from the current virtual dom, and if it doesn't exist, it will create a new one.
|
||||||
type Target = T;
|
pub fn memo(&self) -> Memo<T> {
|
||||||
type Storage = UnsyncStorage;
|
self.resolve()
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn try_read_unchecked(
|
|
||||||
&self,
|
|
||||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
|
||||||
self.memo().try_read_unchecked()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
|
||||||
self.memo().try_peek_unchecked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow calling a signal with memo() syntax
|
|
||||||
///
|
|
||||||
/// Currently only limited to copy types, though could probably specialize for string/arc/rc
|
|
||||||
impl<T: PartialEq + Clone + 'static> Deref for GlobalMemo<T> {
|
|
||||||
type Target = dyn Fn() -> T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
Readable::deref_impl(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use dioxus_core::prelude::{provide_root_context, try_consume_context};
|
use dioxus_core::ScopeId;
|
||||||
use std::{any::Any, cell::RefCell, collections::HashMap, panic::Location, rc::Rc};
|
use generational_box::BorrowResult;
|
||||||
|
use std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc};
|
||||||
|
|
||||||
mod memo;
|
mod memo;
|
||||||
pub use memo::*;
|
pub use memo::*;
|
||||||
|
@ -7,12 +8,182 @@ pub use memo::*;
|
||||||
mod signal;
|
mod signal;
|
||||||
pub use signal::*;
|
pub use signal::*;
|
||||||
|
|
||||||
use crate::Signal;
|
use crate::{Readable, ReadableRef, Signal, Writable, WritableRef};
|
||||||
|
|
||||||
|
/// A trait for an item that can be constructed from an initialization function
|
||||||
|
pub trait InitializeFromFunction<T> {
|
||||||
|
/// Create an instance of this type from an initialization function
|
||||||
|
fn initialize_from_function(f: fn() -> T) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> InitializeFromFunction<T> for T {
|
||||||
|
fn initialize_from_function(f: fn() -> T) -> Self {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A lazy value that is created once per application and can be accessed from anywhere in that application
|
||||||
|
pub struct Global<T, R = T> {
|
||||||
|
constructor: fn() -> R,
|
||||||
|
key: GlobalKey<'static>,
|
||||||
|
phantom: std::marker::PhantomData<fn() -> T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow calling a signal with signal() syntax
|
||||||
|
///
|
||||||
|
/// Currently only limited to copy types, though could probably specialize for string/arc/rc
|
||||||
|
impl<T: Clone + 'static, R: Clone + 'static> Deref for Global<T, R>
|
||||||
|
where
|
||||||
|
T: Readable<Target = R> + InitializeFromFunction<R>,
|
||||||
|
{
|
||||||
|
type Target = dyn Fn() -> R;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { Readable::deref_impl(self) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + 'static, R: 'static> Readable for Global<T, R>
|
||||||
|
where
|
||||||
|
T: Readable<Target = R> + InitializeFromFunction<R>,
|
||||||
|
{
|
||||||
|
type Target = R;
|
||||||
|
type Storage = T::Storage;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn try_read_unchecked(
|
||||||
|
&self,
|
||||||
|
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
||||||
|
self.resolve().try_read_unchecked()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
||||||
|
self.resolve().try_peek_unchecked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn try_write_unchecked(
|
||||||
|
&self,
|
||||||
|
) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {
|
||||||
|
self.resolve().try_write_unchecked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + 'static, R: 'static> Global<T, R>
|
||||||
|
where
|
||||||
|
T: Writable<Target = R> + InitializeFromFunction<R>,
|
||||||
|
{
|
||||||
|
/// Write this value
|
||||||
|
pub fn write(&self) -> T::Mut<'static, R> {
|
||||||
|
self.resolve().try_write_unchecked().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a closure with a mutable reference to the signal's value.
|
||||||
|
/// If the signal has been dropped, this will panic.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn with_mut<O>(&self, f: impl FnOnce(&mut R) -> O) -> O {
|
||||||
|
self.resolve().with_mut(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + 'static, R> Global<T, R>
|
||||||
|
where
|
||||||
|
T: InitializeFromFunction<R>,
|
||||||
|
{
|
||||||
|
#[track_caller]
|
||||||
|
/// Create a new global value
|
||||||
|
pub const fn new(constructor: fn() -> R) -> Self {
|
||||||
|
let key = std::panic::Location::caller();
|
||||||
|
Self {
|
||||||
|
constructor,
|
||||||
|
key: GlobalKey::new(key),
|
||||||
|
phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create this global signal with a specific key.
|
||||||
|
/// This is useful for ensuring that the signal is unique across the application and accessible from
|
||||||
|
/// outside the application too.
|
||||||
|
#[track_caller]
|
||||||
|
pub const fn with_key(constructor: fn() -> R, key: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
constructor,
|
||||||
|
key: GlobalKey::new_from_str(key),
|
||||||
|
phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the key for this global
|
||||||
|
pub fn key(&self) -> GlobalKey<'static> {
|
||||||
|
self.key.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve the global value. This will try to get the existing value from the current virtual dom, and if it doesn't exist, it will create a new one.
|
||||||
|
// NOTE: This is not called "get" or "value" because those methods overlap with Readable and Writable
|
||||||
|
pub fn resolve(&self) -> T {
|
||||||
|
let key = self.key();
|
||||||
|
|
||||||
|
let context = get_global_context();
|
||||||
|
|
||||||
|
// Get the entry if it already exists
|
||||||
|
{
|
||||||
|
let read = context.map.borrow();
|
||||||
|
if let Some(signal) = read.get(&key) {
|
||||||
|
return signal.downcast_ref::<T>().cloned().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, create it
|
||||||
|
// Constructors are always run in the root scope
|
||||||
|
let signal = ScopeId::ROOT.in_runtime(|| T::initialize_from_function(self.constructor));
|
||||||
|
context
|
||||||
|
.map
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(key, Box::new(signal.clone()));
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the scope the signal was created in.
|
||||||
|
pub fn origin_scope(&self) -> ScopeId {
|
||||||
|
ScopeId::ROOT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The context for global signals
|
/// The context for global signals
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default)]
|
||||||
pub struct GlobalSignalContext {
|
pub struct GlobalLazyContext {
|
||||||
signal: Rc<RefCell<HashMap<GlobalKey<'static>, Box<dyn Any>>>>,
|
map: Rc<RefCell<HashMap<GlobalKey<'static>, Box<dyn Any>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A key used to identify a signal in the global signal context
|
/// A key used to identify a signal in the global signal context
|
||||||
|
@ -57,18 +228,18 @@ impl From<&'static Location<'static>> for GlobalKey<'static> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalSignalContext {
|
impl GlobalLazyContext {
|
||||||
/// Get a signal with the given string key
|
/// Get a signal with the given string key
|
||||||
/// The key will be converted to a UUID with the appropriate internal namespace
|
/// The key will be converted to a UUID with the appropriate internal namespace
|
||||||
pub fn get_signal_with_key<T>(&self, key: &str) -> Option<Signal<T>> {
|
pub fn get_signal_with_key<T>(&self, key: &str) -> Option<Signal<T>> {
|
||||||
let key = GlobalKey::new_from_str(key);
|
let key = GlobalKey::new_from_str(key);
|
||||||
|
|
||||||
self.signal.borrow().get(&key).map(|f| {
|
self.map.borrow().get(&key).map(|f| {
|
||||||
*f.downcast_ref::<Signal<T>>().unwrap_or_else(|| {
|
*f.downcast_ref::<Signal<T>>().unwrap_or_else(|| {
|
||||||
panic!(
|
panic!(
|
||||||
"Global signal with key {:?} is not of the expected type. Keys are {:?}",
|
"Global signal with key {:?} is not of the expected type. Keys are {:?}",
|
||||||
key,
|
key,
|
||||||
self.signal.borrow().keys()
|
self.map.borrow().keys()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -76,15 +247,10 @@ impl GlobalSignalContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the global context for signals
|
/// Get the global context for signals
|
||||||
pub fn get_global_context() -> GlobalSignalContext {
|
pub fn get_global_context() -> GlobalLazyContext {
|
||||||
match try_consume_context() {
|
match ScopeId::ROOT.has_context() {
|
||||||
Some(context) => context,
|
Some(context) => context,
|
||||||
None => {
|
None => ScopeId::ROOT.provide_context(Default::default()),
|
||||||
let context = GlobalSignalContext {
|
|
||||||
signal: Rc::new(RefCell::new(HashMap::new())),
|
|
||||||
};
|
|
||||||
provide_root_context(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,171 +1,26 @@
|
||||||
use crate::{read::Readable, ReadableRef};
|
use super::{Global, InitializeFromFunction};
|
||||||
use crate::{write::Writable, GlobalKey};
|
use crate::read::Readable;
|
||||||
use crate::{WritableRef, Write};
|
|
||||||
use dioxus_core::{prelude::ScopeId, Runtime};
|
|
||||||
use generational_box::{BorrowResult, UnsyncStorage};
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use super::get_global_context;
|
|
||||||
use crate::read_impls;
|
use crate::read_impls;
|
||||||
use crate::Signal;
|
use crate::Signal;
|
||||||
|
|
||||||
/// A signal that can be accessed from anywhere in the application and created in a static
|
impl<T> InitializeFromFunction<T> for Signal<T> {
|
||||||
pub struct GlobalSignal<T> {
|
fn initialize_from_function(f: fn() -> T) -> Self {
|
||||||
initializer: fn() -> T,
|
Signal::new(f())
|
||||||
key: GlobalKey<'static>,
|
|
||||||
created_at: &'static std::panic::Location<'static>,
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A signal that can be accessed from anywhere in the application and created in a static
|
||||||
|
pub type GlobalSignal<T> = Global<Signal<T>, T>;
|
||||||
|
|
||||||
impl<T: 'static> GlobalSignal<T> {
|
impl<T: 'static> GlobalSignal<T> {
|
||||||
/// Create a new global signal with the given initializer.
|
|
||||||
#[track_caller]
|
|
||||||
pub const fn new(initializer: fn() -> T) -> GlobalSignal<T> {
|
|
||||||
let key = std::panic::Location::caller();
|
|
||||||
GlobalSignal {
|
|
||||||
initializer,
|
|
||||||
key: GlobalKey::new(key),
|
|
||||||
created_at: key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the key for this global
|
|
||||||
pub fn key(&self) -> GlobalKey<'static> {
|
|
||||||
self.key.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create this global signal with a specific key.
|
|
||||||
/// This is useful for ensuring that the signal is unique across the application and accessible from
|
|
||||||
/// outside the application too.
|
|
||||||
#[track_caller]
|
|
||||||
pub const fn with_key(initializer: fn() -> T, key: &'static str) -> GlobalSignal<T> {
|
|
||||||
GlobalSignal {
|
|
||||||
initializer,
|
|
||||||
key: GlobalKey::new_from_str(key),
|
|
||||||
created_at: std::panic::Location::caller(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the signal that backs this .
|
|
||||||
pub fn signal(&self) -> Signal<T> {
|
|
||||||
let key = self.key();
|
|
||||||
let context = get_global_context();
|
|
||||||
|
|
||||||
let read = context.signal.borrow();
|
|
||||||
|
|
||||||
match read.get(&key) {
|
|
||||||
Some(signal) => *signal.downcast_ref::<Signal<T>>().unwrap(),
|
|
||||||
None => {
|
|
||||||
drop(read);
|
|
||||||
|
|
||||||
// Constructors are always run in the root scope
|
|
||||||
// The signal also exists in the root scope
|
|
||||||
let value = ScopeId::ROOT.in_runtime(self.initializer);
|
|
||||||
let signal = Signal::new_maybe_sync_in_scope_with_caller(
|
|
||||||
value,
|
|
||||||
ScopeId::ROOT,
|
|
||||||
self.created_at,
|
|
||||||
);
|
|
||||||
|
|
||||||
let entry = context.signal.borrow_mut().insert(key, Box::new(signal));
|
|
||||||
debug_assert!(entry.is_none(), "Global signal already exists");
|
|
||||||
|
|
||||||
signal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn maybe_with_rt<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
|
||||||
if Runtime::current().is_err() {
|
|
||||||
f(&(self.initializer)())
|
|
||||||
} else {
|
|
||||||
self.with(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write this value
|
|
||||||
pub fn write(&self) -> Write<'static, T, UnsyncStorage> {
|
|
||||||
self.signal().try_write_unchecked().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the scope the signal was created in.
|
|
||||||
pub fn origin_scope(&self) -> ScopeId {
|
|
||||||
ScopeId::ROOT
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run a closure with a mutable reference to the signal's value.
|
|
||||||
/// If the signal has been dropped, this will panic.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
|
|
||||||
self.signal().with_mut(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the generational id of the signal.
|
/// Get the generational id of the signal.
|
||||||
pub fn id(&self) -> generational_box::GenerationalBoxId {
|
pub fn id(&self) -> generational_box::GenerationalBoxId {
|
||||||
self.signal().id()
|
self.resolve().id()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Readable for GlobalSignal<T> {
|
/// Resolve the global signal. This will try to get the existing value from the current virtual dom, and if it doesn't exist, it will create a new one.
|
||||||
type Target = T;
|
pub fn signal(&self) -> Signal<T> {
|
||||||
type Storage = UnsyncStorage;
|
self.resolve()
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn try_read_unchecked(
|
|
||||||
&self,
|
|
||||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
|
|
||||||
self.signal().try_read_unchecked()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>> {
|
|
||||||
self.signal().try_peek_unchecked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> Writable for GlobalSignal<T> {
|
|
||||||
type Mut<'a, R: ?Sized + 'static> = Write<'a, R, UnsyncStorage>;
|
|
||||||
|
|
||||||
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_)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn try_write_unchecked(
|
|
||||||
&self,
|
|
||||||
) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {
|
|
||||||
self.signal().try_write_unchecked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allow calling a signal with signal() syntax
|
|
||||||
///
|
|
||||||
/// Currently only limited to copy types, though could probably specialize for string/arc/rc
|
|
||||||
impl<T: Clone + 'static> Deref for GlobalSignal<T> {
|
|
||||||
type Target = dyn Fn() -> T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
Readable::deref_impl(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ where
|
||||||
type Target = dyn Fn() -> O;
|
type Target = dyn Fn() -> O;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
Readable::deref_impl(self)
|
unsafe { Readable::deref_impl(self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::read_impls;
|
|
||||||
use crate::write::Writable;
|
use crate::write::Writable;
|
||||||
use crate::{read::Readable, ReadableRef, Signal};
|
use crate::{read::Readable, ReadableRef, Signal};
|
||||||
|
use crate::{read_impls, GlobalMemo};
|
||||||
use crate::{CopyValue, ReadOnlySignal};
|
use crate::{CopyValue, ReadOnlySignal};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
@ -92,6 +92,39 @@ impl<T: 'static> Memo<T> {
|
||||||
memo
|
memo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`GlobalMemo`] that can be used anywhere inside your dioxus app. This memo will automatically be created once per app the first time you use it.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust, no_run
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
|
/// static SIGNAL: GlobalSignal<i32> = Signal::global(|| 0);
|
||||||
|
/// // Create a new global memo that can be used anywhere in your app
|
||||||
|
/// static DOUBLED: GlobalMemo<i32> = Memo::global(|| SIGNAL() * 2);
|
||||||
|
///
|
||||||
|
/// fn App() -> Element {
|
||||||
|
/// rsx! {
|
||||||
|
/// button {
|
||||||
|
/// // When SIGNAL changes, the memo will update because the SIGNAL is read inside DOUBLED
|
||||||
|
/// onclick: move |_| *SIGNAL.write() += 1,
|
||||||
|
/// "{DOUBLED}"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// <div class="warning">
|
||||||
|
///
|
||||||
|
/// Global memos are generally not recommended for use in libraries because it makes it more difficult to allow multiple instances of components you define in your library.
|
||||||
|
///
|
||||||
|
/// </div>
|
||||||
|
#[track_caller]
|
||||||
|
pub const fn global(constructor: fn() -> T) -> GlobalMemo<T>
|
||||||
|
where
|
||||||
|
T: PartialEq,
|
||||||
|
{
|
||||||
|
GlobalMemo::new(constructor)
|
||||||
|
}
|
||||||
|
|
||||||
/// Rerun the computation and update the value of the memo if the result has changed.
|
/// Rerun the computation and update the value of the memo if the result has changed.
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn recompute(&self)
|
fn recompute(&self)
|
||||||
|
@ -200,7 +233,7 @@ where
|
||||||
type Target = dyn Fn() -> T;
|
type Target = dyn Fn() -> T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
Readable::deref_impl(self)
|
unsafe { Readable::deref_impl(self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -221,8 +221,11 @@ pub trait Readable {
|
||||||
<Self::Storage as AnyStorage>::map(self.read(), |v| v.index(index))
|
<Self::Storage as AnyStorage>::map(self.read(), |v| v.index(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SAFETY: You must call this function directly with `self` as the argument.
|
||||||
|
/// This function relies on the size of the object you return from the deref
|
||||||
|
/// being the same as the object you pass in
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn deref_impl<'a>(&self) -> &'a dyn Fn() -> Self::Target
|
unsafe fn deref_impl<'a>(&self) -> &'a dyn Fn() -> Self::Target
|
||||||
where
|
where
|
||||||
Self: Sized + 'a,
|
Self: Sized + 'a,
|
||||||
Self::Target: Clone,
|
Self::Target: Clone,
|
||||||
|
|
|
@ -132,7 +132,7 @@ impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for ReadOnlySignal<T,
|
||||||
type Target = dyn Fn() -> T;
|
type Target = dyn Fn() -> T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
Readable::deref_impl(self)
|
unsafe { Readable::deref_impl(self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{default_impl, fmt_impls, write_impls};
|
use crate::{default_impl, fmt_impls, write_impls, Global};
|
||||||
use crate::{read::*, write::*, CopyValue, GlobalMemo, GlobalSignal, ReadableRef};
|
use crate::{read::*, write::*, CopyValue, GlobalMemo, GlobalSignal, ReadableRef};
|
||||||
use crate::{Memo, WritableRef};
|
use crate::{Memo, WritableRef};
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
@ -87,7 +87,7 @@ impl<T: 'static> Signal<T> {
|
||||||
/// </div>
|
/// </div>
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub const fn global(constructor: fn() -> T) -> GlobalSignal<T> {
|
pub const fn global(constructor: fn() -> T) -> GlobalSignal<T> {
|
||||||
GlobalSignal::new(constructor)
|
Global::new(constructor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +118,10 @@ impl<T: PartialEq + 'static> Signal<T> {
|
||||||
///
|
///
|
||||||
/// </div>
|
/// </div>
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T> {
|
pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T>
|
||||||
|
where
|
||||||
|
T: PartialEq,
|
||||||
|
{
|
||||||
GlobalMemo::new(constructor)
|
GlobalMemo::new(constructor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +465,7 @@ impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for Signal<T, S> {
|
||||||
type Target = dyn Fn() -> T;
|
type Target = dyn Fn() -> T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
Readable::deref_impl(self)
|
unsafe { Readable::deref_impl(self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue