Skip to main content

dioxus_signals/
boxed.rs

1use std::{any::Any, ops::Deref};
2
3use dioxus_core::{IntoAttributeValue, IntoDynNode, Subscribers};
4use generational_box::{BorrowResult, Storage, SyncStorage, UnsyncStorage};
5
6use crate::{
7    CopyValue, Global, InitializeFromFunction, MappedMutSignal, MappedSignal, Memo, Readable,
8    ReadableExt, ReadableRef, Signal, SignalData, Writable, WritableExt, read_impls, write_impls,
9};
10
11/// A signal that can only be read from.
12#[deprecated(
13    since = "0.7.0",
14    note = "Use `ReadSignal` instead. Will be removed in 0.8"
15)]
16pub type ReadOnlySignal<T, S = UnsyncStorage> = ReadSignal<T, S>;
17
18/// A boxed version of [Readable] that can be used to store any readable type.
19pub struct ReadSignal<T: ?Sized, S: BoxedSignalStorage<T> = UnsyncStorage> {
20    value: CopyValue<Box<S::DynReadable<sealed::SealedToken>>, S>,
21}
22
23impl<T: ?Sized + 'static> ReadSignal<T> {
24    /// Create a new boxed readable value.
25    pub fn new(value: impl Readable<Target = T, Storage = UnsyncStorage> + 'static) -> Self {
26        Self::new_maybe_sync(value)
27    }
28}
29
30impl<T: ?Sized + 'static, S: BoxedSignalStorage<T>> ReadSignal<T, S> {
31    /// Create a new boxed readable value which may be sync
32    pub fn new_maybe_sync<R>(value: R) -> Self
33    where
34        S: CreateBoxedSignalStorage<R>,
35        R: Readable<Target = T>,
36    {
37        Self {
38            value: CopyValue::new_maybe_sync(S::new_readable(value, sealed::SealedToken)),
39        }
40    }
41
42    /// Point to another [ReadSignal]. This will subscribe the other [ReadSignal] to all subscribers of this [ReadSignal].
43    pub fn point_to(&self, other: Self) -> BorrowResult {
44        let this_subscribers = self.subscribers();
45        let mut this_subscribers_vec = Vec::new();
46        // Note we don't subscribe directly in the visit closure to avoid a deadlock when pointing to self
47        this_subscribers.visit(|subscriber| this_subscribers_vec.push(*subscriber));
48        let other_subscribers = other.subscribers();
49        for subscriber in this_subscribers_vec {
50            subscriber.subscribe(other_subscribers.clone());
51        }
52        self.value.point_to(other.value)?;
53        Ok(())
54    }
55
56    #[doc(hidden)]
57    /// This is only used by the `props` macro.
58    /// Mark any readers of the signal as dirty
59    pub fn mark_dirty(&mut self) {
60        let subscribers = self.subscribers();
61        let mut this_subscribers_vec = Vec::new();
62        subscribers.visit(|subscriber| this_subscribers_vec.push(*subscriber));
63        for subscriber in this_subscribers_vec {
64            subscribers.remove(&subscriber);
65            subscriber.mark_dirty();
66        }
67    }
68}
69
70impl<T: ?Sized, S: BoxedSignalStorage<T>> Clone for ReadSignal<T, S> {
71    fn clone(&self) -> Self {
72        *self
73    }
74}
75
76impl<T: ?Sized, S: BoxedSignalStorage<T>> Copy for ReadSignal<T, S> {}
77
78impl<T: ?Sized, S: BoxedSignalStorage<T>> PartialEq for ReadSignal<T, S> {
79    fn eq(&self, other: &Self) -> bool {
80        self.value == other.value
81    }
82}
83
84impl<
85    T: Default + 'static,
86    S: CreateBoxedSignalStorage<Signal<T, S>> + BoxedSignalStorage<T> + Storage<SignalData<T>>,
87> Default for ReadSignal<T, S>
88{
89    fn default() -> Self {
90        Self::new_maybe_sync(Signal::new_maybe_sync(T::default()))
91    }
92}
93
94read_impls!(ReadSignal<T, S: BoxedSignalStorage<T>>);
95
96impl<T, S: BoxedSignalStorage<T>> IntoAttributeValue for ReadSignal<T, S>
97where
98    T: Clone + IntoAttributeValue + 'static,
99{
100    fn into_value(self) -> dioxus_core::AttributeValue {
101        self.with(|f| f.clone().into_value())
102    }
103}
104
105impl<T, S> IntoDynNode for ReadSignal<T, S>
106where
107    T: Clone + IntoDynNode + 'static,
108    S: BoxedSignalStorage<T>,
109{
110    fn into_dyn_node(self) -> dioxus_core::DynamicNode {
111        self.with(|f| f.clone().into_dyn_node())
112    }
113}
114
115impl<T: Clone + 'static, S: BoxedSignalStorage<T>> Deref for ReadSignal<T, S> {
116    type Target = dyn Fn() -> T;
117
118    fn deref(&self) -> &Self::Target {
119        unsafe { ReadableExt::deref_impl(self) }
120    }
121}
122
123impl<T: ?Sized, S: BoxedSignalStorage<T>> Readable for ReadSignal<T, S> {
124    type Target = T;
125    type Storage = S;
126
127    #[track_caller]
128    fn try_read_unchecked(
129        &self,
130    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
131    where
132        T: 'static,
133    {
134        self.value.try_peek_unchecked()?.try_read_unchecked()
135    }
136
137    #[track_caller]
138    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>
139    where
140        T: 'static,
141    {
142        self.value.try_peek_unchecked()?.try_peek_unchecked()
143    }
144
145    fn subscribers(&self) -> Subscribers
146    where
147        T: 'static,
148    {
149        self.value.try_peek_unchecked().unwrap().subscribers()
150    }
151}
152
153// We can't implement From<impl Readable<Target = T, Storage = S> > for ReadSignal<T, S>
154// because it would conflict with the From<T> for T implementation, but we can implement it for
155// all specific readable types
156impl<
157    T: 'static,
158    S: CreateBoxedSignalStorage<Signal<T, S>> + BoxedSignalStorage<T> + Storage<SignalData<T>>,
159> From<Signal<T, S>> for ReadSignal<T, S>
160{
161    fn from(value: Signal<T, S>) -> Self {
162        Self::new_maybe_sync(value)
163    }
164}
165impl<T: PartialEq + 'static> From<Memo<T>> for ReadSignal<T> {
166    fn from(value: Memo<T>) -> Self {
167        Self::new(value)
168    }
169}
170impl<T: 'static, S: CreateBoxedSignalStorage<CopyValue<T, S>> + BoxedSignalStorage<T> + Storage<T>>
171    From<CopyValue<T, S>> for ReadSignal<T, S>
172{
173    fn from(value: CopyValue<T, S>) -> Self {
174        Self::new_maybe_sync(value)
175    }
176}
177impl<T, R> From<Global<T, R>> for ReadSignal<R>
178where
179    T: Readable<Target = R, Storage = UnsyncStorage> + InitializeFromFunction<R> + Clone + 'static,
180    R: 'static,
181{
182    fn from(value: Global<T, R>) -> Self {
183        Self::new(value)
184    }
185}
186impl<V, O, F, S> From<MappedSignal<O, V, F>> for ReadSignal<O, S>
187where
188    O: ?Sized + 'static,
189    V: Readable<Storage = S> + 'static,
190    F: Fn(&V::Target) -> &O + 'static,
191    S: BoxedSignalStorage<O> + CreateBoxedSignalStorage<MappedSignal<O, V, F>>,
192{
193    fn from(value: MappedSignal<O, V, F>) -> Self {
194        Self::new_maybe_sync(value)
195    }
196}
197impl<V, O, F, FMut, S> From<MappedMutSignal<O, V, F, FMut>> for ReadSignal<O, S>
198where
199    O: ?Sized + 'static,
200    V: Readable<Storage = S> + 'static,
201    F: Fn(&V::Target) -> &O + 'static,
202    FMut: 'static,
203    S: BoxedSignalStorage<O> + CreateBoxedSignalStorage<MappedMutSignal<O, V, F, FMut>>,
204{
205    fn from(value: MappedMutSignal<O, V, F, FMut>) -> Self {
206        Self::new_maybe_sync(value)
207    }
208}
209impl<T: ?Sized + 'static, S> From<WriteSignal<T, S>> for ReadSignal<T, S>
210where
211    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<WriteSignal<T, S>>,
212{
213    fn from(value: WriteSignal<T, S>) -> Self {
214        Self::new_maybe_sync(value)
215    }
216}
217
218/// A boxed version of [Writable] that can be used to store any writable type.
219pub struct WriteSignal<T: ?Sized, S: BoxedSignalStorage<T> = UnsyncStorage> {
220    value: CopyValue<Box<S::DynWritable<sealed::SealedToken>>, S>,
221}
222
223impl<T: ?Sized + 'static> WriteSignal<T> {
224    /// Create a new boxed writable value.
225    pub fn new(
226        value: impl Writable<Target = T, Storage = UnsyncStorage, WriteMetadata: 'static> + 'static,
227    ) -> Self {
228        Self::new_maybe_sync(value)
229    }
230}
231
232impl<T: ?Sized + 'static, S: BoxedSignalStorage<T>> WriteSignal<T, S> {
233    /// Create a new boxed writable value which may be sync
234    pub fn new_maybe_sync<R>(value: R) -> Self
235    where
236        R: Writable<Target = T, WriteMetadata: 'static>,
237        S: CreateBoxedSignalStorage<R>,
238    {
239        Self {
240            value: CopyValue::new_maybe_sync(S::new_writable(value, sealed::SealedToken)),
241        }
242    }
243}
244
245struct BoxWriteMetadata<W> {
246    value: W,
247}
248
249impl<W: Writable> BoxWriteMetadata<W> {
250    fn new(value: W) -> Self {
251        Self { value }
252    }
253}
254
255impl<W: Readable> Readable for BoxWriteMetadata<W> {
256    type Target = W::Target;
257
258    type Storage = W::Storage;
259
260    fn try_read_unchecked(
261        &self,
262    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
263    where
264        W::Target: 'static,
265    {
266        self.value.try_read_unchecked()
267    }
268
269    fn try_peek_unchecked(
270        &self,
271    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
272    where
273        W::Target: 'static,
274    {
275        self.value.try_peek_unchecked()
276    }
277
278    fn subscribers(&self) -> Subscribers
279    where
280        W::Target: 'static,
281    {
282        self.value.subscribers()
283    }
284}
285
286impl<W> Writable for BoxWriteMetadata<W>
287where
288    W: Writable,
289    W::WriteMetadata: 'static,
290{
291    type WriteMetadata = Box<dyn Any>;
292
293    fn try_write_unchecked(
294        &self,
295    ) -> Result<crate::WritableRef<'static, Self>, generational_box::BorrowMutError>
296    where
297        W::Target: 'static,
298    {
299        self.value
300            .try_write_unchecked()
301            .map(|w| w.map_metadata(|data| Box::new(data) as Box<dyn Any>))
302    }
303}
304
305impl<T: ?Sized, S: BoxedSignalStorage<T>> Clone for WriteSignal<T, S> {
306    fn clone(&self) -> Self {
307        *self
308    }
309}
310
311impl<T: ?Sized, S: BoxedSignalStorage<T>> Copy for WriteSignal<T, S> {}
312
313impl<T: ?Sized, S: BoxedSignalStorage<T>> PartialEq for WriteSignal<T, S> {
314    fn eq(&self, other: &Self) -> bool {
315        self.value == other.value
316    }
317}
318
319read_impls!(WriteSignal<T, S: BoxedSignalStorage<T>>);
320write_impls!(WriteSignal<T, S: BoxedSignalStorage<T>>);
321
322impl<T, S> IntoAttributeValue for WriteSignal<T, S>
323where
324    T: Clone + IntoAttributeValue + 'static,
325    S: BoxedSignalStorage<T>,
326{
327    fn into_value(self) -> dioxus_core::AttributeValue {
328        self.with(|f| f.clone().into_value())
329    }
330}
331
332impl<T, S> IntoDynNode for WriteSignal<T, S>
333where
334    T: Clone + IntoDynNode + 'static,
335    S: BoxedSignalStorage<T>,
336{
337    fn into_dyn_node(self) -> dioxus_core::DynamicNode {
338        self.with(|f| f.clone().into_dyn_node())
339    }
340}
341
342impl<T: Clone + 'static, S: BoxedSignalStorage<T>> Deref for WriteSignal<T, S> {
343    type Target = dyn Fn() -> T;
344
345    fn deref(&self) -> &Self::Target {
346        unsafe { ReadableExt::deref_impl(self) }
347    }
348}
349
350impl<T: ?Sized, S: BoxedSignalStorage<T>> Readable for WriteSignal<T, S> {
351    type Target = T;
352    type Storage = S;
353
354    #[track_caller]
355    fn try_read_unchecked(
356        &self,
357    ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
358    where
359        T: 'static,
360    {
361        self.value.try_peek_unchecked()?.try_read_unchecked()
362    }
363
364    #[track_caller]
365    fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>
366    where
367        T: 'static,
368    {
369        self.value.try_peek_unchecked()?.try_peek_unchecked()
370    }
371
372    fn subscribers(&self) -> Subscribers
373    where
374        T: 'static,
375    {
376        self.value.try_peek_unchecked().unwrap().subscribers()
377    }
378}
379
380impl<T: ?Sized, S: BoxedSignalStorage<T>> Writable for WriteSignal<T, S> {
381    type WriteMetadata = Box<dyn Any>;
382
383    fn try_write_unchecked(
384        &self,
385    ) -> Result<crate::WritableRef<'static, Self>, generational_box::BorrowMutError>
386    where
387        T: 'static,
388    {
389        self.value
390            .try_peek_unchecked()
391            .unwrap()
392            .try_write_unchecked()
393    }
394}
395
396// We can't implement From<impl Writable<Target = T, Storage = S>> for Write<T, S>
397// because it would conflict with the From<T> for T implementation, but we can implement it for
398// all specific readable types
399impl<
400    T: 'static,
401    S: CreateBoxedSignalStorage<Signal<T, S>> + BoxedSignalStorage<T> + Storage<SignalData<T>>,
402> From<Signal<T, S>> for WriteSignal<T, S>
403{
404    fn from(value: Signal<T, S>) -> Self {
405        Self::new_maybe_sync(value)
406    }
407}
408impl<T: 'static, S: CreateBoxedSignalStorage<CopyValue<T, S>> + BoxedSignalStorage<T> + Storage<T>>
409    From<CopyValue<T, S>> for WriteSignal<T, S>
410{
411    fn from(value: CopyValue<T, S>) -> Self {
412        Self::new_maybe_sync(value)
413    }
414}
415impl<T, R> From<Global<T, R>> for WriteSignal<R>
416where
417    T: Writable<Target = R, Storage = UnsyncStorage> + InitializeFromFunction<R> + Clone + 'static,
418    R: 'static,
419{
420    fn from(value: Global<T, R>) -> Self {
421        Self::new(value)
422    }
423}
424impl<V, O, F, FMut, S> From<MappedMutSignal<O, V, F, FMut>> for WriteSignal<O, S>
425where
426    O: ?Sized + 'static,
427    V: Writable<Storage = S> + 'static,
428    F: Fn(&V::Target) -> &O + 'static,
429    FMut: Fn(&mut V::Target) -> &mut O + 'static,
430    S: CreateBoxedSignalStorage<MappedMutSignal<O, V, F, FMut>> + BoxedSignalStorage<O>,
431{
432    fn from(value: MappedMutSignal<O, V, F, FMut>) -> Self {
433        Self::new_maybe_sync(value)
434    }
435}
436
437/// A trait for creating boxed readable and writable signals. This is implemented for
438/// [UnsyncStorage] and [SyncStorage].
439///
440/// You may need to add this trait as a bound when you use [ReadSignal] or [WriteSignal] while
441/// remaining generic over syncness.
442pub trait BoxedSignalStorage<T: ?Sized>:
443    Storage<Box<Self::DynReadable<sealed::SealedToken>>>
444    + Storage<Box<Self::DynWritable<sealed::SealedToken>>>
445    + sealed::Sealed
446    + 'static
447{
448    // This is not a public api, and is sealed to prevent external usage and implementations
449    #[doc(hidden)]
450    type DynReadable<Seal: sealed::SealedTokenTrait>: Readable<Target = T, Storage = Self> + ?Sized;
451    // This is not a public api, and is sealed to prevent external usage and implementations
452    #[doc(hidden)]
453    type DynWritable<Seal: sealed::SealedTokenTrait>: Writable<Target = T, Storage = Self, WriteMetadata = Box<dyn Any>>
454        + ?Sized;
455}
456
457/// A trait for creating boxed readable and writable signals. This is implemented for
458/// [UnsyncStorage] and [SyncStorage].
459///
460/// The storage type must implement `CreateReadOnlySignalStorage<T>` for every readable `T` type
461/// to be used with `ReadSignal` and `WriteSignal`.
462///
463/// You may need to add this trait as a bound when you call [ReadSignal::new_maybe_sync] or
464/// [WriteSignal::new_maybe_sync] while remaining generic over syncness.
465pub trait CreateBoxedSignalStorage<T: Readable + ?Sized>:
466    BoxedSignalStorage<T::Target> + 'static
467{
468    // This is not a public api, and is sealed to prevent external usage and implementations
469    #[doc(hidden)]
470    fn new_readable(
471        value: T,
472        _: sealed::SealedToken,
473    ) -> Box<Self::DynReadable<sealed::SealedToken>>
474    where
475        T: Sized;
476
477    // This is not a public api, and is sealed to prevent external usage and implementations
478    #[doc(hidden)]
479    fn new_writable(
480        value: T,
481        _: sealed::SealedToken,
482    ) -> Box<Self::DynWritable<sealed::SealedToken>>
483    where
484        T: Writable + Sized;
485}
486
487impl<T: ?Sized + 'static> BoxedSignalStorage<T> for UnsyncStorage {
488    type DynReadable<Seal: sealed::SealedTokenTrait> = dyn Readable<Target = T, Storage = Self>;
489    type DynWritable<Seal: sealed::SealedTokenTrait> =
490        dyn Writable<Target = T, Storage = Self, WriteMetadata = Box<dyn Any>>;
491}
492
493impl<T: Readable<Storage = UnsyncStorage> + ?Sized + 'static> CreateBoxedSignalStorage<T>
494    for UnsyncStorage
495{
496    fn new_readable(value: T, _: sealed::SealedToken) -> Box<Self::DynReadable<sealed::SealedToken>>
497    where
498        T: Sized,
499    {
500        Box::new(value)
501    }
502
503    fn new_writable(value: T, _: sealed::SealedToken) -> Box<Self::DynWritable<sealed::SealedToken>>
504    where
505        T: Writable + Sized,
506    {
507        Box::new(BoxWriteMetadata::new(value))
508    }
509}
510
511impl<T: ?Sized + 'static> BoxedSignalStorage<T> for SyncStorage {
512    type DynReadable<Seal: sealed::SealedTokenTrait> =
513        dyn Readable<Target = T, Storage = Self> + Send + Sync;
514    type DynWritable<Seal: sealed::SealedTokenTrait> =
515        dyn Writable<Target = T, Storage = Self, WriteMetadata = Box<dyn Any>> + Send + Sync;
516}
517
518impl<T: Readable<Storage = SyncStorage> + Sync + Send + ?Sized + 'static>
519    CreateBoxedSignalStorage<T> for SyncStorage
520{
521    fn new_readable(value: T, _: sealed::SealedToken) -> Box<Self::DynReadable<sealed::SealedToken>>
522    where
523        T: Sized,
524    {
525        Box::new(value)
526    }
527
528    fn new_writable(value: T, _: sealed::SealedToken) -> Box<Self::DynWritable<sealed::SealedToken>>
529    where
530        T: Writable + Sized,
531    {
532        Box::new(BoxWriteMetadata::new(value))
533    }
534}
535
536mod sealed {
537    use generational_box::{SyncStorage, UnsyncStorage};
538
539    pub trait Sealed {}
540    impl Sealed for UnsyncStorage {}
541    impl Sealed for SyncStorage {}
542
543    pub struct SealedToken;
544
545    pub trait SealedTokenTrait {}
546    impl SealedTokenTrait for SealedToken {}
547}