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
144
145
146
147
148
149
150
151
//! Resting place for the [MetaPublisher] trait.
use std::num::NonZeroU32;
/// API for producing elements to a [meta_queue] or [meta_topic].\
/// Two zero-copy patterns are available:
/// 1) `publish()` is the simplest & safest: a callback closure is provided to fill in the data;
/// 2) `leak_slot()` / `publish_leaked()` offers a more flexible (but more dangerous) option,
/// allowing the allocated slot to participate in more complex logics.
pub trait MetaPublisher<'a, SlotType: 'a> {
/// Passes a slot reference to `setter`, so to fill in the information for the event to be published,
/// to be later retrieved with [MoveSubscriber<>], in a way that the compiler won't escape zero-copying it
/// (even in debug mode).\
/// The returned values serves two purposes:
/// - 1st: If `None`, the container was full and no publishing was done; otherwise, the number of
/// elements present just after publishing `item` is returned -- which would be, at a minimum, 1
/// - 2nd: If the 1st returned value is `None`, this value will be `Some`, giving back to the caller the
/// `setter()` function (FnOnce) for a possible zero-copy reattempt.
///
/// IMPLEMENTORS: #[inline(always)]
fn publish<F: FnOnce(&mut SlotType)>
(&self, setter: F) -> (Option<NonZeroU32>, Option<F>);
/// Store the `item`, to be later retrieved with [MoveSubscriber<>], in a way that the compiler might
/// move the data (copy & forget) rather than zero-copying it -- which may be useful for data that
/// requires a custom dropping function.\
/// The returned values serves two purposes:
/// - 1st: If `None`, the container was full and no publishing was done; otherwise, the number of
/// elements present just after publishing `item` is returned -- which would be, at a minimum, 1
/// - 2nd: If the 1st returned value is `None`, this value will be `Some`, giving back to the caller the
/// `item` for a possible reattempt without incurring in further copying.
///
/// IMPLEMENTORS: #[inline(always)]
fn publish_movable(&self, item: SlotType) -> (Option<NonZeroU32>, Option<SlotType>);
/// Advanced method to publish an element: allocates a slot from the pool, returning a reference to it.\
/// Once called, either [publish_leaked()] or [unleak_slot()] should also be, eventually, called
/// -- or else the slot will never be returned to the pool for reuse.
///
/// IMPLEMENTORS: #[inline(always)]
fn leak_slot(&self) -> Option<(/*ref:*/ &mut SlotType, /*id: */u32)>;
/// Advanced method to publish a slot (previously allocated with [leak_slot()]) and populated by the caller.\
/// The slot will proceed to be handed over to consumers (which should return it to the pool afterwards).\
/// A return of `None` means the container was full and no publishing was done; otherwise, the number of
/// elements present just after publishing `item` is returned -- which would be, at a minimum, 1.\
/// NOTE: unless you're using a shared allocator or an allocator with less slots than this container,
/// `None` won't ever be returned here.
///
/// IMPLEMENTORS: #[inline(always)]
fn publish_leaked_ref(&'a self, slot: &'a SlotType) -> Option<NonZeroU32>;
/// The same as [publish_leaked_ref()], but slightly more efficient
///
/// IMPLEMENTORS: #[inline(always)]
fn publish_leaked_id(&'a self, slot_id: u32) -> Option<NonZeroU32>;
/// Advanced method to return a slot (obtained by [allocate_slot()]) to the pool, so it may be reused,
/// in case the publishing of the item should be aborted.
///
/// IMPLEMENTORS: #[inline(always)]
fn unleak_slot_ref(&'a self, slot: &'a mut SlotType);
/// The same as [unleak_slot_ref()], but slightly more efficient
///
/// IMPLEMENTORS: #[inline(always)]
fn unleak_slot_id(&'a self, slot_id: u32);
/// Possibly returns the number of published (but not-yet-collected) elements by this `meta_publisher`, at the moment of the call -- not synchronized.\
/// Some implementations do "collect" enqueued elements once they are dequeued (for instance, a `ring-buffer` queue), while others (like an unbounded `meta_topic`)
/// never collect them -- in practice, allowing new subscribers to access all elements ever produced.
///
/// IMPLEMENTORS: #[inline(always)]
fn available_elements_count(&self) -> usize;
/// Returns the maximum number of elements this `meta_publisher` can hold -- \
/// * `=0`, if the implementor offers an unbounded container,\
/// * `>0`, if the implementer uses a ring-buffer of that size.
///
/// IMPLEMENTORS: #[inline(always)]
fn max_size(&self) -> usize;
/// Returns a string that might be useful to debug or assert algorithms when writing automated tests
fn debug_info(&self) -> String;
}
/// API for producing elements to a [meta_queue] or [meta_topic] that accepts the possibility that they may be moved,
/// if the compiler is unable to optimize the data transfer to a zero-copy -- typically, publishing will always be
/// zero-copy (provided you're compiling in Release mode), but subscription will always be move.
pub trait MovePublisher<SlotType> {
/// Store the `item`, to be later retrieved with [MoveSubscriber<>], in a way that the compiler might
/// move the data (copy & forget) rather than zero-copying it.\
/// The returned values serves two purposes:
/// - 1st: If `None`, the container was full and no publishing was done; otherwise, the number of
/// elements present just after publishing `item` is returned -- which would be, at a minimum, 1
/// - 2nd: If the 1st returned value is `None`, this value will be `Some`, giving back to the caller the
/// `item` for a possible reattempt without incurring in further copying.
///
/// IMPLEMENTORS: #[inline(always)]
fn publish_movable(&self, item: SlotType) -> (Option<NonZeroU32>, Option<SlotType>);
/// Store an item, to be later retrieved with [MoveSubscriber<>], in a way that
/// no copying nor moving around would ever be made:
/// - After securing a slot for the new element, `setter_fn(&mut slot)` is called to fill it;
/// - If the queue is found to be full, `report_full_fn()` is called. Through this function, specializations may turn implementing structures into a blocking queue and/or compute metrics.
/// The returned boolean indicates if the publisher should try securing a slot again -- `false` meaning "don't retry: nothing could be done to free one up";
/// - `report_len_after_enqueueing_fn(len)` is called after the enqueueing is done and receives the number of not-yet-collected elements present -- a similar info as [available_elements()].
/// Specializers might use this to awake a number of consumers.
///
/// Returns:
/// - `None` if the enqueueing was done;
/// - `Some(setter_fn)` otherwise -- possibly due to a bounded queue being full -- giving the caller the opportunity of retrying (maybe after spin-looping a bit).
/// Keep in mind that blocking-queues are likely never to return false (unless some sort of enqueueing timeout happens).
///
/// Caveats:
/// 1) Slots are reused, so the `setter_fn()` must care to set all fields. No `default()` or any kind of zeroing will be applied to them prior to that function call.
/// As a downside, the `new T { fields }` cannot be used here. If that is needed, please use [publish_movable()]
/// 2) `setter_fn()` should complete instantly, or else the whole queue is likely to hang. If building a `SlotType` is lengthy, one might consider creating it before
/// calling this method and using the `setter_fn()` to simply clone/copy the value.
///
/// IMPLEMENTORS: #[inline(always)]
fn publish<SetterFn: FnOnce(&mut SlotType),
ReportFullFn: Fn() -> bool,
ReportLenAfterEnqueueingFn: FnOnce(u32)>
(&self,
setter_fn: SetterFn,
report_full_fn: ReportFullFn,
report_len_after_enqueueing_fn: ReportLenAfterEnqueueingFn)
-> Option<SetterFn>;
/// Possibly returns the number of published (but not-yet-collected) elements by this `meta_publisher`, at the moment of the call -- not synchronized.\
/// Some implementations do "collect" enqueued elements once they are dequeued (for instance, a `ring-buffer` queue), while others (like an unbounded `meta_topic`)
/// never collect them -- in practice, allowing new subscribers to access all elements ever produced.
///
/// IMPLEMENTORS: #[inline(always)]
fn available_elements_count(&self) -> usize;
/// Returns the maximum number of elements this `meta_publisher` can hold: \
/// * `=0` if the implementor offers an unbounded container,
/// * `>0`, if the implementer uses a ring-buffer of that size.
///
/// IMPLEMENTORS: #[inline(always)]
fn max_size(&self) -> usize;
/// Returns a string that might be useful to debug or assert algorithms when writing automated tests
fn debug_info(&self) -> String;
}