246 lines
7.9 KiB
Rust
246 lines
7.9 KiB
Rust
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<S: Storage<T>, T>() -> Rc<Owner<S>> {
|
|
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<S: Storage<T>, T>(scope: ScopeId) -> Rc<Owner<S>> {
|
|
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<T: 'static, S: Storage<T> = UnsyncStorage> {
|
|
pub(crate) value: GenerationalBox<T, S>,
|
|
origin_scope: ScopeId,
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<T: 'static> serde::Serialize for CopyValue<T>
|
|
where
|
|
T: serde::Serialize,
|
|
{
|
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
self.value.read().serialize(serializer)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de, T: 'static> serde::Deserialize<'de> for CopyValue<T>
|
|
where
|
|
T: serde::Deserialize<'de>,
|
|
{
|
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
let value = T::deserialize(deserializer)?;
|
|
|
|
Ok(Self::new(value))
|
|
}
|
|
}
|
|
|
|
impl<T: 'static> CopyValue<T> {
|
|
/// 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<T: 'static, S: Storage<T>> CopyValue<T, S> {
|
|
/// 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<S::Ref, generational_box::BorrowError> {
|
|
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<S::Mut, generational_box::BorrowMutError> {
|
|
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<O>(&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<O>(&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<T: Clone + 'static, S: Storage<T>> CopyValue<T, S> {
|
|
/// Get the value. If the value has been dropped, this will panic.
|
|
pub fn value(&self) -> T {
|
|
self.read().clone()
|
|
}
|
|
}
|
|
|
|
impl<T: 'static, S: Storage<T>> PartialEq for CopyValue<T, S> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.value.ptr_eq(&other.value)
|
|
}
|
|
}
|
|
|
|
impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
|
|
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<Self>).
|
|
let uninit_callable = MaybeUninit::<Self>::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::<Self>());
|
|
|
|
// 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
|
|
}
|
|
}
|