The open source OpenXR runtime
at main 235 lines 7.9 kB view raw
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. 104 using callback_entry_t = detail::GenericCallbackEntry<CallbackType, mask_t>; 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 131 removeCallback( 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