isoprenoid_unsend/runtime.rs
1//! Low-level types for implementing [`SignalsRuntimeRef`], as well as [`LocalSignalsRuntime`].
2//!
3//! # Features
4//!
5//! Enable the `local_signals_runtime` Cargo feature for [`LocalSignalsRuntime`] to implement [`SignalsRuntimeRef`].
6
7use core::{self};
8use std::{
9 self,
10 fmt::{self, Debug, Formatter},
11 future::Future,
12 marker::PhantomData,
13 mem,
14 num::NonZeroU64,
15};
16
17/// Embedded in signals to refer to a specific signals runtime.
18///
19/// The signals runtime determines when its associated signals are refreshed in response to dependency changes.
20///
21/// [`LocalSignalsRuntime`] provides a usable default.
22///
23/// # Logic
24/// Callback invocations associated with the same `id` **must** be totally orderable.
25/// (Note: Identical `id`s confined to distinct threads are considered distinct.)
26///
27/// # Safety
28///
29/// Callbacks associated with the same `id` **may** be nested in some cases.
30///
31/// Please see the 'Safety' sections on this trait's associated items for additional rules.
32///
33/// ## Definition
34///
35/// An `id` is considered 'started' exactly between the start of each associated call to
36/// [`start`](`SignalsRuntimeRef::start`) and a runtime-specific point during the following
37/// associated [`stop`](`SignalsRuntimeRef::stop`) call after which no associated callbacks
38/// may still be executed by the runtime.
39///
40/// '`id`'s context' is the execution context of any methods on this trait where the same `id`
41/// is used as parameter and additionally that of callbacks associated with `id`.
42pub unsafe trait SignalsRuntimeRef: Clone {
43 /// The signal instance key used by this [`SignalsRuntimeRef`].
44 ///
45 /// Used to manage dependencies and callbacks.
46 type Symbol: Clone + Copy;
47
48 /// Types used in callback signatures.
49 type CallbackTableTypes: ?Sized + CallbackTableTypes;
50
51 /// Creates a fresh unique [`SignalsRuntimeRef::Symbol`] for this instance.
52 ///
53 /// Symbols are usually not interchangeable between different instances of a runtime!
54 /// Runtimes **should** detect and panic on misuse when debug-assertions are enabled.
55 ///
56 /// # Safety
57 ///
58 /// The return value **must** be able to uniquely identify a signal towards this runtime.
59 ///
60 /// Symbols **may** be reused by signals even after [`stop`](`SignalsRuntimeRef::stop`) and
61 /// as such **must not** be reallocated by a given runtime.
62 fn next_id(&self) -> Self::Symbol;
63
64 /// When run in a context that records dependencies, records `id` as dependency of that context.
65 ///
66 /// # Logic
67 ///
68 /// If a call to [`record_dependency`](`SignalsRuntimeRef::record_dependency`) causes a subscription
69 /// change, the runtime **should** call that [`CallbackTable::on_subscribed_change`] callback before
70 /// returning from this function. (This helps to manage on-demand-only resources more efficiently.)
71 ///
72 /// This method **must** function even for an otherwise unknown `id` as long as it was allocated by [`next_id`](`SignalsRuntimeRef::next_id`).
73 fn record_dependency(&self, id: Self::Symbol);
74
75 /// Starts managed callback processing for `id`.
76 ///
77 /// # Logic
78 ///
79 /// Dependencies that are [recorded](`SignalsRuntimeRef::record_dependency`) within
80 /// `init` and [`CallbackTable::update`] on the same thread **must** be recorded
81 /// as and update the dependency set of `id`, respectively.
82 ///
83 /// The [`CallbackTable::on_subscribed_change`] callback **must** run detached from
84 /// outer dependency recording.
85 ///
86 /// # Safety
87 ///
88 /// Before this method returns, `init` **must** have been called synchronously.
89 ///
90 /// Only after `init` completes, the runtime **may** run the functions specified in `callback_table` with
91 /// `callback_data` any number of times and in any order, but only one at a time and only before the next
92 /// [`.stop(id)`](`SignalsRuntimeRef::stop`) call on `self` with an identical `id` completes.
93 ///
94 /// # Panics
95 ///
96 /// This method **may** panic if called when `id` is already started.
97 ///
98 /// # See also
99 ///
100 /// [`SignalsRuntimeRef::stop`], [`SignalsRuntimeRef::purge`]
101 unsafe fn start<T, D: ?Sized>(
102 &self,
103 id: Self::Symbol,
104 init: impl FnOnce() -> T,
105 callback_table: *const CallbackTable<D, Self::CallbackTableTypes>,
106 callback_data: *const D,
107 ) -> T;
108
109 /// Removes callbacks associated with `id`.
110 ///
111 /// # Logic
112 ///
113 /// This method **should not** remove interdependencies,
114 /// just clear the callback information and pending updates for `id`.
115 ///
116 /// The runtime **should** remove callbacks *before* cancelling pending updates.
117 ///
118 /// Calls to [`stop`](`SignalsRuntimeRef::stop`) made while `id` is not started
119 /// **must** return normally and **should not** have observable effects outside diagnostics.
120 ///
121 /// # Safety
122 ///
123 /// After this method returns normally, previously-scheduled callbacks for `id` **must not** run.
124 ///
125 /// Iff this method instead panics, then `id` **must** still be considered started
126 /// and `callback_data` **may** still be accessed.
127 ///
128 /// # Panics
129 ///
130 /// This method **should** panic if called in `id`'s context.
131 /// (The call **may** instead deadlock.)
132 ///
133 /// # See also
134 ///
135 /// [`SignalsRuntimeRef::purge`]
136 fn stop(&self, id: Self::Symbol);
137
138 /// Executes `f` while recording dependencies for `id`,
139 /// updating the recorded dependencies for `id` to the new set.
140 ///
141 /// This process **may** cause subscription notification callbacks to be called.
142 /// Those callbacks **may or may not** happen before this method returns.
143 ///
144 /// # Logic
145 ///
146 /// Whenever calling this method causes removed dependencies to decome unsubscribed,
147 /// their [`CallbackTable::on_subscribed_change`] callback **should** be invoked semantically
148 /// *after* they have been removed as dependency of the signal identified by `id`.
149 /// (This avoids unnecessary invalidation of the latter.)
150 ///
151 /// # Panics
152 ///
153 /// This function **may** panic iff `id` is not started.
154 ///
155 /// # See also
156 ///
157 /// [`SignalsRuntimeRef::purge`]
158 fn update_dependency_set<T>(&self, id: Self::Symbol, f: impl FnOnce() -> T) -> T;
159
160 /// Increases the intrinsic subscription count of `id`.
161 ///
162 /// An intrinsic subscription is one that is active regardless of dependents.
163 ///
164 /// # Logic
165 ///
166 /// This function **must** be callable at any time with any valid `id`.
167 ///
168 /// # See also
169 ///
170 /// [`SignalsRuntimeRef::purge`]
171 fn subscribe(&self, id: Self::Symbol);
172
173 /// Decreases the intrinsic subscription count of `id`.
174 ///
175 /// An intrinsic subscription is one that is active regardless of dependents.
176 ///
177 /// # Logic
178 ///
179 /// If the [`CallbackTable::on_subscribed_change`] returns [`Propagation::FlushOut`],
180 /// that **should** still cause refreshes of the unsubscribing dependencies (except
181 /// for dependencies that have in fact been removed). This ensures that e.g. reference-
182 /// counted resources can be freed appropriately. Such refreshes **may** be deferred.
183 ///
184 /// This function **must** be callable at any time with any valid `id`.
185 ///
186 /// # Panics
187 ///
188 /// This function **should** panic iff the intrinsic subscription count falls below zero.
189 ///
190 /// # Logic
191 ///
192 /// However, the runtime **may** (but **should not**) avoid tracking this separately
193 /// and instead exhibit unexpected behaviour iff there wasn't an at-least-equal number
194 /// of [`subscribe`](`SignalsRuntimeRef::subscribe`) calls with the same `id`.
195 ///
196 /// Attempting to decrease the net number of intrinsic subscriptions below zero
197 /// **may** cause unexpected behaviour (but not undefined behaviour).
198 ///
199 /// Note that [`purge`](`SignalsRuntimeRef::purge`) is expected to reset the net subscription count to zero.
200 ///
201 /// # See also
202 ///
203 /// [`SignalsRuntimeRef::purge`]
204 fn unsubscribe(&self, id: Self::Symbol);
205
206 /// Submits `f` to run exclusively for `id` *without* recording dependencies.
207 ///
208 /// The runtime **should** run `f` eventually, but **may** cancel it in response to
209 /// a [`.stop(id)`](`SignalsRuntimeRef::stop`) call with the same `id`.
210 ///
211 /// # Panics
212 ///
213 /// This function **may** panic unless called between [`.start`](`SignalsRuntimeRef::start`) and [`.stop`](`SignalsRuntimeRef::stop`) for `id`.
214 ///
215 /// # Safety
216 ///
217 /// `f` **must** be dropped or consumed before the next matching [`stop`](`SignalsRuntimeRef::stop`) call returns.
218 fn update_or_enqueue(&self, id: Self::Symbol, f: impl 'static + FnOnce() -> Propagation);
219
220 /// **Immediately** submits `f` to run exclusively for `id` *without* recording dependencies.
221 ///
222 /// Dropping the resulting [`Future`] cancels the scheduled update iff possible.
223 ///
224 /// # Logic
225 ///
226 /// The runtime **should** run `f` eventually, but **may** instead cancel and return it inside
227 /// [`Err`] in response to a [`stop`](`SignalsRuntimeRef::stop`) call with the same `id`.
228 ///
229 /// This method **must not** block indefinitely *as long as `f` doesn't*, regardless of context.
230 /// Calling [`stop`](`SignalsRuntimeRef::stop`) with matching `id` **should** cancel the update and return the [`Err`] variant.
231 ///
232 /// # Safety
233 ///
234 /// `f` **must not** run or be dropped after the next matching [`stop`](`SignalsRuntimeRef::stop`) call returns.
235 /// `f` **must not** run or be dropped after the [`Future`] returned by this function is dropped.
236 fn update_eager<'f, T: 'f, F: 'f + FnOnce() -> (Propagation, T)>(
237 &self,
238 id: Self::Symbol,
239 f: F,
240 ) -> Self::UpdateEager<'f, T, F>;
241
242 /// The type of the [`Future`] returned by [`update_eager`](`SignalsRuntimeRef::update_eager`).
243 ///
244 /// Dropping this [`Future`] **should** cancel the scheduled update if possible.
245 type UpdateEager<'f, T: 'f, F: 'f>: 'f + Future<Output = Result<T, F>>;
246
247 /// Runs `f` exclusively for `id` *without* recording dependencies.
248 ///
249 /// # Panics
250 ///
251 /// This function **should** panic when called in any other exclusivity context.
252 /// (Runtimes **may** limit situations where this can occur in their documentation.)
253 ///
254 /// # Safety
255 ///
256 /// `f` **must** be consumed before this method returns.
257 fn update_blocking<T>(&self, id: Self::Symbol, f: impl FnOnce() -> (Propagation, T)) -> T;
258
259 /// Runs `f` exempted from any outer dependency recordings.
260 ///
261 /// # Safety
262 ///
263 /// `f` **must** be consumed before this method returns.
264 fn run_detached<T>(&self, f: impl FnOnce() -> T) -> T;
265
266 /// # Safety
267 ///
268 /// Iff `id` is stale, its staleness **must** be cleared by running its
269 /// [`update`][`CallbackTable::update`] callback before this method returns.
270 fn refresh(&self, id: Self::Symbol);
271
272 /// Removes existing callbacks, dependency relations (in either direction) associated with `id`.
273 ///
274 /// Ones that are scheduled as a result of this are not necessarily removed!
275 ///
276 /// # Logic
277 ///
278 /// The runtime **should** remove callbacks *after* processing dependency changes.
279 /// The runtime **should** remove callbacks *before* cancelling pending updates.
280 ///
281 /// This method **should** be called last when ceasing use of a particular `id`.
282 /// The runtime **may** indefinitely hold onto resources associated with `id` if this
283 /// method isn't called.
284 ///
285 /// The runtime **must** process resulting subscription changes appropriately. This
286 /// includes notifying `id` of the subscription change from its intrinsic
287 /// subscriptions being removed, where applicable.
288 /// The runtime **must not** indefinitely hold onto resources associated with `id`
289 /// after this method returns.
290 ///
291 /// The caller **may** reuse `id` later on as if fresh.
292 ///
293 /// # Safety
294 ///
295 /// [`purge`](`SignalsRuntimeRef::purge`) implies [`stop`](`SignalsRuntimeRef::stop`).
296 fn purge(&self, id: Self::Symbol);
297
298 /// Hints to the signals runtime that contained operations (usually: on the current thread)
299 /// are related and that update propagation is likely to benefit from batching/deduplication.
300 ///
301 /// Note that the runtime **may** ignore this completely.
302 ///
303 /// # Logic
304 ///
305 /// This function **may** act as "exclusivity context" for nested calls to [`update_blocking`](`SignalsRuntimeRef::update_blocking`),
306 /// causing them to deadlock or panic.
307 #[inline(always)]
308 fn hint_batched_updates<T>(&self, f: impl FnOnce() -> T) -> T {
309 f()
310 }
311}
312
313#[cfg(feature = "local_signals_runtime")]
314mod a_signals_runtime;
315
316#[cfg(feature = "local_signals_runtime")]
317thread_local! {
318 static ISOPRENOID_GLOBAL_SIGNALS_RUNTIME: a_signals_runtime::ASignalsRuntime = a_signals_runtime::ASignalsRuntime::new();
319}
320
321/// `!Send` and `!Sync`!
322#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
323pub(crate) struct ASymbol(pub(crate) NonZeroU64, PhantomData<*mut ()>);
324
325pub(crate) enum ACallbackTableTypes {}
326
327impl CallbackTableTypes for ACallbackTableTypes {
328 type SubscribedStatus = bool;
329}
330
331/// A plain [`SignalsRuntimeRef`] implementation that represents a static signals runtime.
332///
333/// 🚧 This implementation is currently not optimised. 🚧
334///
335/// # Features
336///
337/// Enable the `local_signals_runtime` Cargo feature to implement [`SignalsRuntimeRef`] for this type.
338///
339/// # Logic
340///
341/// This runtime is guaranteed to have settled whenever the last thread-local borrow of it ceases,
342/// but only regarding effects originating on the current thread.
343///
344/// (This means that in addition to transiently borrowing calls, returned [`Future`]s
345/// **may** cause the [`LocalSignalsRuntime`] not to settle until they are dropped.)
346///
347/// Otherwise, it makes no additional guarantees over those specified in [`SignalsRuntimeRef`]'s documentation.
348///
349/// # Panics
350///
351/// [`SignalsRuntimeRef::Symbol`]s associated with the [`LocalSignalsRuntime`] are ordered.
352/// Given [`LSRSymbol`]s `a` and `b`, `b` can depend on `a` only iff `a` < `b` (by creation order).
353#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
354pub struct LocalSignalsRuntime;
355
356impl Debug for LocalSignalsRuntime {
357 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
358 if cfg!(feature = "local_signals_runtime") {
359 #[cfg(feature = "local_signals_runtime")]
360 Debug::fmt(&ISOPRENOID_GLOBAL_SIGNALS_RUNTIME, f)?;
361 Ok(())
362 } else {
363 struct Unavailable;
364 impl Debug for Unavailable {
365 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
366 write!(
367 f,
368 "(unavailable without `isoprenoid/local_signals_runtime` feature)"
369 )
370 }
371 }
372
373 f.debug_struct("LocalSignalsRuntime")
374 .field("state", &Unavailable)
375 .finish_non_exhaustive()
376 }
377 }
378}
379
380/// A [`SignalsRuntimeRef::Symbol`] associated with the [`LocalSignalsRuntime`].
381///
382/// Given [`LSRSymbol`]s `a` and `b`, `b` can depend on `a` only iff `a` < `b` (by creation order).
383#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
384pub struct LSRSymbol(pub(crate) ASymbol);
385
386impl Debug for LSRSymbol {
387 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
388 f.debug_tuple("LSRSymbol").field(&self.0 .0).finish()
389 }
390}
391
392mod global_callback_table_types {
393 use super::ACallbackTableTypes;
394
395 #[allow(unreachable_pub)]
396 #[repr(transparent)]
397 pub struct GlobalCallbackTableTypes(ACallbackTableTypes);
398}
399use global_callback_table_types::GlobalCallbackTableTypes;
400
401impl CallbackTableTypes for GlobalCallbackTableTypes {
402 //SAFETY: Everything here must be the same as for `ACallbackTableTypes`!
403 type SubscribedStatus = bool;
404}
405
406#[cfg(feature = "local_signals_runtime")]
407/// **The feature `"local_signals_runtime"` is required to enable this implementation.**
408unsafe impl SignalsRuntimeRef for LocalSignalsRuntime {
409 type Symbol = LSRSymbol;
410 type CallbackTableTypes = GlobalCallbackTableTypes;
411
412 fn next_id(&self) -> LSRSymbol {
413 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| LSRSymbol((&gsr).next_id()))
414 }
415
416 fn record_dependency(&self, id: Self::Symbol) {
417 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).record_dependency(id.0))
418 }
419
420 unsafe fn start<T, D: ?Sized>(
421 &self,
422 id: Self::Symbol,
423 f: impl FnOnce() -> T,
424 callback_table: *const CallbackTable<D, Self::CallbackTableTypes>,
425 callback_data: *const D,
426 ) -> T {
427 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| {
428 (&gsr).start(
429 id.0,
430 f,
431 //SAFETY: `GlobalCallbackTableTypes` is deeply transmute-compatible and ABI-compatible to `ACallbackTableTypes`.
432 mem::transmute::<
433 *const CallbackTable<D, GlobalCallbackTableTypes>,
434 *const CallbackTable<D, ACallbackTableTypes>,
435 >(callback_table),
436 callback_data,
437 )
438 })
439 }
440
441 fn stop(&self, id: Self::Symbol) {
442 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).stop(id.0))
443 }
444
445 fn update_dependency_set<T>(&self, id: Self::Symbol, f: impl FnOnce() -> T) -> T {
446 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).update_dependency_set(id.0, f))
447 }
448
449 fn subscribe(&self, id: Self::Symbol) {
450 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).subscribe(id.0))
451 }
452
453 fn unsubscribe(&self, id: Self::Symbol) {
454 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).unsubscribe(id.0))
455 }
456
457 fn update_or_enqueue(&self, id: Self::Symbol, f: impl 'static + FnOnce() -> Propagation) {
458 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).update_or_enqueue(id.0, f))
459 }
460
461 fn update_eager<'f, T: 'f, F: 'f + FnOnce() -> (Propagation, T)>(
462 &self,
463 id: Self::Symbol,
464 f: F,
465 ) -> Self::UpdateEager<'f, T, F> {
466 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).update_eager(id.0, f))
467 }
468
469 type UpdateEager<'f, T: 'f, F: 'f> = private::DetachedFuture<'f, Result<T, F>>;
470
471 fn update_blocking<T>(&self, id: Self::Symbol, f: impl FnOnce() -> (Propagation, T)) -> T {
472 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).update_blocking(id.0, f))
473 }
474
475 fn run_detached<T>(&self, f: impl FnOnce() -> T) -> T {
476 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).run_detached(f))
477 }
478
479 fn refresh(&self, id: Self::Symbol) {
480 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).refresh(id.0))
481 }
482
483 fn purge(&self, id: Self::Symbol) {
484 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).purge(id.0))
485 }
486
487 fn hint_batched_updates<T>(&self, f: impl FnOnce() -> T) -> T {
488 ISOPRENOID_GLOBAL_SIGNALS_RUNTIME.with(|gsr| (&gsr).hint_batched_updates(f))
489 }
490}
491
492/// The `unsafe` at-runtime version of [`Callbacks`](`crate::raw::Callbacks`),
493/// mainly for use between [`RawSignal`](`crate::raw::RawSignal`) and [`SignalsRuntimeRef`].
494///
495/// # Safety
496///
497/// The function pointers in this type may only be used as documented on [`SignalsRuntimeRef`].
498#[repr(C)]
499#[non_exhaustive]
500pub struct CallbackTable<T: ?Sized, CTT: ?Sized + CallbackTableTypes> {
501 /// A callback used to refresh stale signals.
502 ///
503 /// Signals that are not currently subscribed **should** *outside of explicit flushing* **not** be refreshed *by the runtime*.
504 /// Signals **should** return only fresh values.
505 /// Signals **may** remain stale indefinitely.
506 /// Signals **may** be destroyed while stale.
507 ///
508 /// # Logic
509 ///
510 /// The runtime **must** record dependencies for this callback and update them afterwards.
511 pub update: Option<unsafe fn(*const T) -> Propagation>,
512
513 /// A callback used to notify a signal of a change in its subscribed-state.
514 ///
515 /// This is separate from the automatic refresh applied to stale signals that become subscribed to.
516 ///
517 /// # Logic
518 ///
519 /// The runtime **must** consider transitive subscriptions.
520 /// The runtime **must** consider a signal's own intrinsic subscriptions.
521 /// The runtime **must not** run this function while recording dependencies (but may start a nested recording in response to the callback).
522 pub on_subscribed_change:
523 Option<unsafe fn(*const T, status: CTT::SubscribedStatus) -> Propagation>,
524}
525
526impl<T: ?Sized, CTT: ?Sized + CallbackTableTypes> Debug for CallbackTable<T, CTT> {
527 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
528 f.debug_struct("CallbackTable")
529 .field("update", &self.update)
530 .field("on_subscribed_change", &self.on_subscribed_change)
531 .finish()
532 }
533}
534
535impl<T: ?Sized, CTT: ?Sized + CallbackTableTypes> Clone for CallbackTable<T, CTT> {
536 fn clone(&self) -> Self {
537 Self {
538 update: self.update.clone(),
539 on_subscribed_change: self.on_subscribed_change.clone(),
540 }
541 }
542}
543
544impl<T: ?Sized, CTT: ?Sized + CallbackTableTypes> PartialEq for CallbackTable<T, CTT> {
545 #[allow(unpredictable_function_pointer_comparisons)] // Used only for interning.
546 fn eq(&self, other: &Self) -> bool {
547 self.update == other.update && self.on_subscribed_change == other.on_subscribed_change
548 }
549}
550
551impl<T: ?Sized, CTT: ?Sized + CallbackTableTypes> Eq for CallbackTable<T, CTT> {}
552
553impl<T: ?Sized, CTT: ?Sized + CallbackTableTypes> PartialOrd for CallbackTable<T, CTT> {
554 #[allow(unpredictable_function_pointer_comparisons)] // Used only for interning.
555 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
556 match self.update.partial_cmp(&other.update) {
557 Some(core::cmp::Ordering::Equal) => {}
558 ord => return ord,
559 }
560 self.on_subscribed_change
561 .partial_cmp(&other.on_subscribed_change)
562 }
563}
564
565impl<T: ?Sized, CTT: ?Sized + CallbackTableTypes> Ord for CallbackTable<T, CTT> {
566 #[allow(unpredictable_function_pointer_comparisons)] // Used only for interning.
567 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
568 match self.update.cmp(&other.update) {
569 core::cmp::Ordering::Equal => {}
570 ord => return ord,
571 }
572 self.on_subscribed_change.cmp(&other.on_subscribed_change)
573 }
574}
575
576/// Describes types appearing in callback signatures for a particular [`SignalsRuntimeRef`] implementation.
577pub trait CallbackTableTypes: 'static {
578 /// A status indicating "how subscribed" a signal now is.
579 ///
580 /// [`LocalSignalsRuntime`] notifies only for the first and removal of the last subscription for each signal,
581 /// so it uses a [`bool`], but other runtimes may notify with the direct or total subscriber count or a more complex measure.
582 type SubscribedStatus;
583}
584
585impl<T: ?Sized, CTT: ?Sized + CallbackTableTypes> CallbackTable<T, CTT> {
586 /// "Type-erases" the pointed-to callback table against the data type `T` by replacing it with `()` in the signature.
587 ///
588 /// Note that the callback functions still may only be called using the originally correct data pointer(s).
589 pub fn into_erased_ptr(this: *const Self) -> *const CallbackTable<(), CTT> {
590 this.cast()
591 }
592
593 /// "Type-erases" the pointed-to callback table against the data type `T` by replacing it with `()` in the signature.
594 ///
595 /// Note that the callback functions still may only be called using the originally correct data pointer(s).
596 pub fn into_erased(self) -> CallbackTable<(), CTT> {
597 unsafe { mem::transmute(self) }
598 }
599}
600
601/// A return value used by [`CallbackTable`]/[`Callbacks`](`crate::raw::Callbacks`) callbacks
602/// to indicate whether to flag dependent signals as stale and optionally also refresh ones not currently subscribed.
603#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
604#[must_use = "The runtime should propagate notifications to dependents only when requested."]
605pub enum Propagation {
606 /// Mark at least directly dependent signals as stale.
607 /// The runtime decides whether and when to refresh them.
608 Propagate,
609 /// Do not mark dependent signals as stale because of this [`Propagation`].
610 Halt,
611 /// Asks the runtime to refresh dependencies, even those that are not subscribed.
612 ///
613 /// This **should** be transitive through [`Propagate`](`Propagation::Propagate`) of dependents,
614 /// but **should not** be transitive through [`Halt`](`Propagation::Halt`).
615 ///
616 /// > **Hint**
617 /// >
618 /// > Use this variant to purge heavy or reference-counted resources store in dependent signals.
619 FlushOut,
620}
621
622mod private {
623 use std::{
624 future::Future,
625 pin::Pin,
626 task::{Context, Poll},
627 };
628
629 use futures_lite::FutureExt;
630
631 #[allow(unreachable_pub)] // Used with "local_signals_runtime".
632 pub struct DetachedFuture<'f, Output: 'f>(
633 pub(super) Pin<Box<dyn 'f + Future<Output = Output>>>,
634 );
635
636 impl<'f, Output: 'f> Future for DetachedFuture<'f, Output> {
637 type Output = Output;
638
639 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
640 self.0.poll(cx)
641 }
642 }
643}