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