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