Rerun C++ SDK
Loading...
Searching...
No Matches
collection.hpp
1#pragma once
2
3#include <algorithm>
4#include <cassert>
5#include <cstring> // std::memset
6#include <utility>
7#include <vector>
8
9#include "collection.hpp"
10#include "collection_adapter.hpp"
11#include "compiler_utils.hpp"
12
13namespace rerun {
14 /// Type of ownership of a collection's data.
15 ///
16 /// User access to this is typically only needed for debugging and testing.
18 /// The collection does not own the data and only has a pointer and a size.
20
21 /// The collection batch owns the data via an std::vector.
23 };
24
25 /// Generic collection of elements that are roughly contiguous in memory.
26 ///
27 /// The most notable feature of the `rerun::Collection` is that its data may be either **owned** or **borrowed**:
28 /// * Borrowed: If data is borrowed it *must* outlive its source (in particular, the pointer to
29 /// the source mustn't invalidate)
30 /// * Owned: Owned data is copied into an internal std::vector
31 /// TODO(#3794): don't do 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 /// Other than being assignable, collections are generally immutable:
38 /// there is no mutable data access in order to not violate the contract with the data lender
39 /// and changes in size are not possible.
40 ///
41 /// ## Implementation notes:
42 ///
43 /// Does intentionally not implement copy construction since this for the owned case this may
44 /// be expensive. Typically, there should be no need to copy rerun collections, so this more
45 /// than likely indicates a bug inside the Rerun SDK.
46 template <typename TElement>
47 class Collection {
48 public:
49 /// Type of the elements in the collection.
50 ///
51 /// Note that calling this `value_type` makes it compatible with the STL.
52 using value_type = TElement;
53
54 /// Type of an adapter given an input container type.
55 ///
56 /// Note that the "container" passed may also be a single element of something.
57 /// The only thing relevant is that there's an Adapter for it.
58 template <typename TContainer>
60 TElement, std::remove_cv_t<std::remove_reference_t<TContainer>>,
61 std::enable_if_t<true>>;
62
63 /// Creates a new empty collection.
65 storage.borrowed.data = nullptr;
66 storage.borrowed.num_instances = 0;
67 }
68
69 /// Construct using a `CollectionAdapter` for the given input type.
70 template <
71 typename TContainer, //
72 // Avoid conflicting with the copy/move constructor.
73 // We could implement this also with an adapter, but this might confuse trait checks like `std::is_copy_constructible`.
74 typename = std::enable_if_t<
75 !std::is_same_v<std::remove_reference_t<TContainer>, Collection<TElement>>> //
76 >
77 Collection(TContainer&& input)
78 : Collection(Adapter<TContainer>()(std::forward<TContainer>(input))) {}
79
80 /// Copy constructor.
81 ///
82 /// If the data is owned, this will copy the data.
83 /// If the data is borrowed, this will copy the borrow,
84 /// meaning there's now (at least) two collections borrowing the same data.
85 Collection(const Collection<TElement>& other) : ownership(other.ownership) {
86 switch (other.ownership) {
88 storage.borrowed = other.storage.borrowed;
89 break;
90 }
91
93 new (&storage.vector_owned) std::vector<TElement>(other.storage.vector_owned);
94 break;
95 }
96 }
97 }
98
99 /// Copy assignment.
100 ///
101 /// If the data is owned, this will copy the data.
102 /// If the data is borrowed, this will copy the borrow,
103 /// meaning there's now (at least) two collections borrowing the same data.
104 void operator=(const Collection<TElement>& other) {
105 this->~Collection<TElement>();
106 new (this) Collection(other);
107 }
108
109 /// Move constructor.
111 swap(other);
112 }
113
114 /// Move assignment.
116 // Need to disable the maybe-uninitialized here. It seems like the compiler may be confused in situations where
117 // we are assigning into an unused optional from a temporary. The fact that this hits the move-assignment without
118 // having called the move constructor is suspicious though and hints of an actual bug.
119 //
120 // See: https://github.com/rerun-io/rerun/issues/4027
121 RERUN_WITH_MAYBE_UNINITIALIZED_DISABLED(this->swap(other);)
122 }
123
124 /// Construct from a initializer list of elements that are compatible with TElement.
125 ///
126 /// Takes ownership of the passed elements.
127 /// If you want to avoid an allocation, you have to manually keep the data on the stack
128 /// (e.g. as `std::array`) and construct the collection from this instead.
129 ///
130 /// This is not done as a `CollectionAdapter` since it tends to cause deduction issues
131 /// (since there's special rules for overload resolution for initializer lists)
132 Collection(std::initializer_list<TElement> data)
133 : ownership(CollectionOwnership::VectorOwned) {
134 // Don't assign, since the vector is in an undefined state and assigning may
135 // attempt to free data.
136 new (&storage.vector_owned) std::vector<TElement>(data);
137 }
138
139 /// Borrows binary compatible data into the collection.
140 ///
141 /// Borrowed data must outlive the collection!
142 /// (If the pointer passed is into an std::vector or similar, this std::vector mustn't be
143 /// resized.)
144 /// The passed type must be binary compatible with the collection type.
145 ///
146 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
147 /// any function or operation taking on a `Collection`.
148 template <typename T>
149 static Collection<TElement> borrow(const T* data, size_t num_instances) {
150 static_assert(
151 sizeof(T) == sizeof(TElement),
152 "T & TElement are not binary compatible: Size mismatch."
153 );
154 static_assert(
155 alignof(T) <= alignof(TElement),
156 "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."
157 );
158
160 batch.ownership = CollectionOwnership::Borrowed;
161 batch.storage.borrowed.data = reinterpret_cast<const TElement*>(data);
162 batch.storage.borrowed.num_instances = num_instances;
163 return batch;
164 }
165
166 /// Borrows binary compatible data into the collection.
167 ///
168 /// Version of `borrow` that takes a void pointer, omitting any checks.
169 ///
170 /// Borrowed data must outlive the collection!
171 /// (If the pointer passed is into an std::vector or similar, this std::vector mustn't be
172 /// resized.)
173 ///
174 /// Since `rerun::Collection` does not provide write access, data is guaranteed to be unchanged by
175 /// any function or operation taking on a `rerun::Collection`.
176 static Collection borrow(const void* data, size_t num_instances) {
177 return borrow(reinterpret_cast<const TElement*>(data), num_instances);
178 }
179
180 /// Takes ownership of a temporary `std::vector`, moving it into the collection.
181 ///
182 /// Takes ownership of the data and moves it into the collection.
183 static Collection<TElement> take_ownership(std::vector<TElement>&& data) {
185 batch.ownership = CollectionOwnership::VectorOwned;
186 // Don't assign, since the vector is in an undefined state and assigning may
187 // attempt to free data.
188 new (&batch.storage.vector_owned) std::vector<TElement>(std::move(data));
189
190 return batch;
191 }
192
193 /// Takes ownership of a single element, moving it into the collection.
195 // TODO(andreas): there should be a special path here to avoid allocating a vector.
196 std::vector<TElement> elements;
197 elements.emplace_back(std::move(data));
198 return take_ownership(std::move(elements));
199 }
200
201 /// Swaps the content of this collection with another.
203 // (writing out this-> here to make it less confusing!)
204 switch (this->ownership) {
206 switch (other.ownership) {
208 std::swap(this->storage.borrowed, other.storage.borrowed);
209 break;
210
212 auto this_borrowed_data_old = this->storage.borrowed;
213 new (&this->storage.vector_owned)
214 std::vector<TElement>(std::move(other.storage.vector_owned));
215 other.storage.borrowed = this_borrowed_data_old;
216 break;
217 }
218 }
219 break;
220 }
221
223 switch (other.ownership) {
225 auto other_borrowed_data_old = other.storage.borrowed;
226 new (&other.storage.vector_owned)
227 std::vector<TElement>(std::move(this->storage.vector_owned));
228 this->storage.borrowed = other_borrowed_data_old;
229 break;
230 }
231
233 std::swap(storage.vector_owned, other.storage.vector_owned);
234 break;
235 }
236 break;
237 }
238 }
239
240 std::swap(ownership, other.ownership);
241 }
242
243 ~Collection() {
244 switch (ownership) {
246 break; // nothing to do.
248 storage.vector_owned.~vector(); // Deallocate the vector!
249 break;
250 }
251 }
252
253 /// Returns the number of instances in this collection.
254 size_t size() const {
255 switch (ownership) {
257 return storage.borrowed.num_instances;
259 return storage.vector_owned.size();
260 }
261 return 0;
262 }
263
264 /// Returns true if the collection is empty.
265 bool empty() const {
266 switch (ownership) {
268 return storage.borrowed.num_instances == 0;
270 return storage.vector_owned.empty();
271 }
272 return 0;
273 }
274
275 /// Returns a raw pointer to the underlying data.
276 ///
277 /// Do not use this if the data is not continuous in memory!
278 /// TODO(#4225): So far it always is continuous, but in the future we want to support strides!
279 ///
280 /// The pointer is only valid as long as backing storage is alive
281 /// which is either until the collection is destroyed the borrowed source is destroyed/moved.
282 const TElement* data() const {
283 switch (ownership) {
285 return storage.borrowed.data;
287 return storage.vector_owned.data();
288 }
289
290 // We need to return something to avoid compiler warnings.
291 // But if we don't mark this as unreachable, GCC will complain that we're dereferencing null down the line.
292 RERUN_UNREACHABLE();
293 // But with this in place, MSVC complains that the return statement is not reachable (GCC/clang on the other hand need it).
294#ifndef _MSC_VER
295 return nullptr;
296#endif
297 }
298
299 /// TODO(andreas): Return proper iterator
300 const TElement* begin() const {
301 return data();
302 }
303
304 /// TODO(andreas): Return proper iterator
305 const TElement* end() const {
306 return data() + size();
307 }
308
309 /// Random read access to the underlying data.
310 const TElement& operator[](size_t i) const {
311 assert(i < size());
312 return data()[i];
313 }
314
315 /// Returns the data ownership of collection.
316 ///
317 /// This is usually only needed for debugging and testing.
319 return ownership;
320 }
321
322 /// Copies the data into a new `std::vector`.
323 std::vector<TElement> to_vector() const {
324 // TODO(andreas): Overload this for `const &` and `&&` to avoid the copy when possible.
325 std::vector<TElement> result;
326 result.reserve(size());
327 result.insert(result.end(), begin(), end());
328 return result;
329 }
330
331 private:
332 template <typename T>
333 union CollectionStorage {
334 struct {
335 const T* data;
336 size_t num_instances;
337 } borrowed;
338
339 std::vector<T> vector_owned;
340
341 CollectionStorage() {
342 std::memset(reinterpret_cast<void*>(this), 0, sizeof(CollectionStorage));
343 }
344
345 ~CollectionStorage() {}
346 };
347
348 CollectionOwnership ownership;
349 CollectionStorage<TElement> storage;
350 };
351} // namespace rerun
352
353// Could keep this separately, but its very hard to use the collection without the basic suite of adapters.
354// Needs to know about `rerun::Collection` which means that it needs to be included after `rerun::Collection` is defined.
355// (it tried to include `Collection.hpp` but if that was our starting point that include wouldn't do anything)
356#include "collection_adapter_builtins.hpp"
Generic collection of elements that are roughly contiguous in memory.
Definition collection.hpp:47
const TElement & operator[](size_t i) const
Random read access to the underlying data.
Definition collection.hpp:310
static Collection< TElement > take_ownership(TElement &&data)
Takes ownership of a single element, moving it into the collection.
Definition collection.hpp:194
static Collection< TElement > take_ownership(std::vector< TElement > &&data)
Takes ownership of a temporary std::vector, moving it into the collection.
Definition collection.hpp:183
bool empty() const
Returns true if the collection is empty.
Definition collection.hpp:265
Collection(Collection< TElement > &&other)
Move constructor.
Definition collection.hpp:110
Collection()
Creates a new empty collection.
Definition collection.hpp:64
CollectionOwnership get_ownership() const
Returns the data ownership of collection.
Definition collection.hpp:318
const TElement * end() const
TODO(andreas): Return proper iterator.
Definition collection.hpp:305
void operator=(const Collection< TElement > &other)
Copy assignment.
Definition collection.hpp:104
static Collection borrow(const void *data, size_t num_instances)
Borrows binary compatible data into the collection.
Definition collection.hpp:176
const TElement * begin() const
TODO(andreas): Return proper iterator.
Definition collection.hpp:300
static Collection< TElement > borrow(const T *data, size_t num_instances)
Borrows binary compatible data into the collection.
Definition collection.hpp:149
void swap(Collection< TElement > &other)
Swaps the content of this collection with another.
Definition collection.hpp:202
TElement value_type
Type of the elements in the collection.
Definition collection.hpp:52
size_t size() const
Returns the number of instances in this collection.
Definition collection.hpp:254
void operator=(Collection< TElement > &&other)
Move assignment.
Definition collection.hpp:115
Collection(TContainer &&input)
Construct using a CollectionAdapter for the given input type.
Definition collection.hpp:77
Collection(const Collection< TElement > &other)
Copy constructor.
Definition collection.hpp:85
Collection(std::initializer_list< TElement > data)
Construct from a initializer list of elements that are compatible with TElement.
Definition collection.hpp:132
std::vector< TElement > to_vector() const
Copies the data into a new std::vector.
Definition collection.hpp:323
const TElement * data() const
Returns a raw pointer to the underlying data.
Definition collection.hpp:282
All Rerun C++ types and functions are in the rerun namespace or one of its nested namespaces.
Definition rerun.hpp:20
CollectionOwnership
Type of ownership of a collection's data.
Definition collection.hpp:17
@ 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.
The rerun::CollectionAdapter trait is responsible for mapping an input argument to a rerun::Collectio...
Definition collection_adapter.hpp:25