+ What I say | What it means
+ |
---|
Table | `soagen`-generated SoA container class
+ |
Column | A contiguous subarray of one data type inside a `soagen`-generated SoA container class
+ |
Row | A single 'element' in a `soagen`-generated SoA container class
+ |
+@endparblock
+
+
+
+@section intro_adding_rows Adding rows
+
+Now then, lets do what we came here to do: stick entities in our new container! Tables have all the same insertion
+operations as std::vector, only with multiple parameters:
+
+```cpp
+// assuming you have #included "entities.hpp" somewhere:
+
+game::entities e;
+
+// push_back works as you'd expect
+e.push_back(0, "foo", {0,0,0}, {1,0,0,0});
+
+// it will also take advantage of the defaults
+// we specified in the config, allowing us to elide
+// some initializers:
+e.push_back(1, "bar");
+
+// insert has the same semantics too
+e.insert(e.end(), 2, "qux", {1,2,3});
+
+// ... but can also take a size_type as the position
+// argument, not just iterators
+e.insert(e.size(), 3, "kek");
+
+for (auto&& row : e)
+ std::cout << row.id << ": " << row.name << "\n";
+```
+
+@out
+0: foo
+1: bar
+2: qux
+3: kek
+@eout
+
+We also have `emplace()` and `emplace_back()` just like std::vector, but there's a catch: the `std::vector` versions
+are variadic to allow for constructing a single value with any number of constructor arguments.
+Since we have multiple values per row, there's no way we can have a variadic `emplace()`/`emplace_back()`
+that somehow deduces which arguments go to which values. We need to add an extra level of indirection for that:
+the soagen::emplacer:
+
+```cpp
+using soagen::emplacer;
+
+e.emplace_back(4, emplacer{ 10, 'A' }, {0,0,0}, {1,0,0,0});
+
+// defaults also work with emplace and emplace_back()
+e.emplace_back(5, emplacer{ 10, 'B' });
+
+for (auto&& row : e)
+ std::cout << row.id << ": " << row.name << "\n";
+```
+
+@out
+0: foo
+1: bar
+2: qux
+3: kek
+4: AAAAAAAAAA
+5: BBBBBBBBBB
+@eout
+
+
+
+@section intro_removing_rows Removing rows
+
+Soagen tables support `pop_back()`, `erase()` and `resize()` just like std::vector,
+and also [`unordered_erase()`] for fast erasure when order doesn't matter:
+
+```cpp
+
+// erase BBBBBBBBBB
+e.pop_back();
+
+// erase AAAAAAAAAA
+e.erase(e.end() - 1);
+
+// erase kek (erase() also supports size_type)
+e.erase(e.size() - 1);
+
+// erase foo quickly using swap-and-pop:
+e.unordered_erase(e.begin());
+
+for (auto&& row : e)
+ std::cout << row.id << ": " << row.name << "\n";
+```
+
+@out
+2: qux <---- this was moved here by unordered_erase()
+1: bar
+@eout
+
+
+
+@section intro_capacity Capacity
+
+Soagen tables have `capacity()`, `max_capacity()`, `reserve()`, and `shrink_to_fit()` as you'd expect:
+
+```cpp
+
+std::cout << "size: " << e.size() << "\n";
+std::cout << "capacity: " << e.capacity() << "\n\n";
+
+e.reserve(100);
+std::cout << "size: " << e.size() << "\n";
+std::cout << "capacity: " << e.capacity() << "\n\n";
+
+e.shrink_to_fit();
+std::cout << "size: " << e.size() << "\n";
+std::cout << "capacity: " << e.capacity() << "\n\n";
+
+
+```
+
+@out
+size: 2
+capacity: 6
+
+size: 2
+capacity: 100
+
+size: 2
+capacity: 2
+@eout
+
+
+
+@section intro_columns Working with columns
+
+Recall that our table has four columns:
+
+- `id: unsigned`
+- `name: std::string`
+- `pos: vec3`
+- `orient: quaternion`
+
+Since we don't have just one element type like a std::vector, we don't access these via `data()` - you use their names!
+Each returns a pointer to their respective column's data:
+
+```cpp
+
+for (std::size_t i = 0; i < e.size(); i++)
+ std::cout << "id " << i << ": " << e.id()[i] << "\n";
+
+for (std::size_t i = 0; i < e.size(); i++)
+ std::cout << "name " << i << ": " << e.name()[i] << "\n";
+
+// ...and so on
+
+```
+
+@out
+id 0: 2
+id 1: 1
+name 0: qux
+name 1: bar
+@eout
+
+
+
+@section intro_rows Working with rows and iterators
+
+Soagen tables support treating rows as if they were regular AoS structs in their own right via #soagen::row.
+This is what you get from `operator[]`, `at()`, and by dereferencing soagen::iterators.
+
+**Rows have reference semantics**; if we retrieve a row from a non-`const` lvalue reference to our `entities` table, we'll
+get a row filled with named lvalue references to the members. For example:
+
+```cpp
+// entities&, operator[]
+auto row = e[0];
+
+// fetches an instance of this:
+struct row
+{
+ unsigned& id;
+ std::string& name;
+ vec3& pos;
+ quaternion& orient;
+};
+```
+
+Similarly, if we took a row from a `const`-qualified lvalue reference to our `entities`,
+we'd get lvalue references to `const` members:
+
+```cpp
+// const entities&, at():
+auto row = std::as_const(e).at(0);
+
+// fetches an instance of this:
+struct row
+{
+ const unsigned& id;
+ const std::string& name;
+ const vec3& pos;
+ const quaternion& orient;
+};
+```
+
+Same applies for rvalues:
+
+```cpp
+// entities&&, dereferencing an iterator:
+auto row = *(std::move(e).begin());
+
+// fetches an instance of this:
+struct row
+{
+ unsigned&& id;
+ std::string&& name;
+ vec3&& pos;
+ quaternion&& orient;
+};
+```
+
+Note that **rows don't have to be all-or-nothing**; if you want a row view of just two of your members, you can call
+[`row()`] to specify the columns you want:
+
+```cpp
+// columns 0 and 1 of row 0
+auto row = e.row<0, 1>(0);
+
+// fetches an instance of this:
+struct row
+{
+ unsigned& id;
+ std::string& name;
+};
+```
+
+This is true of iterators as well:
+
+```cpp
+auto row = e.begin<0, 1>(0);
+```
+
+Any rows generated by dereferencing the iterator above will only have `id` and `name` members.
+
+You can even rearrange the order of the columns if you need to do some swizzling:
+
+```cpp
+auto row = e.row<3,2,1>(0);
+
+// fetches an instance of this:
+struct row
+{
+ quaternion& orient; // column 3
+ vec3& pos; // column 2
+ std::string& name; // column 1
+};
+```
+
+Of course, using the raw column indices is quite susceptible to human error (particularly if you change the table
+members later), so tables all have a `column_indices` member with named constants for each column:
+
+```cpp
+auto row = e.begin