RFC: Buffer Traits -- Fixed-Size Numeric Traits #224
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
RFC: Buffer Traits -- Fixed-Size Numeric Traits
Summary
Extend
trait()to support buffer storage by detecting typed fields in schemas. Typed fields change the backing storage from JS arrays to TypedArrays.No new patterns. The schema structure determines layout (SoA vs AoS). Typed fields determine storage (Array vs ArrayBuffer).
Motivation
Koota has two layout patterns:
We extend SoA to support TypedArrays by detecting
types.f32()etc. in the schema. Buffer is a storage variant of SoA, not a new layout pattern - both use the samestore.field[index]access pattern.{ x: 0 }{ x: number[] }{ x: types.f32(0) }{ x: Float32Array }() => new Thing()T[]{}Type Helpers
API Examples
Buffer Storage
Buffer vs SoA Comparison
Both are SoA layout (separate array per field). Buffer uses ArrayBuffer-backed TypedArrays instead of JS Arrays.
Use SoA (default) for general ECS. JS Arrays handle any value type (strings, objects, etc.).
Use Buffer when you need:
API Compatibility
The core trait API (
get,set,updateEach,getStore) works transparently with buffer traits. Key differences:bufferoption - Only valid for buffer traits, rejected otherwiseBuffer Options
Buffer traits accept optional configuration for capacity and buffer type:
Options:
capacity?: number- Initial element count (default: 1024). Buffers grow automatically by doubling.buffer?: ArrayBuffer | SharedArrayBuffer- Buffer constructor. UseSharedArrayBufferfor worker parallelism.fixed?: boolean- Throw error when capacity exceeded (growth still happens, data preserved).Notes:
bufferapplies at trait level because workers process entire traits, not individual fields.SharedArrayBufferrequires Cross-Origin Isolation headers in browsers. Koota throws if unavailable rather than silently falling back.Design Decisions
Mixed Schemas
Question: Allow
{ x: types.f32(0), y: 0 }or reject?Decision: Reject at compile time AND runtime. All fields must use the same storage type.
ConsistentSchema<T>type returnsneverfor mixed schemas, causing TypeScript errorvalidateSchema()throws: "Koota: Mixed typed and untyped fields are not allowed"Relations Do Not Support TypedArray Fields
Question: Should
relation({ store: { amount: types.f32(0) } })work?Decision: No. TypedArray fields are rejected in relation stores at compile time AND runtime.
Reasons:
Non-exclusive relations have nested storage: For non-exclusive relations, the store structure is
store[key][eid][targetIndex]- an array of arrays per entity. TypedArrays can't represent this because inner arrays are dynamic length.Exclusive relations could work, but the API doesn't expose it: Exclusive relations have flat storage (
store[key][eid]), which could use TypedArrays. However, the relation API accesses data throughentity.get(Relation(target))which reconstructs objects - there's nogetStore()equivalent for bulk iteration.Access patterns differ: Traits are designed for bulk iteration (
for (eid of query) { store.x[eid] }). Relations are designed for targeted lookups (entity.get(ChildOf(parent))). TypedArrays benefit the former, not the latter.Use a separate trait if you need buffer storage: If you need fast bulk access to relationship-like data, model it as a trait instead:
Implementation:
RelationSchema<T>type returnsneverif any field is a TypedFieldcreateRelation()throws: "Koota: Relation stores do not support TypedArray fields"Type Inference
Implementation Details
Type Helper Implementation
Detection Logic
Store Creation
Storage Types
tag{}soa{ x: 0 }{ x: number[] }x:[0,1,2] y:[0,1,2]aos() => TT[][inst0, inst1, inst2]buffer{ x: types.f32(0) }{ x: Float32Array }x:[0,1,2] y:[0,1,2]Implementation Plan
Phase 1: Type Helpers
$typedArraysymboltypes.f32,types.f64,types.i8, etc.isTypedField(),isTypedSchema()typesfrom main indexPhase 2: Buffer Storage
StoreTypeto include'buffer'createStore()to handle typed fieldsPhase 3: Trait Integration
createTrait()to detect typed schemasBufferTraitOptionstype with buffer optionPhase 4: Type Inference
Schematype to include TypedFieldTraitRecordinferenceStoretype inferencePhase 5: Growth
Phase 6: Docs and examples
Files to Modify
References
packages/core/src/trait/trait.tspackages/core/src/storage/stores.tspackages/core/src/types/index.ts