Monado OpenXR Runtime
u_generic_callbacks.hpp
Go to the documentation of this file.
1// Copyright 2021-2023, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Implementation of a generic callback collection, intended to be wrapped for a specific event type.
6 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
7 * @ingroup aux_util
8 */
9
10#pragma once
11
12#include <vector>
13#include <algorithm>
14#include <type_traits>
15#include <cstdint>
16
17namespace xrt::auxiliary::util {
18template <typename CallbackType, typename EventType> struct GenericCallbacks;
19
20namespace detail {
21
22 /*!
23 * @brief Element type stored in @ref GenericCallbacks, for internal use only.
24 */
25 template <typename CallbackType, typename MaskType = std::uint32_t> struct GenericCallbackEntry
26 {
27 CallbackType callback;
28 MaskType event_mask;
29 void *userdata;
30 bool should_remove = false;
31
32 GenericCallbackEntry(CallbackType callback_, MaskType event_mask_, void *userdata_) noexcept
33 : callback(callback_), event_mask(event_mask_), userdata(userdata_)
34 {}
35
36 /*!
37 * Do the two entries match? Used for removal "by value"
38 */
39 bool
40 matches(GenericCallbackEntry const &other) const noexcept
41 {
42 return callback == other.callback && event_mask == other.event_mask &&
43 userdata == other.userdata;
44 }
45
46 bool
47 operator==(GenericCallbackEntry const &other) const noexcept
48 {
49 return matches(other);
50 }
51
52 bool
53 shouldInvoke(MaskType event) const noexcept
54 {
55 return (event_mask & event) != 0;
56 }
57 };
58
59 template <typename T> struct identity
60 {
61 using type = T;
62 };
63
64 // This lets us handle being passed an enum (which we can call underlying_type on) as well as an integer (which
65 // we cannot)
66 template <typename T>
67 using mask_from_enum_t =
68 typename std::conditional_t<std::is_enum<T>::value, std::underlying_type<T>, identity<T>>::type;
69
70} // namespace detail
71
72/*!
73 * @brief A generic collection of callbacks for event types represented as a bitmask, intended to be wrapped for each
74 * usage.
75 *
76 * A registered callback may identify one or more event types (bits in the bitmask) that it wants to be invoked for. A
77 * userdata void pointer is also stored for each callback. Bitmasks are tested at invocation time, and the general
78 * callback format allows for callbacks to indicate they should be removed from the collection. Actually calling each
79 * callback is left to a consumer-provided "invoker" to allow adding context and event data to the call. The "invoker"
80 * also allows the option of whether or how to expose the self-removal capability: yours might simply always return
81 * "false".
82 *
83 * This generic structure supports callbacks that are included multiple times in the collection, if the consuming code
84 * needs it. GenericCallbacks::contains may be used by consuming code before conditionally calling addCallback, to
85 * limit to a single instance in a collection.
86 *
87 * @tparam CallbackType the function pointer type to store for each callback.
88 * @tparam EventBitflagType the event enum type.
89 */
90template <typename CallbackType, typename EventBitflagType> struct GenericCallbacks
91{
92
93public:
94 static_assert(std::is_integral<EventBitflagType>::value || std::is_enum<EventBitflagType>::value,
95 "Your event type must either be an integer or an enum");
96 using callback_t = CallbackType;
97 using event_t = EventBitflagType;
98 using mask_t = detail::mask_from_enum_t<EventBitflagType>;
99
100private:
101 static_assert(std::is_integral<mask_t>::value, "Our enum to mask conversion should have produced an integer");
102
103 //! The type stored for each added callback.
105
106public:
107 /*!
108 * @brief Add a new callback entry with the given callback function pointer, event mask, and user data.
109 *
110 * New callback entries are always added at the end of the collection.
111 */
112 void
113 addCallback(CallbackType callback, mask_t event_mask, void *userdata)
114 {
115 callbacks.emplace_back(callback, event_mask, userdata);
116 }
117
118 /*!
119 * @brief Remove some number of callback entries matching the given callback function pointer, event mask, and
120 * user data.
121 *
122 * @param callback The callback function pointer. Tested for equality with each callback entry.
123 * @param event_mask The callback event mask. Tested for equality with each callback entry.
124 * @param userdata The opaque user data pointer. Tested for equality with each callback entry.
125 * @param num_skip The number of matches to skip before starting to remove callbacks. Defaults to 0.
126 * @param max_remove The number of matches to remove, or negative if no limit. Defaults to -1.
127 *
128 * @returns the number of callbacks removed.
129 */
130 int
132 CallbackType callback, mask_t event_mask, void *userdata, unsigned int num_skip = 0, int max_remove = -1)
133 {
134 if (max_remove == 0) {
135 // We were told to remove none. We can do this very quickly.
136 // Avoids a corner case in the loop where we assume max_remove is non-zero.
137 return 0;
138 }
139 bool found = false;
140
141 const callback_entry_t needle{callback, event_mask, userdata};
142 for (auto &entry : callbacks) {
143 if (entry.matches(needle)) {
144 if (num_skip > 0) {
145 // We are still in our skipping phase.
146 num_skip--;
147 continue;
148 }
149 entry.should_remove = true;
150 found = true;
151 // Negatives (no max) get more negative, which is OK.
152 max_remove--;
153 if (max_remove == 0) {
154 // not looking for more
155 break;
156 }
157 }
158 }
159 if (found) {
160 return purgeMarkedCallbacks();
161 }
162 // if we didn't find any, we removed zero.
163 return 0;
164 }
165
166 /*!
167 * @brief See if the collection contains at least one matching callback.
168 *
169 * @param callback The callback function pointer. Tested for equality with each callback entry.
170 * @param event_mask The callback event mask. Tested for equality with each callback entry.
171 * @param userdata The opaque user data pointer. Tested for equality with each callback entry.
172 *
173 * @returns true if a matching callback is found.
174 */
175 bool
176 contains(CallbackType callback, mask_t event_mask, void *userdata)
177 {
178 const callback_entry_t needle{callback, event_mask, userdata};
179 auto it = std::find(callbacks.begin(), callbacks.end(), needle);
180 return it != callbacks.end();
181 }
182
183 /*!
184 * @brief Invokes the callbacks, by passing the ones we should run to your "invoker" to add any desired
185 * context/event data and forward the call.
186 *
187 * Callbacks are called in order, filtering out those whose event mask does not include the given event.
188 *
189 * @param event The event type to invoke callbacks for.
190 * @param invoker A function/functor accepting the event, a callback function pointer, and the callback entry's
191 * userdata as parameters, and returning true if the callback should be removed from the collection. It is
192 * assumed that the invoker will add any additional context or event data and call the provided callback.
193 *
194 * Typically, a lambda with some captures and a single return statement will be sufficient for an invoker.
195 *
196 * @returns the number of callbacks run
197 */
198 template <typename F>
199 int
200 invokeCallbacks(EventBitflagType event, F &&invoker)
201 {
202 bool needPurge = false;
203
204 int ran = 0;
205 for (auto &entry : callbacks) {
206 if (entry.shouldInvoke(static_cast<mask_t>(event))) {
207 bool willRemove = invoker(event, entry.callback, entry.userdata);
208 if (willRemove) {
209 entry.should_remove = true;
210 needPurge = true;
211 }
212 ran++;
213 }
214 }
215 if (needPurge) {
216 purgeMarkedCallbacks();
217 }
218 return ran;
219 }
220
221private:
222 std::vector<callback_entry_t> callbacks;
223
224 int
225 purgeMarkedCallbacks()
226 {
227 auto b = callbacks.begin();
228 auto e = callbacks.end();
229 auto new_end = std::remove_if(b, e, [](callback_entry_t const &entry) { return entry.should_remove; });
230 auto num_removed = std::distance(new_end, e);
231 callbacks.erase(new_end, e);
232 return static_cast<int>(num_removed);
233 }
234};
235} // namespace xrt::auxiliary::util
A generic collection of callbacks for event types represented as a bitmask, intended to be wrapped fo...
Definition: u_generic_callbacks.hpp:91
void addCallback(CallbackType callback, mask_t event_mask, void *userdata)
Add a new callback entry with the given callback function pointer, event mask, and user data.
Definition: u_generic_callbacks.hpp:113
bool contains(CallbackType callback, mask_t event_mask, void *userdata)
See if the collection contains at least one matching callback.
Definition: u_generic_callbacks.hpp:176
int removeCallback(CallbackType callback, mask_t event_mask, void *userdata, unsigned int num_skip=0, int max_remove=-1)
Remove some number of callback entries matching the given callback function pointer,...
Definition: u_generic_callbacks.hpp:131
int invokeCallbacks(EventBitflagType event, F &&invoker)
Invokes the callbacks, by passing the ones we should run to your "invoker" to add any desired context...
Definition: u_generic_callbacks.hpp:200
Element type stored in GenericCallbacks, for internal use only.
Definition: u_generic_callbacks.hpp:26
bool matches(GenericCallbackEntry const &other) const noexcept
Do the two entries match? Used for removal "by value".
Definition: u_generic_callbacks.hpp:40
Definition: u_generic_callbacks.hpp:60