Removed "worlds".

The default world (thread-local) is now the only implementation.
This commit is contained in:
Filipe Rodrigues 2025-06-12 17:21:02 +01:00
parent 992ce507ba
commit 720c229569
Signed by: zenithsiz
SSH Key Fingerprint: SHA256:Mb5ppb3Sh7IarBO/sBTXLHbYEOz37hJAlslLQPPAPaU
30 changed files with 792 additions and 1984 deletions

12
Cargo.lock generated
View File

@ -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"

View File

@ -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" }

View File

@ -5,7 +5,5 @@ edition = "2021"
[dependencies]
dynatos-world = { workspace = true }
[lints]
workspace = true

View 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()
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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
}

View File

@ -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),

View File

@ -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 }

View File

@ -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 {

View File

@ -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();
}
}

View File

@ -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 || {

View 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()
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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,
};

View File

@ -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,

View File

@ -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

View 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))
}

View File

@ -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()
}

View File

@ -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;
};

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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))
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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;

View File

@ -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
View File

@ -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"