mirror of
https://github.com/Zenithsiz/dynatos.git
synced 2026-02-03 18:13:04 +00:00
Removed "worlds".
The default world (thread-local) is now the only implementation.
This commit is contained in:
parent
992ce507ba
commit
720c229569
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -172,9 +172,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dynatos-context"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dynatos-world",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dynatos-html"
|
||||
@ -241,7 +238,6 @@ dependencies = [
|
||||
"duplicate",
|
||||
"dynatos-context",
|
||||
"dynatos-util",
|
||||
"dynatos-world",
|
||||
"extend",
|
||||
"futures",
|
||||
"itertools",
|
||||
@ -301,14 +297,6 @@ dependencies = [
|
||||
"extend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dynatos-world"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.10.0"
|
||||
|
||||
@ -14,7 +14,6 @@ members = [
|
||||
"dynatos-router",
|
||||
"dynatos-title",
|
||||
"dynatos-util",
|
||||
"dynatos-world",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@ -34,7 +33,6 @@ dynatos-reactive = { path = "dynatos-reactive" }
|
||||
dynatos-router = { path = "dynatos-router" }
|
||||
dynatos-title = { path = "dynatos-title" }
|
||||
dynatos-util = { path = "dynatos-util" }
|
||||
dynatos-world = { path = "dynatos-world" }
|
||||
|
||||
# zutil
|
||||
zutil-cloned = { git = "https://github.com/Zenithsiz/zutil", rev = "978fa5df733d59fc691812ce2fa6072bf901dc7f" }
|
||||
|
||||
@ -5,7 +5,5 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
dynatos-world = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
178
dynatos-context/src/context_stack.rs
Normal file
178
dynatos-context/src/context_stack.rs
Normal file
@ -0,0 +1,178 @@
|
||||
//! Context stack
|
||||
|
||||
// Lints
|
||||
#![expect(clippy::as_conversions, reason = "We need to unsize items and there's no other way")]
|
||||
|
||||
// Imports
|
||||
use {
|
||||
core::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
hash::BuildHasher,
|
||||
marker::PhantomData,
|
||||
},
|
||||
std::{collections::HashMap, hash::DefaultHasher},
|
||||
};
|
||||
|
||||
/// Context stack
|
||||
// TODO: Use type with less indirections?
|
||||
#[thread_local]
|
||||
static CTXS_STACK: CtxsStackImpl<dyn Any> = RefCell::new(HashMap::with_hasher(RandomState));
|
||||
|
||||
/// Handle
|
||||
#[derive(Debug)]
|
||||
pub struct Handle<T>(usize, PhantomData<T>);
|
||||
|
||||
impl<T> Clone for Handle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
impl<T> Copy for Handle<T> {}
|
||||
|
||||
impl<T> !Send for Handle<T> {}
|
||||
impl<T> !Sync for Handle<T> {}
|
||||
|
||||
/// Opaque handle
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct OpaqueHandle {
|
||||
type_id: TypeId,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl !Send for OpaqueHandle {}
|
||||
impl !Sync for OpaqueHandle {}
|
||||
|
||||
/// Pushes a value onto the stack and returns a handle to it
|
||||
pub fn push<T>(value: T) -> Handle<T>
|
||||
where
|
||||
T: Any + 'static,
|
||||
{
|
||||
let mut ctxs = CTXS_STACK
|
||||
.try_borrow_mut()
|
||||
.expect("Cannot modify context while accessing it");
|
||||
let stack = ctxs.entry(TypeId::of::<T>()).or_default();
|
||||
let idx = stack.len();
|
||||
stack.push(Some(Box::new(value) as Box<dyn Any>));
|
||||
|
||||
Handle(idx, PhantomData)
|
||||
}
|
||||
|
||||
/// Uses the value in the top of the stack
|
||||
pub fn with_top<T, F, O>(f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(Option<&T>) -> O,
|
||||
{
|
||||
let type_id = TypeId::of::<T>();
|
||||
let ctxs = CTXS_STACK
|
||||
.try_borrow()
|
||||
.expect("Cannot access context while modifying it");
|
||||
let value = try {
|
||||
let stack = ctxs.get(&type_id)?;
|
||||
let value = stack.last()?.as_ref().expect("Value was already taken");
|
||||
(&**value as &dyn Any)
|
||||
.downcast_ref::<T>()
|
||||
.expect("Value was the wrong type")
|
||||
};
|
||||
|
||||
f(value)
|
||||
}
|
||||
|
||||
/// Uses the value in handle `handle`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the context stack doesn't exist, or
|
||||
/// if the value was already taken.
|
||||
pub fn with<T, F, O>(handle: Handle<T>, f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(&T) -> O,
|
||||
{
|
||||
let opaque_handle = self::to_opaque::<T>(handle);
|
||||
self::with_opaque(opaque_handle, |value| {
|
||||
let value = (value as &dyn Any)
|
||||
.downcast_ref::<T>()
|
||||
.expect("Value was the wrong type");
|
||||
f(value)
|
||||
})
|
||||
}
|
||||
|
||||
/// Takes the value in handle `handle`
|
||||
#[expect(clippy::must_use_candidate, reason = "The user may just want to pop the value")]
|
||||
pub fn take<T>(handle: Handle<T>) -> T
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let opaque_handle = self::to_opaque::<T>(handle);
|
||||
let value = self::take_opaque(opaque_handle);
|
||||
let value = (value as Box<dyn Any>).downcast().expect("Value was the wrong type");
|
||||
|
||||
*value
|
||||
}
|
||||
|
||||
/// Converts a handle to an opaque handle
|
||||
#[must_use]
|
||||
pub fn to_opaque<T>(handle: Handle<T>) -> OpaqueHandle
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
OpaqueHandle {
|
||||
type_id: TypeId::of::<T>(),
|
||||
idx: handle.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses the value in handle `handle` opaquely.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the context stack doesn't exist, or
|
||||
/// if the value was already taken.
|
||||
pub fn with_opaque<F, O>(handle: OpaqueHandle, f: F) -> O
|
||||
where
|
||||
F: FnOnce(&dyn Any) -> O,
|
||||
{
|
||||
let ctxs = CTXS_STACK
|
||||
.try_borrow()
|
||||
.expect("Cannot access context while modifying it");
|
||||
let stack = ctxs.get(&handle.type_id).expect("Context stack should exist");
|
||||
let value = stack
|
||||
.get(handle.idx)
|
||||
.expect("Index was invalid")
|
||||
.as_ref()
|
||||
.expect("Value was already taken");
|
||||
f(&**value)
|
||||
}
|
||||
|
||||
/// Takes the value in handle `handle` opaquely
|
||||
pub fn take_opaque(handle: OpaqueHandle) -> Box<dyn Any> {
|
||||
let mut ctxs = CTXS_STACK
|
||||
.try_borrow_mut()
|
||||
.expect("Cannot modify context while accessing it");
|
||||
let stack = ctxs.get_mut(&handle.type_id).expect("Context stack should exist");
|
||||
let value = stack
|
||||
.get_mut(handle.idx)
|
||||
.and_then(Option::take)
|
||||
.expect("Value was already taken");
|
||||
|
||||
// Then remove any empty entries from the end
|
||||
while stack.last().is_some_and(Option::is_none) {
|
||||
stack.pop().expect("Should have a value at the end");
|
||||
}
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
type CtxsStackImpl<A> = RefCell<HashMap<TypeId, CtxStackImpl<A>, RandomState>>;
|
||||
type CtxStackImpl<A> = Vec<Option<Box<A>>>;
|
||||
|
||||
/// Hash builder for the stacks
|
||||
struct RandomState;
|
||||
|
||||
impl BuildHasher for RandomState {
|
||||
type Hasher = DefaultHasher;
|
||||
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
DefaultHasher::default()
|
||||
}
|
||||
}
|
||||
@ -4,37 +4,30 @@
|
||||
#![feature(try_blocks, thread_local, test, negative_impls, decl_macro, unsize)]
|
||||
|
||||
// Modules
|
||||
pub mod world;
|
||||
|
||||
// Exports
|
||||
pub use self::world::ContextWorld;
|
||||
pub mod context_stack;
|
||||
|
||||
// Imports
|
||||
use {
|
||||
self::world::{ContextStack, ContextStackOpaque},
|
||||
core::{any, marker::Unsize, mem},
|
||||
dynatos_world::WorldDefault,
|
||||
use core::{
|
||||
any::{self, Any},
|
||||
mem,
|
||||
};
|
||||
|
||||
/// A handle to a context value.
|
||||
///
|
||||
/// When dropped, the context value is also dropped.
|
||||
#[must_use = "The handle object keeps a value in context. If dropped, the context is also dropped"]
|
||||
pub struct Handle<T: 'static, W: ContextWorld = WorldDefault> {
|
||||
pub struct Handle<T: 'static> {
|
||||
/// Handle
|
||||
handle: world::Handle<T, W>,
|
||||
handle: context_stack::Handle<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static, W: ContextWorld> Handle<T, W> {
|
||||
impl<T: 'static> Handle<T> {
|
||||
/// Converts this handle to an opaque handle
|
||||
pub fn into_opaque(self) -> OpaqueHandle<W>
|
||||
where
|
||||
W::ContextStack<T>: ContextStackOpaque<W>,
|
||||
{
|
||||
pub fn into_opaque(self) -> OpaqueHandle {
|
||||
// Create the opaque handle and forget ourselves
|
||||
// Note: This is to ensure we don't try to take the value in the [`Drop`] impl
|
||||
let handle = OpaqueHandle {
|
||||
handle: W::ContextStack::<T>::to_opaque(self.handle),
|
||||
handle: context_stack::to_opaque(self.handle),
|
||||
};
|
||||
mem::forget(self);
|
||||
|
||||
@ -55,7 +48,7 @@ impl<T: 'static, W: ContextWorld> Handle<T, W> {
|
||||
where
|
||||
F: FnOnce(&T) -> O,
|
||||
{
|
||||
W::ContextStack::<T>::with::<_, O>(self.handle, f)
|
||||
context_stack::with(self.handle, f)
|
||||
}
|
||||
|
||||
/// Takes the value this handle is providing a context for.
|
||||
@ -71,11 +64,11 @@ impl<T: 'static, W: ContextWorld> Handle<T, W> {
|
||||
|
||||
/// Inner method for [`take`](Self::take), and the [`Drop`] impl.
|
||||
fn take_inner(&self) -> T {
|
||||
W::ContextStack::<T>::take(self.handle)
|
||||
context_stack::take(self.handle)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, W: ContextWorld> Drop for Handle<T, W> {
|
||||
impl<T: 'static> Drop for Handle<T> {
|
||||
#[track_caller]
|
||||
fn drop(&mut self) {
|
||||
let _: T = self.take_inner();
|
||||
@ -86,23 +79,23 @@ impl<T: 'static, W: ContextWorld> Drop for Handle<T, W> {
|
||||
///
|
||||
/// When dropped, the context value is also dropped.
|
||||
#[must_use = "The handle object keeps a value in context. If dropped, the context is also dropped"]
|
||||
pub struct OpaqueHandle<W: ContextWorld = WorldDefault> {
|
||||
pub struct OpaqueHandle {
|
||||
/// Handle
|
||||
handle: world::OpaqueHandle<W>,
|
||||
handle: context_stack::OpaqueHandle,
|
||||
}
|
||||
|
||||
impl<W: ContextWorld> OpaqueHandle<W> {
|
||||
impl OpaqueHandle {
|
||||
/// Uses the value from this handle
|
||||
pub fn with<F, O>(&self, f: F) -> O
|
||||
where
|
||||
F: FnOnce(&world::Any<W>) -> O,
|
||||
F: FnOnce(&dyn Any) -> O,
|
||||
{
|
||||
W::ContextStackOpaque::with_opaque(self.handle, f)
|
||||
context_stack::with_opaque(self.handle, f)
|
||||
}
|
||||
|
||||
/// Takes the value this handle is providing a context for.
|
||||
#[must_use = "If you only wish to drop the context, consider dropping the handle"]
|
||||
pub fn take(self) -> Box<world::Any<W>> {
|
||||
pub fn take(self) -> Box<dyn Any> {
|
||||
// Get the value and forget ourselves
|
||||
// Note: This is to ensure we don't try to take the value in the [`Drop`] impl
|
||||
let value = self.take_inner();
|
||||
@ -112,31 +105,25 @@ impl<W: ContextWorld> OpaqueHandle<W> {
|
||||
}
|
||||
|
||||
/// Inner method for [`take`](Self::take), and the [`Drop`] impl.
|
||||
fn take_inner(&self) -> Box<world::Any<W>> {
|
||||
W::ContextStackOpaque::take_opaque(self.handle)
|
||||
fn take_inner(&self) -> Box<dyn Any> {
|
||||
context_stack::take_opaque(self.handle)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ContextWorld> Drop for OpaqueHandle<W> {
|
||||
impl Drop for OpaqueHandle {
|
||||
#[track_caller]
|
||||
fn drop(&mut self) {
|
||||
let _: Box<world::Any<W>> = self.take_inner();
|
||||
let _: Box<dyn Any> = self.take_inner();
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a value of `T` to the current context.
|
||||
pub fn provide<T>(value: T) -> Handle<T> {
|
||||
self::provide_in::<T, WorldDefault>(value)
|
||||
}
|
||||
|
||||
/// Provides a value of `T` to the current context in world `W`.
|
||||
pub fn provide_in<T, W>(value: T) -> Handle<T, W>
|
||||
pub fn provide<T>(value: T) -> Handle<T>
|
||||
where
|
||||
T: Unsize<world::Bounds<T, W>>,
|
||||
W: ContextWorld,
|
||||
T: Any,
|
||||
{
|
||||
// Push the value onto the stack
|
||||
let handle = W::ContextStack::<T>::push(value);
|
||||
let handle = context_stack::push(value);
|
||||
|
||||
Handle { handle }
|
||||
}
|
||||
@ -146,22 +133,12 @@ where
|
||||
pub fn get<T>() -> Option<T>
|
||||
where
|
||||
T: Copy + 'static,
|
||||
{
|
||||
self::get_in::<T, WorldDefault>()
|
||||
}
|
||||
|
||||
/// Gets a value of `T` on the current context in world `W`.
|
||||
#[must_use]
|
||||
pub fn get_in<T, W>() -> Option<T>
|
||||
where
|
||||
T: Copy + 'static,
|
||||
W: ContextWorld,
|
||||
{
|
||||
#[expect(
|
||||
clippy::redundant_closure_for_method_calls,
|
||||
reason = "Can't use `Option::copied` due to inference issues"
|
||||
)]
|
||||
self::with_in::<T, _, _, W>(|value| value.copied())
|
||||
self::with::<T, _, _>(|value| value.copied())
|
||||
}
|
||||
|
||||
/// Expects a value of `T` on the current context.
|
||||
@ -171,18 +148,7 @@ pub fn expect<T>() -> T
|
||||
where
|
||||
T: Copy + 'static,
|
||||
{
|
||||
self::expect_in::<T, WorldDefault>()
|
||||
}
|
||||
|
||||
/// Expects a value of `T` on the current context in world `W`.
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn expect_in<T, W>() -> T
|
||||
where
|
||||
T: Copy + 'static,
|
||||
W: ContextWorld,
|
||||
{
|
||||
self::with_in::<T, _, _, W>(|value| *value.unwrap_or_else(self::on_missing_context::<T, _>))
|
||||
self::with::<T, _, _>(|value| *value.unwrap_or_else(self::on_missing_context::<T, _>))
|
||||
}
|
||||
|
||||
/// Gets a cloned value of `T` on the current context.
|
||||
@ -190,22 +156,12 @@ where
|
||||
pub fn get_cloned<T>() -> Option<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
self::get_cloned_in::<T, WorldDefault>()
|
||||
}
|
||||
|
||||
/// Gets a cloned value of `T` on the current context.
|
||||
#[must_use]
|
||||
pub fn get_cloned_in<T, W>() -> Option<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
W: ContextWorld,
|
||||
{
|
||||
#[expect(
|
||||
clippy::redundant_closure_for_method_calls,
|
||||
reason = "Can't use `Option::cloned` due to inference issues"
|
||||
)]
|
||||
self::with_in::<T, _, _, W>(|value| value.cloned())
|
||||
self::with::<T, _, _>(|value| value.cloned())
|
||||
}
|
||||
|
||||
/// Expects a cloned value of `T` on the current context.
|
||||
@ -215,18 +171,7 @@ pub fn expect_cloned<T>() -> T
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
self::expect_cloned_in::<T, WorldDefault>()
|
||||
}
|
||||
|
||||
/// Expects a cloned value of `T` on the current context in world `W`.
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn expect_cloned_in<T, W>() -> T
|
||||
where
|
||||
T: Clone + 'static,
|
||||
W: ContextWorld,
|
||||
{
|
||||
self::with_in::<T, _, _, W>(|value| value.unwrap_or_else(self::on_missing_context::<T, _>).clone())
|
||||
self::with::<T, _, _>(|value| value.unwrap_or_else(self::on_missing_context::<T, _>).clone())
|
||||
}
|
||||
|
||||
/// Uses a value of `T` on the current context.
|
||||
@ -235,17 +180,7 @@ where
|
||||
T: 'static,
|
||||
F: FnOnce(Option<&T>) -> O,
|
||||
{
|
||||
self::with_in::<T, F, O, WorldDefault>(f)
|
||||
}
|
||||
|
||||
/// Uses a value of `T` on the current context in world `W`.
|
||||
pub fn with_in<T, F, O, W>(f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(Option<&T>) -> O,
|
||||
W: ContextWorld,
|
||||
{
|
||||
W::ContextStack::<T>::with_top(f)
|
||||
context_stack::with_top(f)
|
||||
}
|
||||
|
||||
/// Uses a value of `T` on the current context, expecting it.
|
||||
@ -255,18 +190,7 @@ where
|
||||
T: 'static,
|
||||
F: FnOnce(&T) -> O,
|
||||
{
|
||||
self::with_expect_in::<T, F, O, WorldDefault>(f)
|
||||
}
|
||||
|
||||
/// Uses a value of `T` on the current context, expecting it in world `W`.
|
||||
#[track_caller]
|
||||
pub fn with_expect_in<T, F, O, W>(f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(&T) -> O,
|
||||
W: ContextWorld,
|
||||
{
|
||||
self::with_in::<T, _, _, W>(|value| value.map(f)).unwrap_or_else(self::on_missing_context::<T, _>)
|
||||
self::with::<T, _, _>(|value| value.map(f)).unwrap_or_else(self::on_missing_context::<T, _>)
|
||||
}
|
||||
|
||||
/// Called when context for type `T` was missing.
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
//! World
|
||||
|
||||
// Lints
|
||||
#![expect(
|
||||
type_alias_bounds,
|
||||
reason = "Although they're not enforced currently, they will be in the future and we want to be explicit already"
|
||||
)]
|
||||
|
||||
// Modules
|
||||
pub mod context_stack;
|
||||
|
||||
// Exports
|
||||
pub use self::context_stack::{ContextStack, ContextStackGlobal, ContextStackOpaque, ContextStackThreadLocal};
|
||||
|
||||
// Imports
|
||||
use {
|
||||
core::any::Any as StdAny,
|
||||
dynatos_world::{World, WorldGlobal, WorldThreadLocal},
|
||||
};
|
||||
|
||||
/// Context world
|
||||
pub trait ContextWorld: World {
|
||||
/// Context stack
|
||||
type ContextStack<T>: ContextStack<T, Self>;
|
||||
|
||||
/// Opaque context stack
|
||||
type ContextStackOpaque: ContextStackOpaque<Self>;
|
||||
}
|
||||
|
||||
impl ContextWorld for WorldThreadLocal {
|
||||
type ContextStack<T> = ContextStackThreadLocal<T>;
|
||||
type ContextStackOpaque = ContextStackThreadLocal<dyn StdAny>;
|
||||
}
|
||||
|
||||
impl ContextWorld for WorldGlobal {
|
||||
type ContextStack<T> = ContextStackGlobal<T>;
|
||||
type ContextStackOpaque = ContextStackGlobal<dyn StdAny>;
|
||||
}
|
||||
|
||||
/// Handle type for the world's context stack
|
||||
pub type Handle<T: ?Sized, W: ContextWorld> = <W::ContextStack<T> as ContextStack<T, W>>::Handle;
|
||||
|
||||
/// Opaque handle type for the world's context stack
|
||||
pub type OpaqueHandle<W: ContextWorld> = <W::ContextStackOpaque as ContextStackOpaque<W>>::OpaqueHandle;
|
||||
|
||||
/// Bounds type for the world's context stack
|
||||
pub type Bounds<T: ?Sized, W: ContextWorld> = <W::ContextStack<T> as ContextStack<T, W>>::Bounds;
|
||||
|
||||
/// Any type for the world's opaque context stack
|
||||
pub type Any<W: ContextWorld> = <W::ContextStackOpaque as ContextStackOpaque<W>>::Any;
|
||||
@ -1,364 +0,0 @@
|
||||
//! Context stack
|
||||
|
||||
// Lints
|
||||
#![expect(clippy::as_conversions, reason = "We need to unsize items and there's no other way")]
|
||||
|
||||
// Imports
|
||||
use {
|
||||
crate::ContextWorld,
|
||||
core::{
|
||||
any::{Any, TypeId},
|
||||
hash::BuildHasher,
|
||||
marker::{PhantomData, Unsize},
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, WorldGlobal, WorldThreadLocal},
|
||||
std::{collections::HashMap, hash::DefaultHasher},
|
||||
};
|
||||
|
||||
/// Context stack
|
||||
pub trait ContextStack<T, W: ContextWorld>: Sized {
|
||||
/// Handle
|
||||
type Handle: Copy;
|
||||
|
||||
/// Bounds
|
||||
type Bounds: ?Sized;
|
||||
|
||||
/// Pushes a value onto the stack and returns a handle to it
|
||||
fn push(value: T) -> Self::Handle
|
||||
where
|
||||
T: Unsize<Self::Bounds> + 'static;
|
||||
|
||||
/// Uses the value in the top of the stack
|
||||
fn with_top<F, O>(f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(Option<&T>) -> O;
|
||||
|
||||
/// Uses the value in handle `handle`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the context stack doesn't exist, or
|
||||
/// if the value was already taken.
|
||||
fn with<F, O>(handle: Self::Handle, f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(&T) -> O;
|
||||
|
||||
/// Takes the value in handle `handle`
|
||||
fn take(handle: Self::Handle) -> T
|
||||
where
|
||||
T: 'static;
|
||||
|
||||
/// Converts a handle to an opaque handle
|
||||
fn to_opaque(handle: Self::Handle) -> super::OpaqueHandle<W>
|
||||
where
|
||||
T: 'static;
|
||||
}
|
||||
|
||||
/// Opaque Context stack
|
||||
pub trait ContextStackOpaque<W: ContextWorld>: Sized {
|
||||
/// Handle
|
||||
type OpaqueHandle: Copy;
|
||||
|
||||
/// Any type
|
||||
type Any: ?Sized + Any + Unsize<dyn Any>;
|
||||
|
||||
/// Uses the value in handle `handle` opaquely.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the context stack doesn't exist, or
|
||||
/// if the value was already taken.
|
||||
fn with_opaque<F, O>(handle: Self::OpaqueHandle, f: F) -> O
|
||||
where
|
||||
F: FnOnce(&Self::Any) -> O;
|
||||
|
||||
/// Takes the value in handle `handle` opaquely
|
||||
fn take_opaque(handle: Self::OpaqueHandle) -> Box<Self::Any>;
|
||||
}
|
||||
|
||||
/// Thread-local context stack
|
||||
pub struct ContextStackThreadLocal<T: ?Sized>(PhantomData<T>);
|
||||
|
||||
/// Context stack for `ContextStackThreadLocal`
|
||||
// TODO: Use type with less indirections?
|
||||
#[thread_local]
|
||||
static CTXS_STACK_THREAD_LOCAL: CtxsStackImpl<WorldThreadLocal, dyn Any> =
|
||||
IMut::<_, WorldThreadLocal>::new(HashMap::with_hasher(RandomState));
|
||||
|
||||
/// Handle for [`ContextStackThreadLocal`]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HandleThreadLocal(usize);
|
||||
|
||||
impl !Send for HandleThreadLocal {}
|
||||
impl !Sync for HandleThreadLocal {}
|
||||
|
||||
/// Opaque handle for [`ContextStackThreadLocal`]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct OpaqueHandleThreadLocal {
|
||||
type_id: TypeId,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl !Send for OpaqueHandleThreadLocal {}
|
||||
impl !Sync for OpaqueHandleThreadLocal {}
|
||||
|
||||
impl<T> ContextStack<T, WorldThreadLocal> for ContextStackThreadLocal<T> {
|
||||
type Bounds = dyn Any;
|
||||
type Handle = HandleThreadLocal;
|
||||
|
||||
fn push(value: T) -> Self::Handle
|
||||
where
|
||||
T: Unsize<Self::Bounds> + 'static,
|
||||
{
|
||||
let idx = self::push::<WorldThreadLocal, _, _>(&CTXS_STACK_THREAD_LOCAL, value);
|
||||
HandleThreadLocal(idx)
|
||||
}
|
||||
|
||||
fn with_top<F, O>(f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(Option<&T>) -> O,
|
||||
{
|
||||
self::with_top::<WorldThreadLocal, _, _, _, _>(&CTXS_STACK_THREAD_LOCAL, f)
|
||||
}
|
||||
|
||||
fn with<F, O>(handle: Self::Handle, f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(&T) -> O,
|
||||
{
|
||||
self::with::<WorldThreadLocal, _, _, _, _>(&CTXS_STACK_THREAD_LOCAL, handle.0, f)
|
||||
}
|
||||
|
||||
fn take(handle: Self::Handle) -> T
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self::take::<WorldThreadLocal, _, _>(&CTXS_STACK_THREAD_LOCAL, handle.0)
|
||||
}
|
||||
|
||||
fn to_opaque(handle: Self::Handle) -> super::OpaqueHandle<WorldThreadLocal>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
OpaqueHandleThreadLocal {
|
||||
type_id: TypeId::of::<T>(),
|
||||
idx: handle.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ContextStackOpaque<WorldThreadLocal> for ContextStackThreadLocal<T> {
|
||||
type Any = dyn Any;
|
||||
type OpaqueHandle = OpaqueHandleThreadLocal;
|
||||
|
||||
fn with_opaque<F, O>(handle: Self::OpaqueHandle, f: F) -> O
|
||||
where
|
||||
F: FnOnce(&Self::Any) -> O,
|
||||
{
|
||||
self::with_opaque::<WorldThreadLocal, _, _, _>(&CTXS_STACK_THREAD_LOCAL, handle.type_id, handle.idx, f)
|
||||
}
|
||||
|
||||
fn take_opaque(handle: Self::OpaqueHandle) -> Box<Self::Any> {
|
||||
self::take_opaque::<WorldThreadLocal, _>(&CTXS_STACK_THREAD_LOCAL, handle.type_id, handle.idx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Global context stack
|
||||
pub struct ContextStackGlobal<T: ?Sized>(PhantomData<T>);
|
||||
|
||||
/// Context stack for `ContextStackGlobal`
|
||||
// TODO: Use type with less indirections?
|
||||
static CTXS_STACK_GLOBAL: CtxsStackImpl<WorldGlobal, dyn Any + Send + Sync> =
|
||||
IMut::<_, WorldGlobal>::new(HashMap::with_hasher(RandomState));
|
||||
|
||||
/// Handle for [`ContextStackGlobal`]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HandleGlobal(usize);
|
||||
|
||||
/// Opaque handle for [`ContextStackGlobal`]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct OpaqueHandleGlobal {
|
||||
type_id: TypeId,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl<T> ContextStack<T, WorldGlobal> for ContextStackGlobal<T> {
|
||||
type Bounds = dyn Any + Send + Sync;
|
||||
type Handle = HandleGlobal;
|
||||
|
||||
fn push(value: T) -> Self::Handle
|
||||
where
|
||||
T: Unsize<Self::Bounds> + 'static,
|
||||
{
|
||||
let idx = self::push::<WorldGlobal, _, _>(&CTXS_STACK_GLOBAL, value);
|
||||
HandleGlobal(idx)
|
||||
}
|
||||
|
||||
fn with_top<F, O>(f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(Option<&T>) -> O,
|
||||
{
|
||||
self::with_top::<WorldGlobal, _, _, _, _>(&CTXS_STACK_GLOBAL, f)
|
||||
}
|
||||
|
||||
fn with<F, O>(handle: Self::Handle, f: F) -> O
|
||||
where
|
||||
T: 'static,
|
||||
F: FnOnce(&T) -> O,
|
||||
{
|
||||
self::with::<WorldGlobal, _, _, _, _>(&CTXS_STACK_GLOBAL, handle.0, f)
|
||||
}
|
||||
|
||||
fn take(handle: Self::Handle) -> T
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self::take::<WorldGlobal, _, _>(&CTXS_STACK_GLOBAL, handle.0)
|
||||
}
|
||||
|
||||
fn to_opaque(handle: Self::Handle) -> super::OpaqueHandle<WorldGlobal>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
OpaqueHandleGlobal {
|
||||
type_id: TypeId::of::<T>(),
|
||||
idx: handle.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ContextStackOpaque<WorldGlobal> for ContextStackGlobal<T> {
|
||||
type Any = dyn Any + Send + Sync;
|
||||
type OpaqueHandle = OpaqueHandleGlobal;
|
||||
|
||||
fn with_opaque<F, O>(handle: Self::OpaqueHandle, f: F) -> O
|
||||
where
|
||||
F: FnOnce(&Self::Any) -> O,
|
||||
{
|
||||
self::with_opaque::<WorldGlobal, _, _, _>(&CTXS_STACK_GLOBAL, handle.type_id, handle.idx, f)
|
||||
}
|
||||
|
||||
fn take_opaque(handle: Self::OpaqueHandle) -> Box<Self::Any> {
|
||||
self::take_opaque::<WorldGlobal, _>(&CTXS_STACK_GLOBAL, handle.type_id, handle.idx)
|
||||
}
|
||||
}
|
||||
|
||||
type CtxsStackImpl<W, A> = IMut<HashMap<TypeId, CtxStackImpl<A>, RandomState>, W>;
|
||||
type CtxStackImpl<A> = Vec<Option<Box<A>>>;
|
||||
|
||||
/// Hash builder for the stacks
|
||||
struct RandomState;
|
||||
|
||||
impl BuildHasher for RandomState {
|
||||
type Hasher = DefaultHasher;
|
||||
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
DefaultHasher::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn push<W, A, T>(ctxs_stack: &CtxsStackImpl<W, A>, value: T) -> usize
|
||||
where
|
||||
W: ContextWorld,
|
||||
A: ?Sized,
|
||||
T: Unsize<A> + 'static,
|
||||
{
|
||||
let mut ctxs = ctxs_stack
|
||||
.try_write()
|
||||
.expect("Cannot modify context while accessing it");
|
||||
let stack = ctxs.entry(TypeId::of::<T>()).or_default();
|
||||
let idx = stack.len();
|
||||
stack.push(Some(Box::new(value) as Box<A>));
|
||||
|
||||
idx
|
||||
}
|
||||
|
||||
fn with_top<W, A, T, F, O>(ctxs_stack: &CtxsStackImpl<W, A>, f: F) -> O
|
||||
where
|
||||
W: ContextWorld,
|
||||
A: ?Sized + Any + Unsize<dyn Any>,
|
||||
T: 'static,
|
||||
F: FnOnce(Option<&T>) -> O,
|
||||
{
|
||||
let type_id = TypeId::of::<T>();
|
||||
let ctxs = ctxs_stack.try_read().expect("Cannot access context while modifying it");
|
||||
let value = try {
|
||||
let stack = ctxs.get(&type_id)?;
|
||||
let value = stack.last()?.as_ref().expect("Value was already taken");
|
||||
(&**value as &dyn Any)
|
||||
.downcast_ref::<T>()
|
||||
.expect("Value was the wrong type")
|
||||
};
|
||||
|
||||
f(value)
|
||||
}
|
||||
|
||||
fn with<W, A, T, F, O>(ctxs_stack: &CtxsStackImpl<W, A>, idx: usize, f: F) -> O
|
||||
where
|
||||
W: ContextWorld,
|
||||
A: ?Sized + Any + Unsize<dyn Any>,
|
||||
T: 'static,
|
||||
F: FnOnce(&T) -> O,
|
||||
{
|
||||
let type_id = TypeId::of::<T>();
|
||||
self::with_opaque::<W, A, _, _>(ctxs_stack, type_id, idx, |value| {
|
||||
let value = (value as &dyn Any)
|
||||
.downcast_ref::<T>()
|
||||
.expect("Value was the wrong type");
|
||||
f(value)
|
||||
})
|
||||
}
|
||||
|
||||
fn take<W, A, T>(ctxs_stack: &CtxsStackImpl<W, A>, idx: usize) -> T
|
||||
where
|
||||
W: ContextWorld,
|
||||
A: ?Sized + Any + Unsize<dyn Any>,
|
||||
T: 'static,
|
||||
{
|
||||
let type_id = TypeId::of::<T>();
|
||||
let value = self::take_opaque::<W, A>(ctxs_stack, type_id, idx);
|
||||
let value = (value as Box<dyn Any>).downcast().expect("Value was the wrong type");
|
||||
|
||||
*value
|
||||
}
|
||||
|
||||
|
||||
fn with_opaque<W, A, F, O>(ctxs_stack: &CtxsStackImpl<W, A>, type_id: TypeId, idx: usize, f: F) -> O
|
||||
where
|
||||
W: ContextWorld,
|
||||
A: ?Sized + 'static,
|
||||
F: FnOnce(&A) -> O,
|
||||
{
|
||||
let ctxs = ctxs_stack.try_read().expect("Cannot access context while modifying it");
|
||||
let stack = ctxs.get(&type_id).expect("Context stack should exist");
|
||||
let value = stack
|
||||
.get(idx)
|
||||
.expect("Index was invalid")
|
||||
.as_ref()
|
||||
.expect("Value was already taken");
|
||||
f(&**value)
|
||||
}
|
||||
|
||||
fn take_opaque<W, A>(ctxs_stack: &CtxsStackImpl<W, A>, type_id: TypeId, idx: usize) -> Box<A>
|
||||
where
|
||||
W: ContextWorld,
|
||||
A: ?Sized,
|
||||
{
|
||||
let mut ctxs = ctxs_stack
|
||||
.try_write()
|
||||
.expect("Cannot modify context while accessing it");
|
||||
let stack = ctxs.get_mut(&type_id).expect("Context stack should exist");
|
||||
let value = stack
|
||||
.get_mut(idx)
|
||||
.and_then(Option::take)
|
||||
.expect("Value was already taken");
|
||||
|
||||
// Then remove any empty entries from the end
|
||||
while stack.last().is_some_and(Option::is_none) {
|
||||
stack.pop().expect("Should have a value at the end");
|
||||
}
|
||||
|
||||
value
|
||||
}
|
||||
@ -8,7 +8,6 @@ use {
|
||||
},
|
||||
dynatos_reactive::{
|
||||
enum_split::{EnumSplitValue, EnumSplitValueUpdateCtx, SignalStorage},
|
||||
ReactiveWorld,
|
||||
Signal,
|
||||
SignalGetClone,
|
||||
SignalGetCopy,
|
||||
@ -414,12 +413,11 @@ impl<T, E> Default for SplitValueStorage<T, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E, S, W> EnumSplitValue<S, W> for Loadable<T, E>
|
||||
impl<T, E, S> EnumSplitValue<S> for Loadable<T, E>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
E: Clone + 'static,
|
||||
S: SignalSet<Self> + Clone + 'static,
|
||||
W: ReactiveWorld,
|
||||
{
|
||||
type SigKind = Loadable<(), ()>;
|
||||
type Signal = Loadable<Signal<T>, Signal<E>>;
|
||||
@ -439,7 +437,7 @@ where
|
||||
self.as_ref().map(|_| ()).map_err(|_| ())
|
||||
}
|
||||
|
||||
fn update(self, storage: &mut Self::SignalsStorage, ctx: EnumSplitValueUpdateCtx<'_, S, W>) {
|
||||
fn update(self, storage: &mut Self::SignalsStorage, ctx: EnumSplitValueUpdateCtx<'_, S>) {
|
||||
match self {
|
||||
Self::Loaded(new_value) => match &storage.loaded {
|
||||
Some(storage) => storage.set(new_value),
|
||||
|
||||
@ -7,7 +7,6 @@ edition = "2021"
|
||||
|
||||
dynatos-context = { workspace = true }
|
||||
dynatos-util = { workspace = true }
|
||||
dynatos-world = { workspace = true }
|
||||
|
||||
derive_more = { workspace = true, features = ["full"] }
|
||||
duplicate = { workspace = true }
|
||||
|
||||
@ -6,7 +6,6 @@ use core::panic::Location;
|
||||
use {
|
||||
crate::{
|
||||
trigger::TriggerExec,
|
||||
ReactiveWorld,
|
||||
SignalBorrow,
|
||||
SignalBorrowMut,
|
||||
SignalGetClone,
|
||||
@ -21,21 +20,18 @@ use {
|
||||
Trigger,
|
||||
},
|
||||
core::{
|
||||
cell::{self, RefCell},
|
||||
fmt,
|
||||
future::Future,
|
||||
ops::{Deref, DerefMut},
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, IMutRef, IMutRefMut, IMutRefMutLike, Rc, RcLike, WorldDefault},
|
||||
futures::{future, stream::AbortHandle},
|
||||
std::rc::Rc,
|
||||
tokio::sync::Notify,
|
||||
};
|
||||
|
||||
/// World for [`AsyncSignal`]
|
||||
#[expect(private_bounds, reason = "We can't *not* leak some implementation details currently")]
|
||||
pub trait AsyncReactiveWorld<F: Loader> = ReactiveWorld where IMut<Inner<F, Self>, Self>: Sized;
|
||||
|
||||
/// Inner
|
||||
struct Inner<F: Loader, W: AsyncReactiveWorld<F>> {
|
||||
struct Inner<F: Loader> {
|
||||
/// Value
|
||||
value: Option<F::Output>,
|
||||
|
||||
@ -46,13 +42,13 @@ struct Inner<F: Loader, W: AsyncReactiveWorld<F>> {
|
||||
handle: Option<AbortHandle>,
|
||||
|
||||
/// Trigger
|
||||
trigger: Rc<Trigger<W>, W>,
|
||||
trigger: Rc<Trigger>,
|
||||
|
||||
/// Notify
|
||||
notify: Rc<Notify, W>,
|
||||
notify: Rc<Notify>,
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> Inner<F, W> {
|
||||
impl<F: Loader> Inner<F> {
|
||||
/// Stops loading the value.
|
||||
///
|
||||
/// Returns if the loader had a future.
|
||||
@ -73,7 +69,7 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> Inner<F, W> {
|
||||
///
|
||||
/// Returns whether this created the loader's future.
|
||||
#[track_caller]
|
||||
pub fn start_loading(&mut self, this: Rc<IMut<Self, W>, W>) -> bool
|
||||
pub fn start_loading(&mut self, this: Rc<RefCell<Self>>) -> bool
|
||||
where
|
||||
F: Loader,
|
||||
{
|
||||
@ -92,16 +88,16 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> Inner<F, W> {
|
||||
// Load the value
|
||||
// Note: If we get aborted, just remove the handle
|
||||
let Ok(value) = fut.await else {
|
||||
this.write().handle = None;
|
||||
this.borrow_mut().handle = None;
|
||||
return;
|
||||
};
|
||||
|
||||
// Then write it and remove the handle
|
||||
let mut inner = this.write();
|
||||
let mut inner = this.borrow_mut();
|
||||
inner.value = Some(value);
|
||||
inner.handle = None;
|
||||
let trigger = inner.trigger.clone();
|
||||
let notify = Rc::<_, W>::clone(&inner.notify);
|
||||
let trigger = Rc::clone(&inner.trigger);
|
||||
let notify = Rc::clone(&inner.notify);
|
||||
drop(inner);
|
||||
|
||||
// Finally trigger and awake all waiters.
|
||||
@ -124,7 +120,7 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> Inner<F, W> {
|
||||
///
|
||||
/// Returns whether a future existed before
|
||||
#[track_caller]
|
||||
pub fn restart_loading(&mut self, this: Rc<IMut<Self, W>, W>) -> bool
|
||||
pub fn restart_loading(&mut self, this: Rc<RefCell<Self>>) -> bool
|
||||
where
|
||||
F: Loader,
|
||||
{
|
||||
@ -145,32 +141,23 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> Inner<F, W> {
|
||||
}
|
||||
|
||||
/// Async signal
|
||||
pub struct AsyncSignal<F: Loader, W: AsyncReactiveWorld<F> = WorldDefault> {
|
||||
pub struct AsyncSignal<F: Loader> {
|
||||
/// Inner
|
||||
inner: Rc<IMut<Inner<F, W>, W>, W>,
|
||||
inner: Rc<RefCell<Inner<F>>>,
|
||||
}
|
||||
|
||||
impl<F: Loader> AsyncSignal<F, WorldDefault> {
|
||||
impl<F: Loader> AsyncSignal<F> {
|
||||
/// Creates a new async signal with a loader
|
||||
#[track_caller]
|
||||
#[must_use]
|
||||
pub fn new(loader: F) -> Self {
|
||||
Self::new_in(loader, WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
/// Creates a new async signal with a loader in a world
|
||||
#[track_caller]
|
||||
#[must_use]
|
||||
pub fn new_in(loader: F, world: W) -> Self {
|
||||
Self {
|
||||
inner: Rc::<_, W>::new(IMut::<_, W>::new(Inner {
|
||||
inner: Rc::new(RefCell::new(Inner {
|
||||
value: None,
|
||||
loader,
|
||||
handle: None,
|
||||
trigger: Rc::<_, W>::new(Trigger::new_in(world)),
|
||||
notify: Rc::<_, W>::new(Notify::new()),
|
||||
trigger: Rc::new(Trigger::new()),
|
||||
notify: Rc::new(Notify::new()),
|
||||
})),
|
||||
}
|
||||
}
|
||||
@ -178,8 +165,12 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
/// Stops loading the value.
|
||||
///
|
||||
/// Returns if the loader had a future.
|
||||
#[expect(
|
||||
clippy::must_use_candidate,
|
||||
reason = "The user may not care whether the future existed"
|
||||
)]
|
||||
pub fn stop_loading(&self) -> bool {
|
||||
self.inner.write().stop_loading()
|
||||
self.inner.borrow_mut().stop_loading()
|
||||
}
|
||||
|
||||
/// Starts loading the value.
|
||||
@ -188,11 +179,15 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
///
|
||||
/// Returns whether this created the loader's future.
|
||||
#[track_caller]
|
||||
#[expect(
|
||||
clippy::must_use_candidate,
|
||||
reason = "The user may not care whether we started the future"
|
||||
)]
|
||||
pub fn start_loading(&self) -> bool
|
||||
where
|
||||
F: Loader,
|
||||
{
|
||||
self.inner.write().start_loading(Rc::<_, W>::clone(&self.inner))
|
||||
self.inner.borrow_mut().start_loading(Rc::clone(&self.inner))
|
||||
}
|
||||
|
||||
/// Restarts the loading.
|
||||
@ -202,11 +197,15 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
///
|
||||
/// Returns whether a future existed before
|
||||
#[track_caller]
|
||||
#[expect(
|
||||
clippy::must_use_candidate,
|
||||
reason = "The user may not care whether the future existed"
|
||||
)]
|
||||
pub fn restart_loading(&self) -> bool
|
||||
where
|
||||
F: Loader,
|
||||
{
|
||||
self.inner.write().restart_loading(Rc::<_, W>::clone(&self.inner))
|
||||
self.inner.borrow_mut().restart_loading(Rc::clone(&self.inner))
|
||||
}
|
||||
|
||||
/// Returns if loading.
|
||||
@ -214,14 +213,14 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
/// This is considered loading if the loader has an active future.
|
||||
#[must_use]
|
||||
pub fn is_loading(&self) -> bool {
|
||||
self.inner.read().is_loading()
|
||||
self.inner.borrow().is_loading()
|
||||
}
|
||||
|
||||
/// Waits for the value to be loaded.
|
||||
///
|
||||
/// If not loading, waits until the loading starts, but does not start it.
|
||||
pub async fn wait(&self) -> BorrowRef<'_, F, W> {
|
||||
let inner = self.inner.read();
|
||||
pub async fn wait(&self) -> BorrowRef<'_, F> {
|
||||
let inner = self.inner.borrow();
|
||||
self.wait_inner(inner).await
|
||||
}
|
||||
|
||||
@ -233,17 +232,23 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
///
|
||||
/// If this future is dropped before completion, the loading
|
||||
/// will be cancelled.
|
||||
pub async fn load(&self) -> BorrowRef<'_, F, W> {
|
||||
pub async fn load(&self) -> BorrowRef<'_, F> {
|
||||
#![expect(
|
||||
clippy::await_holding_refcell_ref,
|
||||
reason = "False positive, we drop it when awaiting"
|
||||
)]
|
||||
|
||||
// If the value is loaded, return it
|
||||
let mut inner = self.inner.write();
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if inner.value.is_some() {
|
||||
return BorrowRef(IMutRefMut::<_, W>::downgrade(inner));
|
||||
drop(inner);
|
||||
return BorrowRef(self.inner.borrow());
|
||||
}
|
||||
|
||||
// Else start loading, and setup a defer to stop loading if we get cancelled.
|
||||
// Note: Stopping loading is a no-op if `wait` successfully returns, we only
|
||||
// care if we're dropped early.
|
||||
let created_loader = inner.start_loading(Rc::<_, W>::clone(&self.inner));
|
||||
let created_loader = inner.start_loading(Rc::clone(&self.inner));
|
||||
scopeguard::defer! {
|
||||
if created_loader {
|
||||
self.stop_loading();
|
||||
@ -251,13 +256,19 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
}
|
||||
|
||||
// Then wait for the value
|
||||
self.wait_inner(IMutRefMut::<_, W>::downgrade(inner)).await
|
||||
drop(inner);
|
||||
self.wait_inner(self.inner.borrow()).await
|
||||
}
|
||||
|
||||
async fn wait_inner<'a>(&'a self, mut inner: IMutRef<'a, Inner<F, W>, W>) -> BorrowRef<'a, F, W> {
|
||||
async fn wait_inner<'a>(&'a self, mut inner: cell::Ref<'a, Inner<F>>) -> BorrowRef<'a, F> {
|
||||
#![expect(
|
||||
clippy::await_holding_refcell_ref,
|
||||
reason = "False positive, we drop it when awaiting"
|
||||
)]
|
||||
|
||||
loop {
|
||||
// Register a handle to be notified
|
||||
let notify = Rc::<_, W>::clone(&inner.notify);
|
||||
let notify = Rc::clone(&inner.notify);
|
||||
let notified = notify.notified();
|
||||
drop(inner);
|
||||
|
||||
@ -266,7 +277,7 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
|
||||
// Finally return the value
|
||||
// Note: If in the meantime the value got overwritten, we wait again
|
||||
inner = self.inner.read();
|
||||
inner = self.inner.borrow();
|
||||
if inner.value.is_some() {
|
||||
break BorrowRef(inner);
|
||||
}
|
||||
@ -298,8 +309,8 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
/// Borrows the value, without loading it
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn borrow_unloaded(&self) -> Option<BorrowRef<'_, F, W>> {
|
||||
let inner = self.inner.read();
|
||||
pub fn borrow_unloaded(&self) -> Option<BorrowRef<'_, F>> {
|
||||
let inner = self.inner.borrow();
|
||||
inner.trigger.gather_subscribers();
|
||||
inner.value.is_some().then(|| BorrowRef(inner))
|
||||
}
|
||||
@ -307,21 +318,21 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> AsyncSignal<F, W> {
|
||||
/// Borrows the value, without loading it or gathering subscribers
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn borrow_unloaded_raw(&self) -> Option<BorrowRef<'_, F, W>> {
|
||||
let inner = self.inner.read();
|
||||
pub fn borrow_unloaded_raw(&self) -> Option<BorrowRef<'_, F>> {
|
||||
let inner = self.inner.borrow();
|
||||
inner.value.is_some().then(|| BorrowRef(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> Clone for AsyncSignal<F, W> {
|
||||
impl<F: Loader> Clone for AsyncSignal<F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Rc::<_, W>::clone(&self.inner),
|
||||
inner: Rc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> fmt::Debug for AsyncSignal<F, W>
|
||||
impl<F: Loader> fmt::Debug for AsyncSignal<F>
|
||||
where
|
||||
F::Output: fmt::Debug,
|
||||
{
|
||||
@ -332,9 +343,9 @@ where
|
||||
}
|
||||
|
||||
/// Reference type for [`SignalBorrow`] impl
|
||||
pub struct BorrowRef<'a, F: Loader, W: AsyncReactiveWorld<F> = WorldDefault>(IMutRef<'a, Inner<F, W>, W>);
|
||||
pub struct BorrowRef<'a, F: Loader>(cell::Ref<'a, Inner<F>>);
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> fmt::Debug for BorrowRef<'_, F, W>
|
||||
impl<F: Loader> fmt::Debug for BorrowRef<'_, F>
|
||||
where
|
||||
F::Output: fmt::Debug,
|
||||
{
|
||||
@ -343,7 +354,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> Deref for BorrowRef<'_, F, W> {
|
||||
impl<F: Loader> Deref for BorrowRef<'_, F> {
|
||||
type Target = F::Output;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -351,7 +362,7 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> Deref for BorrowRef<'_, F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader<Output: Copy>, W: AsyncReactiveWorld<F>> SignalGetCopy for Option<BorrowRef<'_, F, W>> {
|
||||
impl<F: Loader<Output: Copy>> SignalGetCopy for Option<BorrowRef<'_, F>> {
|
||||
type Value = Option<F::Output>;
|
||||
|
||||
fn copy_value(self) -> Self::Value {
|
||||
@ -359,7 +370,7 @@ impl<F: Loader<Output: Copy>, W: AsyncReactiveWorld<F>> SignalGetCopy for Option
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader<Output: Clone>, W: AsyncReactiveWorld<F>> SignalGetClone for Option<BorrowRef<'_, F, W>> {
|
||||
impl<F: Loader<Output: Clone>> SignalGetClone for Option<BorrowRef<'_, F>> {
|
||||
type Value = Option<F::Output>;
|
||||
|
||||
fn clone_value(self) -> Self::Value {
|
||||
@ -367,39 +378,39 @@ impl<F: Loader<Output: Clone>, W: AsyncReactiveWorld<F>> SignalGetClone for Opti
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> SignalBorrow for AsyncSignal<F, W> {
|
||||
impl<F: Loader> SignalBorrow for AsyncSignal<F> {
|
||||
type Ref<'a>
|
||||
= Option<BorrowRef<'a, F, W>>
|
||||
= Option<BorrowRef<'a, F>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn borrow(&self) -> Self::Ref<'_> {
|
||||
// Start loading on borrow
|
||||
let mut inner = self.inner.write();
|
||||
inner.start_loading(Rc::<_, W>::clone(&self.inner));
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
inner.start_loading(Rc::clone(&self.inner));
|
||||
|
||||
// Then get the value
|
||||
inner.trigger.gather_subscribers();
|
||||
inner
|
||||
.value
|
||||
.is_some()
|
||||
.then(|| BorrowRef(IMutRefMut::<_, W>::downgrade(inner)))
|
||||
inner.value.is_some().then(|| {
|
||||
drop(inner);
|
||||
BorrowRef(self.inner.borrow())
|
||||
})
|
||||
}
|
||||
|
||||
fn borrow_raw(&self) -> Self::Ref<'_> {
|
||||
// Start loading on borrow
|
||||
let mut inner = self.inner.write();
|
||||
inner.start_loading(Rc::<_, W>::clone(&self.inner));
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
inner.start_loading(Rc::clone(&self.inner));
|
||||
|
||||
// Then get the value
|
||||
inner
|
||||
.value
|
||||
.is_some()
|
||||
.then(|| BorrowRef(IMutRefMut::<_, W>::downgrade(inner)))
|
||||
inner.value.is_some().then(|| {
|
||||
drop(inner);
|
||||
BorrowRef(self.inner.borrow())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> SignalWith for AsyncSignal<F, W>
|
||||
impl<F: Loader> SignalWith for AsyncSignal<F>
|
||||
where
|
||||
F::Output: 'static,
|
||||
{
|
||||
@ -423,16 +434,16 @@ where
|
||||
}
|
||||
|
||||
/// Reference type for [`SignalBorrowMut`] impl
|
||||
pub struct BorrowRefMut<'a, F: Loader, W: AsyncReactiveWorld<F> = WorldDefault> {
|
||||
pub struct BorrowRefMut<'a, F: Loader> {
|
||||
/// Value
|
||||
value: IMutRefMut<'a, Inner<F, W>, W>,
|
||||
value: cell::RefMut<'a, Inner<F>>,
|
||||
|
||||
/// Trigger on drop
|
||||
// Note: Must be dropped *after* `value`.
|
||||
_trigger_on_drop: Option<TriggerExec<W>>,
|
||||
_trigger_on_drop: Option<TriggerExec>,
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> fmt::Debug for BorrowRefMut<'_, F, W>
|
||||
impl<F: Loader> fmt::Debug for BorrowRefMut<'_, F>
|
||||
where
|
||||
F::Output: fmt::Debug,
|
||||
{
|
||||
@ -441,7 +452,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> Deref for BorrowRefMut<'_, F, W> {
|
||||
impl<F: Loader> Deref for BorrowRefMut<'_, F> {
|
||||
type Target = F::Output;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -449,15 +460,15 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> Deref for BorrowRefMut<'_, F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> DerefMut for BorrowRefMut<'_, F, W> {
|
||||
impl<F: Loader> DerefMut for BorrowRefMut<'_, F> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.value.value.as_mut().expect("Borrow was `None`")
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> SignalBorrowMut for AsyncSignal<F, W> {
|
||||
impl<F: Loader> SignalBorrowMut for AsyncSignal<F> {
|
||||
type RefMut<'a>
|
||||
= Option<BorrowRefMut<'a, F, W>>
|
||||
= Option<BorrowRefMut<'a, F>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
@ -465,7 +476,7 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> SignalBorrowMut for AsyncSignal<F, W>
|
||||
// Note: We don't load when mutably borrowing, since that's probably
|
||||
// not what the user wants
|
||||
// TODO: Should we even stop loading if the value was set in the meantime?
|
||||
let inner = self.inner.write();
|
||||
let inner = self.inner.borrow_mut();
|
||||
|
||||
// Then get the value
|
||||
match inner.value.is_some() {
|
||||
@ -481,7 +492,7 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> SignalBorrowMut for AsyncSignal<F, W>
|
||||
// Note: We don't load when mutably borrowing, since that's probably
|
||||
// not what the user wants
|
||||
// TODO: Should we even stop loading if the value was set in the meantime?
|
||||
let inner = self.inner.write();
|
||||
let inner = self.inner.borrow_mut();
|
||||
|
||||
// Then get the value
|
||||
inner.value.is_some().then(|| BorrowRefMut {
|
||||
@ -491,7 +502,7 @@ impl<F: Loader, W: AsyncReactiveWorld<F>> SignalBorrowMut for AsyncSignal<F, W>
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> SignalUpdate for AsyncSignal<F, W>
|
||||
impl<F: Loader> SignalUpdate for AsyncSignal<F>
|
||||
where
|
||||
F::Output: 'static,
|
||||
{
|
||||
@ -514,14 +525,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> SignalSetDefaultImpl for AsyncSignal<F, W> {}
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> SignalGetDefaultImpl for AsyncSignal<F, W> {}
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> SignalGetClonedDefaultImpl for AsyncSignal<F, W> {}
|
||||
impl<F: Loader> SignalSetDefaultImpl for AsyncSignal<F> {}
|
||||
impl<F: Loader> SignalGetDefaultImpl for AsyncSignal<F> {}
|
||||
impl<F: Loader> SignalGetClonedDefaultImpl for AsyncSignal<F> {}
|
||||
|
||||
// Note: We want to return an `Option<&T>` instead of `&Option<T>`,
|
||||
// so we can't use the default impl
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> !SignalWithDefaultImpl for AsyncSignal<F, W> {}
|
||||
impl<F: Loader, W: AsyncReactiveWorld<F>> !SignalUpdateDefaultImpl for AsyncSignal<F, W> {}
|
||||
impl<F: Loader> !SignalWithDefaultImpl for AsyncSignal<F> {}
|
||||
impl<F: Loader> !SignalUpdateDefaultImpl for AsyncSignal<F> {}
|
||||
|
||||
/// Loader
|
||||
pub trait Loader: 'static {
|
||||
|
||||
@ -37,7 +37,6 @@ use {
|
||||
Effect,
|
||||
EffectRun,
|
||||
EffectRunCtx,
|
||||
ReactiveWorld,
|
||||
SignalBorrow,
|
||||
SignalGetClonedDefaultImpl,
|
||||
SignalGetDefaultImpl,
|
||||
@ -45,25 +44,22 @@ use {
|
||||
Trigger,
|
||||
},
|
||||
core::{
|
||||
cell::{self, RefCell},
|
||||
fmt,
|
||||
marker::{PhantomData, Unsize},
|
||||
ops::{CoerceUnsized, Deref},
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, IMutRef, WorldDefault},
|
||||
};
|
||||
|
||||
/// World for [`Derived`]
|
||||
pub trait DerivedWorld<T, F: ?Sized> = ReactiveWorld where IMut<Option<T>, Self>: Sized;
|
||||
|
||||
/// Derived signal.
|
||||
///
|
||||
/// See the module documentation for more information.
|
||||
pub struct Derived<T, F: ?Sized, W: DerivedWorld<T, F> = WorldDefault> {
|
||||
pub struct Derived<T, F: ?Sized> {
|
||||
/// Effect
|
||||
effect: Effect<EffectFn<T, F, W>, W>,
|
||||
effect: Effect<EffectFn<T, F>>,
|
||||
}
|
||||
|
||||
impl<T, F> Derived<T, F, WorldDefault> {
|
||||
impl<T, F> Derived<T, F> {
|
||||
/// Creates a new derived signal
|
||||
#[track_caller]
|
||||
pub fn new(f: F) -> Self
|
||||
@ -71,41 +67,21 @@ impl<T, F> Derived<T, F, WorldDefault> {
|
||||
T: 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
{
|
||||
Self::new_in(f, WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F, W: DerivedWorld<T, F>> Derived<T, F, W> {
|
||||
/// Creates a new derived signal in a world
|
||||
#[track_caller]
|
||||
#[expect(private_bounds, reason = "We can't *not* leak some implementation details currently")]
|
||||
pub fn new_in(f: F, world: W) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
EffectFn<T, F, W>: Unsize<W::F>,
|
||||
{
|
||||
let value = IMut::<_, W>::new(None);
|
||||
let effect = Effect::new_in(
|
||||
EffectFn {
|
||||
trigger: Trigger::new_in(world.clone()),
|
||||
value,
|
||||
f,
|
||||
},
|
||||
world,
|
||||
);
|
||||
let value = RefCell::new(None);
|
||||
let effect = Effect::new(EffectFn {
|
||||
trigger: Trigger::new(),
|
||||
value,
|
||||
f,
|
||||
});
|
||||
|
||||
Self { effect }
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference type for [`SignalBorrow`] impl
|
||||
pub struct BorrowRef<'a, T: 'a, F: ?Sized, W: DerivedWorld<T, F> = WorldDefault>(
|
||||
IMutRef<'a, Option<T>, W>,
|
||||
PhantomData<fn(F)>,
|
||||
);
|
||||
pub struct BorrowRef<'a, T: 'a, F: ?Sized>(cell::Ref<'a, Option<T>>, PhantomData<fn(F)>);
|
||||
|
||||
impl<T, F: ?Sized, W: DerivedWorld<T, F>> Deref for BorrowRef<'_, T, F, W> {
|
||||
impl<T, F: ?Sized> Deref for BorrowRef<'_, T, F> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -113,15 +89,15 @@ impl<T, F: ?Sized, W: DerivedWorld<T, F>> Deref for BorrowRef<'_, T, F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, F: ?Sized, W: DerivedWorld<T, F>> fmt::Debug for BorrowRef<'_, T, F, W> {
|
||||
impl<T: fmt::Debug, F: ?Sized> fmt::Debug for BorrowRef<'_, T, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(*self.0).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, F: ?Sized, W: DerivedWorld<T, F>> SignalBorrow for Derived<T, F, W> {
|
||||
impl<T: 'static, F: ?Sized> SignalBorrow for Derived<T, F> {
|
||||
type Ref<'a>
|
||||
= BorrowRef<'a, T, F, W>
|
||||
= BorrowRef<'a, T, F>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
@ -133,16 +109,16 @@ impl<T: 'static, F: ?Sized, W: DerivedWorld<T, F>> SignalBorrow for Derived<T, F
|
||||
|
||||
fn borrow_raw(&self) -> Self::Ref<'_> {
|
||||
let effect_fn = self.effect.inner_fn();
|
||||
let value = effect_fn.value.read();
|
||||
let value = effect_fn.value.borrow();
|
||||
BorrowRef(value, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, F: ?Sized, W: DerivedWorld<T, F>> SignalWithDefaultImpl for Derived<T, F, W> {}
|
||||
impl<T: 'static, F: ?Sized, W: DerivedWorld<T, F>> SignalGetDefaultImpl for Derived<T, F, W> {}
|
||||
impl<T: 'static, F: ?Sized, W: DerivedWorld<T, F>> SignalGetClonedDefaultImpl for Derived<T, F, W> {}
|
||||
impl<T: 'static, F: ?Sized> SignalWithDefaultImpl for Derived<T, F> {}
|
||||
impl<T: 'static, F: ?Sized> SignalGetDefaultImpl for Derived<T, F> {}
|
||||
impl<T: 'static, F: ?Sized> SignalGetClonedDefaultImpl for Derived<T, F> {}
|
||||
|
||||
impl<T, F: ?Sized, W: DerivedWorld<T, F>> Clone for Derived<T, F, W> {
|
||||
impl<T, F: ?Sized> Clone for Derived<T, F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
effect: self.effect.clone(),
|
||||
@ -150,49 +126,46 @@ impl<T, F: ?Sized, W: DerivedWorld<T, F>> Clone for Derived<T, F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, F: ?Sized, W: DerivedWorld<T, F>> fmt::Debug for Derived<T, F, W> {
|
||||
impl<T: fmt::Debug, F: ?Sized> fmt::Debug for Derived<T, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let effect_fn = self.effect.inner_fn();
|
||||
let mut debug = f.debug_struct("Derived");
|
||||
debug.field("effect", &self.effect);
|
||||
debug.field("trigger", &effect_fn.trigger);
|
||||
|
||||
match effect_fn.value.try_read() {
|
||||
Some(value) => debug.field("value", &*value).finish(),
|
||||
None => debug.finish_non_exhaustive(),
|
||||
match effect_fn.value.try_borrow() {
|
||||
Ok(value) => debug.field("value", &*value).finish(),
|
||||
Err(_) => debug.finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F1, F2, W> CoerceUnsized<Derived<T, F2, W>> for Derived<T, F1, W>
|
||||
impl<T, F1, F2> CoerceUnsized<Derived<T, F2>> for Derived<T, F1>
|
||||
where
|
||||
F1: ?Sized + Unsize<F2>,
|
||||
F2: ?Sized,
|
||||
W: DerivedWorld<T, F1> + DerivedWorld<T, F2>,
|
||||
Effect<EffectFn<T, F1, W>, W>: CoerceUnsized<Effect<EffectFn<T, F2, W>, W>>,
|
||||
{
|
||||
}
|
||||
|
||||
/// Effect function
|
||||
struct EffectFn<T, F: ?Sized, W: DerivedWorld<T, F>> {
|
||||
struct EffectFn<T, F: ?Sized> {
|
||||
/// Trigger
|
||||
trigger: Trigger<W>,
|
||||
trigger: Trigger,
|
||||
|
||||
/// Value
|
||||
value: IMut<Option<T>, W>,
|
||||
value: RefCell<Option<T>>,
|
||||
|
||||
/// Function
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<T, F, W> EffectRun<W> for EffectFn<T, F, W>
|
||||
impl<T, F> EffectRun for EffectFn<T, F>
|
||||
where
|
||||
T: 'static,
|
||||
F: Fn() -> T,
|
||||
W: DerivedWorld<T, F>,
|
||||
{
|
||||
fn run(&self, _ctx: EffectRunCtx<'_, W>) {
|
||||
*self.value.write() = Some((self.f)());
|
||||
fn run(&self, _ctx: EffectRunCtx<'_>) {
|
||||
*self.value.borrow_mut() = Some((self.f)());
|
||||
self.trigger.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,12 +10,9 @@
|
||||
#[cfg(debug_assertions)]
|
||||
use core::panic::Location;
|
||||
use {
|
||||
crate::{
|
||||
world::{EffectStack, ReactiveWorldInner},
|
||||
ReactiveWorld,
|
||||
WeakTrigger,
|
||||
},
|
||||
crate::{effect_stack, WeakTrigger},
|
||||
core::{
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
marker::{PhantomData, Unsize},
|
||||
@ -23,12 +20,14 @@ use {
|
||||
ptr,
|
||||
sync::atomic::{self, AtomicBool},
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, Rc, RcLike, Weak, WeakLike, WorldDefault},
|
||||
std::collections::HashSet,
|
||||
std::{
|
||||
collections::HashSet,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
|
||||
/// Effect inner
|
||||
pub(crate) struct Inner<F: ?Sized, W: ReactiveWorld> {
|
||||
pub(crate) struct Inner<F: ?Sized> {
|
||||
/// Whether this effect is currently suppressed
|
||||
suppressed: AtomicBool,
|
||||
|
||||
@ -37,28 +36,34 @@ pub(crate) struct Inner<F: ?Sized, W: ReactiveWorld> {
|
||||
defined_loc: &'static Location<'static>,
|
||||
|
||||
/// All dependencies of this effect
|
||||
dependencies: IMut<HashSet<WeakTrigger<W>>, W>,
|
||||
dependencies: RefCell<HashSet<WeakTrigger>>,
|
||||
|
||||
/// Effect runner
|
||||
run: F,
|
||||
}
|
||||
|
||||
/// Effect
|
||||
pub struct Effect<F: ?Sized, W: ReactiveWorld = WorldDefault> {
|
||||
pub struct Effect<F: ?Sized> {
|
||||
/// Inner
|
||||
inner: Rc<Inner<F, W>, W>,
|
||||
inner: Rc<Inner<F>>,
|
||||
}
|
||||
|
||||
impl<F> Effect<F, WorldDefault> {
|
||||
impl<F> Effect<F> {
|
||||
/// Creates a new computed effect.
|
||||
///
|
||||
/// Runs the effect once to gather dependencies.
|
||||
#[track_caller]
|
||||
pub fn new(run: F) -> Self
|
||||
where
|
||||
F: EffectRun<WorldDefault> + 'static,
|
||||
F: EffectRun + 'static,
|
||||
{
|
||||
Self::new_in(run, WorldDefault::default())
|
||||
// Create the effect
|
||||
let effect = Self::new_raw(run);
|
||||
|
||||
// And run it once to gather dependencies.
|
||||
effect.run();
|
||||
|
||||
effect
|
||||
}
|
||||
|
||||
/// Crates a new raw computed effect.
|
||||
@ -67,7 +72,15 @@ impl<F> Effect<F, WorldDefault> {
|
||||
/// dependencies manually.
|
||||
#[track_caller]
|
||||
pub fn new_raw(run: F) -> Self {
|
||||
Self::new_raw_in(run, WorldDefault::default())
|
||||
let inner = Inner {
|
||||
suppressed: AtomicBool::new(false),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_loc: Location::caller(),
|
||||
dependencies: RefCell::new(HashSet::new()),
|
||||
run,
|
||||
};
|
||||
|
||||
Self { inner: Rc::new(inner) }
|
||||
}
|
||||
|
||||
/// Tries to create a new effect.
|
||||
@ -76,58 +89,9 @@ impl<F> Effect<F, WorldDefault> {
|
||||
#[track_caller]
|
||||
pub fn try_new(run: F) -> Option<Self>
|
||||
where
|
||||
F: EffectRun<WorldDefault> + 'static,
|
||||
F: EffectRun + 'static,
|
||||
{
|
||||
Self::try_new_in(run, WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, W: ReactiveWorld> Effect<F, W> {
|
||||
/// Creates a new computed effect within a world.
|
||||
///
|
||||
/// Runs the effect once to gather dependencies.
|
||||
#[track_caller]
|
||||
pub fn new_in(run: F, world: W) -> Self
|
||||
where
|
||||
F: EffectRun<W> + Unsize<W::F> + 'static,
|
||||
{
|
||||
// Create the effect
|
||||
let effect = Self::new_raw_in(run, world);
|
||||
|
||||
// And run it once to gather dependencies.
|
||||
effect.run();
|
||||
|
||||
effect
|
||||
}
|
||||
|
||||
/// Crates a new raw computed effect within a world.
|
||||
///
|
||||
/// The effect won't be run, and instead you must gather
|
||||
/// dependencies manually.
|
||||
#[track_caller]
|
||||
pub fn new_raw_in(run: F, _world: W) -> Self {
|
||||
let inner = Inner {
|
||||
suppressed: AtomicBool::new(false),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_loc: Location::caller(),
|
||||
dependencies: IMut::<_, W>::new(HashSet::new()),
|
||||
run,
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: Rc::<_, W>::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to create a new effect within a world.
|
||||
///
|
||||
/// If the effects ends up being inert, returns `None`
|
||||
#[track_caller]
|
||||
pub fn try_new_in(run: F, world: W) -> Option<Self>
|
||||
where
|
||||
F: EffectRun<W> + Unsize<W::F> + 'static,
|
||||
{
|
||||
let effect = Self::new_in(run, world);
|
||||
let effect = Self::new(run);
|
||||
match effect.is_inert() {
|
||||
true => None,
|
||||
false => Some(effect),
|
||||
@ -135,7 +99,7 @@ impl<F, W: ReactiveWorld> Effect<F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
impl<F: ?Sized> Effect<F> {
|
||||
/// Accesses the inner function
|
||||
#[must_use]
|
||||
pub fn inner_fn(&self) -> &F {
|
||||
@ -150,9 +114,9 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
|
||||
/// Downgrades this effect
|
||||
#[must_use]
|
||||
pub fn downgrade(&self) -> WeakEffect<F, W> {
|
||||
pub fn downgrade(&self) -> WeakEffect<F> {
|
||||
WeakEffect {
|
||||
inner: Rc::<_, W>::downgrade(&self.inner),
|
||||
inner: Rc::downgrade(&self.inner),
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +127,7 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
/// or [`WeakEffect`]s exist that point to it.
|
||||
#[must_use]
|
||||
pub fn is_inert(&self) -> bool {
|
||||
Rc::<_, W>::strong_count(&self.inner) == 1 && Rc::<_, W>::weak_count(&self.inner) == 0
|
||||
Rc::strong_count(&self.inner) == 1 && Rc::weak_count(&self.inner) == 0
|
||||
}
|
||||
|
||||
/// Returns the pointer of this effect
|
||||
@ -171,7 +135,7 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
/// This can be used for creating maps based on equality
|
||||
#[must_use]
|
||||
pub fn inner_ptr(&self) -> *const () {
|
||||
Rc::<_, W>::as_ptr(&self.inner).cast()
|
||||
Rc::as_ptr(&self.inner).cast()
|
||||
}
|
||||
|
||||
/// Creates an effect dependency gatherer
|
||||
@ -179,12 +143,12 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
/// While this type lives, all signals used will be gathered as dependencies
|
||||
/// for this effect.
|
||||
#[must_use]
|
||||
pub fn deps_gatherer(&self) -> EffectDepsGatherer<W>
|
||||
pub fn deps_gatherer(&self) -> EffectDepsGatherer
|
||||
where
|
||||
F: Unsize<W::F> + 'static,
|
||||
F: Unsize<dyn EffectRun> + 'static,
|
||||
{
|
||||
// Push the effect
|
||||
W::EffectStack::push(self.clone());
|
||||
effect_stack::push(self.clone());
|
||||
|
||||
// Then return the gatherer, which will pop the effect from the stack on drop
|
||||
EffectDepsGatherer(PhantomData)
|
||||
@ -195,7 +159,7 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
/// All signals used within `gather` will have this effect as a dependency.
|
||||
pub fn gather_dependencies<G, O>(&self, gather: G) -> O
|
||||
where
|
||||
F: Unsize<W::F> + 'static,
|
||||
F: Unsize<dyn EffectRun> + 'static,
|
||||
G: FnOnce() -> O,
|
||||
{
|
||||
let _gatherer = self.deps_gatherer();
|
||||
@ -208,7 +172,7 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
#[track_caller]
|
||||
pub fn run(&self)
|
||||
where
|
||||
F: EffectRun<W> + Unsize<W::F> + 'static,
|
||||
F: EffectRun + Unsize<dyn EffectRun> + 'static,
|
||||
{
|
||||
// If we're suppressed, don't do anything
|
||||
// TODO: Should we clear our dependencies in this case?
|
||||
@ -220,9 +184,9 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
|
||||
// Clear the dependencies before running
|
||||
#[expect(clippy::iter_over_hash_type, reason = "We don't care about the order here")]
|
||||
for dep in self.inner.dependencies.write().drain() {
|
||||
for dep in self.inner.dependencies.borrow_mut().drain() {
|
||||
let Some(trigger) = dep.upgrade() else { continue };
|
||||
trigger.remove_subscriber(W::unsize_effect(self.clone()).downgrade());
|
||||
trigger.remove_subscriber(self.downgrade());
|
||||
}
|
||||
|
||||
// Otherwise, run it
|
||||
@ -248,13 +212,14 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
}
|
||||
|
||||
/// Returns whether the effect is suppressed
|
||||
#[must_use]
|
||||
pub fn is_suppressed(&self) -> bool {
|
||||
self.inner.suppressed.load(atomic::Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Adds a dependency to this effect
|
||||
pub(crate) fn add_dependency(&self, trigger: WeakTrigger<W>) {
|
||||
self.inner.dependencies.write().insert(trigger);
|
||||
pub(crate) fn add_dependency(&self, trigger: WeakTrigger) {
|
||||
self.inner.dependencies.borrow_mut().insert(trigger);
|
||||
}
|
||||
|
||||
/// Formats this effect into `s`
|
||||
@ -267,7 +232,7 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
s.field_with("dependencies", |f| {
|
||||
let mut s = f.debug_list();
|
||||
|
||||
let Some(deps) = self.inner.dependencies.try_read() else {
|
||||
let Ok(deps) = self.inner.dependencies.try_borrow() else {
|
||||
return s.finish_non_exhaustive();
|
||||
};
|
||||
#[expect(clippy::iter_over_hash_type, reason = "We don't care about the order")]
|
||||
@ -287,40 +252,38 @@ impl<F: ?Sized, W: ReactiveWorld> Effect<F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F1: ?Sized, F2: ?Sized, W: ReactiveWorld> PartialEq<Effect<F2, W>> for Effect<F1, W> {
|
||||
fn eq(&self, other: &Effect<F2, W>) -> bool {
|
||||
impl<F1: ?Sized, F2: ?Sized> PartialEq<Effect<F2>> for Effect<F1> {
|
||||
fn eq(&self, other: &Effect<F2>) -> bool {
|
||||
ptr::eq(self.inner_ptr(), other.inner_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> Eq for Effect<F, W> {}
|
||||
impl<F: ?Sized> Eq for Effect<F> {}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> Clone for Effect<F, W> {
|
||||
impl<F: ?Sized> Clone for Effect<F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Rc::<_, W>::clone(&self.inner),
|
||||
inner: Rc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> Hash for Effect<F, W> {
|
||||
impl<F: ?Sized> Hash for Effect<F> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Rc::<_, W>::as_ptr(&self.inner).hash(state);
|
||||
Rc::as_ptr(&self.inner).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> fmt::Debug for Effect<F, W> {
|
||||
impl<F: ?Sized> fmt::Debug for Effect<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.fmt_debug(f.debug_struct("Effect"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F1, F2, W> CoerceUnsized<Effect<F2, W>> for Effect<F1, W>
|
||||
impl<F1, F2> CoerceUnsized<Effect<F2>> for Effect<F1>
|
||||
where
|
||||
F1: ?Sized + Unsize<F2>,
|
||||
F2: ?Sized,
|
||||
W: ReactiveWorld,
|
||||
Rc<Inner<F1, W>, W>: CoerceUnsized<Rc<Inner<F2, W>, W>>,
|
||||
{
|
||||
}
|
||||
|
||||
@ -328,15 +291,15 @@ where
|
||||
/// Weak effect
|
||||
///
|
||||
/// Used to break ownership between a signal and it's subscribers
|
||||
pub struct WeakEffect<F: ?Sized, W: ReactiveWorld = WorldDefault> {
|
||||
pub struct WeakEffect<F: ?Sized> {
|
||||
/// Inner
|
||||
inner: Weak<Inner<F, W>, W>,
|
||||
inner: Weak<Inner<F>>,
|
||||
}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> WeakEffect<F, W> {
|
||||
impl<F: ?Sized> WeakEffect<F> {
|
||||
/// Upgrades this effect
|
||||
#[must_use]
|
||||
pub fn upgrade(&self) -> Option<Effect<F, W>> {
|
||||
pub fn upgrade(&self) -> Option<Effect<F>> {
|
||||
self.inner.upgrade().map(|inner| Effect { inner })
|
||||
}
|
||||
|
||||
@ -345,16 +308,20 @@ impl<F: ?Sized, W: ReactiveWorld> WeakEffect<F, W> {
|
||||
/// This can be used for creating maps based on equality
|
||||
#[must_use]
|
||||
pub fn inner_ptr(&self) -> *const () {
|
||||
Weak::<_, W>::as_ptr(&self.inner).cast()
|
||||
Weak::as_ptr(&self.inner).cast()
|
||||
}
|
||||
|
||||
/// Runs this effect, if it exists.
|
||||
///
|
||||
/// Returns if the effect still existed
|
||||
#[track_caller]
|
||||
#[expect(
|
||||
clippy::must_use_candidate,
|
||||
reason = "The user may not care whether we actually ran or not"
|
||||
)]
|
||||
pub fn try_run(&self) -> bool
|
||||
where
|
||||
F: EffectRun<W> + Unsize<W::F> + 'static,
|
||||
F: EffectRun + Unsize<dyn EffectRun> + 'static,
|
||||
{
|
||||
// Try to upgrade, else return that it was missing
|
||||
let Some(effect) = self.upgrade() else {
|
||||
@ -366,30 +333,30 @@ impl<F: ?Sized, W: ReactiveWorld> WeakEffect<F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F1: ?Sized, F2: ?Sized, W: ReactiveWorld> PartialEq<WeakEffect<F2, W>> for WeakEffect<F1, W> {
|
||||
fn eq(&self, other: &WeakEffect<F2, W>) -> bool {
|
||||
impl<F1: ?Sized, F2: ?Sized> PartialEq<WeakEffect<F2>> for WeakEffect<F1> {
|
||||
fn eq(&self, other: &WeakEffect<F2>) -> bool {
|
||||
ptr::eq(self.inner_ptr(), other.inner_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> Eq for WeakEffect<F, W> {}
|
||||
impl<F: ?Sized> Eq for WeakEffect<F> {}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> Clone for WeakEffect<F, W> {
|
||||
impl<F: ?Sized> Clone for WeakEffect<F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Weak::<_, W>::clone(&self.inner),
|
||||
inner: Weak::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> Hash for WeakEffect<F, W> {
|
||||
impl<F: ?Sized> Hash for WeakEffect<F> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.inner_ptr().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: ?Sized, W: ReactiveWorld> fmt::Debug for WeakEffect<F, W> {
|
||||
impl<F: ?Sized> fmt::Debug for WeakEffect<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut s = f.debug_struct("WeakEffect");
|
||||
|
||||
@ -400,12 +367,10 @@ impl<F: ?Sized, W: ReactiveWorld> fmt::Debug for WeakEffect<F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F1, F2, W> CoerceUnsized<WeakEffect<F2, W>> for WeakEffect<F1, W>
|
||||
impl<F1, F2> CoerceUnsized<WeakEffect<F2>> for WeakEffect<F1>
|
||||
where
|
||||
F1: ?Sized + Unsize<F2>,
|
||||
F2: ?Sized,
|
||||
W: ReactiveWorld,
|
||||
Weak<Inner<F1, W>, W>: CoerceUnsized<Weak<Inner<F2, W>, W>>,
|
||||
{
|
||||
}
|
||||
|
||||
@ -413,45 +378,38 @@ where
|
||||
///
|
||||
/// While this type is alive, any signals used will
|
||||
/// be added as a dependency.
|
||||
pub struct EffectDepsGatherer<'a, W: ReactiveWorld = WorldDefault>(PhantomData<(&'a (), W)>);
|
||||
pub struct EffectDepsGatherer<'a>(PhantomData<&'a ()>);
|
||||
|
||||
impl<W: ReactiveWorld> Drop for EffectDepsGatherer<'_, W> {
|
||||
impl Drop for EffectDepsGatherer<'_> {
|
||||
fn drop(&mut self) {
|
||||
// Pop our effect from the stack
|
||||
W::EffectStack::pop();
|
||||
effect_stack::pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current running effect
|
||||
#[must_use]
|
||||
pub fn running() -> Option<Effect<<WorldDefault as ReactiveWorldInner>::F>> {
|
||||
self::running_in::<WorldDefault>()
|
||||
}
|
||||
|
||||
/// Returns the current running effect in a world
|
||||
#[must_use]
|
||||
pub fn running_in<W: ReactiveWorld>() -> Option<Effect<W::F, W>> {
|
||||
<W>::EffectStack::top()
|
||||
pub fn running() -> Option<Effect<dyn EffectRun>> {
|
||||
effect_stack::top()
|
||||
}
|
||||
|
||||
/// Effect run
|
||||
pub trait EffectRun<W: ReactiveWorld = WorldDefault> {
|
||||
pub trait EffectRun {
|
||||
/// Runs the effect
|
||||
#[track_caller]
|
||||
fn run(&self, ctx: EffectRunCtx<'_, W>);
|
||||
fn run(&self, ctx: EffectRunCtx<'_>);
|
||||
}
|
||||
|
||||
/// Effect run context
|
||||
pub struct EffectRunCtx<'a, W: ReactiveWorld> {
|
||||
_phantom: PhantomData<(&'a (), W)>,
|
||||
pub struct EffectRunCtx<'a> {
|
||||
_phantom: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<F, W> EffectRun<W> for F
|
||||
impl<F> EffectRun for F
|
||||
where
|
||||
F: Fn(),
|
||||
W: ReactiveWorld,
|
||||
{
|
||||
fn run(&self, _ctx: EffectRunCtx<'_, W>) {
|
||||
fn run(&self, _ctx: EffectRunCtx<'_>) {
|
||||
self();
|
||||
}
|
||||
}
|
||||
@ -486,7 +444,7 @@ mod test {
|
||||
#[test]
|
||||
fn running() {
|
||||
#[thread_local]
|
||||
static RUNNING: OnceCell<Effect<dyn EffectRun, WorldDefault>> = OnceCell::new();
|
||||
static RUNNING: OnceCell<Effect<dyn EffectRun>> = OnceCell::new();
|
||||
|
||||
// Create an effect, and save the running effect within it to `RUNNING`.
|
||||
let effect = Effect::new(move || {
|
||||
|
||||
32
dynatos-reactive/src/effect_stack.rs
Normal file
32
dynatos-reactive/src/effect_stack.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! Effect stack
|
||||
|
||||
// Imports
|
||||
use {
|
||||
crate::{Effect, EffectRun},
|
||||
core::{cell::RefCell, marker::Unsize},
|
||||
};
|
||||
|
||||
/// Effect stack impl
|
||||
type EffectStackImpl<F> = RefCell<Vec<Effect<F>>>;
|
||||
|
||||
/// Effect stack
|
||||
#[thread_local]
|
||||
static EFFECT_STACK: EffectStackImpl<dyn EffectRun> = EffectStackImpl::new(vec![]);
|
||||
|
||||
/// Pushes an effect to the stack.
|
||||
pub fn push<F>(f: Effect<F>)
|
||||
where
|
||||
F: ?Sized + Unsize<dyn EffectRun>,
|
||||
{
|
||||
EFFECT_STACK.borrow_mut().push(f);
|
||||
}
|
||||
|
||||
/// Pops an effect from the stack
|
||||
pub fn pop() {
|
||||
EFFECT_STACK.borrow_mut().pop().expect("Missing added effect");
|
||||
}
|
||||
|
||||
/// Returns the top effect of the stack
|
||||
pub fn top() -> Option<Effect<dyn EffectRun>> {
|
||||
EFFECT_STACK.borrow().last().cloned()
|
||||
}
|
||||
@ -18,7 +18,6 @@ use {
|
||||
Effect,
|
||||
EffectRun,
|
||||
EffectRunCtx,
|
||||
ReactiveWorld,
|
||||
Signal,
|
||||
SignalBorrow,
|
||||
SignalGetCloned,
|
||||
@ -28,49 +27,27 @@ use {
|
||||
SignalWithDefaultImpl,
|
||||
Trigger,
|
||||
},
|
||||
core::{fmt, marker::Unsize},
|
||||
dynatos_world::{IMut, IMutLike, WorldDefault},
|
||||
core::{cell::RefCell, fmt},
|
||||
};
|
||||
|
||||
/// World for [`EnumSplitSignal`]
|
||||
#[expect(private_bounds, reason = "We can't *not* leak some implementation details currently")]
|
||||
pub trait EnumSplitWorld<S, T: EnumSplitValue<S, Self>> =
|
||||
ReactiveWorld where IMut<EffectFnInner<S, T, Self>, Self>: Sized;
|
||||
|
||||
/// Enum split signal
|
||||
pub struct EnumSplitSignal<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T> = WorldDefault> {
|
||||
pub struct EnumSplitSignal<S, T: EnumSplitValue<S>> {
|
||||
/// Effect
|
||||
effect: Effect<EffectFn<S, T, W>, W>,
|
||||
effect: Effect<EffectFn<S, T>>,
|
||||
}
|
||||
|
||||
impl<S, T: EnumSplitValue<S, WorldDefault>> EnumSplitSignal<S, T, WorldDefault> {
|
||||
impl<S, T: EnumSplitValue<S>> EnumSplitSignal<S, T> {
|
||||
/// Creates a new enum split signal
|
||||
pub fn new(signal: S) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
S: SignalGetCloned<Value = T> + SignalSet<T> + Clone + 'static,
|
||||
{
|
||||
Self::new_in(signal, WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> EnumSplitSignal<S, T, W> {
|
||||
/// Creates a new enum split signal in a world
|
||||
#[expect(private_bounds, reason = "We can't *not* leak some implementation details currently")]
|
||||
pub fn new_in(signal: S, world: W) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
S: SignalGetCloned<Value = T> + SignalSet<T> + Clone + 'static,
|
||||
EffectFn<S, T, W>: Unsize<W::F>,
|
||||
{
|
||||
let effect = Effect::new_in(
|
||||
EffectFn {
|
||||
inner: IMut::<_, W>::new(EffectFnInner::default()),
|
||||
trigger: Trigger::new(),
|
||||
signal,
|
||||
},
|
||||
world,
|
||||
);
|
||||
let effect = Effect::new(EffectFn {
|
||||
inner: RefCell::new(EffectFnInner::default()),
|
||||
trigger: Trigger::new(),
|
||||
signal,
|
||||
});
|
||||
|
||||
Self { effect }
|
||||
}
|
||||
@ -78,7 +55,8 @@ impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> EnumSplitSignal<S, T,
|
||||
/// Converts this signal into an effect.
|
||||
// TODO: This only serves to keep effects alive in html nodes,
|
||||
// can we simply do that some other way?
|
||||
pub fn into_effect(self) -> Effect<impl EffectRun<W>, W>
|
||||
#[must_use]
|
||||
pub fn into_effect(self) -> Effect<impl EffectRun>
|
||||
where
|
||||
S: SignalGetCloned<Value = T> + SignalSet<T> + Clone + 'static,
|
||||
{
|
||||
@ -86,7 +64,7 @@ impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> EnumSplitSignal<S, T,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> Clone for EnumSplitSignal<S, T, W> {
|
||||
impl<S, T: EnumSplitValue<S>> Clone for EnumSplitSignal<S, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
effect: self.effect.clone(),
|
||||
@ -94,28 +72,27 @@ impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> Clone for EnumSplitSig
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T, W> fmt::Debug for EnumSplitSignal<S, T, W>
|
||||
impl<S, T> fmt::Debug for EnumSplitSignal<S, T>
|
||||
where
|
||||
T: EnumSplitValue<S, W>,
|
||||
T: EnumSplitValue<S>,
|
||||
T::SignalsStorage: fmt::Debug,
|
||||
T::SigKind: fmt::Debug,
|
||||
W: EnumSplitWorld<S, T>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut s = f.debug_struct("EnumSplitSignal");
|
||||
s.field("effect", &self.effect);
|
||||
|
||||
match self.effect.inner_fn().inner.try_read() {
|
||||
Some(inner) => s
|
||||
match self.effect.inner_fn().inner.try_borrow() {
|
||||
Ok(inner) => s
|
||||
.field("signals", &inner.signals)
|
||||
.field("cur_kind", &inner.cur_kind)
|
||||
.finish(),
|
||||
None => s.finish_non_exhaustive(),
|
||||
Err(_) => s.finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> SignalBorrow for EnumSplitSignal<S, T, W> {
|
||||
impl<S, T: EnumSplitValue<S>> SignalBorrow for EnumSplitSignal<S, T> {
|
||||
type Ref<'a>
|
||||
= T::Signal
|
||||
where
|
||||
@ -129,14 +106,14 @@ impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> SignalBorrow for EnumS
|
||||
fn borrow_raw(&self) -> Self::Ref<'_> {
|
||||
let effect_fn = self.effect.inner_fn();
|
||||
|
||||
let inner = effect_fn.inner.read();
|
||||
let inner = effect_fn.inner.borrow();
|
||||
|
||||
let cur = inner.cur_kind.as_ref().expect("Should have a current signal");
|
||||
T::get_signal(&inner.signals, cur).expect("Signal for current signal was missing")
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> SignalGetCloned for EnumSplitSignal<S, T, W> {
|
||||
impl<S, T: EnumSplitValue<S>> SignalGetCloned for EnumSplitSignal<S, T> {
|
||||
type Value = T::Signal;
|
||||
|
||||
fn get_cloned(&self) -> Self::Value {
|
||||
@ -148,15 +125,15 @@ impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> SignalGetCloned for En
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> SignalWithDefaultImpl for EnumSplitSignal<S, T, W> {}
|
||||
impl<S, T: EnumSplitValue<S>> SignalWithDefaultImpl for EnumSplitSignal<S, T> {}
|
||||
|
||||
// Note: Since our `Borrow` impl doesn't return a reference, we implement
|
||||
// `GetCloned` manually, so we don't want the default impl
|
||||
impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> !SignalGetDefaultImpl for EnumSplitSignal<S, T, W> {}
|
||||
impl<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> !SignalGetClonedDefaultImpl for EnumSplitSignal<S, T, W> {}
|
||||
impl<S, T: EnumSplitValue<S>> !SignalGetDefaultImpl for EnumSplitSignal<S, T> {}
|
||||
impl<S, T: EnumSplitValue<S>> !SignalGetClonedDefaultImpl for EnumSplitSignal<S, T> {}
|
||||
|
||||
/// Effect fn inner
|
||||
struct EffectFnInner<S, T: EnumSplitValue<S, W>, W: ReactiveWorld> {
|
||||
struct EffectFnInner<S, T: EnumSplitValue<S>> {
|
||||
/// Signals
|
||||
signals: T::SignalsStorage,
|
||||
|
||||
@ -164,7 +141,7 @@ struct EffectFnInner<S, T: EnumSplitValue<S, W>, W: ReactiveWorld> {
|
||||
cur_kind: Option<T::SigKind>,
|
||||
}
|
||||
|
||||
impl<S, T: EnumSplitValue<S, W>, W: ReactiveWorld> Default for EffectFnInner<S, T, W> {
|
||||
impl<S, T: EnumSplitValue<S>> Default for EffectFnInner<S, T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
signals: T::SignalsStorage::default(),
|
||||
@ -174,9 +151,9 @@ impl<S, T: EnumSplitValue<S, W>, W: ReactiveWorld> Default for EffectFnInner<S,
|
||||
}
|
||||
|
||||
/// Inner effect function
|
||||
struct EffectFn<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> {
|
||||
struct EffectFn<S, T: EnumSplitValue<S>> {
|
||||
/// Inner
|
||||
inner: IMut<EffectFnInner<S, T, W>, W>,
|
||||
inner: RefCell<EffectFnInner<S, T>>,
|
||||
|
||||
/// Trigger
|
||||
trigger: Trigger,
|
||||
@ -185,18 +162,17 @@ struct EffectFn<S, T: EnumSplitValue<S, W>, W: EnumSplitWorld<S, T>> {
|
||||
signal: S,
|
||||
}
|
||||
|
||||
impl<S, T, W> EffectRun<W> for EffectFn<S, T, W>
|
||||
impl<S, T> EffectRun for EffectFn<S, T>
|
||||
where
|
||||
T: EnumSplitValue<S, W>,
|
||||
T: EnumSplitValue<S>,
|
||||
S: SignalGetCloned<Value = T> + SignalSet<T> + Clone + 'static,
|
||||
W: EnumSplitWorld<S, T>,
|
||||
{
|
||||
fn run(&self, _run_ctx: EffectRunCtx<'_, W>) {
|
||||
fn run(&self, _run_ctx: EffectRunCtx<'_>) {
|
||||
// Get the new value
|
||||
let new_value = self.signal.get_cloned();
|
||||
|
||||
// Then update the current signal
|
||||
let mut inner = self.inner.write();
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let prev_kind = inner.cur_kind.replace(new_value.kind());
|
||||
let update_ctx = EnumSplitValueUpdateCtx::new(self.signal.clone());
|
||||
new_value.update(&mut inner.signals, update_ctx);
|
||||
@ -209,7 +185,7 @@ where
|
||||
}
|
||||
|
||||
/// Enum split value
|
||||
pub trait EnumSplitValue<S, W: ReactiveWorld = WorldDefault> {
|
||||
pub trait EnumSplitValue<S> {
|
||||
/// Signals storage
|
||||
type SignalsStorage: Default;
|
||||
|
||||
@ -226,14 +202,13 @@ pub trait EnumSplitValue<S, W: ReactiveWorld = WorldDefault> {
|
||||
fn kind(&self) -> Self::SigKind;
|
||||
|
||||
/// Updates a signal with this value
|
||||
fn update(self, storage: &mut Self::SignalsStorage, ctx: EnumSplitValueUpdateCtx<'_, S, W>);
|
||||
fn update(self, storage: &mut Self::SignalsStorage, ctx: EnumSplitValueUpdateCtx<'_, S>);
|
||||
}
|
||||
|
||||
impl<S, T, W> EnumSplitValue<S, W> for Option<T>
|
||||
impl<S, T> EnumSplitValue<S> for Option<T>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
S: SignalSet<Self> + Clone + 'static,
|
||||
W: ReactiveWorld,
|
||||
{
|
||||
type SigKind = Option<()>;
|
||||
type Signal = Option<Signal<T>>;
|
||||
@ -252,7 +227,7 @@ where
|
||||
self.as_ref().map(|_| ())
|
||||
}
|
||||
|
||||
fn update(self, storage: &mut Self::SignalsStorage, ctx: EnumSplitValueUpdateCtx<'_, S, W>) {
|
||||
fn update(self, storage: &mut Self::SignalsStorage, ctx: EnumSplitValueUpdateCtx<'_, S>) {
|
||||
let Some(new_value) = self else { return };
|
||||
|
||||
match storage {
|
||||
@ -263,7 +238,6 @@ where
|
||||
}
|
||||
|
||||
/// Extension trait to create an enum split signal
|
||||
// TODO: Add this for other worlds
|
||||
#[extend::ext_sized(name = SignalEnumSplit)]
|
||||
pub impl<S> S {
|
||||
/// Splits this signal into sub-signals
|
||||
|
||||
@ -3,20 +3,20 @@
|
||||
// Imports
|
||||
use {
|
||||
super::{EnumSplitValue, SignalStorage},
|
||||
crate::{effect, Effect, ReactiveWorld, Signal, SignalGetCloned, SignalSet, SignalWith},
|
||||
crate::{effect, Effect, Signal, SignalGetCloned, SignalSet, SignalWith},
|
||||
core::marker::PhantomData,
|
||||
zutil_cloned::cloned,
|
||||
};
|
||||
|
||||
/// Context for [`EnumSplitValue::update`]
|
||||
pub struct EnumSplitValueUpdateCtx<'a, S, W: ReactiveWorld> {
|
||||
pub struct EnumSplitValueUpdateCtx<'a, S> {
|
||||
/// Outer signal
|
||||
outer_signal: S,
|
||||
|
||||
_phantom: PhantomData<(&'a (), W)>,
|
||||
_phantom: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<S, W: ReactiveWorld> EnumSplitValueUpdateCtx<'_, S, W> {
|
||||
impl<S> EnumSplitValueUpdateCtx<'_, S> {
|
||||
/// Creates a new context.
|
||||
pub(crate) const fn new(outer_signal: S) -> Self {
|
||||
Self {
|
||||
@ -33,14 +33,14 @@ impl<S, W: ReactiveWorld> EnumSplitValueUpdateCtx<'_, S, W> {
|
||||
/// created, to avoid recursion
|
||||
pub fn create_signal_storage<T, V, F>(&self, value: V, into_t: F) -> SignalStorage<V>
|
||||
where
|
||||
T: EnumSplitValue<S, W>,
|
||||
T: EnumSplitValue<S>,
|
||||
S: SignalSet<T> + Clone + 'static,
|
||||
V: Clone + 'static,
|
||||
F: Fn(V) -> T + 'static,
|
||||
{
|
||||
let signal = Signal::new(value);
|
||||
|
||||
let cur_effect = effect::running_in::<W>().expect("Missing running effect");
|
||||
let cur_effect = effect::running().expect("Missing running effect");
|
||||
|
||||
// Create the write-back effect.
|
||||
// Note: We don't want to run it and write into the outer at startup, so
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
// Imports
|
||||
use {
|
||||
super::{EnumSplitValue, EnumSplitValueUpdateCtx, SignalStorage},
|
||||
crate::{ReactiveWorld, Signal, SignalSet},
|
||||
crate::{Signal, SignalSet},
|
||||
};
|
||||
|
||||
macro gen_either($Either:ident, $All:ident, $($t:ident: $T:ident),* $(,)?) {
|
||||
@ -17,11 +17,11 @@ macro gen_either($Either:ident, $All:ident, $($t:ident: $T:ident),* $(,)?) {
|
||||
$( $t: $T, )*
|
||||
}
|
||||
|
||||
impl<$( $T, )* S, W> EnumSplitValue<S, W> for $Either<$( $T, )*>
|
||||
impl<$( $T, )* S> EnumSplitValue<S> for $Either<$( $T, )*>
|
||||
where
|
||||
$( $T: Clone + 'static, )*
|
||||
S: SignalSet<Self> + Clone + 'static,
|
||||
W: ReactiveWorld,
|
||||
|
||||
{
|
||||
type SigKind = $Either< $( () ${ignore($T)}, )* >;
|
||||
type Signal = $Either< $( Signal<$T>, )* >;
|
||||
@ -47,7 +47,7 @@ macro gen_either($Either:ident, $All:ident, $($t:ident: $T:ident),* $(,)?) {
|
||||
}
|
||||
}
|
||||
|
||||
fn update(self, storage: &mut Self::SignalsStorage, ctx: EnumSplitValueUpdateCtx<'_, S, W>) {
|
||||
fn update(self, storage: &mut Self::SignalsStorage, ctx: EnumSplitValueUpdateCtx<'_, S>) {
|
||||
match self {
|
||||
$(
|
||||
Self::$T(new_value) => match &storage.$t {
|
||||
|
||||
@ -38,13 +38,14 @@
|
||||
pub mod async_signal;
|
||||
pub mod derived;
|
||||
pub mod effect;
|
||||
pub mod effect_stack;
|
||||
pub mod enum_split;
|
||||
pub mod mapped_signal;
|
||||
pub mod memo;
|
||||
pub mod run_queue;
|
||||
pub mod signal;
|
||||
pub mod trigger;
|
||||
pub mod with_default;
|
||||
pub mod world;
|
||||
|
||||
// Exports
|
||||
pub use self::{
|
||||
@ -75,5 +76,4 @@ pub use self::{
|
||||
},
|
||||
trigger::{IntoSubscriber, Subscriber, Trigger, WeakTrigger},
|
||||
with_default::{SignalWithDefault, WithDefault},
|
||||
world::ReactiveWorld,
|
||||
};
|
||||
|
||||
@ -1,30 +1,16 @@
|
||||
//! Mapped signal
|
||||
|
||||
// TODO: Support other worlds
|
||||
|
||||
// Lints
|
||||
#![expect(type_alias_bounds, reason = "We can't use `T::Residual` without the bound")]
|
||||
|
||||
// Imports
|
||||
use {
|
||||
crate::{
|
||||
world::ReactiveWorldInner,
|
||||
Effect,
|
||||
EffectRun,
|
||||
ReactiveWorld,
|
||||
Signal,
|
||||
SignalGetCloned,
|
||||
SignalSet,
|
||||
SignalUpdate,
|
||||
SignalWith,
|
||||
Trigger,
|
||||
WeakEffect,
|
||||
},
|
||||
crate::{Effect, EffectRun, Signal, SignalGetCloned, SignalSet, SignalUpdate, SignalWith, Trigger, WeakEffect},
|
||||
core::{
|
||||
cell::OnceCell,
|
||||
cell::{OnceCell, RefCell},
|
||||
ops::{ControlFlow, FromResidual, Residual, Try},
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, Rc, RcLike, WorldDefault},
|
||||
std::rc::Rc,
|
||||
zutil_cloned::cloned,
|
||||
};
|
||||
|
||||
@ -76,38 +62,20 @@ where
|
||||
/// If you drop this signal, the relationship between
|
||||
/// the outer and inner signal will be broken, so keep
|
||||
/// this value alive while you use the inner signal
|
||||
pub struct TryMappedSignal<T, W = WorldDefault>
|
||||
pub struct TryMappedSignal<T>
|
||||
where
|
||||
T: Try<Residual: Residual<Signal<T::Output>>>,
|
||||
W: ReactiveWorld,
|
||||
{
|
||||
/// Inner
|
||||
inner: Rc<Inner<T>, W>,
|
||||
inner: Rc<Inner<T>>,
|
||||
}
|
||||
|
||||
impl<T> TryMappedSignal<T, WorldDefault>
|
||||
impl<T> TryMappedSignal<T>
|
||||
where
|
||||
T: Try<Residual: Residual<Signal<T::Output>>>,
|
||||
{
|
||||
/// Creates a new mapped signal from a fallible getter
|
||||
pub fn new<S, TryGet, Set>(input: S, try_get: TryGet, set: Set) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
S: SignalWith + SignalUpdate + Clone + 'static,
|
||||
TryGet: Fn(<S as SignalWith>::Value<'_>) -> T + 'static,
|
||||
Set: Fn(<S as SignalUpdate>::Value<'_>, &T::Output) + 'static,
|
||||
{
|
||||
Self::new_in(input, try_get, set, WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W> TryMappedSignal<T, W>
|
||||
where
|
||||
T: Try<Residual: Residual<Signal<T::Output>>>,
|
||||
W: ReactiveWorld,
|
||||
{
|
||||
/// Creates a new mapped signal from a fallible getter
|
||||
pub fn new_in<S, TryGet, Set>(input: S, try_get: TryGet, set: Set, _world: W) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
S: SignalWith + SignalUpdate + Clone + 'static,
|
||||
@ -115,7 +83,7 @@ where
|
||||
Set: Fn(<S as SignalUpdate>::Value<'_>, &T::Output) + 'static,
|
||||
{
|
||||
// Output signal
|
||||
let output_sig = Rc::<_, WorldDefault>::new(IMut::<_, WorldDefault>::new(None::<SignalTry<T>>));
|
||||
let output_sig = Rc::new(RefCell::new(None::<SignalTry<T>>));
|
||||
|
||||
// Trigger for gathering dependencies on retrieving the output signal,
|
||||
// but *not* on output signal changes.
|
||||
@ -123,9 +91,7 @@ where
|
||||
|
||||
// Weak reference to the `set_effect`, to ensure that we don't end
|
||||
// up with a loop and leak memory
|
||||
let set_weak_effect = Rc::<_, WorldDefault>::new(OnceCell::<
|
||||
WeakEffect<<WorldDefault as ReactiveWorldInner>::F, WorldDefault>,
|
||||
>::new());
|
||||
let set_weak_effect = Rc::new(OnceCell::<WeakEffect<dyn EffectRun>>::new());
|
||||
|
||||
// The getter effect that sets the output signal
|
||||
#[cloned(input, output_sig, trigger, set_weak_effect)]
|
||||
@ -133,7 +99,7 @@ where
|
||||
input.with(|input| {
|
||||
let value = try_get(input);
|
||||
|
||||
let mut output = output_sig.write();
|
||||
let mut output = output_sig.borrow_mut();
|
||||
let (new_output, needs_trigger) = match value.branch() {
|
||||
// If the value was ok, check whether we already had a value or not
|
||||
ControlFlow::Continue(value) => match output.take().map(Try::branch) {
|
||||
@ -192,7 +158,7 @@ where
|
||||
.set(set_effect.downgrade())
|
||||
.expect("Set effect should be uninitialized");
|
||||
|
||||
let inner = Rc::<_, W>::new(Inner {
|
||||
let inner = Rc::new(Inner {
|
||||
output: output_sig,
|
||||
_get_effect: get_effect,
|
||||
_set_effect: set_effect,
|
||||
@ -202,22 +168,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W> Clone for TryMappedSignal<T, W>
|
||||
impl<T> Clone for TryMappedSignal<T>
|
||||
where
|
||||
T: Try<Residual: Residual<Signal<T::Output>>>,
|
||||
W: ReactiveWorld,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
inner: Rc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W> SignalGetCloned for TryMappedSignal<T, W>
|
||||
impl<T> SignalGetCloned for TryMappedSignal<T>
|
||||
where
|
||||
T: Try<Residual: Residual<Signal<T::Output>>>,
|
||||
W: ReactiveWorld,
|
||||
|
||||
SignalTry<T>: Clone,
|
||||
{
|
||||
type Value = SignalTry<T>;
|
||||
@ -226,7 +191,7 @@ where
|
||||
self.inner.trigger.gather_subscribers();
|
||||
self.inner
|
||||
.output
|
||||
.read()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.expect("Output signal was missing")
|
||||
.clone()
|
||||
@ -235,7 +200,7 @@ where
|
||||
fn get_cloned_raw(&self) -> Self::Value {
|
||||
self.inner
|
||||
.output
|
||||
.read()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.expect("Output signal was missing")
|
||||
.clone()
|
||||
@ -243,7 +208,7 @@ where
|
||||
}
|
||||
|
||||
/// Output signal type
|
||||
type OutputSignal<T> = Rc<IMut<Option<SignalTry<T>>, WorldDefault>, WorldDefault>;
|
||||
type OutputSignal<T> = Rc<RefCell<Option<SignalTry<T>>>>;
|
||||
|
||||
/// Signal try type
|
||||
type SignalTry<T: Try> = <T::Residual as Residual<Signal<T::Output>>>::TryType;
|
||||
@ -257,7 +222,7 @@ where
|
||||
T: Try<Residual: Residual<Signal<T::Output>>>,
|
||||
F: FnOnce(&Signal<T::Output>),
|
||||
{
|
||||
let mut output = output_sig.write();
|
||||
let mut output = output_sig.borrow_mut();
|
||||
|
||||
// Take the existing type and branch on it
|
||||
let new_output = match output.take().expect("Output signal was missing").branch() {
|
||||
@ -274,9 +239,9 @@ where
|
||||
/// Mapped signal.
|
||||
///
|
||||
/// Maps a signal, infallibly.
|
||||
pub struct MappedSignal<T, W: ReactiveWorld = WorldDefault>(TryMappedSignal<Result<T, !>, W>);
|
||||
pub struct MappedSignal<T>(TryMappedSignal<Result<T, !>>);
|
||||
|
||||
impl<T> MappedSignal<T, WorldDefault> {
|
||||
impl<T> MappedSignal<T> {
|
||||
/// Creates a new mapped signal from a fallible getter
|
||||
pub fn new<S, Get, Set>(input: S, get: Get, set: Set) -> Self
|
||||
where
|
||||
@ -285,35 +250,21 @@ impl<T> MappedSignal<T, WorldDefault> {
|
||||
Get: Fn(<S as SignalWith>::Value<'_>) -> T + 'static,
|
||||
Set: Fn(<S as SignalUpdate>::Value<'_>, &T) + 'static,
|
||||
{
|
||||
Self::new_in(input, get, set, WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W: ReactiveWorld> MappedSignal<T, W> {
|
||||
/// Creates a new mapped signal from a fallible getter
|
||||
pub fn new_in<S, Get, Set>(input: S, get: Get, set: Set, world: W) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
S: SignalWith + SignalUpdate + Clone + 'static,
|
||||
Get: Fn(<S as SignalWith>::Value<'_>) -> T + 'static,
|
||||
Set: Fn(<S as SignalUpdate>::Value<'_>, &T) + 'static,
|
||||
{
|
||||
Self(TryMappedSignal::new_in(
|
||||
Self(TryMappedSignal::new(
|
||||
input,
|
||||
move |value| Ok(get(value)),
|
||||
move |value, new_value| set(value, new_value),
|
||||
world,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W: ReactiveWorld> Clone for MappedSignal<T, W> {
|
||||
impl<T> Clone for MappedSignal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W: ReactiveWorld> SignalGetCloned for MappedSignal<T, W> {
|
||||
impl<T> SignalGetCloned for MappedSignal<T> {
|
||||
type Value = Signal<T>;
|
||||
|
||||
fn get_cloned(&self) -> Self::Value {
|
||||
@ -326,14 +277,13 @@ impl<T, W: ReactiveWorld> SignalGetCloned for MappedSignal<T, W> {
|
||||
}
|
||||
|
||||
/// Extension trait to add a map a signal
|
||||
// TODO: Add this for other worlds
|
||||
#[extend::ext_sized(name = SignalMapped)]
|
||||
pub impl<S> S
|
||||
where
|
||||
S: SignalWith + SignalUpdate + Clone + 'static,
|
||||
{
|
||||
/// Maps this signal fallibly
|
||||
fn try_mapped<T, TryGet, Set>(self, try_get: TryGet, set: Set) -> TryMappedSignal<T, WorldDefault>
|
||||
fn try_mapped<T, TryGet, Set>(self, try_get: TryGet, set: Set) -> TryMappedSignal<T>
|
||||
where
|
||||
T: Try<Residual: Residual<Signal<T::Output>>> + 'static,
|
||||
TryGet: Fn(<S as SignalWith>::Value<'_>) -> T + 'static,
|
||||
@ -343,7 +293,7 @@ where
|
||||
}
|
||||
|
||||
/// Maps this signal
|
||||
fn mapped<T, Get, Set>(self, get: Get, set: Set) -> MappedSignal<T, WorldDefault>
|
||||
fn mapped<T, Get, Set>(self, get: Get, set: Set) -> MappedSignal<T>
|
||||
where
|
||||
T: 'static,
|
||||
Get: Fn(<S as SignalWith>::Value<'_>) -> T + 'static,
|
||||
|
||||
@ -6,7 +6,6 @@ use {
|
||||
Effect,
|
||||
EffectRun,
|
||||
EffectRunCtx,
|
||||
ReactiveWorld,
|
||||
SignalBorrow,
|
||||
SignalGetClonedDefaultImpl,
|
||||
SignalGetDefaultImpl,
|
||||
@ -14,25 +13,22 @@ use {
|
||||
Trigger,
|
||||
},
|
||||
core::{
|
||||
cell::{self, RefCell},
|
||||
fmt,
|
||||
marker::{PhantomData, Unsize},
|
||||
ops::{CoerceUnsized, Deref},
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, IMutRef, WorldDefault},
|
||||
};
|
||||
|
||||
/// World for [`Memo`]
|
||||
pub trait MemoWorld<T, F: ?Sized> = ReactiveWorld where IMut<Option<T>, Self>: Sized;
|
||||
|
||||
/// Memo signal.
|
||||
///
|
||||
/// See the module documentation for more information.
|
||||
pub struct Memo<T, F: ?Sized, W: MemoWorld<T, F> = WorldDefault> {
|
||||
pub struct Memo<T, F: ?Sized> {
|
||||
/// Effect
|
||||
effect: Effect<EffectFn<T, F, W>, W>,
|
||||
effect: Effect<EffectFn<T, F>>,
|
||||
}
|
||||
|
||||
impl<T, F> Memo<T, F, WorldDefault> {
|
||||
impl<T, F> Memo<T, F> {
|
||||
/// Creates a new memo'd signal
|
||||
#[track_caller]
|
||||
pub fn new(f: F) -> Self
|
||||
@ -40,44 +36,21 @@ impl<T, F> Memo<T, F, WorldDefault> {
|
||||
T: PartialEq + 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
{
|
||||
Self::new_in(f, WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F, W: MemoWorld<T, F>> Memo<T, F, W> {
|
||||
/// Creates a new memo'd signal in a world
|
||||
#[track_caller]
|
||||
#[expect(private_bounds, reason = "We can't *not* leak some implementation details currently")]
|
||||
pub fn new_in(f: F, world: W) -> Self
|
||||
where
|
||||
T: PartialEq + 'static,
|
||||
F: Fn() -> T + 'static,
|
||||
EffectFn<T, F, W>: Unsize<W::F>,
|
||||
{
|
||||
let value = IMut::<_, W>::new(None);
|
||||
let effect = Effect::new_in(
|
||||
EffectFn {
|
||||
trigger: Trigger::new_in(world.clone()),
|
||||
value,
|
||||
f,
|
||||
},
|
||||
world,
|
||||
);
|
||||
let value = RefCell::new(None);
|
||||
let effect = Effect::new(EffectFn {
|
||||
trigger: Trigger::new(),
|
||||
value,
|
||||
f,
|
||||
});
|
||||
|
||||
Self { effect }
|
||||
}
|
||||
}
|
||||
|
||||
/// World for [`BorrowRef`]
|
||||
pub trait BorrowRefWorld<'a, T, F: ?Sized> = MemoWorld<T, F> where IMut<Option<T>, Self>: 'a;
|
||||
|
||||
/// Reference type for [`SignalBorrow`] impl
|
||||
pub struct BorrowRef<'a, T: 'a, F: ?Sized, W: BorrowRefWorld<'a, T, F> = WorldDefault>(
|
||||
IMutRef<'a, Option<T>, W>,
|
||||
PhantomData<fn(F)>,
|
||||
);
|
||||
pub struct BorrowRef<'a, T: 'a, F: ?Sized>(cell::Ref<'a, Option<T>>, PhantomData<fn(F)>);
|
||||
|
||||
impl<'a, T, F: ?Sized, W: BorrowRefWorld<'a, T, F>> Deref for BorrowRef<'a, T, F, W> {
|
||||
impl<T, F: ?Sized> Deref for BorrowRef<'_, T, F> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -85,15 +58,15 @@ impl<'a, T, F: ?Sized, W: BorrowRefWorld<'a, T, F>> Deref for BorrowRef<'a, T, F
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: fmt::Debug, F: ?Sized, W: BorrowRefWorld<'a, T, F>> fmt::Debug for BorrowRef<'a, T, F, W> {
|
||||
impl<T: fmt::Debug, F: ?Sized> fmt::Debug for BorrowRef<'_, T, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(*self.0).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, F: ?Sized, W: MemoWorld<T, F>> SignalBorrow for Memo<T, F, W> {
|
||||
impl<T: 'static, F: ?Sized> SignalBorrow for Memo<T, F> {
|
||||
type Ref<'a>
|
||||
= BorrowRef<'a, T, F, W>
|
||||
= BorrowRef<'a, T, F>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
@ -105,16 +78,16 @@ impl<T: 'static, F: ?Sized, W: MemoWorld<T, F>> SignalBorrow for Memo<T, F, W> {
|
||||
|
||||
fn borrow_raw(&self) -> Self::Ref<'_> {
|
||||
let effect_fn = self.effect.inner_fn();
|
||||
let value = effect_fn.value.read();
|
||||
let value = effect_fn.value.borrow();
|
||||
BorrowRef(value, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, F: ?Sized, W: MemoWorld<T, F>> SignalWithDefaultImpl for Memo<T, F, W> {}
|
||||
impl<T: 'static, F: ?Sized, W: MemoWorld<T, F>> SignalGetDefaultImpl for Memo<T, F, W> {}
|
||||
impl<T: 'static, F: ?Sized, W: MemoWorld<T, F>> SignalGetClonedDefaultImpl for Memo<T, F, W> {}
|
||||
impl<T: 'static, F: ?Sized> SignalWithDefaultImpl for Memo<T, F> {}
|
||||
impl<T: 'static, F: ?Sized> SignalGetDefaultImpl for Memo<T, F> {}
|
||||
impl<T: 'static, F: ?Sized> SignalGetClonedDefaultImpl for Memo<T, F> {}
|
||||
|
||||
impl<T, F: ?Sized, W: MemoWorld<T, F>> Clone for Memo<T, F, W> {
|
||||
impl<T, F: ?Sized> Clone for Memo<T, F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
effect: self.effect.clone(),
|
||||
@ -122,47 +95,44 @@ impl<T, F: ?Sized, W: MemoWorld<T, F>> Clone for Memo<T, F, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, F: ?Sized, W: MemoWorld<T, F>> fmt::Debug for Memo<T, F, W> {
|
||||
impl<T: fmt::Debug, F: ?Sized> fmt::Debug for Memo<T, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let effect_fn = self.effect.inner_fn();
|
||||
let mut debug = f.debug_struct("Memo");
|
||||
match effect_fn.value.try_read() {
|
||||
Some(value) => debug.field("value", &*value).finish(),
|
||||
None => debug.finish_non_exhaustive(),
|
||||
match effect_fn.value.try_borrow() {
|
||||
Ok(value) => debug.field("value", &*value).finish(),
|
||||
Err(_) => debug.finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F1, F2, W> CoerceUnsized<Memo<T, F2, W>> for Memo<T, F1, W>
|
||||
impl<T, F1, F2> CoerceUnsized<Memo<T, F2>> for Memo<T, F1>
|
||||
where
|
||||
F1: ?Sized + Unsize<F2>,
|
||||
F2: ?Sized,
|
||||
W: MemoWorld<T, F1> + MemoWorld<T, F2>,
|
||||
Effect<EffectFn<T, F1, W>, W>: CoerceUnsized<Effect<EffectFn<T, F2, W>, W>>,
|
||||
{
|
||||
}
|
||||
|
||||
/// Effect function
|
||||
struct EffectFn<T, F: ?Sized, W: MemoWorld<T, F>> {
|
||||
struct EffectFn<T, F: ?Sized> {
|
||||
/// Trigger
|
||||
trigger: Trigger<W>,
|
||||
trigger: Trigger,
|
||||
|
||||
/// Value
|
||||
value: IMut<Option<T>, W>,
|
||||
value: RefCell<Option<T>>,
|
||||
|
||||
/// Function
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<T, F, W> EffectRun<W> for EffectFn<T, F, W>
|
||||
impl<T, F> EffectRun for EffectFn<T, F>
|
||||
where
|
||||
T: PartialEq + 'static,
|
||||
F: Fn() -> T,
|
||||
W: MemoWorld<T, F>,
|
||||
{
|
||||
fn run(&self, _ctx: EffectRunCtx<'_, W>) {
|
||||
fn run(&self, _ctx: EffectRunCtx<'_>) {
|
||||
let new_value = (self.f)();
|
||||
let mut value = self.value.write();
|
||||
let mut value = self.value.borrow_mut();
|
||||
|
||||
// Write the new value, if it's different from the previous
|
||||
// Note: Since we're comparing against `Some(_)`, any `None` values
|
||||
|
||||
119
dynatos-reactive/src/run_queue.rs
Normal file
119
dynatos-reactive/src/run_queue.rs
Normal file
@ -0,0 +1,119 @@
|
||||
//! Run queue
|
||||
|
||||
// Imports
|
||||
use {
|
||||
crate::{trigger::SubscriberInfo, Subscriber},
|
||||
core::{
|
||||
cell::{LazyCell, RefCell},
|
||||
cmp::Reverse,
|
||||
hash::{Hash, Hasher},
|
||||
},
|
||||
priority_queue::PriorityQueue,
|
||||
};
|
||||
|
||||
/// Inner item for the priority queue
|
||||
struct Item {
|
||||
/// Subscriber
|
||||
subscriber: Subscriber,
|
||||
|
||||
/// Info
|
||||
info: SubscriberInfo,
|
||||
}
|
||||
|
||||
impl PartialEq for Item {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.subscriber == other.subscriber
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Item {}
|
||||
|
||||
impl Hash for Item {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.subscriber.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner type for the queue impl
|
||||
struct Inner {
|
||||
/// Queue
|
||||
// TODO: We don't need the priority, so just use some kind of
|
||||
// `HashQueue`.
|
||||
queue: PriorityQueue<Item, Reverse<usize>>,
|
||||
|
||||
/// Next index
|
||||
next: usize,
|
||||
|
||||
/// Reference count
|
||||
ref_count: usize,
|
||||
|
||||
/// Whether currently executing the queue
|
||||
is_exec: bool,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
queue: PriorityQueue::new(),
|
||||
next: 0,
|
||||
ref_count: 0,
|
||||
is_exec: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run queue
|
||||
#[thread_local]
|
||||
static RUN_QUEUE: LazyCell<RefCell<Inner>> = LazyCell::new(|| RefCell::new(Inner::new()));
|
||||
|
||||
/// Execution guard
|
||||
pub struct ExecGuard;
|
||||
|
||||
impl Drop for ExecGuard {
|
||||
fn drop(&mut self) {
|
||||
let mut inner = RUN_QUEUE.borrow_mut();
|
||||
inner.is_exec = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Increases the reference count of the queue
|
||||
pub fn inc_ref() {
|
||||
let mut inner = RUN_QUEUE.borrow_mut();
|
||||
inner.ref_count += 1;
|
||||
}
|
||||
|
||||
/// Decreases the reference count of the queue.
|
||||
///
|
||||
/// Returns a guard for execution all effects if
|
||||
/// this was the last trigger exec dropped.
|
||||
///
|
||||
/// Any further created trigger execs won't
|
||||
/// execute the functions and will instead just
|
||||
/// add them to the queue
|
||||
pub fn dec_ref() -> Option<ExecGuard> {
|
||||
let mut inner = RUN_QUEUE.borrow_mut();
|
||||
inner.ref_count = inner
|
||||
.ref_count
|
||||
.checked_sub(1)
|
||||
.expect("Attempted to decrease reference count beyond 0");
|
||||
|
||||
(inner.ref_count == 0 && !inner.queue.is_empty() && !inner.is_exec).then(|| {
|
||||
inner.is_exec = true;
|
||||
ExecGuard
|
||||
})
|
||||
}
|
||||
|
||||
/// Pushes a subscriber to the queue.
|
||||
pub fn push(subscriber: Subscriber, info: SubscriberInfo) {
|
||||
let mut inner = RUN_QUEUE.borrow_mut();
|
||||
|
||||
let next = Reverse(inner.next);
|
||||
inner.queue.push_decrease(Item { subscriber, info }, next);
|
||||
inner.next += 1;
|
||||
}
|
||||
|
||||
/// Pops a subscriber from the front of the queue
|
||||
pub fn pop() -> Option<(Subscriber, SubscriberInfo)> {
|
||||
let (item, _) = RUN_QUEUE.borrow_mut().queue.pop()?;
|
||||
Some((item.subscriber, item.info))
|
||||
}
|
||||
@ -28,75 +28,58 @@ pub use ops::{
|
||||
|
||||
// Imports
|
||||
use {
|
||||
crate::{trigger::TriggerExec, ReactiveWorld, Trigger},
|
||||
crate::{trigger::TriggerExec, Trigger},
|
||||
core::{
|
||||
cell::{self, RefCell},
|
||||
fmt,
|
||||
marker::Unsize,
|
||||
mem,
|
||||
ops::{CoerceUnsized, Deref, DerefMut},
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, IMutRef, IMutRefMut, Rc, RcLike, WorldDefault},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
/// Inner
|
||||
struct Inner<T: ?Sized, W: ReactiveWorld> {
|
||||
struct Inner<T: ?Sized> {
|
||||
/// Trigger
|
||||
trigger: Trigger<W>,
|
||||
trigger: Trigger,
|
||||
|
||||
/// Value
|
||||
value: IMut<T, W>,
|
||||
value: RefCell<T>,
|
||||
}
|
||||
|
||||
/// Signal
|
||||
pub struct Signal<T: ?Sized, W: ReactiveWorld = WorldDefault> {
|
||||
pub struct Signal<T: ?Sized> {
|
||||
/// Inner
|
||||
inner: Rc<Inner<T, W>, W>,
|
||||
inner: Rc<Inner<T>>,
|
||||
}
|
||||
|
||||
impl<T> Signal<T, WorldDefault> {
|
||||
/// Creates a new signal
|
||||
impl<T> Signal<T> {
|
||||
/// Creates a new signal.
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self
|
||||
where
|
||||
IMut<T, WorldDefault>: Sized,
|
||||
{
|
||||
Self::new_in(value, WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W: ReactiveWorld> Signal<T, W> {
|
||||
/// Creates a new signal in a world.
|
||||
#[track_caller]
|
||||
pub fn new_in(value: T, world: W) -> Self
|
||||
where
|
||||
IMut<T, W>: Sized,
|
||||
{
|
||||
pub fn new(value: T) -> Self {
|
||||
let inner = Inner {
|
||||
value: IMut::<_, W>::new(value),
|
||||
trigger: Trigger::new_in(world),
|
||||
value: RefCell::new(value),
|
||||
trigger: Trigger::new(),
|
||||
};
|
||||
Self {
|
||||
inner: Rc::<_, W>::new(inner),
|
||||
}
|
||||
Self { inner: Rc::new(inner) }
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add `Signal::<dyn Any>::downcast` once we add `{T, U}: ?Sized` to the `CoerceUnsized` impl of `Inner`.
|
||||
// Use `Rc::downcast::<Inner<T>>(self.inner as Rc<dyn Any>)`
|
||||
|
||||
impl<T, U, W> CoerceUnsized<Signal<U, W>> for Signal<T, W>
|
||||
impl<T, U> CoerceUnsized<Signal<U>> for Signal<T>
|
||||
where
|
||||
T: ?Sized + Unsize<U>,
|
||||
U: ?Sized,
|
||||
W: ReactiveWorld,
|
||||
Rc<Inner<T, W>, W>: CoerceUnsized<Rc<Inner<U, W>, W>>,
|
||||
{
|
||||
}
|
||||
|
||||
/// Reference type for [`SignalBorrow`] impl
|
||||
pub struct BorrowRef<'a, T: ?Sized + 'a, W: ReactiveWorld = WorldDefault>(IMutRef<'a, T, W>);
|
||||
pub struct BorrowRef<'a, T: ?Sized + 'a>(cell::Ref<'a, T>);
|
||||
|
||||
impl<T: ?Sized, W: ReactiveWorld> Deref for BorrowRef<'_, T, W> {
|
||||
impl<T: ?Sized> Deref for BorrowRef<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -104,15 +87,15 @@ impl<T: ?Sized, W: ReactiveWorld> Deref for BorrowRef<'_, T, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, W: ReactiveWorld> fmt::Debug for BorrowRef<'_, T, W> {
|
||||
impl<T: fmt::Debug> fmt::Debug for BorrowRef<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("BorrowRef").field(&*self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static, W: ReactiveWorld> SignalBorrow for Signal<T, W> {
|
||||
impl<T: ?Sized + 'static> SignalBorrow for Signal<T> {
|
||||
type Ref<'a>
|
||||
= BorrowRef<'a, T, W>
|
||||
= BorrowRef<'a, T>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
@ -123,12 +106,12 @@ impl<T: ?Sized + 'static, W: ReactiveWorld> SignalBorrow for Signal<T, W> {
|
||||
}
|
||||
|
||||
fn borrow_raw(&self) -> Self::Ref<'_> {
|
||||
let value = self.inner.value.read();
|
||||
let value = self.inner.value.borrow();
|
||||
BorrowRef(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, W: ReactiveWorld> SignalReplace<T> for Signal<T, W> {
|
||||
impl<T: 'static> SignalReplace<T> for Signal<T> {
|
||||
type Value = T;
|
||||
|
||||
fn replace(&self, new_value: T) -> Self::Value {
|
||||
@ -141,16 +124,16 @@ impl<T: 'static, W: ReactiveWorld> SignalReplace<T> for Signal<T, W> {
|
||||
}
|
||||
|
||||
/// Reference type for [`SignalBorrowMut`] impl
|
||||
pub struct BorrowRefMut<'a, T: ?Sized + 'a, W: ReactiveWorld = WorldDefault> {
|
||||
pub struct BorrowRefMut<'a, T: ?Sized + 'a> {
|
||||
/// Value
|
||||
value: IMutRefMut<'a, T, W>,
|
||||
value: cell::RefMut<'a, T>,
|
||||
|
||||
/// Trigger executor
|
||||
// Note: Must be dropped *after* `value`.
|
||||
_trigger_exec: Option<TriggerExec<W>>,
|
||||
_trigger_exec: Option<TriggerExec>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized, W: ReactiveWorld> Deref for BorrowRefMut<'_, T, W> {
|
||||
impl<T: ?Sized> Deref for BorrowRefMut<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -158,26 +141,26 @@ impl<T: ?Sized, W: ReactiveWorld> Deref for BorrowRefMut<'_, T, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized, W: ReactiveWorld> DerefMut for BorrowRefMut<'_, T, W> {
|
||||
impl<T: ?Sized> DerefMut for BorrowRefMut<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, W: ReactiveWorld> fmt::Debug for BorrowRefMut<'_, T, W> {
|
||||
impl<T: fmt::Debug> fmt::Debug for BorrowRefMut<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("BorrowRefMut").field(&*self.value).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static, W: ReactiveWorld> SignalBorrowMut for Signal<T, W> {
|
||||
impl<T: ?Sized + 'static> SignalBorrowMut for Signal<T> {
|
||||
type RefMut<'a>
|
||||
= BorrowRefMut<'a, T, W>
|
||||
= BorrowRefMut<'a, T>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn borrow_mut(&self) -> Self::RefMut<'_> {
|
||||
let value = self.inner.value.write();
|
||||
let value = self.inner.value.borrow_mut();
|
||||
BorrowRefMut {
|
||||
value,
|
||||
_trigger_exec: Some(self.inner.trigger.exec()),
|
||||
@ -185,7 +168,7 @@ impl<T: ?Sized + 'static, W: ReactiveWorld> SignalBorrowMut for Signal<T, W> {
|
||||
}
|
||||
|
||||
fn borrow_mut_raw(&self) -> Self::RefMut<'_> {
|
||||
let value = self.inner.value.write();
|
||||
let value = self.inner.value.borrow_mut();
|
||||
BorrowRefMut {
|
||||
value,
|
||||
_trigger_exec: None,
|
||||
@ -194,24 +177,24 @@ impl<T: ?Sized + 'static, W: ReactiveWorld> SignalBorrowMut for Signal<T, W> {
|
||||
}
|
||||
|
||||
|
||||
impl<T: ?Sized, W: ReactiveWorld> SignalSetDefaultImpl for Signal<T, W> {}
|
||||
impl<T: ?Sized, W: ReactiveWorld> SignalGetDefaultImpl for Signal<T, W> {}
|
||||
impl<T: ?Sized, W: ReactiveWorld> SignalGetClonedDefaultImpl for Signal<T, W> {}
|
||||
impl<T: ?Sized, W: ReactiveWorld> SignalWithDefaultImpl for Signal<T, W> {}
|
||||
impl<T: ?Sized, W: ReactiveWorld> SignalUpdateDefaultImpl for Signal<T, W> {}
|
||||
impl<T: ?Sized> SignalSetDefaultImpl for Signal<T> {}
|
||||
impl<T: ?Sized> SignalGetDefaultImpl for Signal<T> {}
|
||||
impl<T: ?Sized> SignalGetClonedDefaultImpl for Signal<T> {}
|
||||
impl<T: ?Sized> SignalWithDefaultImpl for Signal<T> {}
|
||||
impl<T: ?Sized> SignalUpdateDefaultImpl for Signal<T> {}
|
||||
|
||||
impl<T: ?Sized, W: ReactiveWorld> Clone for Signal<T, W> {
|
||||
impl<T: ?Sized> Clone for Signal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Rc::<_, W>::clone(&self.inner),
|
||||
inner: Rc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + fmt::Debug, W: ReactiveWorld> fmt::Debug for Signal<T, W> {
|
||||
impl<T: ?Sized + fmt::Debug> fmt::Debug for Signal<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Signal")
|
||||
.field("value", &&*self.inner.value.read())
|
||||
.field("value", &&*self.inner.value.borrow())
|
||||
.field("trigger", &self.inner.trigger)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@ -5,17 +5,19 @@
|
||||
|
||||
// Imports
|
||||
use {
|
||||
crate::{effect, world::RunQueue, Effect, ReactiveWorld, WeakEffect},
|
||||
crate::{effect, run_queue, Effect, EffectRun, WeakEffect},
|
||||
core::{
|
||||
borrow::Borrow,
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
marker::{PhantomData, Unsize},
|
||||
ops::CoerceUnsized,
|
||||
marker::Unsize,
|
||||
ptr,
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, Rc, RcLike, Weak, WeakLike, WorldDefault},
|
||||
std::collections::{hash_map, HashMap},
|
||||
std::{
|
||||
collections::{hash_map, HashMap},
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
use {
|
||||
@ -25,12 +27,12 @@ use {
|
||||
|
||||
/// Subscribers
|
||||
#[derive(Debug)]
|
||||
pub struct Subscriber<W: ReactiveWorld> {
|
||||
pub struct Subscriber {
|
||||
/// Effect
|
||||
effect: WeakEffect<W::F, W>,
|
||||
effect: WeakEffect<dyn EffectRun>,
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Clone for Subscriber<W> {
|
||||
impl Clone for Subscriber {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
effect: self.effect.clone(),
|
||||
@ -38,22 +40,22 @@ impl<W: ReactiveWorld> Clone for Subscriber<W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> PartialEq for Subscriber<W> {
|
||||
impl PartialEq for Subscriber {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.effect == other.effect
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Eq for Subscriber<W> {}
|
||||
impl Eq for Subscriber {}
|
||||
|
||||
impl<W: ReactiveWorld> Hash for Subscriber<W> {
|
||||
impl Hash for Subscriber {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.effect.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Borrow<WeakEffect<W::F, W>> for Subscriber<W> {
|
||||
fn borrow(&self) -> &WeakEffect<W::F, W> {
|
||||
impl Borrow<WeakEffect<dyn EffectRun>> for Subscriber {
|
||||
fn borrow(&self) -> &WeakEffect<dyn EffectRun> {
|
||||
&self.effect
|
||||
}
|
||||
}
|
||||
@ -107,7 +109,7 @@ impl Default for SubscriberInfo {
|
||||
}
|
||||
|
||||
/// Trigger inner
|
||||
struct Inner<W: ReactiveWorld> {
|
||||
struct Inner {
|
||||
/// Subscribers
|
||||
#[cfg_attr(
|
||||
not(debug_assertions),
|
||||
@ -116,7 +118,7 @@ struct Inner<W: ReactiveWorld> {
|
||||
reason = "It isn't zero-sized with `debug_assertions`"
|
||||
)
|
||||
)]
|
||||
subscribers: IMut<HashMap<Subscriber<W>, SubscriberInfo>, W>,
|
||||
subscribers: RefCell<HashMap<Subscriber, SubscriberInfo>>,
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
/// Where this trigger was defined
|
||||
@ -124,25 +126,16 @@ struct Inner<W: ReactiveWorld> {
|
||||
}
|
||||
|
||||
/// Trigger
|
||||
pub struct Trigger<W: ReactiveWorld = WorldDefault> {
|
||||
pub struct Trigger {
|
||||
/// Inner
|
||||
inner: Rc<Inner<W>, W>,
|
||||
inner: Rc<Inner>,
|
||||
}
|
||||
|
||||
impl Trigger<WorldDefault> {
|
||||
impl Trigger {
|
||||
/// Creates a new trigger
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
Self::new_in(WorldDefault::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Trigger<W> {
|
||||
/// Creates a new trigger in a world
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn new_in(_world: W) -> Self {
|
||||
let inner = Inner {
|
||||
#[cfg_attr(
|
||||
not(debug_assertions),
|
||||
@ -151,20 +144,18 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
reason = "It isn't zero-sized with `debug_assertions`"
|
||||
)
|
||||
)]
|
||||
subscribers: IMut::<_, W>::new(HashMap::new()),
|
||||
subscribers: RefCell::new(HashMap::new()),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_loc: Location::caller(),
|
||||
};
|
||||
Self {
|
||||
inner: Rc::<_, W>::new(inner),
|
||||
}
|
||||
Self { inner: Rc::new(inner) }
|
||||
}
|
||||
|
||||
/// Downgrades this trigger
|
||||
#[must_use]
|
||||
pub fn downgrade(&self) -> WeakTrigger<W> {
|
||||
pub fn downgrade(&self) -> WeakTrigger {
|
||||
WeakTrigger {
|
||||
inner: Rc::<_, W>::downgrade(&self.inner),
|
||||
inner: Rc::downgrade(&self.inner),
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,7 +169,7 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
// TODO: Should we remove all existing subscribers before gathering them?
|
||||
#[track_caller]
|
||||
pub fn gather_subscribers(&self) {
|
||||
match effect::running_in::<W>() {
|
||||
match effect::running() {
|
||||
Some(effect) => {
|
||||
effect.add_dependency(self.downgrade());
|
||||
self.add_subscriber(effect);
|
||||
@ -204,8 +195,8 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
///
|
||||
/// Returns if the subscriber already existed.
|
||||
#[track_caller]
|
||||
fn add_subscriber<S: IntoSubscriber<W>>(&self, subscriber: S) -> bool {
|
||||
let mut subscribers = self.inner.subscribers.write();
|
||||
fn add_subscriber<S: IntoSubscriber>(&self, subscriber: S) -> bool {
|
||||
let mut subscribers = self.inner.subscribers.borrow_mut();
|
||||
match (*subscribers).entry(subscriber.into_subscriber()) {
|
||||
hash_map::Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().update();
|
||||
@ -222,14 +213,14 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
///
|
||||
/// Returns if the subscriber existed
|
||||
#[track_caller]
|
||||
pub(crate) fn remove_subscriber<S: IntoSubscriber<W>>(&self, subscriber: S) -> bool {
|
||||
pub(crate) fn remove_subscriber<S: IntoSubscriber>(&self, subscriber: S) -> bool {
|
||||
Self::remove_subscriber_inner(&self.inner, subscriber)
|
||||
}
|
||||
|
||||
/// Inner function for [`Self::remove_subscriber`]
|
||||
#[track_caller]
|
||||
fn remove_subscriber_inner<S: IntoSubscriber<W>>(inner: &Inner<W>, subscriber: S) -> bool {
|
||||
let mut subscribers = inner.subscribers.write();
|
||||
fn remove_subscriber_inner<S: IntoSubscriber>(inner: &Inner, subscriber: S) -> bool {
|
||||
let mut subscribers = inner.subscribers.borrow_mut();
|
||||
subscribers.remove(&subscriber.into_subscriber()).is_some()
|
||||
}
|
||||
|
||||
@ -239,7 +230,11 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
/// executor is dropped, and there are no other executors alive,
|
||||
/// all queues effects are run.
|
||||
#[track_caller]
|
||||
pub fn exec(&self) -> TriggerExec<W> {
|
||||
#[expect(
|
||||
clippy::must_use_candidate,
|
||||
reason = "The user can just immediately drop the value to execute if they don't care"
|
||||
)]
|
||||
pub fn exec(&self) -> TriggerExec {
|
||||
self.exec_inner(
|
||||
#[cfg(debug_assertions)]
|
||||
Location::caller(),
|
||||
@ -250,11 +245,11 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
pub(crate) fn exec_inner(
|
||||
&self,
|
||||
#[cfg(debug_assertions)] exec_defined_loc: &'static Location<'static>,
|
||||
) -> TriggerExec<W> {
|
||||
let subscribers = self.inner.subscribers.read();
|
||||
) -> TriggerExec {
|
||||
let subscribers = self.inner.subscribers.borrow();
|
||||
|
||||
// Increase the ref count
|
||||
W::RunQueue::inc_ref();
|
||||
run_queue::inc_ref();
|
||||
|
||||
// Then all all of our subscribers
|
||||
// TODO: Should we care about the order? Randomizing it is probably good, since
|
||||
@ -272,7 +267,7 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
}
|
||||
|
||||
// TODO: Should the run queue use strong effects?
|
||||
W::RunQueue::push(subscriber.clone(), info.clone());
|
||||
run_queue::push(subscriber.clone(), info.clone());
|
||||
}
|
||||
|
||||
TriggerExec {
|
||||
@ -280,7 +275,6 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
trigger_defined_loc: self.inner.defined_loc,
|
||||
#[cfg(debug_assertions)]
|
||||
exec_defined_loc,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,79 +291,79 @@ impl<W: ReactiveWorld> Trigger<W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld + Default> Default for Trigger<W> {
|
||||
impl Default for Trigger {
|
||||
fn default() -> Self {
|
||||
Self::new_in(W::default())
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> PartialEq for Trigger<W> {
|
||||
impl PartialEq for Trigger {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
ptr::eq(Rc::<_, W>::as_ptr(&self.inner), Rc::<_, W>::as_ptr(&other.inner))
|
||||
ptr::eq(Rc::as_ptr(&self.inner), Rc::as_ptr(&other.inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Eq for Trigger<W> {}
|
||||
impl Eq for Trigger {}
|
||||
|
||||
|
||||
impl<W: ReactiveWorld> Clone for Trigger<W> {
|
||||
impl Clone for Trigger {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Rc::<_, W>::clone(&self.inner),
|
||||
inner: Rc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Hash for Trigger<W> {
|
||||
impl Hash for Trigger {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Rc::<_, W>::as_ptr(&self.inner).hash(state);
|
||||
Rc::as_ptr(&self.inner).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> fmt::Debug for Trigger<W> {
|
||||
impl fmt::Debug for Trigger {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.fmt_debug(f.debug_struct("Trigger"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Weak trigger
|
||||
pub struct WeakTrigger<W: ReactiveWorld> {
|
||||
pub struct WeakTrigger {
|
||||
/// Inner
|
||||
inner: Weak<Inner<W>, W>,
|
||||
inner: Weak<Inner>,
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> WeakTrigger<W> {
|
||||
impl WeakTrigger {
|
||||
/// Upgrades this weak trigger
|
||||
#[must_use]
|
||||
pub fn upgrade(&self) -> Option<Trigger<W>> {
|
||||
pub fn upgrade(&self) -> Option<Trigger> {
|
||||
let inner = self.inner.upgrade()?;
|
||||
Some(Trigger { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> PartialEq for WeakTrigger<W> {
|
||||
impl PartialEq for WeakTrigger {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
ptr::eq(Weak::<_, W>::as_ptr(&self.inner), Weak::<_, W>::as_ptr(&other.inner))
|
||||
ptr::eq(Weak::as_ptr(&self.inner), Weak::as_ptr(&other.inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Eq for WeakTrigger<W> {}
|
||||
impl Eq for WeakTrigger {}
|
||||
|
||||
impl<W: ReactiveWorld> Clone for WeakTrigger<W> {
|
||||
impl Clone for WeakTrigger {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: Weak::<_, W>::clone(&self.inner),
|
||||
inner: Weak::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Hash for WeakTrigger<W> {
|
||||
impl Hash for WeakTrigger {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Weak::<_, W>::as_ptr(&self.inner).hash(state);
|
||||
Weak::as_ptr(&self.inner).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> fmt::Debug for WeakTrigger<W> {
|
||||
impl fmt::Debug for WeakTrigger {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut s = f.debug_struct("WeakTrigger");
|
||||
|
||||
@ -381,13 +375,13 @@ impl<W: ReactiveWorld> fmt::Debug for WeakTrigger<W> {
|
||||
}
|
||||
|
||||
/// Types that may be converted into a subscriber
|
||||
pub trait IntoSubscriber<W: ReactiveWorld> {
|
||||
pub trait IntoSubscriber {
|
||||
/// Converts this type into a weak effect.
|
||||
#[track_caller]
|
||||
fn into_subscriber(self) -> Subscriber<W>;
|
||||
fn into_subscriber(self) -> Subscriber;
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> IntoSubscriber<W> for Subscriber<W> {
|
||||
impl IntoSubscriber for Subscriber {
|
||||
fn into_subscriber(self) -> Self {
|
||||
self
|
||||
}
|
||||
@ -401,19 +395,17 @@ impl<W: ReactiveWorld> IntoSubscriber<W> for Subscriber<W> {
|
||||
[ &'_ Effect ] [ self.downgrade() ];
|
||||
[ WeakEffect ] [ self ];
|
||||
)]
|
||||
impl<F, W> IntoSubscriber<W> for T<F, W>
|
||||
impl<F> IntoSubscriber for T<F>
|
||||
where
|
||||
F: ?Sized + Unsize<W::F>,
|
||||
W: ReactiveWorld,
|
||||
WeakEffect<F, W>: CoerceUnsized<WeakEffect<W::F, W>>,
|
||||
F: ?Sized + Unsize<dyn EffectRun>,
|
||||
{
|
||||
fn into_subscriber(self) -> Subscriber<W> {
|
||||
fn into_subscriber(self) -> Subscriber {
|
||||
Subscriber { effect: effect_value }
|
||||
}
|
||||
}
|
||||
|
||||
/// Trigger executor
|
||||
pub struct TriggerExec<W: ReactiveWorld> {
|
||||
pub struct TriggerExec {
|
||||
/// Trigger defined location
|
||||
#[cfg(debug_assertions)]
|
||||
trigger_defined_loc: &'static Location<'static>,
|
||||
@ -421,21 +413,18 @@ pub struct TriggerExec<W: ReactiveWorld> {
|
||||
/// Execution defined location
|
||||
#[cfg(debug_assertions)]
|
||||
exec_defined_loc: &'static Location<'static>,
|
||||
|
||||
/// Phantom
|
||||
_phantom: PhantomData<W>,
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Drop for TriggerExec<W> {
|
||||
impl Drop for TriggerExec {
|
||||
fn drop(&mut self) {
|
||||
// Decrease the reference count, and if we weren't the last, quit
|
||||
let Some(_exec_guard) = W::RunQueue::dec_ref() else {
|
||||
let Some(_exec_guard) = run_queue::dec_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If we were the last, keep popping effects and running them until
|
||||
// the run queue is empty
|
||||
while let Some((subscriber, info)) = W::RunQueue::pop() {
|
||||
while let Some((subscriber, info)) = run_queue::pop() {
|
||||
let Some(effect) = subscriber.effect.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
//! World
|
||||
|
||||
// Lints
|
||||
#![expect(
|
||||
type_alias_bounds,
|
||||
reason = "Although they're not enforced currently, they will be in the future and we want to be explicit already"
|
||||
)]
|
||||
|
||||
// TODO: Get rid of all of the `*World` types strewn about. They only exist because we can't provide
|
||||
// the necessary bounds, such as `T: Unsize<U> => Rc<T>: CoerceUnsized<Rc<U>>`.
|
||||
|
||||
// Modules
|
||||
pub mod effect_stack;
|
||||
pub mod run_queue;
|
||||
|
||||
// Exports
|
||||
pub use self::{
|
||||
effect_stack::{EffectStack, EffectStackGlobal, EffectStackThreadLocal},
|
||||
run_queue::{RunQueue, RunQueueGlobal, RunQueueThreadLocal},
|
||||
};
|
||||
|
||||
// Imports
|
||||
use {
|
||||
crate::{effect, trigger, Effect, EffectRun, WeakTrigger},
|
||||
core::{marker::Unsize, ops::CoerceUnsized},
|
||||
dynatos_world::{IMut, Weak, World, WorldGlobal, WorldThreadLocal},
|
||||
std::collections::{HashMap, HashSet},
|
||||
};
|
||||
|
||||
/// Reactive world
|
||||
pub trait ReactiveWorldInner: World {
|
||||
/// Effect function
|
||||
type F: ?Sized + Unsize<Self::F> + 'static;
|
||||
|
||||
/// Effect stack
|
||||
type EffectStack: EffectStack<Self>;
|
||||
|
||||
/// Run queue
|
||||
type RunQueue: RunQueue<Self>;
|
||||
|
||||
/// Unsizes an effect `Effect<F, W>` to `Effect<Self::F, W>`
|
||||
// TODO: Encode the capability somehow...
|
||||
fn unsize_effect<F>(effect: Effect<F, Self>) -> Effect<Self::F, Self>
|
||||
where
|
||||
F: ?Sized + Unsize<Self::F>,
|
||||
Self: ReactiveWorld;
|
||||
}
|
||||
|
||||
// TODO: Remove this once we can assume these bounds, or somehow encode them into `ReactiveWorldInner`
|
||||
#[expect(private_bounds, reason = "We can't *not* leak some implementation details currently")]
|
||||
#[cfg_attr(
|
||||
not(debug_assertions),
|
||||
expect(
|
||||
clippy::zero_sized_map_values,
|
||||
reason = "It isn't zero sized with `debug_assertions`"
|
||||
)
|
||||
)]
|
||||
pub trait ReactiveWorld = ReactiveWorldInner
|
||||
where
|
||||
<Self as ReactiveWorldInner>::F: EffectRun<Self>,
|
||||
Weak<effect::Inner<<Self as ReactiveWorldInner>::F, Self>, Self>:
|
||||
CoerceUnsized<Weak<effect::Inner<<Self as ReactiveWorldInner>::F, Self>, Self>>,
|
||||
IMut<HashMap<crate::Subscriber<Self>, trigger::SubscriberInfo>, Self>: Sized,
|
||||
IMut<HashSet<WeakTrigger<Self>>, Self>: Sized,
|
||||
IMut<(), Self>: Sized;
|
||||
|
||||
impl ReactiveWorldInner for WorldThreadLocal {
|
||||
type EffectStack = EffectStackThreadLocal;
|
||||
type F = dyn EffectRun<Self> + 'static;
|
||||
type RunQueue = RunQueueThreadLocal;
|
||||
|
||||
fn unsize_effect<F>(effect: Effect<F, Self>) -> Effect<Self::F, Self>
|
||||
where
|
||||
F: ?Sized + Unsize<Self::F>,
|
||||
Self: ReactiveWorld,
|
||||
{
|
||||
effect
|
||||
}
|
||||
}
|
||||
impl ReactiveWorldInner for WorldGlobal {
|
||||
type EffectStack = EffectStackGlobal;
|
||||
type F = dyn EffectRun<Self> + Send + Sync + 'static;
|
||||
type RunQueue = RunQueueGlobal;
|
||||
|
||||
fn unsize_effect<F>(effect: Effect<F, Self>) -> Effect<Self::F, Self>
|
||||
where
|
||||
F: ?Sized + Unsize<Self::F>,
|
||||
Self: ReactiveWorld,
|
||||
{
|
||||
effect
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
//! Effect stack
|
||||
|
||||
// Imports
|
||||
use {
|
||||
super::{ReactiveWorld, ReactiveWorldInner},
|
||||
crate::{Effect, EffectRun},
|
||||
core::marker::Unsize,
|
||||
dynatos_world::{IMut, IMutLike, WorldGlobal, WorldThreadLocal},
|
||||
};
|
||||
|
||||
/// Effect stack
|
||||
// TODO: Require `W: ReactiveWorld` once that doesn't result in a cycle overflow.
|
||||
pub trait EffectStack<W>: Sized {
|
||||
/// Pushes an effect to the stack.
|
||||
fn push<F>(f: Effect<F, W>)
|
||||
where
|
||||
F: ?Sized + Unsize<W::F>,
|
||||
W: ReactiveWorld;
|
||||
|
||||
/// Pops an effect from the stack
|
||||
fn pop();
|
||||
|
||||
/// Returns the top effect of the stack
|
||||
fn top() -> Option<Effect<W::F, W>>
|
||||
where
|
||||
W: ReactiveWorld;
|
||||
}
|
||||
|
||||
/// Effect stack impl
|
||||
type EffectStackImpl<F: ?Sized, W> = IMut<Vec<Effect<F, W>>, W>;
|
||||
|
||||
/// Thread-local effect stack, using `StdRc` and `StdRefCell`
|
||||
pub struct EffectStackThreadLocal;
|
||||
|
||||
/// Effect stack for `EffectStackThreadLocal`
|
||||
#[thread_local]
|
||||
static EFFECT_STACK_STD_RC: EffectStackImpl<dyn EffectRun<WorldThreadLocal>, WorldThreadLocal> =
|
||||
EffectStackImpl::<_, WorldThreadLocal>::new(vec![]);
|
||||
|
||||
impl EffectStack<WorldThreadLocal> for EffectStackThreadLocal {
|
||||
fn push<F>(f: Effect<F, WorldThreadLocal>)
|
||||
where
|
||||
F: ?Sized + Unsize<<WorldThreadLocal as ReactiveWorldInner>::F>,
|
||||
{
|
||||
EFFECT_STACK_STD_RC.write().push(f);
|
||||
}
|
||||
|
||||
fn pop() {
|
||||
EFFECT_STACK_STD_RC.write().pop().expect("Missing added effect");
|
||||
}
|
||||
|
||||
fn top() -> Option<Effect<<WorldThreadLocal as ReactiveWorldInner>::F, WorldThreadLocal>> {
|
||||
EFFECT_STACK_STD_RC.read().last().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Global effect stack, using `StdArc` and `StdRefCell`
|
||||
pub struct EffectStackGlobal;
|
||||
|
||||
/// Effect stack for `EffectStackGlobal`
|
||||
static EFFECT_STACK_STD_ARC: EffectStackImpl<dyn EffectRun<WorldGlobal> + Send + Sync, WorldGlobal> =
|
||||
EffectStackImpl::<_, WorldGlobal>::new(vec![]);
|
||||
|
||||
|
||||
impl EffectStack<WorldGlobal> for EffectStackGlobal {
|
||||
fn push<F>(f: Effect<F, WorldGlobal>)
|
||||
where
|
||||
F: ?Sized + Unsize<<WorldGlobal as ReactiveWorldInner>::F>,
|
||||
{
|
||||
EFFECT_STACK_STD_ARC.write().push(f);
|
||||
}
|
||||
|
||||
fn pop() {
|
||||
EFFECT_STACK_STD_ARC.write().pop().expect("Missing added effect");
|
||||
}
|
||||
|
||||
fn top() -> Option<Effect<<WorldGlobal as ReactiveWorldInner>::F, WorldGlobal>> {
|
||||
EFFECT_STACK_STD_ARC.read().last().cloned()
|
||||
}
|
||||
}
|
||||
@ -1,211 +0,0 @@
|
||||
//! Run queue
|
||||
|
||||
// Imports
|
||||
use {
|
||||
super::ReactiveWorld,
|
||||
crate::{trigger::SubscriberInfo, Subscriber},
|
||||
core::{
|
||||
cell::LazyCell,
|
||||
cmp::Reverse,
|
||||
hash::{Hash, Hasher},
|
||||
},
|
||||
dynatos_world::{IMut, IMutLike, WorldGlobal, WorldThreadLocal},
|
||||
priority_queue::PriorityQueue,
|
||||
std::sync::LazyLock,
|
||||
};
|
||||
|
||||
/// Run queue
|
||||
// TODO: Require `W: ReactiveWorld` once that doesn't result in a cycle overflow.
|
||||
pub trait RunQueue<W>: Sized {
|
||||
/// Exec guard
|
||||
type ExecGuard;
|
||||
|
||||
/// Increases the reference count of the queue
|
||||
fn inc_ref();
|
||||
|
||||
/// Decreases the reference count of the queue.
|
||||
///
|
||||
/// Returns a guard for execution all effects if
|
||||
/// this was the last trigger exec dropped.
|
||||
///
|
||||
/// Any further created trigger execs won't
|
||||
/// execute the functions and will instead just
|
||||
/// add them to the queue
|
||||
fn dec_ref() -> Option<Self::ExecGuard>;
|
||||
|
||||
/// Pushes a subscriber to the queue.
|
||||
fn push(subscriber: Subscriber<W>, info: SubscriberInfo)
|
||||
where
|
||||
W: ReactiveWorld;
|
||||
|
||||
/// Pops a subscriber from the front of the queue
|
||||
fn pop() -> Option<(Subscriber<W>, SubscriberInfo)>
|
||||
where
|
||||
W: ReactiveWorld;
|
||||
}
|
||||
|
||||
/// Inner item for the priority queue
|
||||
struct Item<W: ReactiveWorld> {
|
||||
/// Subscriber
|
||||
subscriber: Subscriber<W>,
|
||||
|
||||
/// Info
|
||||
info: SubscriberInfo,
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> PartialEq for Item<W> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.subscriber == other.subscriber
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Eq for Item<W> {}
|
||||
|
||||
impl<W: ReactiveWorld> Hash for Item<W> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.subscriber.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner type for the queue impl
|
||||
struct Inner<W: ReactiveWorld> {
|
||||
/// Queue
|
||||
// TODO: We don't need the priority, so just use some kind of
|
||||
// `HashQueue`.
|
||||
queue: PriorityQueue<Item<W>, Reverse<usize>>,
|
||||
|
||||
/// Next index
|
||||
next: usize,
|
||||
|
||||
/// Reference count
|
||||
ref_count: usize,
|
||||
|
||||
/// Whether currently executing the queue
|
||||
is_exec: bool,
|
||||
}
|
||||
|
||||
impl<W: ReactiveWorld> Inner<W> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
queue: PriorityQueue::new(),
|
||||
next: 0,
|
||||
ref_count: 0,
|
||||
is_exec: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run queue impl
|
||||
type RunQueueImpl<W> = IMut<Inner<W>, W>;
|
||||
|
||||
/// Thread-local run queue, using `StdRc` and `StdRefCell`
|
||||
pub struct RunQueueThreadLocal;
|
||||
|
||||
/// Run queue for `RunQueueThreadLocal`
|
||||
#[thread_local]
|
||||
static RUN_QUEUE_STD_RC: LazyCell<RunQueueImpl<WorldThreadLocal>> =
|
||||
LazyCell::new(|| RunQueueImpl::<WorldThreadLocal>::new(Inner::new()));
|
||||
|
||||
/// `RunQueueThreadLocal` execution guard
|
||||
pub struct RunQueueExecGuardThreadLocal;
|
||||
|
||||
impl Drop for RunQueueExecGuardThreadLocal {
|
||||
fn drop(&mut self) {
|
||||
let mut inner = RUN_QUEUE_STD_RC.write();
|
||||
inner.is_exec = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl RunQueue<WorldThreadLocal> for RunQueueThreadLocal {
|
||||
type ExecGuard = RunQueueExecGuardThreadLocal;
|
||||
|
||||
fn inc_ref() {
|
||||
let mut inner = RUN_QUEUE_STD_RC.write();
|
||||
inner.ref_count += 1;
|
||||
}
|
||||
|
||||
fn dec_ref() -> Option<Self::ExecGuard> {
|
||||
let mut inner = RUN_QUEUE_STD_RC.write();
|
||||
inner.ref_count = inner
|
||||
.ref_count
|
||||
.checked_sub(1)
|
||||
.expect("Attempted to decrease reference count beyond 0");
|
||||
|
||||
(inner.ref_count == 0 && !inner.queue.is_empty() && !inner.is_exec).then(|| {
|
||||
inner.is_exec = true;
|
||||
RunQueueExecGuardThreadLocal
|
||||
})
|
||||
}
|
||||
|
||||
fn push(subscriber: Subscriber<WorldThreadLocal>, info: SubscriberInfo) {
|
||||
let mut inner = RUN_QUEUE_STD_RC.write();
|
||||
|
||||
let next = Reverse(inner.next);
|
||||
inner.queue.push_decrease(Item { subscriber, info }, next);
|
||||
inner.next += 1;
|
||||
}
|
||||
|
||||
fn pop() -> Option<(Subscriber<WorldThreadLocal>, SubscriberInfo)>
|
||||
where
|
||||
WorldThreadLocal: ReactiveWorld,
|
||||
{
|
||||
let (item, _) = RUN_QUEUE_STD_RC.write().queue.pop()?;
|
||||
Some((item.subscriber, item.info))
|
||||
}
|
||||
}
|
||||
|
||||
/// Global run queue, using `StdArc` and `StdRefCell`
|
||||
pub struct RunQueueGlobal;
|
||||
|
||||
/// Run queue for `RunQueueGlobal`
|
||||
static RUN_QUEUE_STD_ARC: LazyLock<RunQueueImpl<WorldGlobal>> =
|
||||
LazyLock::new(|| RunQueueImpl::<WorldGlobal>::new(Inner::new()));
|
||||
|
||||
/// `RunQueueGlobal` execution guard
|
||||
pub struct RunQueueExecGuardGlobal;
|
||||
|
||||
impl Drop for RunQueueExecGuardGlobal {
|
||||
fn drop(&mut self) {
|
||||
let mut inner = RUN_QUEUE_STD_ARC.write();
|
||||
assert!(inner.is_exec, "Run queue stopped execution before guard was dropped");
|
||||
inner.is_exec = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl RunQueue<WorldGlobal> for RunQueueGlobal {
|
||||
type ExecGuard = RunQueueExecGuardGlobal;
|
||||
|
||||
fn inc_ref() {
|
||||
let mut inner = RUN_QUEUE_STD_ARC.write();
|
||||
inner.ref_count += 1;
|
||||
}
|
||||
|
||||
fn dec_ref() -> Option<Self::ExecGuard> {
|
||||
let mut inner = RUN_QUEUE_STD_ARC.write();
|
||||
inner.ref_count = inner
|
||||
.ref_count
|
||||
.checked_sub(1)
|
||||
.expect("Attempted to decrease reference count beyond 0");
|
||||
|
||||
(inner.ref_count == 0 && !inner.queue.is_empty() && !inner.is_exec).then(|| {
|
||||
inner.is_exec = true;
|
||||
RunQueueExecGuardGlobal
|
||||
})
|
||||
}
|
||||
|
||||
fn push(subscriber: Subscriber<WorldGlobal>, info: SubscriberInfo) {
|
||||
let mut inner = RUN_QUEUE_STD_ARC.write();
|
||||
|
||||
let next = Reverse(inner.next);
|
||||
inner.queue.push_decrease(Item { subscriber, info }, next);
|
||||
inner.next += 1;
|
||||
}
|
||||
|
||||
fn pop() -> Option<(Subscriber<WorldGlobal>, SubscriberInfo)>
|
||||
where
|
||||
WorldGlobal: ReactiveWorld,
|
||||
{
|
||||
let (item, _) = RUN_QUEUE_STD_ARC.write().queue.pop()?;
|
||||
Some((item.subscriber, item.info))
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "dynatos-world"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
derive_more = { workspace = true, features = ["full"] }
|
||||
parking_lot = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@ -1,190 +0,0 @@
|
||||
//! Inner-mutability types
|
||||
|
||||
// Imports
|
||||
use core::{
|
||||
cell::{self, RefCell},
|
||||
ops,
|
||||
};
|
||||
|
||||
/// Inner mutability family
|
||||
pub trait IMutFamily: Sized {
|
||||
/// Returns the inner mutability type of `T`
|
||||
type IMut<T: ?Sized>: ?Sized + IMutLike<T>;
|
||||
}
|
||||
|
||||
/// Inner mutability-like
|
||||
pub trait IMutLike<T: ?Sized> {
|
||||
/// Reference
|
||||
type Ref<'a>: IMutRefLike<'a, T, IMut = Self>
|
||||
where
|
||||
T: 'a,
|
||||
Self: 'a;
|
||||
|
||||
/// Mutable reference
|
||||
type RefMut<'a>: IMutRefMutLike<'a, T, IMut = Self>
|
||||
where
|
||||
T: 'a,
|
||||
Self: 'a;
|
||||
|
||||
/// Creates a new value
|
||||
fn new(value: T) -> Self
|
||||
where
|
||||
T: Sized;
|
||||
|
||||
/// Gets a read-lock to this value
|
||||
fn read(&self) -> Self::Ref<'_>;
|
||||
|
||||
/// Gets a write-lock to this value
|
||||
fn write(&self) -> Self::RefMut<'_>;
|
||||
|
||||
/// Tries to get a read-lock to this value
|
||||
fn try_read(&self) -> Option<Self::Ref<'_>>;
|
||||
|
||||
/// Tries to get a write-lock to this value
|
||||
fn try_write(&self) -> Option<Self::RefMut<'_>>;
|
||||
}
|
||||
|
||||
/// Inner mutability immutable reference like
|
||||
pub trait IMutRefLike<'a, T: ?Sized + 'a>: 'a + ops::Deref<Target = T> {
|
||||
/// The [`IMutLike`] of this type
|
||||
type IMut: ?Sized + IMutLike<T, Ref<'a> = Self>;
|
||||
}
|
||||
|
||||
/// Inner mutability mutable reference like
|
||||
pub trait IMutRefMutLike<'a, T: ?Sized + 'a>: 'a + ops::DerefMut<Target = T> {
|
||||
/// The [`IMutLike`] of this type
|
||||
type IMut: ?Sized + IMutLike<T, RefMut<'a> = Self>;
|
||||
|
||||
/// Downgrades this to a [`IMutRefLike`]
|
||||
fn downgrade(this: Self) -> <Self::IMut as IMutLike<T>>::Ref<'a>;
|
||||
}
|
||||
|
||||
/// Refcell family of inner-mutability
|
||||
pub struct StdRefcell;
|
||||
|
||||
impl IMutFamily for StdRefcell {
|
||||
type IMut<T: ?Sized> = RefCell<T>;
|
||||
}
|
||||
|
||||
impl<T: ?Sized> IMutLike<T> for RefCell<T> {
|
||||
type Ref<'a>
|
||||
= cell::Ref<'a, T>
|
||||
where
|
||||
Self: 'a;
|
||||
type RefMut<'a>
|
||||
= RefCellRefMut<'a, T>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn new(value: T) -> Self
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
Self::new(value)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn read(&self) -> Self::Ref<'_> {
|
||||
self.borrow()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn write(&self) -> Self::RefMut<'_> {
|
||||
RefCellRefMut {
|
||||
borrow: self.borrow_mut(),
|
||||
refcell: self,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_read(&self) -> Option<Self::Ref<'_>> {
|
||||
self.try_borrow().ok()
|
||||
}
|
||||
|
||||
fn try_write(&self) -> Option<Self::RefMut<'_>> {
|
||||
let borrow = self.try_borrow_mut().ok()?;
|
||||
Some(RefCellRefMut { borrow, refcell: self })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> IMutRefLike<'a, T> for cell::Ref<'a, T> {
|
||||
type IMut = RefCell<T>;
|
||||
}
|
||||
|
||||
/// Wrapper around `core::cell::RefMut`.
|
||||
#[derive(derive_more::Deref, derive_more::DerefMut, derive_more::Debug)]
|
||||
#[debug("{borrow:?}")]
|
||||
pub struct RefCellRefMut<'a, T: ?Sized> {
|
||||
/// Borrow
|
||||
#[deref(forward)]
|
||||
#[deref_mut]
|
||||
borrow: cell::RefMut<'a, T>,
|
||||
|
||||
/// Original refcell
|
||||
// Note: This field is necessary for downgrading.
|
||||
refcell: &'a RefCell<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> IMutRefMutLike<'a, T> for RefCellRefMut<'a, T> {
|
||||
type IMut = RefCell<T>;
|
||||
|
||||
fn downgrade(this: Self) -> <Self::IMut as IMutLike<T>>::Ref<'a> {
|
||||
// Note: RefCell is single threaded, so there are no races here
|
||||
drop(this.borrow);
|
||||
this.refcell.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
/// `parking_lot::RwLock` family of inner-mutability
|
||||
pub struct ParkingLotRwLock;
|
||||
|
||||
impl IMutFamily for ParkingLotRwLock {
|
||||
type IMut<T: ?Sized> = parking_lot::RwLock<T>;
|
||||
}
|
||||
|
||||
impl<T: ?Sized> IMutLike<T> for parking_lot::RwLock<T> {
|
||||
type Ref<'a>
|
||||
= parking_lot::RwLockReadGuard<'a, T>
|
||||
where
|
||||
Self: 'a;
|
||||
type RefMut<'a>
|
||||
= parking_lot::RwLockWriteGuard<'a, T>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn new(value: T) -> Self
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
Self::new(value)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn read(&self) -> Self::Ref<'_> {
|
||||
self.read()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn write(&self) -> Self::RefMut<'_> {
|
||||
self.write()
|
||||
}
|
||||
|
||||
fn try_read(&self) -> Option<Self::Ref<'_>> {
|
||||
self.try_read()
|
||||
}
|
||||
|
||||
fn try_write(&self) -> Option<Self::RefMut<'_>> {
|
||||
self.try_write()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> IMutRefLike<'a, T> for parking_lot::RwLockReadGuard<'a, T> {
|
||||
type IMut = parking_lot::RwLock<T>;
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> IMutRefMutLike<'a, T> for parking_lot::RwLockWriteGuard<'a, T> {
|
||||
type IMut = parking_lot::RwLock<T>;
|
||||
|
||||
fn downgrade(this: Self) -> <Self::IMut as IMutLike<T>>::Ref<'a> {
|
||||
Self::downgrade(this)
|
||||
}
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
//! `dynatos`'s world types.
|
||||
|
||||
// Features
|
||||
#![feature(
|
||||
unsize,
|
||||
coerce_unsized,
|
||||
unboxed_closures,
|
||||
fn_traits,
|
||||
test,
|
||||
thread_local,
|
||||
trait_alias,
|
||||
once_cell_try,
|
||||
async_fn_traits,
|
||||
local_waker
|
||||
)]
|
||||
// Lints
|
||||
#![expect(
|
||||
type_alias_bounds,
|
||||
reason = "Although they're not enforced currently, they will be in the future and we want to be explicit already"
|
||||
)]
|
||||
|
||||
// TODO: Get rid of all of the `*World` types strewn about. They only exist because we can't provide
|
||||
// the necessary bounds, such as `T: Unsize<U> => Rc<T>: CoerceUnsized<Rc<U>>`.
|
||||
|
||||
// Modules
|
||||
pub mod imut;
|
||||
pub mod rc;
|
||||
|
||||
// Exports
|
||||
pub use self::{
|
||||
imut::{IMutFamily, IMutLike, IMutRefLike, IMutRefMutLike, ParkingLotRwLock, StdRefcell},
|
||||
rc::{RcFamily, RcLike, StdArc, StdRc, WeakLike},
|
||||
};
|
||||
|
||||
/// World
|
||||
pub trait World: Sized + Clone + 'static {
|
||||
/// Reference-counted pointer family
|
||||
type Rc: RcFamily;
|
||||
|
||||
/// Inner mutability family
|
||||
type IMut: IMutFamily;
|
||||
}
|
||||
|
||||
/// Thread-local world
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct WorldThreadLocal;
|
||||
|
||||
impl World for WorldThreadLocal {
|
||||
type IMut = StdRefcell;
|
||||
type Rc = StdRc;
|
||||
}
|
||||
|
||||
/// Global world
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct WorldGlobal;
|
||||
|
||||
impl World for WorldGlobal {
|
||||
type IMut = ParkingLotRwLock;
|
||||
type Rc = StdArc;
|
||||
}
|
||||
|
||||
/// The `Rc` of the world `W`
|
||||
pub type Rc<T: ?Sized, W: World> = <W::Rc as RcFamily>::Rc<T>;
|
||||
|
||||
/// The `Weak` of the world `W`
|
||||
pub type Weak<T: ?Sized, W: World> = <W::Rc as RcFamily>::Weak<T>;
|
||||
|
||||
/// The `IMut` of the world `W`
|
||||
pub type IMut<T: ?Sized, W: World> = <W::IMut as IMutFamily>::IMut<T>;
|
||||
|
||||
/// The `IMutRef` of the world `W`
|
||||
pub type IMutRef<'a, T: ?Sized + 'a, W: World> = <IMut<T, W> as IMutLike<T>>::Ref<'a>;
|
||||
|
||||
/// The `IMutRefMut` of the world `W`
|
||||
pub type IMutRefMut<'a, T: ?Sized + 'a, W: World> = <IMut<T, W> as IMutLike<T>>::RefMut<'a>;
|
||||
|
||||
/// Default world
|
||||
pub type WorldDefault = WorldThreadLocal;
|
||||
@ -1,145 +0,0 @@
|
||||
//! Reference-counted pointer
|
||||
|
||||
// Imports
|
||||
use {
|
||||
core::ops,
|
||||
std::{rc, sync},
|
||||
};
|
||||
|
||||
/// Reference-counted pointer family
|
||||
pub trait RcFamily: Sized {
|
||||
/// Returns the reference counted type of `T`
|
||||
type Rc<T: ?Sized>: RcLike<T, Family = Self>;
|
||||
|
||||
/// Weak type
|
||||
type Weak<T: ?Sized>: WeakLike<T, Family = Self>;
|
||||
}
|
||||
|
||||
/// A reference-counted pointer
|
||||
pub trait RcLike<T: ?Sized>: ops::Deref<Target = T> + Clone {
|
||||
/// The family of this pointer
|
||||
type Family: RcFamily<Rc<T> = Self>;
|
||||
|
||||
/// Creates a new Rc from a value
|
||||
fn new(value: T) -> Self
|
||||
where
|
||||
T: Sized;
|
||||
|
||||
/// Downgrades this Rc to a Weak
|
||||
fn downgrade(this: &Self) -> <Self::Family as RcFamily>::Weak<T>;
|
||||
|
||||
/// Gets a pointer to the inner data of this Rc
|
||||
fn as_ptr(this: &Self) -> *const T;
|
||||
|
||||
/// Returns the strong count of this Rc
|
||||
fn strong_count(this: &Self) -> usize;
|
||||
|
||||
/// Returns the weak count of this Rc
|
||||
fn weak_count(this: &Self) -> usize;
|
||||
}
|
||||
|
||||
/// A Reference-counted weak pointer
|
||||
pub trait WeakLike<T: ?Sized>: Clone {
|
||||
/// The family of this pointer
|
||||
type Family: RcFamily<Weak<T> = Self>;
|
||||
|
||||
/// Upgrades this weak to an rc
|
||||
fn upgrade(&self) -> Option<<Self::Family as RcFamily>::Rc<T>>;
|
||||
|
||||
/// Gets a pointer to the inner data of this rc
|
||||
fn as_ptr(&self) -> *const T;
|
||||
}
|
||||
|
||||
/// Arc family of reference-counter pointers
|
||||
pub struct StdArc;
|
||||
|
||||
impl RcFamily for StdArc {
|
||||
type Rc<T: ?Sized> = sync::Arc<T>;
|
||||
type Weak<T: ?Sized> = sync::Weak<T>;
|
||||
}
|
||||
|
||||
impl<T: ?Sized> RcLike<T> for sync::Arc<T> {
|
||||
type Family = StdArc;
|
||||
|
||||
fn new(value: T) -> Self
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
Self::new(value)
|
||||
}
|
||||
|
||||
fn downgrade(this: &Self) -> <Self::Family as RcFamily>::Weak<T> {
|
||||
Self::downgrade(this)
|
||||
}
|
||||
|
||||
fn as_ptr(this: &Self) -> *const T {
|
||||
Self::as_ptr(this)
|
||||
}
|
||||
|
||||
fn strong_count(this: &Self) -> usize {
|
||||
Self::strong_count(this)
|
||||
}
|
||||
|
||||
fn weak_count(this: &Self) -> usize {
|
||||
Self::weak_count(this)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> WeakLike<T> for sync::Weak<T> {
|
||||
type Family = StdArc;
|
||||
|
||||
fn upgrade(&self) -> Option<<Self::Family as RcFamily>::Rc<T>> {
|
||||
self.upgrade()
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const T {
|
||||
self.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Rc family of reference-counter pointers
|
||||
pub struct StdRc;
|
||||
|
||||
impl RcFamily for StdRc {
|
||||
type Rc<T: ?Sized> = rc::Rc<T>;
|
||||
type Weak<T: ?Sized> = rc::Weak<T>;
|
||||
}
|
||||
|
||||
impl<T: ?Sized> RcLike<T> for rc::Rc<T> {
|
||||
type Family = StdRc;
|
||||
|
||||
fn new(value: T) -> Self
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
Self::new(value)
|
||||
}
|
||||
|
||||
fn downgrade(this: &Self) -> <Self::Family as RcFamily>::Weak<T> {
|
||||
Self::downgrade(this)
|
||||
}
|
||||
|
||||
fn as_ptr(this: &Self) -> *const T {
|
||||
Self::as_ptr(this)
|
||||
}
|
||||
|
||||
fn strong_count(this: &Self) -> usize {
|
||||
Self::strong_count(this)
|
||||
}
|
||||
|
||||
fn weak_count(this: &Self) -> usize {
|
||||
Self::weak_count(this)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> WeakLike<T> for rc::Weak<T> {
|
||||
type Family = StdRc;
|
||||
|
||||
fn upgrade(&self) -> Option<<Self::Family as RcFamily>::Rc<T>> {
|
||||
self.upgrade()
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const T {
|
||||
self.as_ptr()
|
||||
}
|
||||
}
|
||||
12
examples/Cargo.lock
generated
12
examples/Cargo.lock
generated
@ -194,9 +194,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dynatos-context"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dynatos-world",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dynatos-html"
|
||||
@ -263,7 +260,6 @@ dependencies = [
|
||||
"duplicate",
|
||||
"dynatos-context",
|
||||
"dynatos-util",
|
||||
"dynatos-world",
|
||||
"extend",
|
||||
"futures",
|
||||
"itertools",
|
||||
@ -307,14 +303,6 @@ dependencies = [
|
||||
"extend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dynatos-world"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.10.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user