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}