FastLED 3.10.8
Loading...
Searching...
No Matches
basic_vector.cpp.hpp
Go to the documentation of this file.
1// IWYU pragma: private
2
6
8#include "fl/stl/cstring.h" // fl::memcpy, fl::memmove, fl::memset
9#include "platforms/assert_defs.h" // FASTLED_ASSERT
10#include "fl/stl/noexcept.h"
11
12namespace fl {
13
14// Shared move-n loop over the per-type move_construct thunk, so each element
15// type does not need its own uninitialized_move_n thunk (#4794).
17 fl::size count) const FL_NO_EXCEPT {
18 FASTLED_ASSERT(mOps->move_construct != nullptr,
19 "vector: element type is not move-constructible");
20 fl::u8* d = static_cast<fl::u8*>(dst);
21 fl::u8* sp = static_cast<fl::u8*>(src);
22 for (fl::size i = 0; i < count; ++i) {
23 mOps->move_construct(d + i * mElementSize, sp + i * mElementSize);
24 }
25}
26
27// ======= TRIVIAL ELEMENT HELPERS =======
28
29void vector_basic::trivial_copy(void* dst, const void* src, fl::size count) const FL_NO_EXCEPT {
30 fl::memcpy(dst, src, count * mElementSize);
31}
32
33void vector_basic::trivial_move_left(void* dst, const void* src, fl::size count) const FL_NO_EXCEPT {
34 fl::memmove(dst, src, count * mElementSize);
35}
36
37void vector_basic::trivial_default_construct(void* ptr, fl::size count) const FL_NO_EXCEPT {
38 fl::memset(ptr, 0, count * mElementSize);
39}
40
41void vector_basic::trivial_swap(void* a, void* b) const FL_NO_EXCEPT {
42 // Stack buffer for small elements, heap for large
43 char stack_buf[64];
44 void* tmp;
45 bool heap_allocated = false;
46 if (mElementSize <= sizeof(stack_buf)) {
47 tmp = stack_buf;
48 } else {
49 tmp = mResource->allocate(mElementSize);
50 heap_allocated = true;
51 }
52 fl::memcpy(tmp, a, mElementSize);
54 fl::memcpy(b, tmp, mElementSize);
55 if (heap_allocated) {
56 mResource->deallocate(tmp, mElementSize);
57 }
58}
59
60// ======= CAPACITY / GROWTH =======
61
63 if (n <= mCapacity) return;
64 fl::size new_capacity = (3 * mCapacity) / 2;
65 if (new_capacity < n) {
66 new_capacity = n;
67 }
68 grow_to(new_capacity);
69}
70
71void vector_basic::grow_to(fl::size new_capacity) FL_NO_EXCEPT {
72 // PATH 1: Try in-place reallocate if on heap and trivially copyable
73 if (!isInline() && mArray && !mOps) {
74 void* result = mResource->reallocate(mArray, mCapacity * mElementSize,
75 new_capacity * mElementSize);
76 if (result) {
77 mArray = result;
78 mCapacity = new_capacity;
79 return;
80 }
81 }
82
83 // PATH 2: Allocate new buffer, move elements, free old
84 FASTLED_ASSERT(mResource != nullptr, "memory_resource is null");
85 fl::size alloc_bytes = new_capacity * mElementSize;
86 FASTLED_ASSERT(alloc_bytes > 0, "zero allocation in grow_to");
87 void* new_buf = mResource->allocate(alloc_bytes);
88 // Allocation failure is recoverable: callers detect unchanged capacity.
89 if (!new_buf) return;
90
91 // Move existing elements to new buffer
92 if (mSize > 0 && mArray) {
93 if (mOps) {
95 mOps->destroy_n(mArray, mSize);
96 } else {
97 trivial_copy(new_buf, mArray, mSize);
98 }
99 }
100
101 // Free old buffer (only if it's on the heap, not inline)
102 if (!isInline() && mArray) {
103 mResource->deallocate(mArray, mCapacity * mElementSize);
104 }
105
106 mArray = new_buf;
107 mCapacity = new_capacity;
108}
109
111 if (n > mCapacity) {
112 grow_to(n);
113 }
114}
115
117 if (mSize == 0) {
118 if (!isInline() && mArray) {
119 mResource->deallocate(mArray, mCapacity * mElementSize);
120 }
121 if (hasInlineBuffer()) {
124 } else {
125 mArray = nullptr;
126 mCapacity = 0;
127 }
128 return;
129 }
130
131 // If data fits in inline buffer and we're on heap, move back to inline
132 if (hasInlineBuffer() && !isInline() && mSize <= mInlineCapacity) {
133 void* inline_buf = inlineBufferPtr();
134 if (mOps) {
136 mOps->destroy_n(mArray, mSize);
137 } else {
138 trivial_copy(inline_buf, mArray, mSize);
139 }
140 mResource->deallocate(mArray, mCapacity * mElementSize);
141 mArray = inline_buf;
143 return;
144 }
145
146 // Shrink heap allocation
147 if (!isInline() && mCapacity > mSize) {
148 void* new_buf = mResource->allocate(mSize * mElementSize);
149 if (new_buf) {
150 if (mOps) {
152 mOps->destroy_n(mArray, mSize);
153 } else {
154 trivial_copy(new_buf, mArray, mSize);
155 }
156 mResource->deallocate(mArray, mCapacity * mElementSize);
157 mArray = new_buf;
159 }
160 }
161}
162
163// ======= PUSH / POP =======
164
167 if (mSize >= mCapacity) return; // allocation failed
168 void* dst = element_ptr(mSize);
169 if (mOps) {
170 mOps->copy_construct(dst, element);
171 } else {
172 fl::memcpy(dst, element, mElementSize);
173 }
174 ++mSize;
175}
176
179 if (mSize >= mCapacity) return; // allocation failed
180 FASTLED_ASSERT(mArray != nullptr, "mArray null after ensure_capacity in push_back_move");
181 void* dst = element_ptr(mSize);
182 if (mOps) {
183 mOps->move_construct(dst, element);
184 } else {
185 fl::memcpy(dst, element, mElementSize);
186 }
187 ++mSize;
188}
189
191 if (mSize == 0) return;
192 --mSize;
193 if (mOps) {
194 mOps->destroy_n(element_ptr(mSize), 1);
195 }
196 // For trivial types, nothing to destroy
197}
198
199// ======= CLEAR =======
200
202 if (mArray && mSize > 0) {
203 if (mOps) {
204 mOps->destroy_n(mArray, mSize);
205 }
206 // For trivial types, nothing to destroy
207 }
208 mSize = 0;
209}
210
211// ======= ERASE =======
212
214 if (index >= mSize) return;
215 erase_range_impl(index, 1);
216}
217
218void vector_basic::erase_range_impl(fl::size first_index, fl::size count) FL_NO_EXCEPT {
219 if (count == 0 || first_index >= mSize) return;
220 if (first_index + count > mSize) {
221 count = mSize - first_index;
222 }
223
224 fl::size remaining = mSize - first_index - count;
225
226 if (mOps) {
227 // Destroy erased elements
228 mOps->destroy_n(element_ptr(first_index), count);
229 // Shift remaining elements left one at a time
230 for (fl::size i = 0; i < remaining; ++i) {
231 void* dst = element_ptr(first_index + i);
232 void* src = element_ptr(first_index + count + i);
233 mOps->move_construct(dst, src);
234 mOps->destroy_n(src, 1);
235 }
236 } else {
237 // Trivial: memmove the remaining elements left
238 if (remaining > 0) {
239 trivial_move_left(element_ptr(first_index),
240 element_ptr(first_index + count),
241 remaining);
242 }
243 }
244 mSize -= count;
245}
246
247// ======= INSERT =======
248
249void vector_basic::insert_copy_impl(fl::size index, const void* element) FL_NO_EXCEPT {
250 if (index > mSize) index = mSize;
252 if (mSize >= mCapacity) return; // allocation failed
253
254 if (mOps) {
255 // Shift elements right, starting from the end
256 if (mSize > index) {
257 // Move-construct the last element into uninitialized space
258 mOps->move_construct(element_ptr(mSize), element_ptr(mSize - 1));
259 // Move-assign backwards for the rest
260 for (fl::size i = mSize - 1; i > index; --i) {
261 // Destroy dst, then move-construct from src
262 mOps->destroy_n(element_ptr(i), 1);
263 mOps->move_construct(element_ptr(i), element_ptr(i - 1));
264 }
265 // Destroy the slot and copy-construct the new element
266 mOps->destroy_n(element_ptr(index), 1);
267 }
268 mOps->copy_construct(element_ptr(index), element);
269 } else {
270 // Trivial: memmove right, then memcpy
271 if (mSize > index) {
272 trivial_move_left(element_ptr(index + 1), element_ptr(index),
273 mSize - index);
274 }
275 fl::memcpy(element_ptr(index), element, mElementSize);
276 }
277 ++mSize;
278}
279
280void vector_basic::insert_move_impl(fl::size index, void* element) FL_NO_EXCEPT {
281 if (index > mSize) index = mSize;
283 if (mSize >= mCapacity) return; // allocation failed
284
285 if (mOps) {
286 // Shift elements right, starting from the end
287 if (mSize > index) {
288 mOps->move_construct(element_ptr(mSize), element_ptr(mSize - 1));
289 for (fl::size i = mSize - 1; i > index; --i) {
290 mOps->destroy_n(element_ptr(i), 1);
291 mOps->move_construct(element_ptr(i), element_ptr(i - 1));
292 }
293 mOps->destroy_n(element_ptr(index), 1);
294 }
295 mOps->move_construct(element_ptr(index), element);
296 } else {
297 if (mSize > index) {
298 trivial_move_left(element_ptr(index + 1), element_ptr(index),
299 mSize - index);
300 }
301 fl::memcpy(element_ptr(index), element, mElementSize);
302 }
303 ++mSize;
304}
305
306// ======= RESIZE =======
307
309 if (n == mSize) return;
310
311 if (n < mSize) {
312 // Shrink: destroy excess elements
313 if (mOps) {
314 mOps->destroy_n(element_ptr(n), mSize - n);
315 }
316 mSize = n;
317 return;
318 }
319
320 // Grow: ensure capacity and default-construct new elements
322 if (mCapacity < n) return; // allocation failed
323
324 if (mOps) {
325 if (mOps->default_construct) {
326 for (fl::size i = mSize; i < n; ++i) {
327 mOps->default_construct(element_ptr(i));
328 }
329 } else {
330 // Type has no default constructor — zero-fill as fallback
332 }
333 } else {
335 }
336 mSize = n;
337}
338
340 if (n == mSize) return;
341
342 if (n < mSize) {
343 if (mOps) {
344 mOps->destroy_n(element_ptr(n), mSize - n);
345 }
346 mSize = n;
347 return;
348 }
349
351 if (mCapacity < n) return;
352
353 if (mOps) {
354 for (fl::size i = mSize; i < n; ++i) {
355 mOps->copy_construct(element_ptr(i), value);
356 }
357 } else {
358 for (fl::size i = mSize; i < n; ++i) {
360 }
361 }
362 mSize = n;
363}
364
365// ======= SWAP =======
366
368 // Swap all members. This works correctly even with inline buffers
369 // because we swap the offset too, and the inline data stays in place.
370 // HOWEVER: if either vector uses inline storage, we need to handle
371 // it carefully since inline buffers can't be swapped by pointer.
372
373 bool this_inline = isInline();
374 bool other_inline = other.isInline();
375
376 if (!this_inline && !other_inline) {
377 // Both on heap: just swap pointers and metadata
378 void* tmp_array = mArray;
379 mArray = other.mArray;
380 other.mArray = tmp_array;
381
382 fl::size tmp_cap = mCapacity;
383 mCapacity = other.mCapacity;
384 other.mCapacity = tmp_cap;
385 } else if (this_inline && other_inline) {
386 // Both inline: swap element data in-place
387 fl::size max_size = mSize > other.mSize ? mSize : other.mSize;
388 for (fl::size i = 0; i < max_size; ++i) {
389 if (i < mSize && i < other.mSize) {
390 if (mOps) {
391 mOps->swap_elements(element_ptr(i), other.element_ptr(i));
392 } else {
393 trivial_swap(element_ptr(i), other.element_ptr(i));
394 }
395 } else if (i < mSize) {
396 // Move this[i] to other[i], destroy this[i]
397 if (mOps) {
398 mOps->move_construct(other.element_ptr(i), element_ptr(i));
399 mOps->destroy_n(element_ptr(i), 1);
400 } else {
401 fl::memcpy(other.element_ptr(i), element_ptr(i), mElementSize);
402 }
403 } else {
404 // Move other[i] to this[i], destroy other[i]
405 if (mOps) {
406 mOps->move_construct(element_ptr(i), other.element_ptr(i));
407 mOps->destroy_n(other.element_ptr(i), 1);
408 } else {
409 fl::memcpy(element_ptr(i), other.element_ptr(i), mElementSize);
410 }
411 }
412 }
413 } else if (this_inline) {
414 // this is inline, other is on heap
415 // Move this's inline data to a temp, take other's heap pointer,
416 // move temp data to other's inline buffer
417 void* other_array = other.mArray;
418 fl::size other_cap = other.mCapacity;
419
420 // Move this's elements to other's inline buffer
421 if (other.hasInlineBuffer() && mSize <= other.mInlineCapacity) {
422 other.mArray = other.inlineBufferPtr();
423 other.mCapacity = other.mInlineCapacity;
424 if (mOps) {
425 uninitialized_move_n_impl(other.mArray, mArray, mSize);
426 mOps->destroy_n(mArray, mSize);
427 } else {
428 if (mSize > 0) trivial_copy(other.mArray, mArray, mSize);
429 }
430 } else {
431 // Other has no inline buffer or data doesn't fit - allocate heap
432 other.mArray = other.mResource->allocate(mSize * mElementSize);
433 other.mCapacity = mSize;
434 if (mOps) {
435 uninitialized_move_n_impl(other.mArray, mArray, mSize);
436 mOps->destroy_n(mArray, mSize);
437 } else {
438 if (mSize > 0) trivial_copy(other.mArray, mArray, mSize);
439 }
440 }
441
442 // This takes other's heap pointer
443 mArray = other_array;
444 mCapacity = other_cap;
445 } else {
446 // other is inline, this is on heap - mirror of above
447 other.swap_impl(*this);
448 return;
449 }
450
451 // Swap sizes
452 fl::size tmp_size = mSize;
453 mSize = other.mSize;
454 other.mSize = tmp_size;
455
456 // Swap resources
457 memory_resource* tmp_res = mResource;
458 mResource = other.mResource;
459 other.mResource = tmp_res;
460}
461
462// ======= COPY / MOVE HELPERS =======
463
465 clear_impl();
466 if (other.mSize == 0) return;
467
468 reserve_impl(other.mSize);
469 if (mCapacity < other.mSize) return;
470
471 if (mOps) {
472 for (fl::size i = 0; i < other.mSize; ++i) {
473 mOps->copy_construct(element_ptr(i), other.element_ptr(i));
474 }
475 } else {
476 trivial_copy(mArray, other.mArray, other.mSize);
477 }
478 mSize = other.mSize;
479}
480
482 if (other.isInline()) {
483 // Can't steal inline buffer — must move elements
484 reserve_impl(other.mSize);
485 if (mOps) {
486 uninitialized_move_n_impl(mArray, other.mArray, other.mSize);
487 } else {
488 if (other.mSize > 0) {
489 trivial_copy(mArray, other.mArray, other.mSize);
490 }
491 }
492 mSize = other.mSize;
493 mResource = other.mResource;
494 other.mSize = 0;
495 } else {
496 // Steal heap pointer
497 mArray = other.mArray;
498 mSize = other.mSize;
499 mCapacity = other.mCapacity;
500 mResource = other.mResource;
501
502 // Leave other in valid empty state
503 if (other.hasInlineBuffer()) {
504 other.mArray = other.inlineBufferPtr();
505 other.mCapacity = other.mInlineCapacity;
506 } else {
507 other.mArray = nullptr;
508 other.mCapacity = 0;
509 }
510 other.mSize = 0;
511 }
512}
513
515 if (this == &other) return;
516 clear_impl();
517 if (!isInline() && mArray) {
518 mResource->deallocate(mArray, mCapacity * mElementSize);
519 mArray = nullptr;
520 mCapacity = 0;
521 }
522 if (hasInlineBuffer()) {
525 }
526 move_from(other);
527}
528
529// ======= DESTRUCTOR =======
530
532 clear_impl();
533 if (!isInline() && mArray) {
534 mResource->deallocate(mArray, mCapacity * mElementSize);
535 }
536}
537
538} // namespace fl
Type-erased base class for fl::vector<T>.
Polymorphic memory resource base class (PMR-style).
~vector_basic() FL_NO_EXCEPT
void move_assign(vector_basic &other) FL_NO_EXCEPT
Move-assign from another vector_basic (clears this first).
bool hasInlineBuffer() const FL_NO_EXCEPT
Does this vector have an inline buffer at all?
const vector_element_ops * mOps
void trivial_swap(void *a, void *b) const FL_NO_EXCEPT
void trivial_copy(void *dst, const void *src, fl::size count) const FL_NO_EXCEPT
void ensure_capacity(fl::size n) FL_NO_EXCEPT
Ensure capacity for at least n elements. Grows if needed.
void * element_ptr(fl::size index) FL_NO_EXCEPT
Pointer to element at index (byte arithmetic).
void * inlineBufferPtr() FL_NO_EXCEPT
Compute inline buffer pointer from offset.
void resize_impl(fl::size n) FL_NO_EXCEPT
Resize to n elements. New elements are default-constructed (zeroed for trivial).
void resize_value_impl(fl::size n, const void *value) FL_NO_EXCEPT
Resize to n elements. New elements are copy-constructed from value.
void push_back_move_impl(void *element) FL_NO_EXCEPT
bool isInline() const FL_NO_EXCEPT
Is data currently in the inline buffer?
void shrink_to_fit_impl() FL_NO_EXCEPT
fl::size mElementSize
void push_back_copy_impl(const void *element) FL_NO_EXCEPT
void grow_to(fl::size new_capacity) FL_NO_EXCEPT
Grow to new_capacity. Moves existing elements.
void insert_move_impl(fl::size index, void *element) FL_NO_EXCEPT
Insert element at index by move. Shifts subsequent elements right.
void copy_from(const vector_basic &other) FL_NO_EXCEPT
Copy all elements from another vector_basic.
memory_resource * mResource
void trivial_default_construct(void *ptr, fl::size count) const FL_NO_EXCEPT
void swap_impl(vector_basic &other) FL_NO_EXCEPT
Swap contents with another vector_basic.
void uninitialized_move_n_impl(void *dst, void *src, fl::size count) const FL_NO_EXCEPT
void trivial_move_left(void *dst, const void *src, fl::size count) const FL_NO_EXCEPT
vector_basic(fl::size elementSize, memory_resource *resource, const vector_element_ops *ops) FL_NO_EXCEPT
Heap-only vector (no inline buffer).
void clear_impl() FL_NO_EXCEPT
void reserve_impl(fl::size n) FL_NO_EXCEPT
fl::size mInlineCapacity
void erase_impl(fl::size index) FL_NO_EXCEPT
Erase element at index. Shifts subsequent elements left.
void erase_range_impl(fl::size first_index, fl::size count) FL_NO_EXCEPT
Erase range [first_index, first_index + count).
void move_from(vector_basic &other) FL_NO_EXCEPT
Move-steal contents from another vector_basic.
void pop_back_impl() FL_NO_EXCEPT
void insert_copy_impl(fl::size index, const void *element) FL_NO_EXCEPT
Insert element at index by copy. Shifts subsequent elements right.
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
constexpr int type_rank< T >::value
FASTLED_FORCE_INLINE void * memset(void *s, int c, size_t n) FL_NO_EXCEPT
Definition cstring.h:148
FASTLED_FORCE_INLINE void * memmove(void *dest, const void *src, size_t n) FL_NO_EXCEPT
Definition cstring.h:144
FASTLED_FORCE_INLINE void * memcpy(void *dest, const void *src, size_t n) FL_NO_EXCEPT
Definition cstring.h:136
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
Base definition for an LED controller.
Definition crgb.hpp:179