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 // Self-assignment would destroy `other` before copying from it.
111 if (this == &other) {
112 return;
113 }
114 this->~Collection<TElement>();
115 new (this) Collection(other);
116 }
117
118 /// Move constructor.
120 swap(other);
121 }
122
123 /// Move assignment.
125 // Need to disable the maybe-uninitialized here. It seems like the compiler may be confused in situations where
126 // we are assigning into an unused optional from a temporary. The fact that this hits the move-assignment without
127 // having called the move constructor is suspicious though and hints of an actual bug.
128 //
129 // See: https://github.com/rerun-io/rerun/issues/4027
130 RR_WITH_MAYBE_UNINITIALIZED_DISABLED(this->swap(other);)
131 }
132
133 /// Construct from a initializer list of elements that are compatible with TElement.
134 ///
135 /// Takes ownership of the passed elements.
136 /// If you want to avoid an allocation, you have to manually keep the data on the stack
137 /// (e.g. as `std::array`) and construct the collection from this instead.
138 ///
139 /// This is not done as a `CollectionAdapter` since it tends to cause deduction issues
140 /// (since there's special rules for overload resolution for initializer lists)
141 Collection(std::initializer_list<TElement> data)
142 : ownership(CollectionOwnership::VectorOwned) {
143 // Don't assign, since the vector is in an undefined state and assigning may
144 // attempt to free data.
145 new (&storage.vector_owned) std::vector<TElement>(data);
146 }
147
148 /// Borrows binary compatible data into the collection from a typed pointer.
149 ///
150 /// Borrowed data must outlive the collection!
151 /// (If the pointer passed is into an std::vector or similar, this std::vector mustn't be
152 /// resized.)
153 /// The passed type must be binary compatible with the collection type.
154 ///
155 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
156 /// any function or operation taking on a `Collection`.
157 template <typename T>
158 static Collection<TElement> borrow(const T* data, size_t num_instances = 1) {
159 static_assert(
160 sizeof(T) == sizeof(TElement),
161 "T & TElement are not binary compatible: Size mismatch."
162 );
163 static_assert(
164 alignof(T) <= alignof(TElement),
165 "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."
166 );
167
169 batch.ownership = CollectionOwnership::Borrowed;
170 batch.storage.borrowed.data = reinterpret_cast<const TElement*>(data);
171 batch.storage.borrowed.num_instances = num_instances;
172 return batch;
173 }
174
175 /// Borrows binary compatible data into the collection from an untyped pointer.
176 ///
177 /// This version of `borrow` that takes a void pointer, omitting any checks.
178 ///
179 /// Borrowed data must outlive the collection!
180 /// (If the pointer passed is into an std::vector or similar, this std::vector mustn't be
181 /// resized.)
182 ///
183 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
184 /// any function or operation taking on a `rerun::Collection`.
185 static Collection borrow(const void* data, size_t num_instances = 1) {
186 return borrow(reinterpret_cast<const TElement*>(data), num_instances);
187 }
188
189 /// Borrows binary compatible data into the collection from a vector.
190 ///
191 /// Borrowed data must outlive the collection!
192 /// The referenced vector must not be resized and mustn't be temporary.
193 ///
194 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
195 /// any function or operation taking on a `rerun::Collection`.
196 template <typename T>
197 static Collection borrow(const std::vector<T>& data) {
198 return borrow(data.data(), data.size());
199 }
200
201 /// Takes ownership of a temporary `std::vector`, moving it into the collection.
202 ///
203 /// Takes ownership of the data and moves it into the collection.
204 static Collection<TElement> take_ownership(std::vector<TElement>&& data) {
206 batch.ownership = CollectionOwnership::VectorOwned;
207 // Don't assign, since the vector is in an undefined state and assigning may
208 // attempt to free data.
209 new (&batch.storage.vector_owned) std::vector<TElement>(std::move(data));
210
211 return batch;
212 }
213
214 /// Takes ownership of a single element, moving it into the collection.
216 // TODO(#4256): there should be a special path here to avoid allocating a vector.
217 std::vector<TElement> elements;
218 elements.emplace_back(std::move(data));
219 return take_ownership(std::move(elements));
220 }
221
222 /// Takes ownership of a single element, copying it into the collection.
223 static Collection<TElement> take_ownership(const TElement& data) {
224 // TODO(#4256): there should be a special path here to avoid allocating a vector.
225 std::vector<TElement> elements = {data};
226 return take_ownership(std::move(elements));
227 }
228
229 /// Swaps the content of this collection with another.
231 // (writing out this-> here to make it less confusing!)
232 switch (this->ownership) {
234 switch (other.ownership) {
236 std::swap(this->storage.borrowed, other.storage.borrowed);
237 break;
238
240 auto this_borrowed_data_old = this->storage.borrowed;
241 new (&this->storage.vector_owned)
242 std::vector<TElement>(std::move(other.storage.vector_owned));
243 other.storage.borrowed = this_borrowed_data_old;
244 break;
245 }
246
247 default:
248 assert(false && "unreachable");
249 }
250 break;
251 }
252
254 switch (other.ownership) {
256 auto other_borrowed_data_old = other.storage.borrowed;
257 new (&other.storage.vector_owned)
258 std::vector<TElement>(std::move(this->storage.vector_owned));
259 this->storage.borrowed = other_borrowed_data_old;
260 break;
261 }
262
264 std::swap(storage.vector_owned, other.storage.vector_owned);
265 break;
266
267 default:
268 assert(false && "unreachable");
269 }
270 break;
271 }
272
273 default:
274 assert(false && "unreachable");
275 }
276
277 std::swap(ownership, other.ownership);
278 }
279
280 ~Collection() {
281 switch (ownership) {
283 break; // nothing to do.
284
286 storage.vector_owned.~vector(); // Deallocate the vector!
287 break;
288
289 default:
290 assert(false && "unreachable");
291 }
292 }
293
294 /// Returns the number of instances in this collection.
295 size_t size() const {
296 switch (ownership) {
298 return storage.borrowed.num_instances;
299
301 return storage.vector_owned.size();
302
303 default:
304 assert(false && "unreachable");
305 }
306 return 0;
307 }
308
309 /// Returns true if the collection is empty.
310 bool empty() const {
311 switch (ownership) {
313 return storage.borrowed.num_instances == 0;
314
316 return storage.vector_owned.empty();
317
318 default:
319 assert(false && "unreachable");
320 }
321 return 0;
322 }
323
324 /// Returns a raw pointer to the underlying data.
325 ///
326 /// Do not use this if the data is not continuous in memory!
327 /// TODO(#4257): So far it always is continuous, but in the future we want to support strides!
328 ///
329 /// The pointer is only valid as long as backing storage is alive
330 /// which is either until the collection is destroyed the borrowed source is destroyed/moved.
331 const TElement* data() const {
332 switch (ownership) {
334 return storage.borrowed.data;
335
337 return storage.vector_owned.data();
338
339 default:
340 assert(false && "unreachable");
341 }
342
343 // We need to return something to avoid compiler warnings.
344 // But if we don't mark this as unreachable, GCC will complain that we're dereferencing null down the line.
345 RR_UNREACHABLE();
346 // But with this in place, MSVC complains that the return statement is not reachable (GCC/clang on the other hand need it).
347#ifndef _MSC_VER
348 return nullptr;
349#endif
350 }
351
352 /// TODO(andreas): Return proper iterator
353 const TElement* begin() const {
354 return data();
355 }
356
357 /// TODO(andreas): Return proper iterator
358 const TElement* end() const {
359 return data() + size();
360 }
361
362 /// Random read access to the underlying data.
363 const TElement& operator[](size_t i) const {
364 assert(i < size());
365 return data()[i];
366 }
367
368 /// Returns the data ownership of collection.
369 ///
370 /// This is usually only needed for debugging and testing.
372 return ownership;
373 }
374
375 /// Copies the data into a new `std::vector`.
376 std::vector<TElement> to_vector() const& {
377 std::vector<TElement> result;
378 result.reserve(size());
379 result.insert(result.end(), begin(), end());
380 return result;
381 }
382
383 /// Copies the data into a new `std::vector`.
384 ///
385 /// If possible, this will move the underlying data.
386 std::vector<TElement> to_vector() && {
387 switch (ownership) {
389 std::vector<TElement> result;
390 result.reserve(size());
391 result.insert(result.end(), begin(), end());
392 return result;
393 }
394
396 // Ensure move constructor is called, so `storage.vector_owned` is in a valid state.
397 return std::move(storage.vector_owned);
398 }
399
400 default:
401 assert(false && "unreachable");
402 }
403 return std::vector<TElement>();
404 }
405
406 /// Reinterpret this collection as a collection of bytes.
408 switch (ownership) {
411 reinterpret_cast<const uint8_t*>(data()),
412 size() * sizeof(TElement)
413 );
414 }
415
417 auto ptr = reinterpret_cast<const uint8_t*>(data());
418 auto num_bytes = size() * sizeof(TElement);
420 std::vector<uint8_t>(ptr, ptr + num_bytes)
421 );
422 }
423
424 default:
425 assert(false && "unreachable");
426 }
427 return Collection<uint8_t>();
428 }
429
430 private:
431 template <typename T>
432 union CollectionStorage {
433 struct {
434 const T* data;
435 size_t num_instances;
436 } borrowed;
437
438 std::vector<T> vector_owned;
439
440 CollectionStorage() {
441 std::memset(reinterpret_cast<void*>(this), 0, sizeof(CollectionStorage));
442 }
443
444 ~CollectionStorage() {}
445 };
446
447 CollectionOwnership ownership;
448 CollectionStorage<TElement> storage;
449 };
450
451 // Convenience functions for creating typed collections via explicit borrow & ownership taking.
452 // These are useful to avoid having to specify the type of the collection.
453 // E.g. instead of `rerun::Collection<uint8_t>::borrow(data, num_instances)`,
454 // you can just write `rerun::borrow(data, num_instances)`.
455
456 /// Borrows binary data into a `Collection` from a pointer.
457 ///
458 /// Borrowed data must outlive the collection!
459 /// (If the pointer passed is into an std::vector or similar, this std::vector mustn't be
460 /// resized.)
461 /// The passed type must be binary compatible with the collection type.
462 ///
463 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
464 /// any function or operation taking on a `Collection`.
465 template <typename TElement>
466 inline Collection<TElement> borrow(const TElement* data, size_t num_instances = 1) {
467 return Collection<TElement>::borrow(data, num_instances);
468 }
469
470 /// Borrows binary data into the collection from a vector.
471 ///
472 /// Borrowed data must outlive the collection!
473 /// The referenced vector must not be resized and mustn't be temporary.
474 ///
475 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
476 /// any function or operation taking on a `rerun::Collection`.
477 template <typename TElement>
478 inline Collection<TElement> borrow(const std::vector<TElement>& data) {
479 return Collection<TElement>::borrow(data);
480 }
481
482 /// Takes ownership of a temporary `std::vector`, moving it into the collection.
483 ///
484 /// Takes ownership of the data and moves it into the collection.
485 template <typename TElement>
486 inline Collection<TElement> take_ownership(std::vector<TElement> data) {
487 return Collection<TElement>::take_ownership(std::move(data));
488 }
489
490 /// Takes ownership of a single element, moving it into the collection.
491 template <typename TElement>
492 inline Collection<TElement> take_ownership(TElement data) {
493 return Collection<TElement>::take_ownership(std::move(data));
494 }
495} // namespace rerun
496
497// Could keep this separately, but its very hard to use the collection without the basic suite of adapters.
498// Needs to know about `rerun::Collection` which means that it needs to be included after `rerun::Collection` is defined.
499// (it tried to include `Collection.hpp` but if that was our starting point that include wouldn't do anything)
500#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:363
static Collection< TElement > take_ownership(TElement &&data)
Takes ownership of a single element, moving it into the collection.
Definition collection.hpp:215
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:158
static Collection< TElement > take_ownership(std::vector< TElement > &&data)
Takes ownership of a temporary std::vector, moving it into the collection.
Definition collection.hpp:204
bool empty() const
Returns true if the collection is empty.
Definition collection.hpp:310
Collection(Collection< TElement > &&other)
Move constructor.
Definition collection.hpp:119
static Collection borrow(const std::vector< T > &data)
Borrows binary compatible data into the collection from a vector.
Definition collection.hpp:197
Collection()
Creates a new empty collection.
Definition collection.hpp:66
CollectionOwnership get_ownership() const
Returns the data ownership of collection.
Definition collection.hpp:371
const TElement * end() const
TODO(andreas): Return proper iterator.
Definition collection.hpp:358
void operator=(const Collection< TElement > &other)
Copy assignment.
Definition collection.hpp:109
const TElement * begin() const
TODO(andreas): Return proper iterator.
Definition collection.hpp:353
void swap(Collection< TElement > &other)
Swaps the content of this collection with another.
Definition collection.hpp:230
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:223
size_t size() const
Returns the number of instances in this collection.
Definition collection.hpp:295
void operator=(Collection< TElement > &&other)
Move assignment.
Definition collection.hpp:124
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:185
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:141
std::vector< TElement > to_vector() const &
Copies the data into a new std::vector.
Definition collection.hpp:376
Collection< uint8_t > to_uint8() const
Reinterpret this collection as a collection of bytes.
Definition collection.hpp:407
std::vector< TElement > to_vector() &&
Copies the data into a new std::vector.
Definition collection.hpp:386
const TElement * data() const
Returns a raw pointer to the underlying data.
Definition collection.hpp:331
All Rerun C++ types and functions are in the rerun namespace or one of its nested namespaces.
Definition rerun.hpp:23
Collection< TElement > take_ownership(std::vector< TElement > data)
Takes ownership of a temporary std::vector, moving it into the collection.
Definition collection.hpp:486
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:466
The rerun::CollectionAdapter trait is responsible for mapping an input argument to a rerun::Collectio...
Definition collection_adapter.hpp:25