Components
- CPU model review
- Execute instructions sequentially
- Computational operations fixed cost
- Random memory accesses are expensive
- Unpredictable branches are expensive
- SoA review
- Object organization for an array of things, not each individual one
- e.g. Particles (with arrays inside), not Particle[]
- Avoid cache pollution with unneeded data
- More predictable cache access
- Can group stuff that is always used together.
- Components
- Replace with sparse arrays / maps
- Map Entity ID to Data
- std::map<id, data> = red/black tree
- std::unordered_map<id, data> = hash map (C++ 11)
- Empty = not that type, not that data
- Replace booleans, enums & inheritance with existence
- Most processing = iterate over existing elements, not all entities
- Avoids ifs & branches: only process active data
- C++ maps allow exactly that
- Cache efficiency
- Packed array of data
- map<id, component_index>
- Related: Briggs & Torczon sparse set
- Sparse[key] = index
- Dense[index] = key
- Optional Data[index] = dense data
- Size = count(dense)
- Lookup(key):
- Sparse[key] < Size && Dense[ Sparse[key] ] == key
- Don't need to 0-fill, rely on key match
- Insert:
- Sparse[key] = Size
- Dense[Size++] = key
- Clear: Size = 0
- Object is just functions operating on component arrays
- In the limit, just Entity ID and existence in component arrays
- Function = transform on data
- Component structs
- Exposed data part of interface, don't use accessor functions
- Member functions are about processing, not access protection.
- Existence Processing
- Common code for SoA and AoS: loop, test condition, operate
- Replace with loop over dense list of objects
- Fewer conditionals
- Don't bring objects into cache unless you will use them
- Also
- Design patterns
- Factory
- Generate object
- For component: Get ID, insert into component tables
- Singleton
- Singleton = global (with defined creation time)
- Globals common in DOP
- All component tables
- Sizes and max IDs
- Globals ≠ bad
- Most DOP programmers consider global temporaries bad
- Global better than pointers passed through classes
- Pointers can be invalid, and just as hard to track
- Data Oriented Design patterns
- In-place transform
- Loop over elements in sparse array, updating state
- A to B transform
- Loop over damaged characters, updating dead characters
- Non-list A: Traverse scene graph, generating render bins
- Generative transform
- Procedurally generate list
- Sorted table
- Keep sorted or sort for use
- Often bubble sort is effective for incremental changes
- Multi-sorted
- Sort by multiple criteria
- Sort is just list of keys in order used
- Gatherer: Merge tables
- Tasker
- Break into parallel parts.
- Often combine with gather
- Dispatcher
- Process table, create sub tables