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