LCOV - code coverage report
Current view: top level - storage - Soa.hpp (source / functions) Coverage Total Hit
Test: coverage_clean.info Lines: 93.6 % 140 131
Test Date: 2026-03-30 03:13:12 Functions: 94.7 % 76 72

            Line data    Source code
       1              : #pragma once
       2              : #include <cstdint>
       3              : #include <utility>
       4              : #include <vector>
       5              : 
       6              : // =============================================================================
       7              : //  SoABase<Derived, IdType, Capacity, GenerationBits>
       8              : //
       9              : //  CRTP base that provides all bookkeeping and methods for a fixed-capacity
      10              : //  SoA. The concrete struct (generated by DEFINE_SOA) only needs to:
      11              : //    1. Declare named data arrays as plain members.
      12              : //    2. Implement swap_elements(a, b) using those members.
      13              : //
      14              : //  The user never sees this file. They interact only with the struct produced
      15              : //  by DEFINE_SOA and access fields directly:
      16              : //
      17              : //    bc.position[i]        — in solver loops  (i = packed data index)
      18              : //    bc.position[bc.index_of(id)]  — single-body access by stable ID
      19              : // =============================================================================
      20              : 
      21              : /**
      22              :  * @brief CRTP base for fixed-capacity Structure-of-Arrays containers.
      23              :  *
      24              :  * Provides stable versioned IDs, O(1) add/remove/lookup, and iteration helpers.
      25              :  * Concrete structs are generated via DEFINE_SOA; this base is not used
      26              :  * directly.
      27              :  *
      28              :  * @tparam Derived        The generated concrete struct (CRTP pattern).
      29              :  * @tparam IdType         Unsigned integer type for IDs (e.g. uint32_t).
      30              :  * @tparam Capacity       Compile-time maximum number of live items.
      31              :  * @tparam GenerationBits Bits of IdType reserved for the generation counter.
      32              :  *                        Set to 0 to disable versioning entirely.
      33              :  */
      34              : template <typename Derived, typename IdType, IdType Capacity,
      35              :           uint8_t GenerationBits>
      36              : struct SoABase
      37              : {
      38              :   // ── Compile-time helpers ─────────────────────────────────────────────────
      39              : 
      40              :   static constexpr uint8_t SLOT_BITS = sizeof(IdType) * 8 - GenerationBits;
      41              : 
      42              :   static constexpr IdType SLOT_MASK =
      43              :       (GenerationBits == 0) ? ~IdType(0)
      44              :                             : ((IdType(1) << SLOT_BITS) - IdType(1));
      45              : 
      46              :   static constexpr IdType INVALID = ~IdType(0);
      47              : 
      48              :   static_assert(Capacity <= SLOT_MASK + IdType(1),
      49              :                 "SoABase: Capacity exceeds the addressable slot range for the "
      50              :                 "given GenerationBits.");
      51              : 
      52              :   // ── Bookkeeping ───────────────────────────────────────────────────────────
      53              : 
      54              :   IdType _size = 0;
      55              :   IdType _next_slot = 0;
      56              :   IdType _n_free = 0;
      57              : 
      58              :   IdType _data_index[Capacity]; ///< slot  → packed data index (or INVALID)
      59              :   IdType _ids[Capacity];        ///< packed data index → full versioned ID
      60              :   IdType _free_slots[Capacity]; ///< recycled slot stack
      61              :   IdType _generation[Capacity]; ///< slot → generation counter
      62              : 
      63           27 :   SoABase()
      64           27 :   {
      65         1755 :     for (IdType i = 0; i < Capacity; ++i)
      66         1728 :       _generation[i] = 0;
      67           27 :   }
      68              : 
      69              :   // ── CRTP accessor ─────────────────────────────────────────────────────────
      70              : 
      71           19 :   Derived &self() { return *static_cast<Derived *>(this); }
      72              :   const Derived &self() const { return *static_cast<const Derived *>(this); }
      73              : 
      74              :   // ── add ───────────────────────────────────────────────────────────────────
      75              : 
      76              :   /**
      77              :    * @brief Allocate a new item and return its stable ID.
      78              :    *
      79              :    * All fields at the new packed slot are zero-initialised (the concrete
      80              :    * struct's arrays are value-initialised at construction; add() only
      81              :    * advances the bookkeeping, so callers should write fields immediately).
      82              :    *
      83              :    * @return Stable versioned ID. Store it to reference this item later.
      84              :    */
      85           81 :   IdType add()
      86              :   {
      87           81 :     IdType slot = (_n_free > 0) ? _free_slots[--_n_free] : _next_slot++;
      88           81 :     IdType data_idx = _size;
      89           81 :     IdType gen = _generation[slot];
      90           81 :     IdType id = make_id(slot, gen);
      91              : 
      92           81 :     _data_index[slot] = data_idx;
      93           81 :     _ids[data_idx] = id;
      94              : 
      95           81 :     ++_size;
      96           81 :     return id;
      97              :   }
      98              : 
      99              :   // ── emplace ───────────────────────────────────────────────────────────────
     100              : 
     101              :   /**
     102              :    * @brief Allocate a new item and immediately write fields via a callback.
     103              :    *
     104              :    * @tparam Fn  Callable with signature `void(IdType id, Derived& self)`.
     105              :    * @param  fn  Called right after allocation with the new ID and the
     106              :    * collection.
     107              :    * @return     Stable versioned ID.
     108              :    *
     109              :    * @par Example
     110              :    * @code
     111              :    *   bc.emplace([&](uint32_t id, auto& c) {
     112              :    *       uint32_t i    = c.index_of(id);
     113              :    *       c.position[i] = {0, 5, 0};
     114              :    *       c.mass[i]     = 2.0f;
     115              :    *   });
     116              :    * @endcode
     117              :    */
     118              :   template <typename Fn>
     119            2 :   IdType emplace(Fn &&fn)
     120              :   {
     121            2 :     IdType id = add();
     122            2 :     fn(id, self());
     123            2 :     return id;
     124              :   }
     125              : 
     126              :   // ── add_n ─────────────────────────────────────────────────────────────────
     127              : 
     128              :   /**
     129              :    * @brief Bulk-allocate @p n items, calling @p on_added for each.
     130              :    *
     131              :    * @tparam Fn       Callable with signature `void(IdType id)`.
     132              :    * @param  n        Number of items to allocate.
     133              :    * @param  on_added Called once per new item.
     134              :    */
     135              :   template <typename Fn>
     136            8 :   void add_n(IdType n, Fn &&on_added)
     137              :   {
     138           42 :     for (IdType i = 0; i < n; ++i)
     139           34 :       on_added(add());
     140            8 :   }
     141              : 
     142              :   // ── remove ────────────────────────────────────────────────────────────────
     143              : 
     144              :   /**
     145              :    * @brief Remove an item in O(1) via swap-and-pop.
     146              :    *
     147              :    * The last live item slides into the freed slot; data order is NOT preserved.
     148              :    * Stale or already-removed IDs are silently ignored.
     149              :    *
     150              :    * @param id  Stable ID returned by add() or emplace().
     151              :    */
     152           18 :   void remove(IdType id)
     153              :   {
     154           18 :     if (!_size)
     155            1 :       return;
     156              : 
     157           17 :     const IdType slot = slot_of(id);
     158           17 :     if (slot >= _next_slot)
     159            0 :       return;
     160              : 
     161           17 :     const IdType idx = _data_index[slot];
     162           17 :     if (idx >= _size)
     163            0 :       return;
     164              : 
     165              :     if constexpr (GenerationBits > 0)
     166           17 :       if (gen_of(_ids[idx]) != gen_of(id))
     167            0 :         return;
     168              : 
     169           17 :     const IdType last_idx = _size - 1;
     170           17 :     const IdType last_id = _ids[last_idx];
     171           17 :     const IdType last_slot = slot_of(last_id);
     172              : 
     173           17 :     self().swap_elements(idx, last_idx); // CRTP call into concrete struct
     174           17 :     _ids[idx] = _ids[last_idx];
     175              : 
     176           17 :     _data_index[last_slot] = idx;
     177           17 :     _data_index[slot] = INVALID;
     178              : 
     179              :     if constexpr (GenerationBits > 0)
     180           17 :       ++_generation[slot];
     181              : 
     182           17 :     _free_slots[_n_free++] = slot;
     183           17 :     --_size;
     184              :   }
     185              : 
     186              :   // ── index_of ─────────────────────────────────────────────────────────────
     187              : 
     188              :   /**
     189              :    * @brief Return the packed data index for a stable ID.
     190              :    *
     191              :    * Use this when you need to write or read a single item by ID outside
     192              :    * of a full iteration loop.
     193              :    *
     194              :    * No bounds-checking is performed. Call contains() first if the ID may be
     195              :    * stale.
     196              :    *
     197              :    * @param id  Stable ID.
     198              :    * @return    Packed array index in [0, count()).
     199              :    *
     200              :    * @par Example
     201              :    * @code
     202              :    *   uint32_t i    = bc.index_of(ball);
     203              :    *   bc.position[i] = {1, 2, 3};
     204              :    * @endcode
     205              :    */
     206           52 :   IdType index_of(IdType id) const { return _data_index[slot_of(id)]; }
     207              : 
     208              :   // ── contains ──────────────────────────────────────────────────────────────
     209              : 
     210              :   /**
     211              :    * @brief Return true if @p id refers to a currently live item.
     212              :    *
     213              :    * @param id  ID to test (may be stale or out-of-range).
     214              :    */
     215           25 :   bool contains(IdType id) const
     216              :   {
     217           25 :     const IdType slot = slot_of(id);
     218           25 :     if (slot >= _next_slot)
     219            3 :       return false;
     220           22 :     const IdType idx = _data_index[slot];
     221           22 :     if (idx >= _size)
     222            2 :       return false;
     223              :     if constexpr (GenerationBits > 0)
     224           20 :       return gen_of(_ids[idx]) == gen_of(id);
     225              :     return true;
     226              :   }
     227              : 
     228              :   // ── Iteration ─────────────────────────────────────────────────────────────
     229              : 
     230              :   /**
     231              :    * @brief Iterate over all live items by packed data index.
     232              :    *
     233              :    * @tparam Fn  Callable with signature `void(IdType data_index)`.
     234              :    */
     235              :   template <typename Fn>
     236            2 :   void for_each_index(Fn &&fn)
     237              :   {
     238            9 :     for (IdType i = 0; i < _size; ++i)
     239            7 :       fn(i);
     240            2 :   }
     241              : 
     242              :   /// @copydoc for_each_index
     243              :   template <typename Fn>
     244              :   void for_each_index(Fn &&fn) const
     245              :   {
     246              :     for (IdType i = 0; i < _size; ++i)
     247              :       fn(i);
     248              :   }
     249              : 
     250              :   /**
     251              :    * @brief Iterate over all live items by stable versioned ID.
     252              :    *
     253              :    * @tparam Fn  Callable with signature `void(IdType id)`.
     254              :    */
     255              :   template <typename Fn>
     256            1 :   void for_each_id(Fn &&fn)
     257              :   {
     258            5 :     for (IdType i = 0; i < _size; ++i)
     259            4 :       fn(_ids[i]);
     260            1 :   }
     261              : 
     262              :   /// @copydoc for_each_id
     263              :   template <typename Fn>
     264              :   void for_each_id(Fn &&fn) const
     265              :   {
     266              :     for (IdType i = 0; i < _size; ++i)
     267              :       fn(_ids[i]);
     268              :   }
     269              : 
     270              :   // ── Misc ──────────────────────────────────────────────────────────────────
     271              : 
     272              :   /// @brief Return the number of currently live items.
     273           13 :   IdType count() const { return _size; }
     274              : 
     275              :   /// @brief Return the compile-time maximum capacity.
     276              :   static constexpr IdType max_capacity() { return Capacity; }
     277              : 
     278              :   /**
     279              :    * @brief Remove all items. Generation counters are preserved so old IDs stay
     280              :    * stale.
     281              :    */
     282            3 :   void clear()
     283              :   {
     284            3 :     _size = 0;
     285            3 :     _n_free = 0;
     286            3 :     _next_slot = 0;
     287            3 :   }
     288              : 
     289              :   // ── ID helpers ────────────────────────────────────────────────────────────
     290              : 
     291          111 :   static constexpr IdType slot_of(IdType id) { return id & SLOT_MASK; }
     292           74 :   static constexpr IdType gen_of(IdType id) { return id >> SLOT_BITS; }
     293           81 :   static constexpr IdType make_id(IdType slot, IdType g)
     294              :   {
     295           81 :     return slot | (g << SLOT_BITS);
     296              :   }
     297              : };
     298              : 
     299              : // =============================================================================
     300              : //  DEFINE_SOA(Name, IdType, Capacity, GenBits, FIELDS_MACRO)
     301              : // =============================================================================
     302              : 
     303              : /**
     304              :  * @brief Define a named SoA struct with direct field access.
     305              :  *
     306              :  * Generates a struct where every field from FIELDS_MACRO becomes a plain
     307              :  * array member, giving clean `bc.position[i]` access in loops.
     308              :  *
     309              :  * @param Name         Name of the generated struct (e.g. BodyCollection).
     310              :  * @param IdType       Integer type for IDs (e.g. uint32_t).
     311              :  * @param Cap          Compile-time max item count.
     312              :  * @param GenBits      Bits reserved for the generation counter (0 = disabled).
     313              :  * @param FIELDS_MACRO X-macro listing fields as X(Type, name).
     314              :  *
     315              :  * @par Minimal example
     316              :  * @code
     317              :  *   #define PARTICLE_FIELDS(X) \
     318              :  *       X(vec3,   position)    \
     319              :  *       X(vec3,   velocity)    \
     320              :  *       X(float,  mass)
     321              :  *
     322              :  *   DEFINE_SOA(ParticleCollection, uint32_t, 1024, 8, PARTICLE_FIELDS)
     323              :  *
     324              :  *   ParticleCollection pc;
     325              :  *   uint32_t id = pc.add();
     326              :  *   uint32_t i  = pc.index_of(id);
     327              :  *   pc.position[i] = {0, 5, 0};
     328              :  *   pc.mass[i]     = 1.0f;
     329              :  *
     330              :  *   // Solver loop — same as the old struct-of-vectors style
     331              :  *   for (uint32_t i = 0; i < pc.count(); ++i)
     332              :  *       pc.position[i] += pc.velocity[i] * dt;
     333              :  *
     334              :  *   pc.remove(id);
     335              :  *   pc.contains(id); // false
     336              :  * @endcode
     337              :  */
     338              : #define DEFINE_SOA(Name, IdType, Cap, GenBits, FIELDS_MACRO)                 \
     339              :   struct Name : SoABase<Name, IdType, Cap, GenBits>                          \
     340              :   {                                                                          \
     341              :     /* Make Cap available as a plain name inside the struct body so that     \
     342              :        DEFINE_SOA_ARRAY_ can use it as an array bound. Template parameters   \
     343              :        of the base class are NOT directly in scope in the derived struct. */ \
     344              :     static constexpr IdType _cap = Cap;                                      \
     345              :                                                                              \
     346              :     /* ── Named data arrays ── */                                            \
     347              :     FIELDS_MACRO(DEFINE_SOA_ARRAY_)                                          \
     348              :                                                                              \
     349              :     /* ── swap_elements: called by SoABase::remove() via CRTP ── */          \
     350              :     void swap_elements(IdType _a, IdType _b)                                 \
     351              :     {                                                                        \
     352              :       using std::swap;                                                       \
     353              :       FIELDS_MACRO(DEFINE_SOA_SWAP_)                                         \
     354              :     }                                                                        \
     355              :   };
     356              : 
     357              : // Per-field helpers consumed by DEFINE_SOA — not for direct use.
     358              : // _cap is the constexpr injected above; it is in scope here because
     359              : // DEFINE_SOA_ARRAY_ is always expanded inside the struct body.
     360              : #define DEFINE_SOA_ARRAY_(T, name) T name[_cap];
     361              : #define DEFINE_SOA_SWAP_(T, name) swap(name[_a], name[_b]);
     362              : 
     363              : // =============================================================================
     364              : //  DEFINE_DYN_SOA(Name, IdType, GenBits, FIELDS_MACRO)
     365              : // =============================================================================
     366              : 
     367              : /**
     368              :  * @brief CRTP base for dynamically-allocated Structure-of-Arrays containers.
     369              :  *
     370              :  * Mirrors SoABase exactly, but all bookkeeping arrays are std::vector.
     371              :  * Capacity grows at runtime; call reserve() to pre-allocate.
     372              :  *
     373              :  * @tparam Derived        The generated concrete struct.
     374              :  * @tparam IdType         Unsigned integer type for IDs.
     375              :  * @tparam GenerationBits Bits reserved for the generation counter.
     376              :  */
     377              : template <typename Derived, typename IdType, uint8_t GenerationBits>
     378              : struct DynSoABase
     379              : {
     380              :   static constexpr uint8_t SLOT_BITS = sizeof(IdType) * 8 - GenerationBits;
     381              :   static constexpr IdType SLOT_MASK =
     382              :       (GenerationBits == 0) ? ~IdType(0)
     383              :                             : ((IdType(1) << SLOT_BITS) - IdType(1));
     384              :   static constexpr IdType INVALID = ~IdType(0);
     385              : 
     386              :   IdType _size = 0;
     387              :   IdType _next_slot = 0;
     388              :   IdType _n_free = 0;
     389              : 
     390              :   std::vector<IdType> _data_index;
     391              :   std::vector<IdType> _ids;
     392              :   std::vector<IdType> _free_slots;
     393              :   std::vector<IdType> _generation;
     394              : 
     395          326 :   explicit DynSoABase(IdType initial_capacity = 0)
     396          326 :   {
     397          326 :     if (initial_capacity > 0)
     398            0 :       _reserve_bookkeeping(initial_capacity);
     399          326 :   }
     400              : 
     401          716 :   Derived &self() { return *static_cast<Derived *>(this); }
     402              :   const Derived &self() const { return *static_cast<const Derived *>(this); }
     403              : 
     404              :   /**
     405              :    * @brief Pre-allocate space for @p n items in all arrays.
     406              :    *
     407              :    * Call before a burst of add() calls to avoid repeated reallocation.
     408              :    * After reserving, pointers returned by data arrays are stable until
     409              :    * capacity is exceeded again.
     410              :    */
     411            1 :   void reserve(IdType n)
     412              :   {
     413            1 :     _reserve_bookkeeping(n);
     414            1 :     self().reserve_data(n); // CRTP: each concrete struct reserves its vectors
     415            1 :   }
     416              : 
     417              :   /**
     418              :    * @brief Allocate a new item and return its stable ID.
     419              :    */
     420          613 :   IdType add()
     421              :   {
     422              :     IdType slot;
     423          613 :     if (_n_free > 0)
     424              :     {
     425            1 :       slot = _free_slots[--_n_free];
     426            1 :       _free_slots.pop_back();
     427              :     }
     428              :     else
     429              :     {
     430          612 :       slot = _next_slot++;
     431          612 :       _data_index.push_back(INVALID);
     432          612 :       _generation.push_back(0);
     433              :     }
     434              : 
     435          613 :     IdType data_idx = _size;
     436          613 :     IdType gen = _generation[slot];
     437          613 :     IdType id = make_id(slot, gen);
     438              : 
     439          613 :     _data_index[slot] = data_idx;
     440          613 :     _ids.push_back(id);
     441              : 
     442          613 :     self()
     443          613 :         .push_back_data(); // CRTP: push one zero-init element per field vector
     444          613 :     ++_size;
     445          613 :     return id;
     446              :   }
     447              : 
     448              :   template <typename Fn>
     449              :   IdType emplace(Fn &&fn)
     450              :   {
     451              :     IdType id = add();
     452              :     fn(id, self());
     453              :     return id;
     454              :   }
     455              : 
     456              :   template <typename Fn>
     457              :   void add_n(IdType n, Fn &&on_added)
     458              :   {
     459              :     reserve(_size + n);
     460              :     for (IdType i = 0; i < n; ++i)
     461              :       on_added(add());
     462              :   }
     463              : 
     464           51 :   void remove(IdType id)
     465              :   {
     466           51 :     if (!_size)
     467            0 :       return;
     468           51 :     const IdType slot = slot_of(id);
     469           51 :     if (slot >= _next_slot)
     470            0 :       return;
     471           51 :     const IdType idx = _data_index[slot];
     472           51 :     if (idx >= _size)
     473            0 :       return;
     474              :     if constexpr (GenerationBits > 0)
     475           51 :       if (gen_of(_ids[idx]) != gen_of(id))
     476            0 :         return;
     477              : 
     478           51 :     const IdType last_idx = _size - 1;
     479           51 :     const IdType last_slot = slot_of(_ids[last_idx]);
     480              : 
     481           51 :     self().swap_elements(idx, last_idx);
     482           51 :     _ids[idx] = _ids[last_idx];
     483              : 
     484           51 :     _data_index[last_slot] = idx;
     485           51 :     _data_index[slot] = INVALID;
     486              :     if constexpr (GenerationBits > 0)
     487           51 :       ++_generation[slot];
     488              : 
     489           51 :     self().pop_back_data(); // CRTP: pop_back every field vector
     490           51 :     _ids.pop_back();
     491           51 :     _free_slots.push_back(slot);
     492           51 :     ++_n_free;
     493           51 :     --_size;
     494              :   }
     495              : 
     496          852 :   IdType index_of(IdType id) const { return _data_index[slot_of(id)]; }
     497              : 
     498           14 :   bool contains(IdType id) const
     499              :   {
     500           14 :     const IdType slot = slot_of(id);
     501           14 :     if (slot >= _next_slot)
     502            0 :       return false;
     503           14 :     const IdType idx = _data_index[slot];
     504           14 :     if (idx >= _size)
     505            2 :       return false;
     506              :     if constexpr (GenerationBits > 0)
     507           12 :       return gen_of(_ids[idx]) == gen_of(id);
     508              :     return true;
     509              :   }
     510              : 
     511              :   template <typename Fn>
     512              :   void for_each_index(Fn &&fn)
     513              :   {
     514              :     for (IdType i = 0; i < _size; ++i)
     515              :       fn(i);
     516              :   }
     517              :   template <typename Fn>
     518              :   void for_each_index(Fn &&fn) const
     519              :   {
     520              :     for (IdType i = 0; i < _size; ++i)
     521              :       fn(i);
     522              :   }
     523              :   template <typename Fn>
     524              :   void for_each_id(Fn &&fn)
     525              :   {
     526              :     for (IdType i = 0; i < _size; ++i)
     527              :       fn(_ids[i]);
     528              :   }
     529              :   template <typename Fn>
     530              :   void for_each_id(Fn &&fn) const
     531              :   {
     532              :     for (IdType i = 0; i < _size; ++i)
     533              :       fn(_ids[i]);
     534              :   }
     535              : 
     536       194275 :   IdType count() const { return _size; }
     537              : 
     538              :   void clear()
     539              :   {
     540              :     self().clear_data();
     541              :     _ids.clear();
     542              :     _size = 0;
     543              :     _n_free = 0;
     544              :     _next_slot = 0;
     545              :   }
     546              : 
     547          968 :   static constexpr IdType slot_of(IdType id) { return id & SLOT_MASK; }
     548          126 :   static constexpr IdType gen_of(IdType id) { return id >> SLOT_BITS; }
     549          613 :   static constexpr IdType make_id(IdType s, IdType g)
     550              :   {
     551          613 :     return s | (g << SLOT_BITS);
     552              :   }
     553              : 
     554              : private:
     555            1 :   void _reserve_bookkeeping(IdType n)
     556              :   {
     557            1 :     _data_index.reserve(n);
     558            1 :     _ids.reserve(n);
     559            1 :     _free_slots.reserve(n);
     560            1 :     _generation.reserve(n);
     561            1 :   }
     562              : };
     563              : 
     564              : /**
     565              :  * @brief Define a named dynamic SoA struct with direct field access.
     566              :  *
     567              :  * Same API as DEFINE_SOA, but capacity grows at runtime.
     568              :  * Fields become std::vector members; access is still `bc.position[i]`.
     569              :  *
     570              :  * @warning Pointers into field vectors are invalidated on reallocation.
     571              :  *          Call bc.reserve(n) before a burst of bc.add() calls if you need
     572              :  *          stable pointers (e.g. before constructing a view for a solver loop).
     573              :  *
     574              :  * @par Example
     575              :  * @code
     576              :  *   DEFINE_DYN_SOA(ParticleCollection, uint32_t, 8, PARTICLE_FIELDS)
     577              :  *
     578              :  *   ParticleCollection pc;
     579              :  *   uint32_t id = pc.add();
     580              :  *   pc.position[pc.index_of(id)] = {0, 5, 0};
     581              :  *
     582              :  *   for (uint32_t i = 0; i < pc.count(); ++i)
     583              :  *       pc.position[i] += pc.velocity[i] * dt;
     584              :  * @endcode
     585              :  */
     586              : #define DEFINE_DYN_SOA(Name, IdType, GenBits, FIELDS_MACRO)               \
     587              :   struct Name : DynSoABase<Name, IdType, GenBits>                         \
     588              :   {                                                                       \
     589              :     /* ── Named field vectors ── */                                       \
     590              :     FIELDS_MACRO(DEFINE_DYN_SOA_VEC_)                                     \
     591              :                                                                           \
     592              :     explicit Name(IdType initial_capacity = 0)                            \
     593              :         : DynSoABase<Name, IdType, GenBits>(initial_capacity) {}          \
     594              :                                                                           \
     595              :     /* Called by DynSoABase::reserve() */                                 \
     596              :     void reserve_data(IdType n) { FIELDS_MACRO(DEFINE_DYN_SOA_RESERVE_) } \
     597              :     /* Called by DynSoABase::add() */                                     \
     598              :     void push_back_data() { FIELDS_MACRO(DEFINE_DYN_SOA_PUSH_) }          \
     599              :     /* Called by DynSoABase::remove() */                                  \
     600              :     void pop_back_data() { FIELDS_MACRO(DEFINE_DYN_SOA_POP_) }            \
     601              :     /* Called by DynSoABase::clear() */                                   \
     602              :     void clear_data() { FIELDS_MACRO(DEFINE_DYN_SOA_CLEAR_) }             \
     603              :     /* Called by DynSoABase::remove() */                                  \
     604              :     void swap_elements(IdType _a, IdType _b)                              \
     605              :     {                                                                     \
     606              :       using std::swap;                                                    \
     607              :       FIELDS_MACRO(DEFINE_SOA_SWAP_)                                      \
     608              :     }                                                                     \
     609              :   };
     610              : 
     611              : // Per-field helpers for DEFINE_DYN_SOA — not for direct use.
     612              : #define DEFINE_DYN_SOA_VEC_(T, name) std::vector<T> name;
     613              : #define DEFINE_DYN_SOA_RESERVE_(T, name) name.reserve(n);
     614              : #define DEFINE_DYN_SOA_PUSH_(T, name) name.push_back({});
     615              : #define DEFINE_DYN_SOA_POP_(T, name) name.pop_back();
     616              : #define DEFINE_DYN_SOA_CLEAR_(T, name) name.clear();
     617              : // swap reuses DEFINE_SOA_SWAP_ — works for both vector and array indexing.
        

Generated by: LCOV version 2.0-1