GC problems surfaced... there is a reason Julia can't have void references. How do I make a type-stable entity update system?

One of the most critical questions you need to ask yourself: Is it possible to add components (i.e. different kinds of data) to existing entities?

If I read the flecs FAQ right, it allows that. This is something that doesn’t work with types – a julia, C++ or java object cannot change its class at runtime.

Supposing you don’t need that, a reasonable architecture is the following:

Entity handles are 64 bit integers. Of these, you reserve e.g. 16 bit for entity kind, and 32 bit for entity index within the type.

When you have e.g. a position Tuple{UInt32, UInt32} that is attached to some kinds of entities, then you have a positions::Vector{Vector{Tuple{UInt32, UInt32}}} and you access it by get_position(positions, untypedHandle) = positions[kind(untypedHandle)][seq(untypedHandle)]. (consider using Memory instead!)

When you e.g. want to apply an update function for all entities, you would write something like

for typedEntityIterator = getTypedEntityIterators(system)
foreach(typedEntityIterator) do entity
update!(entity)
end
end

The crucial point is that in this kind of setting, the getTypedEntityIterators will return typed iterators of handles. So the foreach call will be type-unstable.

This is intended! It is a function barrier! And then, within the foreach call, all the entities you handle have the same type, which makes for nice fast code.

You typically want to do the same in e.g. java. This is because its 1. nice for the branch predictor and 2. helps the JIT specialize the code.

The main difference is that julia performance will be abysmal without these techniques, while C++ / java will degrade to merely mediocre performance.

PS. Another difference is that in java/C++ you can have your cake on typed handles, and eat it: You can have different subclasses on the handle, and store the kind information twice: In the 16 bit, plus inside the RTTI / class pointer. Then, via overloading, you get very fast operations if the runtime type is statically known, and still get fast operations if it is unknown, and you never get slow megamorphic virtual function calls.

In julia this doesn’t work: Julia can never work efficiently on objects of unknown type, even if the missing info on the type would be superfluous for the operation at hand. So you need to be more disciplined about where the type is known or not.

1 Like

I noticed that Flecs supports C++ and provides an example in the README initializing a system with a number of components via template parameters. There’s a C++ example of components being added and removed in the Quickstart. I don’t really know C or C++ so I don’t know what’s going on there; it’s not apparent to me how worlds, systems, and entities are structured (Maybe this would be informative? How are components/entity stored in memory? · Issue #3 · SanderMertens/flecs · GitHub). If any of that can be translated to Julia, that would be a good start.

Thank you… however, my ECS is kinda not like Flecs or others along the line. There wouldn’t even be entity indices. Instead, there would be bulk updates.

You won’t need none bitstypes with that setup :wink:
The MutableRef is a bitstype so, as long as you use that in your structs you should be fine.
But I’m not sure if I understand your problem completely, so might not really help :person_shrugging:

You may already be aware of its existence, but it’s worth mentioning existing julia ECS packages like Overseer.jl. It may be sufficient for you or at least a source of ideas.