diff --git a/leptos_reactive/src/context.rs b/leptos_reactive/src/context.rs index 008ad4a31..3ad14ac91 100644 --- a/leptos_reactive/src/context.rs +++ b/leptos_reactive/src/context.rs @@ -3,8 +3,8 @@ use crate::{runtime::with_runtime, Scope}; use std::any::{Any, TypeId}; -/// Provides a context value of type `T` to the current reactive [Scope](crate::Scope) -/// and all of its descendants. This can be consumed using [use_context](crate::use_context). +/// Provides a context value of type `T` to the current reactive [`Scope`](crate::Scope) +/// and all of its descendants. This can be consumed using [`use_context`](crate::use_context). /// /// This is useful for passing values down to components or functions lower in a /// hierarchy without needs to “prop drill” by passing them through each layer as @@ -61,9 +61,9 @@ where } /// Extracts a context value of type `T` from the reactive system by traversing -/// it upwards, beginning from the current [Scope](crate::Scope) and iterating +/// it upwards, beginning from the current [`Scope`](crate::Scope) and iterating /// through its parents, if any. The context value should have been provided elsewhere -/// using [provide_context](crate::provide_context). +/// using [`provide_context`](crate::provide_context). /// /// This is useful for passing values down to components or functions lower in a /// hierarchy without needs to “prop drill” by passing them through each layer as diff --git a/leptos_reactive/src/diagnostics.rs b/leptos_reactive/src/diagnostics.rs index e2e0200be..c458ec7b0 100644 --- a/leptos_reactive/src/diagnostics.rs +++ b/leptos_reactive/src/diagnostics.rs @@ -1,5 +1,3 @@ -use cfg_if::cfg_if; - // The point of these diagnostics is to give useful error messages when someone // tries to access a reactive variable outside the reactive scope. They track when // you create a signal/memo, and where you access it non-reactively. @@ -23,7 +21,7 @@ pub(crate) struct AccessDiagnostics {} #[doc(hidden)] pub struct SpecialNonReactiveZone {} -cfg_if! { +cfg_if::cfg_if! { if #[cfg(debug_assertions)] { use std::cell::Cell; @@ -66,7 +64,7 @@ impl SpecialNonReactiveZone { #[macro_export] macro_rules! diagnostics { ($this:ident) => {{ - cfg_if! { + cfg_if::cfg_if! { if #[cfg(debug_assertions)] { AccessDiagnostics { defined_at: $this.defined_at, diff --git a/leptos_reactive/src/effect.rs b/leptos_reactive/src/effect.rs index e9aa92e2e..76c0e995d 100644 --- a/leptos_reactive/src/effect.rs +++ b/leptos_reactive/src/effect.rs @@ -11,14 +11,14 @@ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc}; /// Effects are intended to run *side-effects* of the system, not to synchronize state /// *within* the system. In other words: don't write to signals within effects. /// (If you need to define a signal that depends on the value of other signals, use a -/// derived signal or [create_memo](crate::create_memo)). +/// derived signal or [`create_memo`](crate::create_memo)). /// /// The effect function is called with an argument containing whatever value it returned /// the last time it ran. On the initial run, this is `None`. /// /// By default, effects **do not run on the server**. This means you can call browser-specific /// APIs within the effect function without causing issues. If you need an effect to run on -/// the server, use [create_isomorphic_effect]. +/// the server, use [`create_isomorphic_effect`]. /// ``` /// # use leptos_reactive::*; /// # use log::*; @@ -76,7 +76,7 @@ where } } -/// Creates an effect; unlike effects created by [create_effect], isomorphic effects will run on +/// Creates an effect; unlike effects created by [`create_effect`], isomorphic effects will run on /// the server as well as the client. /// ``` /// # use leptos_reactive::*; diff --git a/leptos_reactive/src/lib.rs b/leptos_reactive/src/lib.rs index 8d550c984..9692552aa 100644 --- a/leptos_reactive/src/lib.rs +++ b/leptos_reactive/src/lib.rs @@ -17,18 +17,19 @@ //! Here are the most commonly-used functions and types you'll need to build a reactive system: //! //! ### Signals -//! 1. *Signals:* [create_signal](crate::create_signal), which returns a ([ReadSignal](crate::ReadSignal), -//! [WriteSignal](crate::WriteSignal)) tuple, or [create_rw_signal](crate::create_rw_signal), which returns -//! a signal [RwSignal](crate::RwSignal) without this read-write segregation. +//! 1. *Signals:* [`create_signal`](crate::create_signal), which returns a ([`ReadSignal`](crate::ReadSignal), +//! [`WriteSignal`](crate::WriteSignal)) tuple, or [`create_rw_signal`](crate::create_rw_signal), which returns +//! a signal [`RwSignal`](crate::RwSignal) without this read-write segregation. //! 2. *Derived Signals:* any function that relies on another signal. -//! 3. *Memos:* [create_memo](crate::create_memo), which returns a [Memo](crate::Memo). -//! 4. *Resources:* [create_resource], which converts an `async` [std::future::Future] into a -//! synchronous [Resource](crate::Resource) signal. +//! 3. *Memos:* [`create_memo`], which returns a [`Memo`](crate::Memo). +//! 4. *Resources:* [`create_resource`], which converts an `async` [`Future`](std::future::Future) into a +//! synchronous [`Resource`](crate::Resource) signal. +//! 5. *Triggers:* [`create_trigger`], creates a purely reactive [`Trigger`] primitive without any associated state. //! //! ### Effects -//! 1. Use [create_effect](crate::create_effect) when you need to synchronize the reactive system +//! 1. Use [`create_effect`](crate::create_effect) when you need to synchronize the reactive system //! with something outside it (for example: logging to the console, writing to a file or local storage) -//! 2. The Leptos DOM renderer wraps any [Fn] in your template with [create_effect](crate::create_effect), so +//! 2. The Leptos DOM renderer wraps any [`Fn`] in your template with [`create_effect`](crate::create_effect), so //! components you write do *not* need explicit effects to synchronize with the DOM. //! //! ### Example @@ -90,6 +91,7 @@ mod spawn; mod spawn_microtask; mod stored_value; pub mod suspense; +mod trigger; pub use context::*; pub use diagnostics::SpecialNonReactiveZone; @@ -109,6 +111,7 @@ pub use spawn::*; pub use spawn_microtask::*; pub use stored_value::*; pub use suspense::SuspenseContext; +pub use trigger::*; mod macros { macro_rules! debug_warn { diff --git a/leptos_reactive/src/memo.rs b/leptos_reactive/src/memo.rs index 7241c7da1..26de5f64f 100644 --- a/leptos_reactive/src/memo.rs +++ b/leptos_reactive/src/memo.rs @@ -5,7 +5,6 @@ use crate::{ SignalDispose, SignalGet, SignalGetUntracked, SignalStream, SignalWith, SignalWithUntracked, }; -use cfg_if::cfg_if; use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc}; /// Creates an efficient derived reactive value based on other reactive values. @@ -21,7 +20,7 @@ use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc}; /// create a derived signal. But if the derivation calculation is expensive, you should /// create a memo. /// -/// As with [create_effect](crate::create_effect), the argument to the memo function is the previous value, +/// As with [`create_effect`](crate::create_effect), the argument to the memo function is the previous value, /// i.e., the current value of the memo, which will be `None` for the initial calculation. /// /// ``` @@ -99,7 +98,7 @@ where /// create a derived signal. But if the derivation calculation is expensive, you should /// create a memo. /// -/// As with [create_effect](crate::create_effect), the argument to the memo function is the previous value, +/// As with [`create_effect`](crate::create_effect), the argument to the memo function is the previous value, /// i.e., the current value of the memo, which will be `None` for the initial calculation. /// /// ## Core Trait Implementations diff --git a/leptos_reactive/src/node.rs b/leptos_reactive/src/node.rs index adfbf0975..101c70d1a 100644 --- a/leptos_reactive/src/node.rs +++ b/leptos_reactive/src/node.rs @@ -8,13 +8,22 @@ slotmap::new_key_type! { #[derive(Clone)] pub(crate) struct ReactiveNode { - pub value: Rc>, + pub value: Option>>, pub state: ReactiveNodeState, pub node_type: ReactiveNodeType, } +impl ReactiveNode { + pub fn value(&self) -> Rc> { + self.value + .clone() + .expect("ReactiveNode.value to have a value") + } +} + #[derive(Clone)] pub(crate) enum ReactiveNodeType { + Trigger, Signal, Memo { f: Rc }, Effect { f: Rc }, diff --git a/leptos_reactive/src/resource.rs b/leptos_reactive/src/resource.rs index 7acad0f2d..cf4dbd098 100644 --- a/leptos_reactive/src/resource.rs +++ b/leptos_reactive/src/resource.rs @@ -20,19 +20,19 @@ use std::{ rc::Rc, }; -/// Creates [Resource](crate::Resource), which is a signal that reflects the +/// Creates a [`Resource`](crate::Resource), which is a signal that reflects the /// current state of an asynchronous task, allowing you to integrate `async` -/// [Future]s into the synchronous reactive system. +/// [`Future`]s into the synchronous reactive system. /// -/// Takes a `fetcher` function that generates a [Future] when called and a +/// Takes a `fetcher` function that generates a [`Future`] when called and a /// `source` signal that provides the argument for the `fetcher`. Whenever the -/// value of the `source` changes, a new [Future] will be created and run. +/// value of the `source` changes, a new [`Future`] will be created and run. /// /// When server-side rendering is used, the server will handle running the -/// [Future] and will stream the result to the client. This process requires the -/// output type of the Future to be [Serializable]. If your output cannot be -/// serialized, or you just want to make sure the [Future] runs locally, use -/// [create_local_resource()]. +/// [`Future`] and will stream the result to the client. This process requires the +/// output type of the Future to be [`Serializable`]. If your output cannot be +/// serialized, or you just want to make sure the [`Future`] runs locally, use +/// [`create_local_resource()`]. /// /// ``` /// # use leptos_reactive::*; @@ -79,14 +79,14 @@ where create_resource_with_initial_value(cx, source, fetcher, initial_value) } -/// Creates a [Resource](crate::Resource) with the given initial value, which -/// will only generate and run a [Future] using the `fetcher` when the `source` changes. +/// Creates a [`Resource`](crate::Resource) with the given initial value, which +/// will only generate and run a [`Future`] using the `fetcher` when the `source` changes. /// /// When server-side rendering is used, the server will handle running the -/// [Future] and will stream the result to the client. This process requires the -/// output type of the Future to be [Serializable]. If your output cannot be -/// serialized, or you just want to make sure the [Future] runs locally, use -/// [create_local_resource_with_initial_value()]. +/// [`Future`] and will stream the result to the client. This process requires the +/// output type of the Future to be [`Serializable`]. If your output cannot be +/// serialized, or you just want to make sure the [`Future`] runs locally, use +/// [`create_local_resource_with_initial_value()`]. #[cfg_attr( debug_assertions, instrument( @@ -120,7 +120,7 @@ where ) } -/// Creates a “blocking” [Resource](crate::Resource). When server-side rendering is used, +/// Creates a “blocking” [`Resource`](crate::Resource). When server-side rendering is used, /// this resource will cause any `` you read it under to block the initial /// chunk of HTML from being sent to the client. This means that if you set things like /// HTTP headers or `` metadata in that ``, that header material will @@ -228,16 +228,16 @@ where } } -/// Creates a _local_ [Resource](crate::Resource), which is a signal that +/// Creates a _local_ [`Resource`](crate::Resource), which is a signal that /// reflects the current state of an asynchronous task, allowing you to -/// integrate `async` [Future]s into the synchronous reactive system. +/// integrate `async` [`Future`]s into the synchronous reactive system. /// -/// Takes a `fetcher` function that generates a [Future] when called and a +/// Takes a `fetcher` function that generates a [`Future`] when called and a /// `source` signal that provides the argument for the `fetcher`. Whenever the -/// value of the `source` changes, a new [Future] will be created and run. +/// value of the `source` changes, a new [`Future`] will be created and run. /// -/// Unlike [create_resource()], this [Future] is always run on the local system -/// and therefore it's result type does not need to be [Serializable]. +/// Unlike [`create_resource()`], this [`Future`] is always run on the local system +/// and therefore it's result type does not need to be [`Serializable`]. /// /// ``` /// # use leptos_reactive::*; @@ -273,13 +273,13 @@ where create_local_resource_with_initial_value(cx, source, fetcher, initial_value) } -/// Creates a _local_ [Resource](crate::Resource) with the given initial value, -/// which will only generate and run a [Future] using the `fetcher` when the +/// Creates a _local_ [`Resource`](crate::Resource) with the given initial value, +/// which will only generate and run a [`Future`] using the `fetcher` when the /// `source` changes. /// -/// Unlike [create_resource_with_initial_value()], this [Future] will always run +/// Unlike [`create_resource_with_initial_value()`], this [`Future`] will always run /// on the local system and therefore its output type does not need to be -/// [Serializable]. +/// [`Serializable`]. #[cfg_attr( debug_assertions, instrument( @@ -443,7 +443,7 @@ where /// resource is still pending). Also subscribes the running effect to this /// resource. /// - /// If you want to get the value without cloning it, use [Resource::with]. + /// If you want to get the value without cloning it, use [`Resource::with`]. /// (`value.read(cx)` is equivalent to `value.with(cx, T::clone)`.) #[track_caller] pub fn read(&self, cx: Scope) -> Option @@ -463,10 +463,10 @@ where /// Applies a function to the current value of the resource, and subscribes /// the running effect to this resource. If the resource hasn't yet /// resolved, the function won't be called and this will return - /// [Option::None]. + /// [`Option::None`]. /// /// If you want to get the value by cloning it, you can use - /// [Resource::read]. + /// [`Resource::read`]. #[track_caller] pub fn with(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option { let location = std::panic::Location::caller(); @@ -501,8 +501,8 @@ where }); } - /// Returns a [std::future::Future] that will resolve when the resource has loaded, - /// yield its [ResourceId] and a JSON string. + /// Returns a [`Future`] that will resolve when the resource has loaded, + /// yield its [`ResourceId`] and a JSON string. #[cfg(any(feature = "ssr", doc))] pub async fn to_serialization_resolver( &self, @@ -526,17 +526,17 @@ where /// A signal that reflects the /// current state of an asynchronous task, allowing you to integrate `async` -/// [Future]s into the synchronous reactive system. +/// [`Future`]s into the synchronous reactive system. /// -/// Takes a `fetcher` function that generates a [Future] when called and a +/// Takes a `fetcher` function that generates a [`Future`] when called and a /// `source` signal that provides the argument for the `fetcher`. Whenever the -/// value of the `source` changes, a new [Future] will be created and run. +/// value of the `source` changes, a new [`Future`] will be created and run. /// /// When server-side rendering is used, the server will handle running the -/// [Future] and will stream the result to the client. This process requires the -/// output type of the Future to be [Serializable]. If your output cannot be -/// serialized, or you just want to make sure the [Future] runs locally, use -/// [create_local_resource()]. +/// [`Future`] and will stream the result to the client. This process requires the +/// output type of the Future to be [`Serializable`]. If your output cannot be +/// serialized, or you just want to make sure the [`Future`] runs locally, use +/// [`create_local_resource()`]. /// /// ``` /// # use leptos_reactive::*; @@ -583,7 +583,7 @@ where // Resources slotmap::new_key_type! { - /// Unique ID assigned to a [Resource](crate::Resource). + /// Unique ID assigned to a [`Resource`](crate::Resource). pub struct ResourceId; } diff --git a/leptos_reactive/src/runtime.rs b/leptos_reactive/src/runtime.rs index 1f2aaf45c..458207cca 100644 --- a/leptos_reactive/src/runtime.rs +++ b/leptos_reactive/src/runtime.rs @@ -4,8 +4,8 @@ use crate::{ node::{NodeId, ReactiveNode, ReactiveNodeState, ReactiveNodeType}, AnyComputation, AnyResource, Effect, Memo, MemoState, ReadSignal, ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId, - ScopeProperty, SerializableResource, StoredValueId, UnserializableResource, - WriteSignal, + ScopeProperty, SerializableResource, StoredValueId, Trigger, + UnserializableResource, WriteSignal, }; use cfg_if::cfg_if; use core::hash::BuildHasherDefault; @@ -117,15 +117,16 @@ impl Runtime { // memos and effects rerun // signals simply have their value let changed = match node.node_type { - ReactiveNodeType::Signal => true, - ReactiveNodeType::Memo { f } - | ReactiveNodeType::Effect { f } => { + ReactiveNodeType::Signal | ReactiveNodeType::Trigger => true, + ReactiveNodeType::Memo { ref f } + | ReactiveNodeType::Effect { ref f } => { + let value = node.value(); // set this node as the observer self.with_observer(node_id, move || { // clean up sources of this memo/effect self.cleanup(node_id); - f.run(Rc::clone(&node.value)) + f.run(value) }) } }; @@ -191,12 +192,17 @@ impl Runtime { pub(crate) fn mark_dirty(&self, node: NodeId) { //crate::macros::debug_warn!("marking {node:?} dirty"); let mut nodes = self.nodes.borrow_mut(); - let mut pending_effects = self.pending_effects.borrow_mut(); - let subscribers = self.node_subscribers.borrow(); - let current_observer = self.observer.get(); - // mark self dirty if let Some(current_node) = nodes.get_mut(node) { + if current_node.state == ReactiveNodeState::DirtyMarked { + return; + } + + let mut pending_effects = self.pending_effects.borrow_mut(); + let subscribers = self.node_subscribers.borrow(); + let current_observer = self.observer.get(); + + // mark self dirty Runtime::mark( node, current_node, @@ -325,13 +331,7 @@ impl Runtime { } } - pub(crate) fn run_effects(runtime_id: RuntimeId) { - _ = with_runtime(runtime_id, |runtime| { - runtime.run_your_effects(); - }); - } - - pub(crate) fn run_your_effects(&self) { + pub(crate) fn run_effects(&self) { if !self.batching.get() { let effects = self.pending_effects.take(); for effect_id in effects { @@ -384,7 +384,7 @@ pub(crate) fn with_runtime( #[doc(hidden)] #[must_use = "Runtime will leak memory if Runtime::dispose() is never called."] -/// Creates a new reactive [Runtime]. This should almost always be handled by the framework. +/// Creates a new reactive [`Runtime`]. This should almost always be handled by the framework. pub fn create_runtime() -> RuntimeId { cfg_if! { if #[cfg(any(feature = "csr", feature = "hydrate"))] { @@ -397,17 +397,17 @@ pub fn create_runtime() -> RuntimeId { #[cfg(not(any(feature = "csr", feature = "hydrate")))] slotmap::new_key_type! { - /// Unique ID assigned to a [Runtime](crate::Runtime). + /// Unique ID assigned to a Runtime. pub struct RuntimeId; } -/// Unique ID assigned to a [Runtime](crate::Runtime). +/// Unique ID assigned to a Runtime. #[cfg(any(feature = "csr", feature = "hydrate"))] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct RuntimeId; impl RuntimeId { - /// Removes the runtime, disposing all its child [Scope](crate::Scope)s. + /// Removes the runtime, disposing all its child [`Scope`](crate::Scope)s. pub fn dispose(self) { cfg_if! { if #[cfg(not(any(feature = "csr", feature = "hydrate")))] { @@ -468,13 +468,35 @@ impl RuntimeId { ret } + #[track_caller] + #[inline(always)] // only because it's placed here to fit in with the other create methods + pub(crate) fn create_trigger(self) -> Trigger { + let id = with_runtime(self, |runtime| { + runtime.nodes.borrow_mut().insert(ReactiveNode { + value: None, + state: ReactiveNodeState::Clean, + node_type: ReactiveNodeType::Trigger, + }) + }) + .expect( + "tried to create a trigger in a runtime that has been disposed", + ); + + Trigger { + id, + runtime: self, + #[cfg(debug_assertions)] + defined_at: std::panic::Location::caller(), + } + } + pub(crate) fn create_concrete_signal( self, value: Rc>, ) -> NodeId { with_runtime(self, |runtime| { runtime.nodes.borrow_mut().insert(ReactiveNode { - value, + value: Some(value), state: ReactiveNodeState::Clean, node_type: ReactiveNodeType::Signal, }) @@ -539,7 +561,7 @@ impl RuntimeId { values .map(|value| { signals.insert(ReactiveNode { - value: Rc::new(RefCell::new(value)), + value: Some(Rc::new(RefCell::new(value))), state: ReactiveNodeState::Clean, node_type: ReactiveNodeType::Signal, }) @@ -598,7 +620,7 @@ impl RuntimeId { ) -> NodeId { with_runtime(self, |runtime| { let id = runtime.nodes.borrow_mut().insert(ReactiveNode { - value: Rc::clone(&value), + value: Some(Rc::clone(&value)), state: ReactiveNodeState::Clean, node_type: ReactiveNodeType::Effect { f: Rc::clone(&effect), @@ -625,7 +647,7 @@ impl RuntimeId { ) -> NodeId { with_runtime(self, |runtime| { runtime.nodes.borrow_mut().insert(ReactiveNode { - value, + value: Some(value), // memos are lazy, so are dirty when created // will be run the first time we ask for it state: ReactiveNodeState::Dirty, @@ -775,12 +797,13 @@ impl Runtime { f } + /// Do not call on triggers pub(crate) fn get_value( &self, node_id: NodeId, ) -> Option>> { let signals = self.nodes.borrow(); - signals.get(node_id).map(|node| Rc::clone(&node.value)) + signals.get(node_id).map(|node| node.value()) } } diff --git a/leptos_reactive/src/scope.rs b/leptos_reactive/src/scope.rs index 5967431be..eb75c2543 100644 --- a/leptos_reactive/src/scope.rs +++ b/leptos_reactive/src/scope.rs @@ -82,7 +82,7 @@ pub fn run_scope_undisposed( /// when it is removed from the list. /// /// Every other function in this crate takes a `Scope` as its first argument. Since `Scope` -/// is [Copy] and `'static` this does not add much overhead or lifetime complexity. +/// is [`Copy`] and `'static` this does not add much overhead or lifetime complexity. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Scope { #[doc(hidden)] @@ -226,7 +226,7 @@ impl Scope { /// /// This will /// 1. dispose of all child `Scope`s - /// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup). + /// 2. run all cleanup functions defined for this scope by [`on_cleanup`](crate::on_cleanup). /// 3. dispose of all signals, effects, and resources owned by this `Scope`. pub fn dispose(self) { _ = with_runtime(self.runtime, |runtime| { @@ -264,7 +264,8 @@ impl Scope { if let Some(owned) = owned { for property in owned { match property { - ScopeProperty::Signal(id) => { + ScopeProperty::Signal(id) + | ScopeProperty::Trigger(id) => { // remove the signal runtime.nodes.borrow_mut().remove(id); let subs = runtime @@ -339,7 +340,7 @@ fn push_cleanup(cx: Scope, cleanup_fn: Box) { }); } -/// Creates a cleanup function, which will be run when a [Scope] is disposed. +/// Creates a cleanup function, which will be run when a [`Scope`] is disposed. /// /// It runs after child scopes have been disposed, but before signals, effects, and resources /// are invalidated. @@ -349,34 +350,35 @@ pub fn on_cleanup(cx: Scope, cleanup_fn: impl FnOnce() + 'static) { } slotmap::new_key_type! { - /// Unique ID assigned to a [Scope](crate::Scope). + /// Unique ID assigned to a [`Scope`](crate::Scope). pub struct ScopeId; } #[derive(Debug)] pub(crate) enum ScopeProperty { + Trigger(NodeId), Signal(NodeId), Effect(NodeId), Resource(ResourceId), StoredValue(StoredValueId), } -/// Creating a [Scope](crate::Scope) gives you a disposer, which can be called +/// Creating a [`Scope`](crate::Scope) gives you a disposer, which can be called /// to dispose of that reactive scope. /// /// This will /// 1. dispose of all child `Scope`s -/// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup). +/// 2. run all cleanup functions defined for this scope by [`on_cleanup`](crate::on_cleanup). /// 3. dispose of all signals, effects, and resources owned by this `Scope`. #[repr(transparent)] pub struct ScopeDisposer(pub(crate) Scope); impl ScopeDisposer { - /// Disposes of a reactive [Scope](crate::Scope). + /// Disposes of a reactive [`Scope`](crate::Scope). /// /// This will /// 1. dispose of all child `Scope`s - /// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup). + /// 2. run all cleanup functions defined for this scope by [`on_cleanup`](crate::on_cleanup). /// 3. dispose of all signals, effects, and resources owned by this `Scope`. #[inline(always)] pub fn dispose(self) { @@ -385,20 +387,20 @@ impl ScopeDisposer { } impl Scope { - /// Returns IDs for all [Resource](crate::Resource)s found on any scope. + /// Returns IDs for all [`Resource`](crate::Resource)s found on any scope. pub fn all_resources(&self) -> Vec { with_runtime(self.runtime, |runtime| runtime.all_resources()) .unwrap_or_default() } - /// Returns IDs for all [Resource](crate::Resource)s found on any scope that are + /// Returns IDs for all [`Resource`](crate::Resource)s found on any scope that are /// pending from the server. pub fn pending_resources(&self) -> Vec { with_runtime(self.runtime, |runtime| runtime.pending_resources()) .unwrap_or_default() } - /// Returns IDs for all [Resource](crate::Resource)s found on any scope. + /// Returns IDs for all [`Resource`](crate::Resource)s found on any scope. pub fn serialization_resolvers( &self, ) -> FuturesUnordered> { @@ -408,7 +410,7 @@ impl Scope { .unwrap_or_default() } - /// Registers the given [SuspenseContext](crate::SuspenseContext) with the current scope, + /// Registers the given [`SuspenseContext`](crate::SuspenseContext) with the current scope, /// calling the `resolver` when its resources are all resolved. pub fn register_suspense( &self, @@ -522,7 +524,7 @@ impl Scope { runtime.batching.set(batching.1); std::mem::forget(batching); - runtime.run_your_effects(); + runtime.run_effects(); val }) .expect( diff --git a/leptos_reactive/src/selector.rs b/leptos_reactive/src/selector.rs index 144e26e91..4f819146a 100644 --- a/leptos_reactive/src/selector.rs +++ b/leptos_reactive/src/selector.rs @@ -9,7 +9,7 @@ use std::{ /// Creates a conditional signal that only notifies subscribers when a change /// in the source signal’s value changes whether it is equal to the key value -/// (as determined by [PartialEq].) +/// (as determined by [`PartialEq`].) /// /// **You probably don’t need this,** but it can be a very useful optimization /// in certain situations (e.g., “set the class `selected` if `selected() == this_row_index`) diff --git a/leptos_reactive/src/serialization.rs b/leptos_reactive/src/serialization.rs index d92800378..a0ebd4422 100644 --- a/leptos_reactive/src/serialization.rs +++ b/leptos_reactive/src/serialization.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use thiserror::Error; /// Describes errors that can occur while serializing and deserializing data, -/// typically during the process of streaming [Resource](crate::Resource)s from +/// typically during the process of streaming [`Resource`](crate::Resource)s from /// the server to the client. #[derive(Debug, Clone, Error)] pub enum SerializationError { @@ -19,7 +19,7 @@ pub enum SerializationError { /// Describes an object that can be serialized to or from a supported format /// Currently those are JSON and Cbor /// -/// This is primarily used for serializing and deserializing [Resource](crate::Resource)s +/// This is primarily used for serializing and deserializing [`Resource`](crate::Resource)s /// so they can begin on the server and be resolved on the client, but can be used /// for any data that needs to be serialized/deserialized. /// diff --git a/leptos_reactive/src/signal.rs b/leptos_reactive/src/signal.rs index f6d02f289..36ebf9061 100644 --- a/leptos_reactive/src/signal.rs +++ b/leptos_reactive/src/signal.rs @@ -8,7 +8,6 @@ use crate::{ runtime::{with_runtime, RuntimeId}, Runtime, Scope, ScopeProperty, }; -use cfg_if::cfg_if; use futures::Stream; use std::{ any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, pin::Pin, rc::Rc, @@ -110,7 +109,7 @@ pub trait SignalGet { /// the running effect to this signal. /// /// # Panics - /// Panics if you try to access a signal that was created in a [Scope] that has been disposed. + /// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed. #[track_caller] fn get(&self) -> T; @@ -126,7 +125,7 @@ pub trait SignalWith { /// the running effect to this signal. /// /// # Panics - /// Panics if you try to access a signal that was created in a [Scope] that has been disposed. + /// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed. #[track_caller] fn with(&self, f: impl FnOnce(&T) -> O) -> O; @@ -198,7 +197,7 @@ pub trait SignalGetUntracked { /// current scope. /// /// # Panics - /// Panics if you try to access a signal that was created in a [Scope] that has been disposed. + /// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed. #[track_caller] fn get_untracked(&self) -> T; @@ -215,7 +214,7 @@ pub trait SignalWithUntracked { /// value without creating a dependency on the current scope. /// /// # Panics - /// Panics if you try to access a signal that was created in a [Scope] that has been disposed. + /// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed. #[track_caller] fn with_untracked(&self, f: impl FnOnce(&T) -> O) -> O; @@ -276,7 +275,7 @@ pub trait SignalStream { /// whenever it changes. /// /// # Panics - /// Panics if you try to access a signal that was created in a [Scope] that has been disposed. + /// Panics if you try to access a signal that was created in a [`Scope`] that has been disposed. // We're returning an opaque type until impl trait in trait // positions are stabilized, and also so any underlying // changes are non-breaking @@ -284,7 +283,7 @@ pub trait SignalStream { fn to_stream(&self, cx: Scope) -> Pin>>; } -/// This trait allows disposing a signal before its [Scope] has been disposed. +/// This trait allows disposing a signal before its [`Scope`] has been disposed. pub trait SignalDispose { /// Disposes of the signal. This: /// 1. Detaches the signal from the reactive graph, preventing it from triggering @@ -300,8 +299,8 @@ pub trait SignalDispose { /// and notifies other code when it has changed. This is the /// core primitive of Leptos’s reactive system. /// -/// Takes a reactive [Scope] and the initial value as arguments, -/// and returns a tuple containing a [ReadSignal] and a [WriteSignal], +/// Takes a reactive [`Scope`] and the initial value as arguments, +/// and returns a tuple containing a [`ReadSignal`] and a [`WriteSignal`], /// each of which can be called as a function. /// /// ``` @@ -353,7 +352,7 @@ pub fn create_signal( s } -/// Works exactly as [create_signal], but creates multiple signals at once. +/// Works exactly as [`create_signal`], but creates multiple signals at once. #[cfg_attr( debug_assertions, instrument( @@ -373,7 +372,7 @@ pub fn create_many_signals( cx.runtime.create_many_signals_with_map(cx, values, |x| x) } -/// Works exactly as [create_many_signals], but applies the map function to each signal pair. +/// Works exactly as [`create_many_signals`], but applies the map function to each signal pair. #[cfg_attr( debug_assertions, instrument( @@ -398,7 +397,7 @@ where } /// Creates a signal that always contains the most recent value emitted by a -/// [Stream](futures::stream::Stream). +/// [`Stream`](futures::stream::Stream). /// If the stream has not yet emitted a value since the signal was created, the signal's /// value will be `None`. /// @@ -419,7 +418,7 @@ pub fn create_signal_from_stream( #[allow(unused_mut)] // allowed because needed for SSR mut stream: impl Stream + Unpin + 'static, ) -> ReadSignal> { - cfg_if! { + cfg_if::cfg_if! { if #[cfg(feature = "ssr")] { _ = stream; let (read, _) = create_signal(cx, None); @@ -445,7 +444,7 @@ pub fn create_signal_from_stream( /// and notifies other code when it has changed. This is the /// core primitive of Leptos’s reactive system. /// -/// `ReadSignal` is also [Copy] and `'static`, so it can very easily moved into closures +/// `ReadSignal` is also [`Copy`] and `'static`, so it can very easily moved into closures /// or copied structs. /// /// ## Core Trait Implementations @@ -638,7 +637,7 @@ impl SignalWith for ReadSignal { match with_runtime(self.runtime, |runtime| { self.id.try_with(runtime, f, diagnostics) }) - .expect("runtime to be alive ") + .expect("runtime to be alive") { Ok(o) => o, Err(_) => panic_getting_dead_signal( @@ -816,13 +815,13 @@ impl Copy for ReadSignal {} /// and notifies other code when it has changed. This is the /// core primitive of Leptos’s reactive system. /// -/// Calling [WriteSignal::update] will mutate the signal’s value in place, +/// Calling [`WriteSignal::update`] will mutate the signal’s value in place, /// and notify all subscribers that the signal’s value has changed. /// -/// `WriteSignal` implements [Fn], such that `set_value(new_value)` is equivalent to +/// `WriteSignal` implements [`Fn`], such that `set_value(new_value)` is equivalent to /// `set_value.update(|value| *value = new_value)`. /// -/// `WriteSignal` is [Copy] and `'static`, so it can very easily moved into closures +/// `WriteSignal` is [`Copy`] and `'static`, so it can very easily moved into closures /// or copied structs. /// /// ## Core Trait Implementations @@ -1131,7 +1130,7 @@ pub fn create_rw_signal(cx: Scope, value: T) -> RwSignal { } /// A signal that combines the getter and setter into one value, rather than -/// separating them into a [ReadSignal] and a [WriteSignal]. You may prefer this +/// separating them into a [`ReadSignal`] and a [`WriteSignal`]. You may prefer this /// its style, or it may be easier to pass around in a context or as a function argument. /// /// ## Core Trait Implementations @@ -1724,7 +1723,7 @@ impl RwSignal { /// Returns a write-only handle to the signal. /// /// Useful if you're trying to give write access to another component, or split an - /// `RwSignal` into a [ReadSignal] and a [WriteSignal]. + /// [`RwSignal`] into a [`ReadSignal`] and a [`WriteSignal`]. /// ``` /// # use leptos_reactive::*; /// # create_scope(create_runtime(), |cx| { @@ -1873,7 +1872,7 @@ impl NodeId { runtime.update_if_necessary(*self); let nodes = runtime.nodes.borrow(); let node = nodes.get(*self).ok_or(SignalError::Disposed)?; - Ok(Rc::clone(&node.value)) + Ok(node.value()) } #[track_caller] @@ -1997,13 +1996,14 @@ impl NodeId { None }; - // mark descendants dirty - runtime.mark_dirty(*self); - // notify subscribers - if updated.is_some() && !runtime.batching.get() { - Runtime::run_effects(runtime_id); - }; + if updated.is_some() { + // mark descendants dirty + runtime.mark_dirty(*self); + + runtime.run_effects(); + } + updated }) .unwrap_or_default() diff --git a/leptos_reactive/src/signal_wrappers_read.rs b/leptos_reactive/src/signal_wrappers_read.rs index af150500a..83281d30e 100644 --- a/leptos_reactive/src/signal_wrappers_read.rs +++ b/leptos_reactive/src/signal_wrappers_read.rs @@ -21,8 +21,8 @@ where } } -/// A wrapper for any kind of readable reactive signal: a [ReadSignal](crate::ReadSignal), -/// [Memo](crate::Memo), [RwSignal](crate::RwSignal), or derived signal closure. +/// A wrapper for any kind of readable reactive signal: a [`ReadSignal`](crate::ReadSignal), +/// [`Memo`](crate::Memo), [`RwSignal`](crate::RwSignal), or derived signal closure. /// /// This allows you to create APIs that take any kind of `Signal` as an argument, /// rather than adding a generic `F: Fn() -> T`. Values can be access with the same diff --git a/leptos_reactive/src/signal_wrappers_write.rs b/leptos_reactive/src/signal_wrappers_write.rs index f8f75caf1..5d670c2f3 100644 --- a/leptos_reactive/src/signal_wrappers_write.rs +++ b/leptos_reactive/src/signal_wrappers_write.rs @@ -18,8 +18,8 @@ where } } -/// A wrapper for any kind of settable reactive signal: a [WriteSignal](crate::WriteSignal), -/// [RwSignal](crate::RwSignal), or closure that receives a value and sets a signal depending +/// A wrapper for any kind of settable reactive signal: a [`WriteSignal`](crate::WriteSignal), +/// [`RwSignal`](crate::RwSignal), or closure that receives a value and sets a signal depending /// on it. /// /// This allows you to create APIs that take any kind of `SignalSetter` as an argument, diff --git a/leptos_reactive/src/slice.rs b/leptos_reactive/src/slice.rs index f7c29b646..03912f305 100644 --- a/leptos_reactive/src/slice.rs +++ b/leptos_reactive/src/slice.rs @@ -3,9 +3,9 @@ use crate::{ SignalUpdate, SignalWith, }; -/// Derives a reactive slice of an [RwSignal](crate::RwSignal). +/// Derives a reactive slice of an [`RwSignal`](crate::RwSignal). /// -/// Slices have the same guarantees as [Memos](crate::Memo): +/// Slices have the same guarantees as [`Memo`s](crate::Memo): /// they only emit their value when it has actually been changed. /// /// Slices need a getter and a setter, and you must make sure that diff --git a/leptos_reactive/src/spawn.rs b/leptos_reactive/src/spawn.rs index 23427b955..7d486f82a 100644 --- a/leptos_reactive/src/spawn.rs +++ b/leptos_reactive/src/spawn.rs @@ -2,7 +2,7 @@ use cfg_if::cfg_if; use std::future::Future; -/// Spawns and runs a thread-local [std::future::Future] in a platform-independent way. +/// Spawns and runs a thread-local [`Future`] in a platform-independent way. /// /// This can be used to interface with any `async` code. pub fn spawn_local(fut: F) diff --git a/leptos_reactive/src/spawn_microtask.rs b/leptos_reactive/src/spawn_microtask.rs index d5c80d71d..9730e7582 100644 --- a/leptos_reactive/src/spawn_microtask.rs +++ b/leptos_reactive/src/spawn_microtask.rs @@ -7,7 +7,7 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] { - /// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method + /// Exposes the [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method /// in the browser, and simply runs the given function when on the server. pub fn queue_microtask(task: impl FnOnce() + 'static) { microtask(wasm_bindgen::closure::Closure::once_into_js(task)); @@ -21,7 +21,7 @@ cfg_if! { fn microtask(task: wasm_bindgen::JsValue); } } else { - /// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method + /// Exposes the [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method /// in the browser, and simply runs the given function when on the server. pub fn queue_microtask(task: impl FnOnce() + 'static) { task(); diff --git a/leptos_reactive/src/stored_value.rs b/leptos_reactive/src/stored_value.rs index 3a890f6b3..1671a68c9 100644 --- a/leptos_reactive/src/stored_value.rs +++ b/leptos_reactive/src/stored_value.rs @@ -3,17 +3,17 @@ use crate::{with_runtime, RuntimeId, Scope, ScopeProperty}; use std::{cell::RefCell, marker::PhantomData, rc::Rc}; slotmap::new_key_type! { - /// Unique ID assigned to a [StoredValue]. + /// Unique ID assigned to a [`StoredValue`]. pub(crate) struct StoredValueId; } -/// A **non-reactive** wrapper for any value, which can be created with [store_value]. +/// A **non-reactive** wrapper for any value, which can be created with [`store_value`]. /// -/// If you want a reactive wrapper, use [create_signal](crate::create_signal). +/// If you want a reactive wrapper, use [`create_signal`](crate::create_signal). /// /// This allows you to create a stable reference for any value by storing it within -/// the reactive system. Like the signal types (e.g., [ReadSignal](crate::ReadSignal) -/// and [RwSignal](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal +/// the reactive system. Like the signal types (e.g., [`ReadSignal`](crate::ReadSignal) +/// and [`RwSignal`](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal /// types, it is not reactive; accessing it does not cause effects to subscribe, and /// updating it does not notify anything else. #[derive(Debug, PartialEq, Eq, Hash)] @@ -43,7 +43,7 @@ impl StoredValue { /// to this signal. /// /// # Panics - /// Panics if you try to access a value stored in a [Scope] that has been disposed. + /// Panics if you try to access a value stored in a [`Scope`] that has been disposed. /// /// # Examples /// ``` @@ -77,7 +77,7 @@ impl StoredValue { /// to this signal. /// /// # Panics - /// Panics if you try to access a value stored in a [Scope] that has been disposed. + /// Panics if you try to access a value stored in a [`Scope`] that has been disposed. /// /// # Examples /// ``` @@ -128,7 +128,7 @@ impl StoredValue { /// Applies a function to the current stored value. /// /// # Panics - /// Panics if you try to access a value stored in a [Scope] that has been disposed. + /// Panics if you try to access a value stored in a [`Scope`] that has been disposed. /// /// # Examples /// ``` @@ -155,7 +155,7 @@ impl StoredValue { /// Applies a function to the current stored value. /// /// # Panics - /// Panics if you try to access a value stored in a [Scope] that has been disposed. + /// Panics if you try to access a value stored in a [`Scope`] that has been disposed. /// /// # Examples /// ``` @@ -375,8 +375,8 @@ impl StoredValue { /// Creates a **non-reactive** wrapper for any value by storing it within /// the reactive system. /// -/// Like the signal types (e.g., [ReadSignal](crate::ReadSignal) -/// and [RwSignal](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal +/// Like the signal types (e.g., [`ReadSignal`](crate::ReadSignal) +/// and [`RwSignal`](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal /// types, it is not reactive; accessing it does not cause effects to subscribe, and /// updating it does not notify anything else. /// ```compile_fail diff --git a/leptos_reactive/src/suspense.rs b/leptos_reactive/src/suspense.rs index 97af49ccd..3879340b0 100644 --- a/leptos_reactive/src/suspense.rs +++ b/leptos_reactive/src/suspense.rs @@ -8,7 +8,7 @@ use crate::{ use futures::Future; use std::{borrow::Cow, collections::VecDeque, pin::Pin}; -/// Tracks [Resource](crate::Resource)s that are read under a suspense context, +/// Tracks [`Resource`](crate::Resource)s that are read under a suspense context, /// i.e., within a [`Suspense`](https://docs.rs/leptos_core/latest/leptos_core/fn.Suspense.html) component. #[derive(Copy, Clone, Debug)] pub struct SuspenseContext { diff --git a/leptos_reactive/src/trigger.rs b/leptos_reactive/src/trigger.rs new file mode 100644 index 000000000..458299761 --- /dev/null +++ b/leptos_reactive/src/trigger.rs @@ -0,0 +1,252 @@ +#![forbid(unsafe_code)] + +use crate::{ + diagnostics, + diagnostics::*, + node::NodeId, + runtime::{with_runtime, RuntimeId}, + Scope, ScopeProperty, SignalGet, SignalSet, SignalUpdate, +}; + +/// Reactive Trigger, notifies reactive code to rerun. +/// +/// See [`create_trigger`] for more. +#[derive(Clone, Copy)] +pub struct Trigger { + pub(crate) runtime: RuntimeId, + pub(crate) id: NodeId, + + #[cfg(debug_assertions)] + pub(crate) defined_at: &'static std::panic::Location<'static>, +} + +impl Trigger { + /// Notifies any reactive code where this trigger is tracked to rerun. + pub fn notify(&self) { + assert!(self.try_notify(), "Trigger::notify(): runtime not alive") + } + + /// Attempts to notify any reactive code where this trigger is tracked to rerun. + /// + /// Returns `None` if the runtime has been disposed. + pub fn try_notify(&self) -> bool { + with_runtime(self.runtime, |runtime| { + runtime.mark_dirty(self.id); + runtime.run_effects(); + }) + .is_ok() + } + + /// Subscribes the running effect to this trigger. + pub fn track(&self) { + assert!(self.try_track(), "Trigger::track(): runtime not alive") + } + + /// Attempts to subscribe the running effect to this trigger, returning + /// `None` if the runtime has been disposed. + pub fn try_track(&self) -> bool { + let diagnostics = diagnostics!(self); + + with_runtime(self.runtime, |runtime| { + self.id.subscribe(runtime, diagnostics); + }) + .is_ok() + } +} + +/// Creates a [`Trigger`], a kind of reactive primitive. +/// +/// A trigger is a data-less signal with the sole purpose +/// of notifying other reactive code of a change. This can be useful +/// for when using external data not stored in signals, for example. +/// +/// Take a reactive [`Scope`] and returns the [`Trigger`] handle, which +/// can be called as a function to track the trigger in the current +/// reactive context. +/// +/// ``` +/// # use leptos_reactive::*; +/// # create_scope(create_runtime(), |cx| { +/// use std::{cell::RefCell, fmt::Write, rc::Rc}; +/// +/// let external_data = Rc::new(RefCell::new(1)); +/// let output = Rc::new(RefCell::new(String::new())); +/// +/// let rerun_on_data = create_trigger(cx); +/// +/// let o = output.clone(); +/// let e = external_data.clone(); +/// create_effect(cx, move |_| { +/// rerun_on_data(); // or rerun_on_data.track(); +/// write!(o.borrow_mut(), "{}", *e.borrow()); +/// *e.borrow_mut() += 1; +/// }); +/// # if !cfg!(feature = "ssr") { +/// assert_eq!(*output.borrow(), "1"); +/// +/// rerun_on_data.notify(); // reruns the above effect +/// +/// assert_eq!(*output.borrow(), "12"); +/// # } +/// # }).dispose(); +/// ``` +#[cfg_attr( + debug_assertions, + instrument( + level = "trace", + skip_all, + fields(scope = ?cx.id) + ) +)] +#[track_caller] +pub fn create_trigger(cx: Scope) -> Trigger { + let t = cx.runtime.create_trigger(); + cx.push_scope_property(ScopeProperty::Trigger(t.id)); + t +} + +impl SignalGet<()> for Trigger { + #[cfg_attr( + debug_assertions, + instrument( + level = "trace", + name = "Trigger::get()", + skip_all, + fields( + id = ?self.id, + defined_at = %self.defined_at + ) + ) + )] + #[track_caller] + #[inline(always)] + fn get(&self) { + self.track() + } + + #[cfg_attr( + debug_assertions, + instrument( + level = "trace", + name = "Trigger::try_get()", + skip_all, + fields( + id = ?self.id, + defined_at = %self.defined_at + ) + ) + )] + #[inline(always)] + fn try_get(&self) -> Option<()> { + self.try_track().then_some(()) + } +} + +impl SignalUpdate<()> for Trigger { + #[cfg_attr( + debug_assertions, + instrument( + name = "Trigger::update()", + level = "trace", + skip_all, + fields( + id = ?self.id, + defined_at = %self.defined_at + ) + ) + )] + #[inline(always)] + fn update(&self, f: impl FnOnce(&mut ())) { + self.try_update(f).expect("runtime to be alive") + } + + #[cfg_attr( + debug_assertions, + instrument( + name = "Trigger::try_update()", + level = "trace", + skip_all, + fields( + id = ?self.id, + defined_at = %self.defined_at + ) + ) + )] + #[inline(always)] + fn try_update(&self, f: impl FnOnce(&mut ()) -> O) -> Option { + // run callback with runtime before dirtying the trigger, + // consistent with signals. + with_runtime(self.runtime, |runtime| { + let res = f(&mut ()); + + runtime.mark_dirty(self.id); + runtime.run_effects(); + + Some(res) + }) + .ok() + .flatten() + } +} + +impl SignalSet<()> for Trigger { + #[cfg_attr( + debug_assertions, + instrument( + level = "trace", + name = "Trigger::set()", + skip_all, + fields( + id = ?self.id, + defined_at = %self.defined_at + ) + ) + )] + #[inline(always)] + fn set(&self, _: ()) { + self.notify(); + } + + #[cfg_attr( + debug_assertions, + instrument( + level = "trace", + name = "Trigger::try_set()", + skip_all, + fields( + id = ?self.id, + defined_at = %self.defined_at + ) + ) + )] + #[inline(always)] + fn try_set(&self, _: ()) -> Option<()> { + self.try_notify().then_some(()) + } +} + +#[cfg(not(feature = "stable"))] +impl FnOnce<()> for Trigger { + type Output = (); + + #[inline(always)] + extern "rust-call" fn call_once(self, _args: ()) -> Self::Output { + self.track() + } +} + +#[cfg(not(feature = "stable"))] +impl FnMut<()> for Trigger { + #[inline(always)] + extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output { + self.track() + } +} + +#[cfg(not(feature = "stable"))] +impl Fn<()> for Trigger { + #[inline(always)] + extern "rust-call" fn call(&self, _args: ()) -> Self::Output { + self.track() + } +}