It has been in the ones I’ve worked on in the past. Especially in modding contexts it an get wildly extreme.
Yeah this is what my engine did that I described above, that was the memory trade-off.
Then the state change would effect the wrong entity, or you start jumping around in memory by storing the ‘parent’ entity within this and accessing it out of order for each iterable.
That is why it should not do what specs is, but rather implement ECS Groups.
Essentially this (using what I’ve seen of Legion’s terminology):
Given these components (keeping it simple for the purpose of this example, a name is a typename and a group id is an integer:
|Component Name||Group ID|
- Instead of a single ‘Chunk’ system, you instead have multiple Chunk systems, the number of which is defined by the components, in the above example there would be 5 Chunk storage systems.
- Any time components of the same Group ID are stored, legion works as it does now, they get stored together in the chunk, adding/removing one of those components involves copying the whole entity’s chunk worth of data to the new area.
- Any time components of different Group ID’s are stored, legion will put them in separate chunk storage systems.
- Creating a query for components that are all the same Group ID then legion would work as it does now.
- Creating a query for components that are not all of the same Group ID would essentially do two queries and then join those to get the final tuple of values.
- Doing a chunk iteration seems like it would be as it is, the ‘other’ chunk storages of differing group ID’s would just be returned in that same chunk iterator.
- The internals of course would change, but the user API would stay relatively identical.
Now how the group ID is specified could come a few different ways, the most simple might just be a trait with a single function for callback on the type, which could become part of a deriver if so wished. If it doesn’t exist it could fall back to, say, group 0, thus by default everything gets batched/chunked together, there-fore separating things out would need to be done explicitly.
No needing to special case optional components or anything of the sort. The complexity increase would come from having an entity existing across chunks, which essentially would work as it does now but with joined queries of a count of queries equaling the amount of unique groups.
A complete separate thing, but my old engine also allowed for scripting systems to state how much memory a component took and the C++ side managed all that, the scripting system layer was able to use that memory however it saw fit (for luajit I had a simple offset access system for a limited set of types for example, I tried to keep everything
memcpy'able for serialization speed reasons, but if necessary other data could be stored, but then serialization functionality had to be written for it). This would make allowing modders via scripting systems a much easier and efficient way to handle tightly packed components (more rare ones can of course be done entirely in-scripting system via entity ID of course, if the scripting system received live/death events of any entity it registered on, which in my engine was just another component to say what wanted to listen to events from a given entity). This would be a useful feature for legion to support as well.