TLDR : Is this okay to create a NativeArray of ArchetypeChunks in a system, and read it in another system?
I’m working on a custom collision system, here is the idea :
public struct PhysicsWorld : IComponentData
{
public NativeArray<ArchetypeChunk> chunks;
public NativeArray<int> chunkBodyStarts;
public SweepData chunkSweepData;
public SweepData bodySweepData;
}
A BuildPhysicsWorldSystem creates the PhysicsWorld Singleton & compute a broadphase for each chunk.
I like the idea of storing the query chunks in the singleton, since SweepData contains relative indices, and other systems can simply iterate on them without making other queries.
Everything goes fine for reading systems running in the same FixedStepSimulationSystemGroup, but if I try to access the chunks in another group (TransformSystem) the chunks may be invalid : It’s random, but often happens when starting the game.
Double checked everything, it’s not a dependency problem : The chunk internal pointer is invalid.
I didn’t saw this pattern in DOTS code. Native arrays of chunks are always used locally in a system. Is this something forbidden or am I missing something?
The NativeArray is only valid until a structural change happens. As soon as a component is added or removed from any entity, or an entity is created or destroyed, or a shared component is modified, all your indices could potentially be completely wrong and unusable. If you have really good control over your system execution order, you can sometimes make this carry an array through a couple of systems. But most of the time you are better off keeping the array localized to a single update of a single system.
I’ve got a fair bit of experience in the area of collision detection and spatial queries as well as leveraging the ECS data layouts to do crazy things, so feel free to reach out to me if you have further questions!
As DreamingImLatios says, ArchetypeChunks should remain valid until a structural change happens. If chunks are being invalidated, a structural change has occurred(or a bug has).
If you’re reading the chunks from a different SystemGroup it’s probably getting invalidated because some systems in between have caused structural changes, and this is especially likely since your systems are in both FixedUpdate and normal update groups. This should work perfectly fine if all your systems execute in order within one group.
Since I don’t do any structural modification for now, my guess is that the chunks were randomly invalidated at the start of the game depending on the sub-scene loading between the FixedStepSystem & Transform system groups.
Along these lines, what if I want to separate concerns… one System to detect collisions, one to spawn new entities (think: of mining asteroids), and one to destroy entites (think: blowing up space ships). Clearly, you don’t want to do all of that in one system. How do we create the native array of collisions and then share it among other systems to spawn and destroy?
I ended up putting all collision detection systems in the same CollisionSystemGroup. The first being the one populating the collision world, directly followed by the query systems (ie. DamageSystem).
Query systems can’t make structural change as said before, the chunks referenced by the CollisionWorld would be invalidated, but you can either :
Queue entity creation/destruction in an EntityCommandBuffer executed at the end, or after the CollisionSystemGroup.
Fill an event list with all necessary data, shared between systems by a singleton component
I have a very unconventional (but performant) way of doing this. Instead of detecting all collisions in the world, my collision detection algorithm can be set up to detect exclusively collisions between two specific EntityQueries. Because of this, I put the detection for a specific interaction along with the code for that interaction in the same system (the interaction code gets called directly from the collision detection algorithm). This avoids the need for any temporary buffers to store collision results. I do however cache the acceleration structures so that multiple systems can use them, but that doesn’t get invalidated by structural changes (apart from some Entities being in such a structure when they shouldn’t be). With that said, for single-player games, I don’t have any structural changes in SimulationSystemGroup. It is all in either Initialization or Presentation. SimulationSystemGroup just creates one giant job chain.