Fixed Collections¶
Components must be unmanaged structs, so a component field can't hold a List<T> or an array. For small bounded collections inside a component — equipment slots, active buffs, tracked targets — Trecs.Collections provides two size-specialized value types that store elements inline.
| Type | Shape | Use when |
|---|---|---|
FixedArray<N> |
Fixed length, every slot always live | The count is the capacity — no runtime "how many?" |
FixedList<N> |
Fixed capacity, variable Count |
The count varies per entity up to a known bound |
Both require T : unmanaged. Available sizes: 2, 4, 8, 16, 32, 64, 128, 256.
All fixed collections are marked [NonCopyable] because their data is stored inline — copying the struct duplicates the internal storage, and mutations on the copy silently leave the original unchanged. Pass them by ref or in, not by value. Any struct that contains a fixed-collection field is itself non-copyable (the rule propagates transitively). See Copy semantics for details.
Read / write split¶
The indexer returns ref readonly T — read with arr[i], but you can't write through it. Writes go through .Mut(i), which returns ref T:
ref readonly var x = ref arr[i]; // readonly ref (no copy, works through `in` too)
arr.Mut(i) = 5; // write (requires mutable reference to arr)
ref var r = ref arr.Mut(i); // mutable ref for in-place updates
r += 1;
This makes in FixedArray<N> / in FixedList<N> parameters safe: readers can index freely (no defensive copy, no hidden mutation), and writes fail to compile because Mut is a this ref extension that can't be called through a readonly reference.
void Foo(in FixedArray16<int> arr)
{
int x = arr[0]; // OK
arr.Mut(0) = 5; // COMPILE ERROR: arr is readonly, can't call `Mut`
}
FixedArray<N>¶
A fixed-length array stored inline. default(FixedArray8<float3>) gives 8 zeroed slots; nothing to initialize.
using Trecs.Collections;
public partial struct Waypoints : IEntityComponent
{
public FixedArray8<float3> Points;
}
ref Waypoints wp = ref handle.Component<Waypoints>(world).Write;
wp.Points.Mut(0) = new float3(1, 0, 0);
ref readonly float3 p = ref wp.Points[2]; // ref readonly — no element copy
ref float3 m = ref wp.Points.Mut(3); // mutable ref
m.x += 1.0f;
Members: Length, readonly indexer, == / !=.
Extension: Mut(i) → ref T.
FixedList<N>¶
A FixedList<N><T> is a FixedArray<N><T> plus a Count of live slots. Capacity is fixed at N; Count starts at 0 and grows with Add up to Capacity.
public partial struct ContactPoints : IEntityComponent
{
public FixedList16<EntityHandle> Contacts;
}
ref ContactPoints cp = ref handle.Component<ContactPoints>(world).Write;
cp.Contacts.Add(otherHandle);
// Remove-during-iterate: walk backwards to avoid index skips.
for (int i = cp.Contacts.Count - 1; i >= 0; i--)
{
if (ShouldRemove(cp.Contacts[i]))
cp.Contacts.RemoveAtSwapBack(i);
}
Members: Count, Capacity, IsEmpty, readonly indexer, == / !=.
Extensions: Mut(i), Add, Clear, RemoveAt, RemoveAtSwapBack.
| Operation | Cost | Preserves order? |
|---|---|---|
Add(item) |
O(1) | — |
Clear() |
O(1) (just resets Count; does not zero the buffer) |
— |
RemoveAt(i) |
O(N) (shifts trailing elements) | Yes |
RemoveAtSwapBack(i) |
O(1) (overwrites slot i with the last element) |
No |
Mut(i) = x |
O(1) | — |
== compares Count and live slots only — bytes past Count (leftover from prior Clear / RemoveAt calls, or uninitialized) are ignored.
Choosing a size¶
The type name picks the footprint. A FixedArray256<float4x4> is 16 KB per entity, used or not. Pick the smallest variant that covers your worst case, rounding up to the next power of two — for a FixedList<100>, use FixedList128 and accept the slack. Memory is usually cheap.
When to reach for something else¶
- The upper bound varies widely across entities, or usually sits far below the cap. A
FixedArray256<T>that's typically empty wastes storage on every entity in the template. Use a dynamic collection (TrecsList<T>/TrecsArray<T>/TrecsDictionary<TKey, TValue>) instead — the component holds a small inline handle and the elements grow on the world's heap, snapshot-safe.
Relation to Unity's FixedList*Bytes¶
Unity's Unity.Collections ships FixedList32Bytes<T> through FixedList4096Bytes<T> — same idea, different axis. Differences:
| Trecs | Unity FixedList*Bytes |
|
|---|---|---|
| Sizing axis | Element count (FixedList16<T> = 16 slots, always) |
Total bytes (FixedList64Bytes<T> = ~62 / sizeof(T) slots) |
| Element read | ref readonly T — zero-copy, including through in parameters |
T by value — copies the element on every read. .ElementAt(i) returns ref T but isn't callable through in |
| Element write | .Mut(i) returns ref T; not callable through in (compile-error) |
list[i] = x setter; not callable through in (compile-error) |
| API surface | Minimal: Add, Clear, RemoveAt, RemoveAtSwapBack, Mut |
Extensive: IndexOf, Contains, Sort, IEnumerable<T>, cross-size equality |
| Count-less variant | FixedArray<N> |
none |