diff --git a/reactive_graph/src/owner/arc_stored_value.rs b/reactive_graph/src/owner/arc_stored_value.rs index aee18270b4..d8064c0c2f 100644 --- a/reactive_graph/src/owner/arc_stored_value.rs +++ b/reactive_graph/src/owner/arc_stored_value.rs @@ -1,6 +1,6 @@ use crate::{ signal::guards::{Plain, ReadGuard, UntrackedWriteGuard}, - traits::{DefinedAt, IsDisposed, ReadValue, WriteValue}, + traits::{DefinedAt, IntoInner, IsDisposed, ReadValue, WriteValue}, }; use std::{ fmt::{Debug, Formatter}, @@ -124,3 +124,12 @@ impl IsDisposed for ArcStoredValue { false } } + +impl IntoInner for ArcStoredValue { + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + Some(Arc::into_inner(self.value)?.into_inner().unwrap()) + } +} diff --git a/reactive_graph/src/owner/arena_item.rs b/reactive_graph/src/owner/arena_item.rs index 0a572d3e26..9b7d9de286 100644 --- a/reactive_graph/src/owner/arena_item.rs +++ b/reactive_graph/src/owner/arena_item.rs @@ -2,7 +2,7 @@ use super::{ arena::{Arena, NodeId}, LocalStorage, Storage, SyncStorage, OWNER, }; -use crate::traits::{Dispose, IsDisposed}; +use crate::traits::{Dispose, IntoInner, IsDisposed}; use send_wrapper::SendWrapper; use std::{any::Any, hash::Hash, marker::PhantomData}; @@ -134,3 +134,12 @@ impl Dispose for ArenaItem { Arena::with_mut(|arena| arena.remove(self.node)); } } + +impl> IntoInner for ArenaItem { + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + S::take(self.node) + } +} diff --git a/reactive_graph/src/owner/storage.rs b/reactive_graph/src/owner/storage.rs index 1c16378ae5..4dbc3f09ca 100644 --- a/reactive_graph/src/owner/storage.rs +++ b/reactive_graph/src/owner/storage.rs @@ -54,6 +54,10 @@ pub trait Storage: Send + Sync + 'static { /// Sets a new value for the stored value. If it has been disposed, returns `Some(T)`. fn try_set(node: NodeId, value: T) -> Option; + + /// Takes an item from the arena if it exists and can be accessed from this thread. + /// If it cannot be casted, it will still be removed from the arena. + fn take(node: NodeId) -> Option; } /// A form of [`Storage`] that stores the type as itself, with no wrapper. @@ -100,6 +104,16 @@ where } }) } + + fn take(node: NodeId) -> Option { + Arena::with_mut(|arena| { + let m = arena.remove(node)?; + match m.downcast::() { + Ok(inner) => Some(*inner), + Err(_) => None, + } + }) + } } /// A form of [`Storage`] that stores the type with a wrapper that makes it `Send + Sync`, but only @@ -148,4 +162,14 @@ where } }) } + + fn take(node: NodeId) -> Option { + Arena::with_mut(|arena| { + let m = arena.remove(node)?; + match m.downcast::>() { + Ok(inner) => Some(inner.take()), + Err(_) => None, + } + }) + } } diff --git a/reactive_graph/src/owner/stored_value.rs b/reactive_graph/src/owner/stored_value.rs index d3c9916321..44c892b7fa 100644 --- a/reactive_graph/src/owner/stored_value.rs +++ b/reactive_graph/src/owner/stored_value.rs @@ -4,7 +4,9 @@ use super::{ }; use crate::{ signal::guards::{Plain, ReadGuard, UntrackedWriteGuard}, - traits::{DefinedAt, Dispose, IsDisposed, ReadValue, WriteValue}, + traits::{ + DefinedAt, Dispose, IntoInner, IsDisposed, ReadValue, WriteValue, + }, unwrap_signal, }; use std::{ @@ -162,6 +164,19 @@ impl Dispose for StoredValue { } } +impl IntoInner for StoredValue +where + T: 'static, + S: Storage>, +{ + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + self.value.into_inner()?.into_inner() + } +} + impl From> for StoredValue where T: Send + Sync + 'static, diff --git a/reactive_graph/src/signal.rs b/reactive_graph/src/signal.rs index df79a18650..5136fdacb5 100644 --- a/reactive_graph/src/signal.rs +++ b/reactive_graph/src/signal.rs @@ -113,7 +113,8 @@ pub fn arc_signal(value: T) -> (ArcReadSignal, ArcWriteSignal) { pub fn signal( value: T, ) -> (ReadSignal, WriteSignal) { - RwSignal::new(value).split() + let (r, w) = arc_signal(value); + (r.into(), w.into()) } /// Creates an arena-allocated signal. diff --git a/reactive_graph/src/signal/arc_read.rs b/reactive_graph/src/signal/arc_read.rs index b4a62567da..8c4088204b 100644 --- a/reactive_graph/src/signal/arc_read.rs +++ b/reactive_graph/src/signal/arc_read.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{ graph::SubscriberSet, - traits::{DefinedAt, IsDisposed, ReadUntracked}, + traits::{DefinedAt, IntoInner, IsDisposed, ReadUntracked}, }; use core::fmt::{Debug, Formatter, Result}; use std::{ @@ -128,6 +128,15 @@ impl IsDisposed for ArcReadSignal { } } +impl IntoInner for ArcReadSignal { + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + Some(Arc::into_inner(self.value)?.into_inner().unwrap()) + } +} + impl AsSubscriberSet for ArcReadSignal { type Output = Arc>; diff --git a/reactive_graph/src/signal/arc_rw.rs b/reactive_graph/src/signal/arc_rw.rs index cd18e3b4e5..93380889ca 100644 --- a/reactive_graph/src/signal/arc_rw.rs +++ b/reactive_graph/src/signal/arc_rw.rs @@ -6,7 +6,7 @@ use super::{ use crate::{ graph::{ReactiveNode, SubscriberSet}, prelude::{IsDisposed, Notify}, - traits::{DefinedAt, ReadUntracked, UntrackableGuard, Write}, + traits::{DefinedAt, IntoInner, ReadUntracked, UntrackableGuard, Write}, }; use core::fmt::{Debug, Formatter, Result}; use std::{ @@ -230,6 +230,15 @@ impl IsDisposed for ArcRwSignal { } } +impl IntoInner for ArcRwSignal { + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + Some(Arc::into_inner(self.value)?.into_inner().unwrap()) + } +} + impl AsSubscriberSet for ArcRwSignal { type Output = Arc>; diff --git a/reactive_graph/src/signal/arc_write.rs b/reactive_graph/src/signal/arc_write.rs index 580b55a0f5..18c9247195 100644 --- a/reactive_graph/src/signal/arc_write.rs +++ b/reactive_graph/src/signal/arc_write.rs @@ -2,7 +2,7 @@ use super::guards::{UntrackedWriteGuard, WriteGuard}; use crate::{ graph::{ReactiveNode, SubscriberSet}, prelude::{IsDisposed, Notify}, - traits::{DefinedAt, UntrackableGuard, Write}, + traits::{DefinedAt, IntoInner, UntrackableGuard, Write}, }; use core::fmt::{Debug, Formatter, Result}; use std::{ @@ -116,6 +116,15 @@ impl IsDisposed for ArcWriteSignal { } } +impl IntoInner for ArcWriteSignal { + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + Some(Arc::into_inner(self.value)?.into_inner().unwrap()) + } +} + impl Notify for ArcWriteSignal { fn notify(&self) { self.inner.mark_dirty(); diff --git a/reactive_graph/src/signal/read.rs b/reactive_graph/src/signal/read.rs index af7ec12160..dbddb19b72 100644 --- a/reactive_graph/src/signal/read.rs +++ b/reactive_graph/src/signal/read.rs @@ -6,7 +6,7 @@ use super::{ use crate::{ graph::SubscriberSet, owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage}, - traits::{DefinedAt, Dispose, IsDisposed, ReadUntracked}, + traits::{DefinedAt, Dispose, IntoInner, IsDisposed, ReadUntracked}, unwrap_signal, }; use core::fmt::Debug; @@ -122,6 +122,18 @@ impl IsDisposed for ReadSignal { } } +impl IntoInner for ReadSignal +where + S: Storage>, +{ + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + self.inner.into_inner()?.into_inner() + } +} + impl AsSubscriberSet for ReadSignal where S: Storage>, diff --git a/reactive_graph/src/signal/rw.rs b/reactive_graph/src/signal/rw.rs index 91310b7348..79b784a5cb 100644 --- a/reactive_graph/src/signal/rw.rs +++ b/reactive_graph/src/signal/rw.rs @@ -8,7 +8,7 @@ use crate::{ owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage}, signal::guards::{UntrackedWriteGuard, WriteGuard}, traits::{ - DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked, + DefinedAt, Dispose, IntoInner, IsDisposed, Notify, ReadUntracked, UntrackableGuard, Write, }, unwrap_signal, @@ -313,6 +313,18 @@ impl IsDisposed for RwSignal { } } +impl IntoInner for RwSignal +where + S: Storage>, +{ + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + self.inner.into_inner()?.into_inner() + } +} + impl AsSubscriberSet for RwSignal where S: Storage>, diff --git a/reactive_graph/src/signal/write.rs b/reactive_graph/src/signal/write.rs index 2be0e8e693..54e87c8d3a 100644 --- a/reactive_graph/src/signal/write.rs +++ b/reactive_graph/src/signal/write.rs @@ -1,7 +1,10 @@ use super::{guards::WriteGuard, ArcWriteSignal}; use crate::{ - owner::{ArenaItem, Storage, SyncStorage}, - traits::{DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Write}, + owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage}, + traits::{ + DefinedAt, Dispose, IntoInner, IsDisposed, Notify, UntrackableGuard, + Write, + }, }; use core::fmt::Debug; use guardian::ArcRwLockWriteGuardian; @@ -108,12 +111,52 @@ impl DefinedAt for WriteSignal { } } +impl From> for WriteSignal +where + T: Send + Sync + 'static, +{ + #[track_caller] + fn from(value: ArcWriteSignal) -> Self { + WriteSignal { + #[cfg(debug_assertions)] + defined_at: Location::caller(), + inner: ArenaItem::new_with_storage(value), + } + } +} + +impl FromLocal> for WriteSignal +where + T: 'static, +{ + #[track_caller] + fn from_local(value: ArcWriteSignal) -> Self { + WriteSignal { + #[cfg(debug_assertions)] + defined_at: Location::caller(), + inner: ArenaItem::new_with_storage(value), + } + } +} + impl IsDisposed for WriteSignal { fn is_disposed(&self) -> bool { self.inner.is_disposed() } } +impl IntoInner for WriteSignal +where + S: Storage>, +{ + type Value = T; + + #[inline(always)] + fn into_inner(self) -> Option { + self.inner.into_inner()?.into_inner() + } +} + impl Notify for WriteSignal where T: 'static, diff --git a/reactive_graph/src/traits.rs b/reactive_graph/src/traits.rs index 540fc5fd18..eeb5e0871c 100644 --- a/reactive_graph/src/traits.rs +++ b/reactive_graph/src/traits.rs @@ -630,6 +630,18 @@ pub trait IsDisposed { fn is_disposed(&self) -> bool; } +/// Turns a signal back into a raw value. +pub trait IntoInner { + /// The type of the value contained in the signal. + type Value; + + /// Returns the inner value if this is the only reference to to the signal. + /// Otherwise, returns `None` and drops this reference. + /// # Panics + /// Panics if the inner lock is poisoned. + fn into_inner(self) -> Option; +} + /// Describes where the signal was defined. This is used for diagnostic warnings and is purely a /// debug-mode tool. pub trait DefinedAt { diff --git a/reactive_graph/tests/signal.rs b/reactive_graph/tests/signal.rs index 0052dd1328..582178195c 100644 --- a/reactive_graph/tests/signal.rs +++ b/reactive_graph/tests/signal.rs @@ -2,8 +2,8 @@ use reactive_graph::{ owner::Owner, signal::{arc_signal, signal, ArcRwSignal, RwSignal}, traits::{ - Get, GetUntracked, Read, Set, Update, UpdateUntracked, With, - WithUntracked, Write, + Dispose, Get, GetUntracked, IntoInner, Read, Set, Update, + UpdateUntracked, With, WithUntracked, Write, }, }; @@ -108,3 +108,35 @@ fn update_signal() { set_a.set(4); assert_eq!(a.get(), 4); } + +#[test] +fn into_inner_signal() { + let owner = Owner::new(); + owner.set(); + + let rw_signal = RwSignal::new(1); + assert_eq!(rw_signal.get(), 1); + assert_eq!(rw_signal.into_inner(), Some(1)); +} + +#[test] +fn into_inner_arc_signal() { + let owner = Owner::new(); + owner.set(); + + let (a, b) = arc_signal(2); + assert_eq!(a.get(), 2); + std::mem::drop(b); + assert_eq!(a.into_inner(), Some(2)); +} + +#[test] +fn into_inner_non_arc_signal() { + let owner = Owner::new(); + owner.set(); + + let (a, b) = signal(2); + assert_eq!(a.get(), 2); + b.dispose(); + assert_eq!(a.into_inner(), Some(2)); +}