Structs and Arrays
ConvergeDB supports two composite field types: structs (one level of nesting) and arrays (variable-length collections with a declared maximum).
Struct fields
Section titled “Struct fields”A struct field embeds a group of related sub-fields within an entity. Define the sub-struct with [ConvergenceStruct]:
[ConvergenceStruct]public partial struct InventorySlot{ [Field(0)] public ulong ItemId { get; set; } [Field(1)] public uint Quantity { get; set; }}Then use it as a field type in an entity:
[ConvergenceEntity("Character")]public partial struct Character{ [Field(0)] public ulong CharacterId { get; set; } [Field(1)] public InventorySlot PrimaryWeapon { get; set; } [Field(2)] public InventorySlot SecondaryWeapon { get; set; }
public ReadOnlyMemory<byte> EntityId { get; init; }}Restrictions
Section titled “Restrictions”Sub-struct fields may only contain:
- Scalar types (
ulong,float,bool, etc.) - Fixed-length
stringwithMaxLengthandFixedLength = true - Fixed-length
byte[]withMaxLengthandFixedLength = true
Nested structs within structs are not supported. VarString, VarBytes, and arrays within structs are not supported. This keeps the storage layout flat and predictable.
Array fields
Section titled “Array fields”Array fields hold a variable number of elements up to a declared maximum. Use the MaxCount parameter on the [Field] attribute:
[ConvergenceEntity("Recipe")]public partial struct Recipe{ [Field(0)] public ulong RecipeId { get; set; } [Field(1, MaxLength = 32)] public string Name { get; set; } [Field(2)] public uint CraftTimeMs { get; set; } [Field(3, MaxCount = 20)] public ulong[] IngredientIds { get; set; }
public ReadOnlyMemory<byte> EntityId { get; init; }}Supported element types
Section titled “Supported element types”Arrays can contain:
- Any scalar type (
ulong[],int[],float[], etc.) - Fixed-length
stringwithMaxLength - Fixed-length
byte[]withMaxLength [ConvergenceStruct]types
Arrays of arrays are not supported.
Array of structs
Section titled “Array of structs”Combine struct definitions with array fields for collections of complex elements:
[ConvergenceStruct]public partial struct Ingredient{ [Field(0)] public ulong ItemId { get; set; } [Field(1)] public uint Count { get; set; }}
[ConvergenceEntity("Recipe")]public partial struct Recipe{ [Field(0)] public ulong RecipeId { get; set; } [Field(1, MaxLength = 32)] public string Name { get; set; } [Field(2)] public uint CraftTimeMs { get; set; } [Field(3, MaxCount = 20)] public Ingredient[] Ingredients { get; set; }
public ReadOnlyMemory<byte> EntityId { get; init; }}Storage: two-zone layout
Section titled “Storage: two-zone layout”Arrays, VarString, and VarBytes fields all use a two-zone row layout. The entity row is split into two regions:
-
Fixed zone — all scalar fields, fixed-length strings/bytes, structs, and a 4-byte directory entry for each variable-length field. The fixed zone has a constant size for every entity of a kind.
-
Data zone — packed variable-length data. Array elements are stored contiguously at the actual element count, not the declared
MaxCount. VarString and VarBytes store only the actual byte content.
Fixed zone Data zone┌──────────────┬───────────────────┐ ┌──────────────────────────────┐│ scalar fields │ directory entries │ │ packed array/varstring data ││ (fixed size) │ (4B per VL field)│ │ (actual content only) │└──────────────┴───────────────────┘ └──────────────────────────────┘Each directory entry is 4 bytes: a u16 element count and a u16 data offset into the data zone.
This means entities with small arrays or short strings do not pay the cost of the full MaxCount or MaxLength allocation. An inventory with 10 of 250 possible slots stores only the 10 actual elements.
For kinds with no variable-length fields (no arrays, no VarString, no VarBytes), the data zone is empty and the layout is identical to a simple fixed-size row.
The maximum element count per array field is 1024. See Limits.
PATCH behaviour for arrays
Section titled “PATCH behaviour for arrays”Arrays are patched as whole values. If a PATCH includes an array field, the entire array is replaced. Element-level patching (updating a single element within an array) is not supported. To update one element, read the current array, modify the element, and patch the full array back.
The same applies to VarString and VarBytes fields: a PATCH replaces the entire value.