XRTraits C++ OpenXR Utilities
TwoCall.h
Go to the documentation of this file.
1 // Copyright 2018-2019, Collabora, Ltd.
2 // SPDX-License-Identifier: BSL-1.0
3 /*!
4  * @file
5  * @brief Header providing wrappers for returning a variable-length collection
6  * by repeatedly calling a "two-call idiom" OpenXR function for you. Lets you
7  * pretend it's only a single call, possibly returning a std::vector<> (for some
8  * variants).
9  * @author Ryan Pavlik <ryan.pavlik@collabora.com>
10  */
11 
12 #pragma once
13 
14 // Internal Includes
15 #include "InitXrType.h"
16 
17 #ifdef XRTRAITS_USE_EXCEPTIONS
19 #endif
20 
21 // Library Includes
22 // - none
23 
24 // Standard Includes
25 // - none
26 
27 namespace xrtraits {
28 
29 /*!
30  * @defgroup TwoCall Two-Call Idiom Wrappers
31  *
32  * @brief Functions simplifying calls that return a variable-length
33  * buffer/array.
34  *
35  * Some of these functions may take a leading argument, such as a size hint or a
36  * container to populate. However, after that possible argument, the rest are
37  * standardized:
38  *
39  * - wrappedCall is something callable (lambda or function). It will receive
40  * `capacityInput`, `countOutput`, and `array` parameters as its only (if no
41  * more arguments are passed to the function) or last (if additional
42  * parameters are passed) parameters.
43  * - Any additional arguments passed will be forwarded to the call
44  * **before** the `capacityInput`, `countOutput`, and `array` parameters.
45  */
46 
47 /*!
48  * @example twocall-just-function.cpp
49  *
50  * This example shows how to use doTwoCall() when a function requires no
51  * additional arguments.
52  */
53 
54 /*!
55  * @example twocall-function-and-one-arg.cpp
56  *
57  * This example shows how to use doTwoCall() when a function requires one
58  * additional argument.
59  *
60  * The wrapper will make a call like the following as required to retrieve all
61  * properties (adjusting the parameters and array between calls):
62  *
63  * ```c++
64  * XrResult result = xrEnumerateInstanceExtensionProperties(nullptr,
65  * capacityInput, &countOutput, array);
66  * ```
67  *
68  * Note how the one additional argument, `nullptr`, has been forwarded to the
69  * call before the capacity, count, and array args.
70  */
71 
72 /*!
73  * @example twocall-size-hint-and-initialized.cpp
74  *
75  * This example shows a typical usage of doTwoCallWithSizeHint(): when locating
76  * views. The number of views should in theory be known, but for safety and
77  * consistency this function is specified in two-call style.
78  *
79  * It also shows a few uses of xrtraits::Initialized - one (`viewState`) with no
80  * arguments (which sets just the `type` and `next`), and one (`viewLocateInfo`)
81  * with some arguments that aren't a chained struct. In this case, `type` and
82  * `next` are initialized like normal, while the subsequent members of
83  * XrViewLocateInfo, `displayTime` and `space`, are initialized from `frameTime`
84  * and `local` respectively.
85  *
86  * Because a size hint was provided, this call will initially create a vector of
87  * `numViews` `XrView` structures before attempting to call xrLocateViews. This
88  * will generally reduce the number of calls to xrLocateViews to 1, since the
89  * initial capacity should be sufficient. The wrapper will make a call like the
90  * following as required to retrieve all properties (adjusting the parameters
91  * and array between calls):
92  *
93  * ```c++
94  * XrResult result = xrLocateViews(session, &viewLocateInfo, &viewState,
95  * capacityInput, &countOutput, array);
96  * ```
97  *
98  * Note how the several additional arguments were forwarded to the
99  * call before the capacity, count, and array args.
100  */
101 
102 /*!
103  * @example twocall-in-place-sized-and-initialized.cpp
104  *
105  * This example shows a mostly-equivalent scenario to @ref
106  * twocall-size-hint-and-initialized.cpp, but instead uses the
107  * doTwoCallInPlace() function which avoids exceptions and returns the XrResult
108  * from the final wrapped call. See that other example for more detail,
109  * including explanations of the xrtraits::Initialized usage.
110  *
111  * Because `views` is not empty, that will be used as a size hint, so if (as
112  * expected) `xrLocateViews` output fits in that size, only a single call to the
113  * wrapped function is needed.
114  */
115 
116 /*!
117  * @example twocall-in-place-just-function.cpp
118  *
119  * This example shows a mostly-equivalent scenario to @ref
120  * twocall-just-function.cpp, but instead uses the doTwoCallInPlace() function
121  * which avoids exceptions and returns the XrResult from the final wrapped call.
122  * See that other example for more detail overall.
123  *
124  * Because `views` is empty, the normal two-call process will take place: A call
125  * with 0 capacity will be made first, the vector will be resized (with its
126  * elements initialized appropriately). After this capacity check, another call
127  * to the wrapped function will be made, now with the current capacity and
128  * array, and will hopefully be successful.
129  */
130 
131 #ifndef XRTRAITS_DOXYGEN
132 namespace detail {
133 
134  static constexpr uint32_t MAX_CALLS_FOR_TWO_CALL_IDIOM = 5;
135  struct TwoCallResult
136  {
137  bool doneCalling = false;
138  XrResult returnCode = XR_SUCCESS;
139  //! Only valid if returnCode is XR_SUCCESS or XR_ERROR_
140  uint32_t count = 0;
141  };
142  template <typename F, typename... Args>
143  static inline TwoCallResult getCount(F&& wrappedCall, Args&&... a)
144  {
145  TwoCallResult ret;
146  ret.returnCode = wrappedCall(std::forward<Args>(a)..., 0,
147  &ret.count, nullptr);
148 
149  if (ret.returnCode != XR_SUCCESS) {
150  // Zero should always give success, whether there are 0
151  // items or more.
152  ret.doneCalling = true;
153  } else if (ret.count == 0) {
154  // We asked for the count, and it was zero, so all done.
155  ret.doneCalling = true;
156  }
157  return ret;
158  }
159 
160  template <typename T, typename F, typename... Args>
161  static inline TwoCallResult callOnce(std::vector<T>& container,
162  F&& wrappedCall, Args&&... a)
163  {
164  using namespace xrtraits;
165 
166  TwoCallResult ret;
167  if (container.empty()) {
168  // No capacity, just treat as a count retrieval.
169  ret = getCount(std::forward<F>(wrappedCall),
170  std::forward<Args>(a)...);
171  } else {
172  // We have at least some capacity already.
173  ret.returnCode =
174  wrappedCall(std::forward<Args>(a)...,
175  uint32_t(container.size()), &ret.count,
176  container.data());
177 
178  // If we get a non-size related error, or a success,
179  // we're done.
180  ret.doneCalling =
181  ret.returnCode != XR_ERROR_SIZE_INSUFFICIENT;
182  }
183  // Resize accordingly
184  if (ret.returnCode == XR_SUCCESS ||
185  ret.returnCode == XR_ERROR_SIZE_INSUFFICIENT) {
186  container.resize(ret.count, make_zeroed<T>());
187  }
188 
189  return ret;
190  }
191 
192  template <typename T, typename F, typename... Args>
193  static inline XrResult twoCallLoop(uint32_t max_calls,
194  std::vector<T>& container,
195  F&& wrappedCall, Args&&... a)
196  {
197  TwoCallResult result;
198  // Repeatedly call until we succeed, fail, or get bored of
199  // resizing.
200  for (uint32_t i = 0; !result.doneCalling && i < max_calls;
201  ++i) {
202  result =
203  callOnce(container, std::forward<F>(wrappedCall),
204  std::forward<Args>(a)...);
205  }
206  return result.returnCode;
207  }
208 } // namespace detail
209 
210 #endif // !XRTRAITS_DOXYGEN
211 
212 #ifdef XRTRAITS_USE_EXCEPTIONS
213 /*! Perform the two call idiom, returning a vector.
214  *
215  * @tparam T The type of the buffer element you expect
216  * @param wrappedCall A function or lambda that takes the `capacityInput`,
217  * `countOutput`, and `array` parameters as its only or last parameters.
218  * @param a Any additional arguments passed to this call will be forwarded to
219  * the call **before** the `capacityInput`, `countOutput`, and `array`
220  * parameters.
221  *
222  * @throws if final call does not return XR_SUCCESS
223  *
224  * Requires XR_USE_EXCEPTIONS.
225  *
226  * @ingroup TwoCall
227  */
228 template <typename T, typename F, typename... Args>
229 inline std::vector<T> doTwoCall(F&& wrappedCall, Args&&... a)
230 {
232  auto countResults = detail::getCount(std::forward<F>(wrappedCall),
233  std::forward<Args>(a)...);
234 
236  countResults.returnCode, "Failed getting count in two-call idiom");
237 
238  std::vector<T> container = make_zeroed_vector<T>(countResults.count);
239  if (!container.empty()) {
240  XrResult result = detail::twoCallLoop(
241  detail::MAX_CALLS_FOR_TWO_CALL_IDIOM, container,
242  std::forward<F>(wrappedCall), std::forward<Args>(a)...);
244  result, "Failed in retrieving two-call values");
245  }
246 
247  return container;
248 }
249 
250 /*! Perform the two call idiom, returning a vector, when we already have a hint
251  * about the capacity.
252  *
253  * @tparam T The type of the buffer element you expect
254  * @param sizeHint The buffer size to attempt: if sufficient, only one call to
255  * the wrappedCall will be made.
256  * @param wrappedCall A function or lambda that takes the `capacityInput`,
257  * `countOutput`, and `array` parameters as its only or last parameters.
258  * @param a Any additional arguments passed to this call will be forwarded to
259  * the call **before** the `capacityInput`, `countOutput`, and `array`
260  * parameters.
261  *
262  * @throws if final call does not return XR_SUCCESS
263  *
264  * Requires XR_USE_EXCEPTIONS.
265  *
266  * @ingroup TwoCall
267  */
268 template <typename T, typename F, typename... Args>
269 inline std::vector<T> doTwoCallWithSizeHint(uint32_t sizeHint, F&& wrappedCall,
270  Args&&... a)
271 {
273  std::vector<T> container = make_zeroed_vector<T>(sizeHint);
274  XrResult result = detail::twoCallLoop(
275  detail::MAX_CALLS_FOR_TWO_CALL_IDIOM, container,
276  std::forward<F>(wrappedCall), std::forward<Args>(a)...);
278  result, "Failed in retrieving two-call values");
279  return container;
280 }
281 #endif // XRTRAITS_USE_EXCEPTIONS
282 
283 /*! Perform the two call idiom, returning XrResult, to populate an existing
284  * container, whose size may hint at expected count.
285  *
286  * @param container The container to fill. If it is not empty, the buffer size
287  * will be used as a size hint: if sufficient, only one call to the wrappedCall
288  * will be made.
289  * @param wrappedCall A function or lambda that takes the `capacityInput`,
290  * `countOutput`, and `array` parameters as its only or last parameters.
291  * @param a Any additional arguments passed to this call will be forwarded to
292  * the call **before** the `capacityInput`, `countOutput`, and `array`
293  * parameters.
294  *
295  * Does not require XR_USE_EXCEPTIONS.
296  *
297  * @ingroup TwoCall
298  */
299 template <typename T, typename F, typename... Args>
300 inline XrResult doTwoCallInPlace(std::vector<T>& container, F&& wrappedCall,
301  Args&&... a)
302 {
303 
304  return detail::twoCallLoop(detail::MAX_CALLS_FOR_TWO_CALL_IDIOM,
305  container, std::forward<F>(wrappedCall),
306  std::forward<Args>(a)...);
307 }
308 
309 } // namespace xrtraits
Main namespace for these C++ OpenXR utilities.
Definition: GetChained.h:26
std::vector< T > doTwoCallWithSizeHint(uint32_t sizeHint, F &&wrappedCall, Args &&... a)
Definition: TwoCall.h:269
Header providing a variety of ways to create or initialize an OpenXR "tagged struct".
void throwIfNotUnqualifiedSuccess(XrResult result, const char *errorMessage)
std::vector< T > doTwoCall(F &&wrappedCall, Args &&... a)
Definition: TwoCall.h:229
XrResult doTwoCallInPlace(std::vector< T > &container, F &&wrappedCall, Args &&... a)
Definition: TwoCall.h:300
std::vector< T > make_zeroed_vector(size_t n)
Definition: InitXrType.h:114