···11+// Copyright 2021, Collabora, Ltd.
22+// SPDX-License-Identifier: BSL-1.0
33+/*!
44+ * @file
55+ * @brief Implementation of a generic callback collection, intended to be wrapped for a specific event type.
66+ * @author Ryan Pavlik <ryan.pavlik@collabora.com>
77+ * @ingroup aux_util
88+ */
99+1010+#include <vector>
1111+#include <algorithm>
1212+1313+namespace xrt::auxiliary::util {
1414+template <typename CallbackType, typename EventType> struct GenericCallbacks;
1515+1616+namespace detail {
1717+1818+ /*!
1919+ * @brief Element type stored in @ref GenericCallbacks, for internal use only.
2020+ */
2121+ template <typename CallbackType, typename MaskType = uint32_t> struct GenericCallbackEntry
2222+ {
2323+ CallbackType callback;
2424+ MaskType event_mask;
2525+ void *userdata;
2626+ bool should_remove = false;
2727+2828+ GenericCallbackEntry(CallbackType callback_, MaskType event_mask_, void *userdata_) noexcept
2929+ : callback(callback_), event_mask(event_mask_), userdata(userdata_)
3030+ {}
3131+3232+ /*!
3333+ * Do the two entries match? Used for removal "by value"
3434+ */
3535+ bool
3636+ matches(GenericCallbackEntry const &other) const noexcept
3737+ {
3838+ return callback == other.callback && event_mask == other.event_mask &&
3939+ userdata == other.userdata;
4040+ }
4141+4242+ bool
4343+ operator==(GenericCallbackEntry const &other) const noexcept
4444+ {
4545+ return matches(other);
4646+ }
4747+4848+ bool
4949+ shouldInvoke(MaskType event) const noexcept
5050+ {
5151+ return (event_mask & event) != 0;
5252+ }
5353+ };
5454+5555+ template <typename T> struct identity
5656+ {
5757+ using type = T;
5858+ };
5959+6060+ // This lets us handle being passed an enum (which we can call underlying_type on) as well as an integer (which
6161+ // we cannot)
6262+ template <typename T>
6363+ using mask_from_enum_t =
6464+ typename std::conditional_t<std::is_enum<T>::value, std::underlying_type<T>, identity<T>>::type;
6565+6666+} // namespace detail
6767+6868+/*!
6969+ * @brief A generic collection of callbacks for event types represented as a bitmask, intended to be wrapped for each
7070+ * usage.
7171+ *
7272+ * A registered callback may identify one or more event types (bits in the bitmask) that it wants to be invoked for. A
7373+ * userdata void pointer is also stored for each callback. Bitmasks are tested at invocation time, and the general
7474+ * callback format allows for callbacks to indicate they should be removed from the collection. Actually calling each
7575+ * callback is left to a consumer-provided "invoker" to allow adding context and event data to the call. The "invoker"
7676+ * also allows the option of whether or how to expose the self-removal capability: yours might simply always return
7777+ * "false".
7878+ *
7979+ * This generic structure supports callbacks that are included multiple times in the collection, if the consuming code
8080+ * needs it. GenericCallbacks::contains may be used by consuming code before conditionally calling addCallback, to
8181+ * limit to a single instance in a collection.
8282+ *
8383+ * @tparam CallbackType the function pointer type to store for each callback.
8484+ * @tparam EventType the event enum type.
8585+ */
8686+template <typename CallbackType, typename EventType> struct GenericCallbacks
8787+{
8888+8989+public:
9090+ static_assert(std::is_integral<EventType>::value || std::is_enum<EventType>::value,
9191+ "Your event type must either be an integer or an enum");
9292+ using callback_t = CallbackType;
9393+ using event_t = EventType;
9494+ using mask_t = detail::mask_from_enum_t<EventType>;
9595+9696+private:
9797+ static_assert(std::is_integral<mask_t>::value, "Our enum to mask conversion should have produced an integer");
9898+9999+ //! The type stored for each added callback.
100100+ using callback_entry_t = detail::GenericCallbackEntry<CallbackType, mask_t>;
101101+102102+public:
103103+ /*!
104104+ * @brief Add a new callback entry with the given callback function pointer, event mask, and user data.
105105+ *
106106+ * New callback entries are always added at the end of the collection.
107107+ */
108108+ void
109109+ addCallback(CallbackType callback, mask_t event_mask, void *userdata)
110110+ {
111111+ callbacks.emplace_back(callback, event_mask, userdata);
112112+ }
113113+114114+ /*!
115115+ * @brief Remove some number of callback entries matching the given callback function pointer, event mask, and
116116+ * user data.
117117+ *
118118+ * @param callback The callback function pointer. Tested for equality with each callback entry.
119119+ * @param event_mask The callback event mask. Tested for equality with each callback entry.
120120+ * @param userdata The opaque user data pointer. Tested for equality with each callback entry.
121121+ * @param num_skip The number of matches to skip before starting to remove callbacks. Defaults to 0.
122122+ * @param max_remove The number of matches to remove, or negative if no limit. Defaults to -1.
123123+ *
124124+ * @returns the number of callbacks removed.
125125+ */
126126+ int
127127+ removeCallback(
128128+ CallbackType callback, mask_t event_mask, void *userdata, unsigned int num_skip = 0, int max_remove = -1)
129129+ {
130130+ if (max_remove == 0) {
131131+ // We were told to remove none. We can do this very quickly.
132132+ // Avoids a corner case in the loop where we assume max_remove is non-zero.
133133+ return 0;
134134+ }
135135+ bool found = false;
136136+137137+ const callback_entry_t needle{callback, event_mask, userdata};
138138+ for (auto &entry : callbacks) {
139139+ if (entry.matches(needle)) {
140140+ if (num_skip > 0) {
141141+ // We are still in our skipping phase.
142142+ num_skip--;
143143+ continue;
144144+ }
145145+ entry.should_remove = true;
146146+ found = true;
147147+ // Negatives (no max) get more negative, which is OK.
148148+ max_remove--;
149149+ if (max_remove == 0) {
150150+ // not looking for more
151151+ break;
152152+ }
153153+ }
154154+ }
155155+ if (found) {
156156+ return purgeMarkedCallbacks();
157157+ }
158158+ // if we didn't find any, we removed zero.
159159+ return 0;
160160+ }
161161+162162+ /*!
163163+ * @brief See if the collection contains at least one matching callback.
164164+ *
165165+ * @param callback The callback function pointer. Tested for equality with each callback entry.
166166+ * @param event_mask The callback event mask. Tested for equality with each callback entry.
167167+ * @param userdata The opaque user data pointer. Tested for equality with each callback entry.
168168+ *
169169+ * @returns true if a matching callback is found.
170170+ */
171171+ bool
172172+ contains(CallbackType callback, mask_t event_mask, void *userdata)
173173+ {
174174+ const callback_entry_t needle{callback, event_mask, userdata};
175175+ auto it = std::find(callbacks.begin(), callbacks.end(), needle);
176176+ return it != callbacks.end();
177177+ }
178178+179179+ /*!
180180+ * @brief Invokes the callbacks, by passing the ones we should run to your "invoker" to add any desired
181181+ * context/event data and forward the call.
182182+ *
183183+ * Callbacks are called in order, filtering out those whose event mask does not include the given event.
184184+ *
185185+ * @param event The event type to invoke callbacks for.
186186+ * @param invoker A function/functor accepting the event, a callback function pointer, and the callback entry's
187187+ * userdata as parameters, and returning true if the callback should be removed from the collection. It is
188188+ * assumed that the invoker will add any additional context or event data and call the provided callback.
189189+ *
190190+ * Typically, a lambda with some captures and a single return statement will be sufficient for an invoker.
191191+ *
192192+ * @returns the number of callbacks run
193193+ */
194194+ template <typename F>
195195+ int
196196+ invokeCallbacks(EventType event, F &&invoker)
197197+ {
198198+ bool needPurge = false;
199199+200200+ int ran = 0;
201201+ for (auto &entry : callbacks) {
202202+ if (entry.shouldInvoke(static_cast<mask_t>(event))) {
203203+ bool willRemove = invoker(event, entry.callback, entry.userdata);
204204+ if (willRemove) {
205205+ entry.should_remove = true;
206206+ needPurge = true;
207207+ }
208208+ ran++;
209209+ }
210210+ }
211211+ if (needPurge) {
212212+ purgeMarkedCallbacks();
213213+ }
214214+ return ran;
215215+ }
216216+217217+private:
218218+ std::vector<callback_entry_t> callbacks;
219219+220220+ int
221221+ purgeMarkedCallbacks()
222222+ {
223223+ auto b = callbacks.begin();
224224+ auto e = callbacks.end();
225225+ auto new_end = std::remove_if(b, e, [](callback_entry_t const &entry) { return entry.should_remove; });
226226+ auto num_removed = std::distance(new_end, e);
227227+ callbacks.erase(new_end, e);
228228+ return static_cast<int>(num_removed);
229229+ }
230230+};
231231+} // namespace xrt::auxiliary::util