use generational_box::GenerationalBoxId; use generational_box::UnsyncStorage; use std::mem::MaybeUninit; use std::ops::Deref; use std::rc::Rc; use dioxus_core::prelude::*; use dioxus_core::ScopeId; use generational_box::{GenerationalBox, Owner, Storage}; use crate::Effect; fn current_owner, T>() -> Rc> { match Effect::current() { // If we are inside of an effect, we should use the owner of the effect as the owner of the value. Some(effect) => { let scope_id = effect.source; owner_in_scope(scope_id) } // Otherwise either get an owner from the current scope or create a new one. None => match has_context() { Some(rt) => rt, None => { let owner = Rc::new(S::owner()); provide_context(owner) } }, } } fn owner_in_scope, T>(scope: ScopeId) -> Rc> { match consume_context_from_scope(scope) { Some(rt) => rt, None => { let owner = Rc::new(S::owner()); scope.provide_context(owner) } } } /// CopyValue is a wrapper around a value to make the value mutable and Copy. /// /// It is internally backed by [`generational_box::GenerationalBox`]. pub struct CopyValue = UnsyncStorage> { pub(crate) value: GenerationalBox, origin_scope: ScopeId, } #[cfg(feature = "serde")] impl serde::Serialize for CopyValue where T: serde::Serialize, { fn serialize(&self, serializer: S) -> Result { self.value.read().serialize(serializer) } } #[cfg(feature = "serde")] impl<'de, T: 'static> serde::Deserialize<'de> for CopyValue where T: serde::Deserialize<'de>, { fn deserialize>(deserializer: D) -> Result { let value = T::deserialize(deserializer)?; Ok(Self::new(value)) } } impl CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. #[track_caller] pub fn new(value: T) -> Self { Self::new_maybe_sync(value) } /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. #[track_caller] pub fn new_in_scope(value: T, scope: ScopeId) -> Self { Self::new_maybe_sync_in_scope(value, scope) } } impl> CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. #[track_caller] pub fn new_maybe_sync(value: T) -> Self { let owner = current_owner(); Self { value: owner.insert(value), origin_scope: current_scope_id().expect("in a virtual dom"), } } pub(crate) fn new_with_caller( value: T, #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, ) -> Self { let owner = current_owner(); Self { value: owner.insert_with_caller( value, #[cfg(debug_assertions)] caller, ), origin_scope: current_scope_id().expect("in a virtual dom"), } } /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. #[track_caller] pub fn new_maybe_sync_in_scope(value: T, scope: ScopeId) -> Self { let owner = owner_in_scope(scope); Self { value: owner.insert(value), origin_scope: scope, } } pub(crate) fn invalid() -> Self { let owner = current_owner(); Self { value: owner.invalid(), origin_scope: current_scope_id().expect("in a virtual dom"), } } /// Get the scope this value was created in. pub fn origin_scope(&self) -> ScopeId { self.origin_scope } /// Try to read the value. If the value has been dropped, this will return None. #[track_caller] pub fn try_read(&self) -> Result { self.value.try_read() } /// Read the value. If the value has been dropped, this will panic. #[track_caller] pub fn read(&self) -> S::Ref { self.value.read() } /// Try to write the value. If the value has been dropped, this will return None. #[track_caller] pub fn try_write(&mut self) -> Result { self.value.try_write() } /// Write the value without any lifetime hints. If the value has been dropped, this will panic. /// /// Note: This is completely safe because the value is stored in a generational box. The lifetime that normally is passed to the returned reference is only used as a hint to user to prevent runtime overlapping borrow panics. #[track_caller] pub fn write_unchecked(&self) -> S::Mut { self.value.write() } /// Write the value. If the value has been dropped, this will panic. #[track_caller] pub fn write(&self) -> S::Mut { self.value.write() } /// Set the value. If the value has been dropped, this will panic. #[track_caller] pub fn set(&self, value: T) { self.value.set(value); } /// Run a function with a reference to the value. If the value has been dropped, this will panic. #[track_caller] pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { let write = self.read(); f(&*write) } /// Run a function with a mutable reference to the value. If the value has been dropped, this will panic. #[track_caller] pub fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { let mut write = self.write(); f(&mut *write) } /// Get the generational id of the value. pub fn id(&self) -> GenerationalBoxId { self.value.id() } } impl> CopyValue { /// Get the value. If the value has been dropped, this will panic. pub fn value(&self) -> T { self.read().clone() } } impl> PartialEq for CopyValue { fn eq(&self, other: &Self) -> bool { self.value.ptr_eq(&other.value) } } impl> Deref for CopyValue { type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { // 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). let uninit_callable = MaybeUninit::::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::()); // 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 } }