The open source OpenXR runtime
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

a/util: Add a generic callbacks collection.

Based on the work for the Android surface callbacks,
just finished being made generic since we'll need to reuse it.

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