1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! A compact reentrant type-erased event-listener registry for `no_std` with `alloc`.
//!
//! A [`Registry<T>`](local::Registry) holds an intrusive list of variable-sized
//! listeners for events of type `T::Event`, linked by thin pointers. Each listener
//! is registered through a pinned `&Registry`, where it is moved into a single heap
//! allocation, put into the list, and referred to by a single-word handle.
//!
//! The handle is returned as a [`Guard`](local::Guard) by [`register`](local::Registry::register).
//! It is an RAII scope guard that unregisters its listener when dropped. The
//! cancellation is scan-free O(1) thanks to the intrusiveness. A guard may
//! safely outlive its registry, in which case dropping it does nothing.
//!
//! Events can be passed to listeners either by value with [`ByVal<T>`](ByVal) (cloned
//! per listener) or by reference with [`ByRef<T>`](ByRef). You can also implement the
//! [`EventFamily`] yourself for event types that borrow from the sender's stack.
//!
//! Some features of each registry can be selected at compile time via the [`Policy`](locked::Policy) trait.
//! The library also provides a `Future` adapter that yields the immediate next event
//! for use by async code, but will not provide a `Stream` adapter since there is no
//! queue or backpressure.
//!
//! # Reentrancy
//!
//! This implementation tolerates all kinds of recursive update scenarios.
//! The expected behaviors are listed below.
//!
//! - **Listener cancelling itself in its callback**\
//! The in-progress call will run to completion and will be destructed afterwards.
//! - **Listener cancelling other listeners**\
//! Cancelled listeners further down the queue will not receive the in-flight event.
//! - **Listener calling [`broadcast`](local::Registry::broadcast) again in its callback (recursive notification)**\
//! Callbacks for the new event will immediately run inside the nested `broadcast` call.
//! - **Registering new listeners inside a listener callback**\
//! With [`LIFO`] (default) ordering, the new listeners will not receive the in-flight event.
//! Otherwise ([`FIFO`]), they will.
//! - **Accessing the registry in listener's destructor**\
//! Listener's destructor may freely register, broadcast, or cancel any listener, including itself.
//!
//! # Cleaning up empty registry
//!
//! A [`last_listener_cancelled`](local::Policy::last_listener_cancelled) callback can be
//! provided to a registry: it will be called when cancelling the last listener leaves the
//! registry empty. It is meant as a clean-up hook: when a registry lives in a parent data
//! structure, the hook lets you remove the now-unused registry from that structure.
//! For the multithreaded variant, [`try_seal`](locked::Registry::try_seal) is provided to
//! atomically confirm it is still empty and disable further registration.
//!
//! # Caveats
//!
//! - This crate requires a nightly compiler (needed for accessing vtable pointers).
//! - Does not support panic-unwind. Although unwinding shouldn't cause UB, it will cause leaks and deadlocks.
use PhantomData;
use null;
extern crate alloc;
>);
>);
;
;
// Bits stored in `Node::state`.
const ALIVE: usize = 1;
const RECURSIVE_CANCEL: usize = 2;
const RECURSIVE_VISIT: usize = 4;
/// Single-threaded implementation.
/// Thread-safe implementation via a per-registry lock.
// Run tests using:
// - MIRIFLAGS=-Zmiri-many-seeds cargo miri test
// - MIRIFLAGS="-Zmiri-tree-borrows -Zmiri-many-seeds" cargo miri test