Rerun C++ SDK
Loading...
Searching...
No Matches
collection.hpp
1#pragma once
2
3#include <algorithm>
4#include <cassert>
5#include <cstdint>
6#include <cstring> // std::memset
7#include <utility>
8#include <vector>
9
10#include "collection.hpp"
11#include "collection_adapter.hpp"
12#include "compiler_utils.hpp"
13
14namespace rerun {
15 /// Type of ownership of a collection's data.
16 ///
17 /// User access to this is typically only needed for debugging and testing.
19 /// The collection does not own the data and only has a pointer and a size.
21
22 /// The collection batch owns the data via an std::vector.
24 };
25
26 /// Generic collection of elements that are roughly contiguous in memory.
27 ///
28 /// The most notable feature of the `rerun::Collection` is that its data may be either **owned** or **borrowed**:
29 /// * Borrowed: ⚠️ If data is borrowed it *must* outlive its source ⚠️
30 /// (in particular, the pointer to the source mustn't invalidate)
31 /// * Owned: Owned data is copied into an internal std::vector
32 ///
33 /// Collections are either filled explicitly using `Collection::borrow` &`Collection::take_ownership`
34 /// or (most commonly in user code) implicitly using the `CollectionAdapter` trait
35 /// (see documentation for `CollectionAdapter` for more information on how data can be adapted).
36 ///
37 /// ⚠️ To ensure that passed data is not destroyed, move it into the collection using `std::move`.
38 ///
39 /// Other than being assignable, collections are generally immutable:
40 /// there is no mutable data access in order to not violate the contract with the data lender
41 /// and changes in size are not possible.
42 ///
43 /// ## Implementation notes:
44 ///
45 /// Does intentionally not implement copy construction since for the owned case this may
46 /// be expensive. Typically, there should be no need to copy rerun collections, so this more
47 /// than likely indicates a bug inside the Rerun SDK.
48 template <typename TElement>
49 class Collection {
50 public:
51 /// Type of the elements in the collection.
52 ///
53 /// Note that calling this `value_type` makes it compatible with the STL.
54 using value_type = TElement;
55
56 /// Type of an adapter given an input container type.
57 ///
58 /// Note that the "container" passed may also be a single element of something.
59 /// The only thing relevant is that there's an Adapter for it.
60 template <typename TContainer>
62 TElement, std::remove_cv_t<std::remove_reference_t<TContainer>>,
63 std::enable_if_t<true>>;
64
65 /// Creates a new empty collection.
67 storage.borrowed.data = nullptr;
68 storage.borrowed.num_instances = 0;
69 }
70
71 /// Construct using a `CollectionAdapter` for the given input type.
72 template <
73 typename TContainer, //
74 // Avoid conflicting with the copy/move constructor.
75 // We could implement this also with an adapter, but this might confuse trait checks like `std::is_copy_constructible`.
76 typename = std::enable_if_t<
77 !std::is_same_v<std::remove_reference_t<TContainer>, Collection<TElement>>> //
78 >
79 Collection(TContainer&& input)
80 : Collection(Adapter<TContainer>()(std::forward<TContainer>(input))) {}
81
82 /// Copy constructor.
83 ///
84 /// If the data is owned, this will copy the data.
85 /// If the data is borrowed, this will copy the borrow,
86 /// meaning there's now (at least) two collections borrowing the same data.
87 Collection(const Collection<TElement>& other) : ownership(other.ownership) {
88 switch (other.ownership) {
90 storage.borrowed = other.storage.borrowed;
91 break;
92 }
93
95 new (&storage.vector_owned) std::vector<TElement>(other.storage.vector_owned);
96 break;
97 }
98
99 default:
100 assert(false && "unreachable");
101 }
102 }
103
104 /// Copy assignment.
105 ///
106 /// If the data is owned, this will copy the data.
107 /// If the data is borrowed, this will copy the borrow,
108 /// meaning there's now (at least) two collections borrowing the same data.
109 void operator=(const Collection<TElement>& other) {
110 this->~Collection<TElement>();
111 new (this) Collection(other);
112 }
113
114 /// Move constructor.
116 swap(other);
117 }
118
119 /// Move assignment.
121 // Need to disable the maybe-uninitialized here. It seems like the compiler may be confused in situations where
122 // we are assigning into an unused optional from a temporary. The fact that this hits the move-assignment without
123 // having called the move constructor is suspicious though and hints of an actual bug.
124 //
125 // See: https://github.com/rerun-io/rerun/issues/4027
126 RR_WITH_MAYBE_UNINITIALIZED_DISABLED(this->swap(other);)
127 }
128
129 /// Construct from a initializer list of elements that are compatible with TElement.
130 ///
131 /// Takes ownership of the passed elements.
132 /// If you want to avoid an allocation, you have to manually keep the data on the stack
133 /// (e.g. as `std::array`) and construct the collection from this instead.
134 ///
135 /// This is not done as a `CollectionAdapter` since it tends to cause deduction issues
136 /// (since there's special rules for overload resolution for initializer lists)
137 Collection(std::initializer_list<TElement> data)
138 : ownership(CollectionOwnership::VectorOwned) {
139 // Don't assign, since the vector is in an undefined state and assigning may
140 // attempt to free data.
141 new (&storage.vector_owned) std::vector<TElement>(data);
142 }
143
144 /// Borrows binary compatible data into the collection from a typed pointer.
145 ///
146 /// Borrowed data must outlive the collection!
147 /// (If the pointer passed is into an std::vector or similar, this std::vector mustn't be
148 /// resized.)
149 /// The passed type must be binary compatible with the collection type.
150 ///
151 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
152 /// any function or operation taking on a `Collection`.
153 template <typename T>
154 static Collection<TElement> borrow(const T* data, size_t num_instances = 1) {
155 static_assert(
156 sizeof(T) == sizeof(TElement),
157 "T & TElement are not binary compatible: Size mismatch."
158 );
159 static_assert(
160 alignof(T) <= alignof(TElement),
161 "T & TElement are not binary compatible: TElement has a higher alignment requirement than T. This implies that pointers to T may not have the alignment needed to access TElement."
162 );
163
165 batch.ownership = CollectionOwnership::Borrowed;
166 batch.storage.borrowed.data = reinterpret_cast<const TElement*>(data);
167 batch.storage.borrowed.num_instances = num_instances;
168 return batch;
169 }
170
171 /// Borrows binary compatible data into the collection from an untyped pointer.
172 ///
173 /// This version of `borrow` that takes a void pointer, omitting any checks.
174 ///
175 /// Borrowed data must outlive the collection!
176 /// (If the pointer passed is into an std::vector or similar, this std::vector mustn't be
177 /// resized.)
178 ///
179 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
180 /// any function or operation taking on a `rerun::Collection`.
181 static Collection borrow(const void* data, size_t num_instances = 1) {
182 return borrow(reinterpret_cast<const TElement*>(data), num_instances);
183 }
184
185 /// Borrows binary compatible data into the collection from a vector.
186 ///
187 /// Borrowed data must outlive the collection!
188 /// The referenced vector must not be resized and musn't be temporary.
189 ///
190 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
191 /// any function or operation taking on a `rerun::Collection`.
192 template <typename T>
193 static Collection borrow(const std::vector<T>& data) {
194 return borrow(data.data(), data.size());
195 }
196
197 /// Takes ownership of a temporary `std::vector`, moving it into the collection.
198 ///
199 /// Takes ownership of the data and moves it into the collection.
200 static Collection<TElement> take_ownership(std::vector<TElement>&& data) {
202 batch.ownership = CollectionOwnership::VectorOwned;
203 // Don't assign, since the vector is in an undefined state and assigning may
204 // attempt to free data.
205 new (&batch.storage.vector_owned) std::vector<TElement>(std::move(data));
206
207 return batch;
208 }
209
210 /// Takes ownership of a single element, moving it into the collection.
212 // TODO(#4256): there should be a special path here to avoid allocating a vector.
213 std::vector<TElement> elements;
214 elements.emplace_back(std::move(data));
215 return take_ownership(std::move(elements));
216 }
217
218 /// Takes ownership of a single element, copying it into the collection.
219 static Collection<TElement> take_ownership(const TElement& data) {
220 // TODO(#4256): there should be a special path here to avoid allocating a vector.
221 std::vector<TElement> elements = {data};
222 return take_ownership(std::move(elements));
223 }
224
225 /// Swaps the content of this collection with another.
227 // (writing out this-> here to make it less confusing!)
228 switch (this->ownership) {
230 switch (other.ownership) {
232 std::swap(this->storage.borrowed, other.storage.borrowed);
233 break;
234
236 auto this_borrowed_data_old = this->storage.borrowed;
237 new (&this->storage.vector_owned)
238 std::vector<TElement>(std::move(other.storage.vector_owned));
239 other.storage.borrowed = this_borrowed_data_old;
240 break;
241 }
242
243 default:
244 assert(false && "unreachable");
245 }
246 break;
247 }
248
250 switch (other.ownership) {
252 auto other_borrowed_data_old = other.storage.borrowed;
253 new (&other.storage.vector_owned)
254 std::vector<TElement>(std::move(this->storage.vector_owned));
255 this->storage.borrowed = other_borrowed_data_old;
256 break;
257 }
258
260 std::swap(storage.vector_owned, other.storage.vector_owned);
261 break;
262
263 default:
264 assert(false && "unreachable");
265 }
266 break;
267 }
268
269 default:
270 assert(false && "unreachable");
271 }
272
273 std::swap(ownership, other.ownership);
274 }
275
276 ~Collection() {
277 switch (ownership) {
279 break; // nothing to do.
280
282 storage.vector_owned.~vector(); // Deallocate the vector!
283 break;
284
285 default:
286 assert(false && "unreachable");
287 }
288 }
289
290 /// Returns the number of instances in this collection.
291 size_t size() const {
292 switch (ownership) {
294 return storage.borrowed.num_instances;
295
297 return storage.vector_owned.size();
298
299 default:
300 assert(false && "unreachable");
301 }
302 return 0;
303 }
304
305 /// Returns true if the collection is empty.
306 bool empty() const {
307 switch (ownership) {
309 return storage.borrowed.num_instances == 0;
310
312 return storage.vector_owned.empty();
313
314 default:
315 assert(false && "unreachable");
316 }
317 return 0;
318 }
319
320 /// Returns a raw pointer to the underlying data.
321 ///
322 /// Do not use this if the data is not continuous in memory!
323 /// TODO(#4257): So far it always is continuous, but in the future we want to support strides!
324 ///
325 /// The pointer is only valid as long as backing storage is alive
326 /// which is either until the collection is destroyed the borrowed source is destroyed/moved.
327 const TElement* data() const {
328 switch (ownership) {
330 return storage.borrowed.data;
331
333 return storage.vector_owned.data();
334
335 default:
336 assert(false && "unreachable");
337 }
338
339 // We need to return something to avoid compiler warnings.
340 // But if we don't mark this as unreachable, GCC will complain that we're dereferencing null down the line.
341 RR_UNREACHABLE();
342 // But with this in place, MSVC complains that the return statement is not reachable (GCC/clang on the other hand need it).
343#ifndef _MSC_VER
344 return nullptr;
345#endif
346 }
347
348 /// TODO(andreas): Return proper iterator
349 const TElement* begin() const {
350 return data();
351 }
352
353 /// TODO(andreas): Return proper iterator
354 const TElement* end() const {
355 return data() + size();
356 }
357
358 /// Random read access to the underlying data.
359 const TElement& operator[](size_t i) const {
360 assert(i < size());
361 return data()[i];
362 }
363
364 /// Returns the data ownership of collection.
365 ///
366 /// This is usually only needed for debugging and testing.
368 return ownership;
369 }
370
371 /// Copies the data into a new `std::vector`.
372 std::vector<TElement> to_vector() const& {
373 std::vector<TElement> result;
374 result.reserve(size());
375 result.insert(result.end(), begin(), end());
376 return result;
377 }
378
379 /// Copies the data into a new `std::vector`.
380 ///
381 /// If possible, this will move the underlying data.
382 std::vector<TElement> to_vector() && {
383 switch (ownership) {
385 std::vector<TElement> result;
386 result.reserve(size());
387 result.insert(result.end(), begin(), end());
388 return result;
389 }
390
392 return std::move(storage.vector_owned);
393 }
394
395 default:
396 assert(false && "unreachable");
397 }
398 return std::vector<TElement>();
399 }
400
401 /// Reinterpret this collection as a collection of bytes.
403 switch (ownership) {
406 reinterpret_cast<const uint8_t*>(data()),
407 size() * sizeof(TElement)
408 );
409 }
410
412 auto ptr = reinterpret_cast<const uint8_t*>(data());
413 auto num_bytes = size() * sizeof(TElement);
415 std::vector<uint8_t>(ptr, ptr + num_bytes)
416 );
417 }
418
419 default:
420 assert(false && "unreachable");
421 }
422 return Collection<uint8_t>();
423 }
424
425 private:
426 template <typename T>
427 union CollectionStorage {
428 struct {
429 const T* data;
430 size_t num_instances;
431 } borrowed;
432
433 std::vector<T> vector_owned;
434
435 CollectionStorage() {
436 std::memset(reinterpret_cast<void*>(this), 0, sizeof(CollectionStorage));
437 }
438
439 ~CollectionStorage() {}
440 };
441
442 CollectionOwnership ownership;
443 CollectionStorage<TElement> storage;
444 };
445
446 // Convenience functions for creating typed collections via explicit borrow & ownership taking.
447 // These are useful to avoid having to specify the type of the collection.
448 // E.g. instead of `rerun::Collection<uint8_t>::borrow(data, num_instances)`,
449 // you can just write `rerun::borrow(data, num_instances)`.
450
451 /// Borrows binary data into a `Collection` from a pointer.
452 ///
453 /// Borrowed data must outlive the collection!
454 /// (If the pointer passed is into an std::vector or similar, this std::vector mustn't be
455 /// resized.)
456 /// The passed type must be binary compatible with the collection type.
457 ///
458 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
459 /// any function or operation taking on a `Collection`.
460 template <typename TElement>
461 inline Collection<TElement> borrow(const TElement* data, size_t num_instances = 1) {
462 return Collection<TElement>::borrow(data, num_instances);
463 }
464
465 /// Borrows binary data into the collection from a vector.
466 ///
467 /// Borrowed data must outlive the collection!
468 /// The referenced vector must not be resized and musn't be temporary.
469 ///
470 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
471 /// any function or operation taking on a `rerun::Collection`.
472 template <typename TElement>
473 inline Collection<TElement> borrow(const std::vector<TElement>& data) {
474 return Collection<TElement>::borrow(data);
475 }
476
477 /// Takes ownership of a temporary `std::vector`, moving it into the collection.
478 ///
479 /// Takes ownership of the data and moves it into the collection.
480 template <typename TElement>
481 inline Collection<TElement> take_ownership(std::vector<TElement> data) {
482 return Collection<TElement>::take_ownership(std::move(data));
483 }
484
485 /// Takes ownership of a single element, moving it into the collection.
486 template <typename TElement>
487 inline Collection<TElement> take_ownership(TElement data) {
488 return Collection<TElement>::take_ownership(std::move(data));
489 }
490} // namespace rerun
491
492// Could keep this separately, but its very hard to use the collection without the basic suite of adapters.
493// Needs to know about `rerun::Collection` which means that it needs to be included after `rerun::Collection` is defined.
494// (it tried to include `Collection.hpp` but if that was our starting point that include wouldn't do anything)
495#include "collection_adapter_builtins.hpp"
Generic collection of elements that are roughly contiguous in memory.
Definition collection.hpp:49
const TElement & operator[](size_t i) const
Random read access to the underlying data.
Definition collection.hpp:359
static Collection< TElement > take_ownership(TElement &&data)
Takes ownership of a single element, moving it into the collection.
Definition collection.hpp:211
static Collection< TElement > borrow(const T *data, size_t num_instances=1)
Borrows binary compatible data into the collection from a typed pointer.
Definition collection.hpp:154
static Collection< TElement > take_ownership(std::vector< TElement > &&data)
Takes ownership of a temporary std::vector, moving it into the collection.
Definition collection.hpp:200
bool empty() const
Returns true if the collection is empty.
Definition collection.hpp:306
Collection(Collection< TElement > &&other)
Move constructor.
Definition collection.hpp:115
static Collection borrow(const std::vector< T > &data)
Borrows binary compatible data into the collection from a vector.
Definition collection.hpp:193
Collection()
Creates a new empty collection.
Definition collection.hpp:66
CollectionOwnership get_ownership() const
Returns the data ownership of collection.
Definition collection.hpp:367
const TElement * end() const
TODO(andreas): Return proper iterator.
Definition collection.hpp:354
void operator=(const Collection< TElement > &other)
Copy assignment.
Definition collection.hpp:109
const TElement * begin() const
TODO(andreas): Return proper iterator.
Definition collection.hpp:349
void swap(Collection< TElement > &other)
Swaps the content of this collection with another.
Definition collection.hpp:226
TElement value_type
Type of the elements in the collection.
Definition collection.hpp:54
static Collection< TElement > take_ownership(const TElement &data)
Takes ownership of a single element, copying it into the collection.
Definition collection.hpp:219
size_t size() const
Returns the number of instances in this collection.
Definition collection.hpp:291
void operator=(Collection< TElement > &&other)
Move assignment.
Definition collection.hpp:120
static Collection borrow(const void *data, size_t num_instances=1)
Borrows binary compatible data into the collection from an untyped pointer.
Definition collection.hpp:181
Collection(TContainer &&input)
Construct using a CollectionAdapter for the given input type.
Definition collection.hpp:79
Collection(const Collection< TElement > &other)
Copy constructor.
Definition collection.hpp:87
Collection(std::initializer_list< TElement > data)
Construct from a initializer list of elements that are compatible with TElement.
Definition collection.hpp:137
std::vector< TElement > to_vector() const &
Copies the data into a new std::vector.
Definition collection.hpp:372
Collection< uint8_t > to_uint8() const
Reinterpret this collection as a collection of bytes.
Definition collection.hpp:402
std::vector< TElement > to_vector() &&
Copies the data into a new std::vector.
Definition collection.hpp:382
const TElement * data() const
Returns a raw pointer to the underlying data.
Definition collection.hpp:327
All Rerun C++ types and functions are in the rerun namespace or one of its nested namespaces.
Definition rerun.hpp:22
Collection< TElement > take_ownership(std::vector< TElement > data)
Takes ownership of a temporary std::vector, moving it into the collection.
Definition collection.hpp:481
CollectionOwnership
Type of ownership of a collection's data.
Definition collection.hpp:18
@ Borrowed
The collection does not own the data and only has a pointer and a size.
@ VectorOwned
The collection batch owns the data via an std::vector.
Collection< TElement > borrow(const TElement *data, size_t num_instances=1)
Borrows binary data into a Collection from a pointer.
Definition collection.hpp:461
The rerun::CollectionAdapter trait is responsible for mapping an input argument to a rerun::Collectio...
Definition collection_adapter.hpp:25