As a disclaimer, I have no experience with Julia, so bear with me for using some wrong terminology. However, I have experience with archetype-based ECS and implemented Ark, which is probably the fastest one for Go.
Unfortunately, I am not allowed to post links yet, so I hope I can provide them in a second reply for convenience.
I think that your plan of specializing on every archetype is bound for failure. The main reason being the huge overhead of actually compiling methods (because the compiler is slow). So you’d never want to compile new methods on the fly because that for sure would cause a lag spike. Which means you either need to figure out which archetypes can possible exists (likely impossible) or precompile every variant which is also impossible.
@abraemer This seems based on a misconception of how (archetypal) ECS works, presumably coming from OOP habits. There is no dynamic dispatch required at all to make this work.
The key point here is that entities don’t “contain” components, but are just IDs. Components are stored in archetypes, which are “tables” with array-like columns, one for each component that is present in the archetype. See Ark’s user guide chapter on architecture for a graphical representation of the concept.
Finally, there are queries (or “systems”), which do the logic. A query iterates the matching archetypes, i.e. those that have the components the query is interested in. The query fetches (only!) the relevant component columns and either provides these columns to the user, or iterates over the entities (i.e. rows) of the archetype and provides the user access to the components at that row.
There is zero dynamic dispatch required for that, and no function specialization. Also, there is no runtime check per entity, as checking archetypes for their components is already sufficient.
This can already be super fast, particularly faster than “array of structs” (i.e., iterating an array of entity “objects”) because it is very cache friendly. Only the required data (i.e. component columns) is fetched, and components are contiguous in memory. My ECS Ark achieves 2ns per entity for the classic Position+Velocity example, i.e. fetch 2 components, and add X and Y of one (velocity) to the other (position). Julia could probably be do this faster due to more aggressive compiler optimizations compared to Go. Additionally, Juila may use SIMD…
Regarding combinatorial explosion: this is not really an issue, as it does just not occur in real use cases like games or simulations. Even with entity relationships (see Ark’s resp. user guide chapter), which create an archetype per relation target entity, this is not really a problem when not over-used mindlessly.
Some Julia-specific considerations for performance:
- Components should be immutable, so that they actually end up stored in component arrays, and don’t escape to the heap as mutable types would. This means that after updates, components must be replaced. Not nice for the API, but well…
- SIMD would be very useful to speed up further, but I have no idea (yet) how that could be realized in Julia
- The “update fusion” optimization does probably not gain much, but could still be easily implemented by the user (i.e. not worth it to try to implement it in the ECS itself)
On the other hand, adding two numbers cost me about 17 ns. If you could save 1 addition per update, that’s already worth it.
This should be below 1ns!
@Tarny_GG_Channie in case you still want to build an ECS, feel free to take Ark’s architecture as a basis. This was also the way to implement the high performance Mojo ECS Larecs.
I can also highly recommend reading the ECS blog post deries by Sander Mertens, the author of Flecs, on Medium.
Building a high-performance ECS is definitely something that can be done by a single developer in a few weeks, even far beyond the basic functionality (not speaking about something as sophisticated and feature-rich as Flecs though). I am considering doing this for Julia, following Ark’s architecture. In case I really start, I will let you know.