When I use Entities.Foreach and pass in some components, Unity automatically schedules the jobs to not run in parallel.
Not so, if the components are empty (tag components). That means, if I configure one system to write to the empty component
struct TagComponent : IComponentdata {}
and another system to read from it, they are not automatically declared as dependence of each other and Unity could potentially schedule these jobs to run in parallel.
Usecase: My Systems are reading/writing from a NativeArray. I want to have a tag component AccessToDataArray, that I can pass into the systems. If I writing to the nativearray, I pass the tag component in as if I wanted to write to it, if I am reading from it I pass it in as if I am reading from the tag component. Unity automatically adds the right dependencies between the systems and I am safe without having to bother with manually injecting dependencies everywhere. What I do for now is filling the component with useless data:
The recommended approach for this is to keep the JobHandle dependencies stored alongside the NativeContainer. I wrote a mechanism for associating collections with entities and it automatically updates SystemBase.Dependency for me when retreived and writes back the Dependency to the collections afterwards. I get per-entity granularity with this technique rather than per-type.
At the time of this discussion, I was doing a similar thing to what you are doing. I have since moved on from that pattern and now have my own mechanism for handling dependencies for collections that gives me finer granularity and allows me to track many containers at once.
Well what he suggests will not be performant at all in most cases when I understand it right.
All I want is that if one system writes to something and another system wants to write or read that thing, they are automatically made dependent on each other. That functionality already exists, it is what components do. If I add component to my systems, then the Entities package works it magic and injects the systems as dependency of each other - I can access the NativeArrays without warning and without doing any dependency handling myself.
The only problem is, that as soon as the Component is empty (Tag component), Unity stops doing the dependency magic. One the one hand this makes sense, since I cannot write to a empty Component anyway, so why should it inject dependencies. But on the other hand, this would be a nice way to use the automatic dependency management which already exists for your own purposes.
Also I cannot think of a reason against handling tag components same as the other component.
@frankfringe I see tag components more as “filter” components, so I don’t see making sense to create dependencies by using them.
Wouldn’t using an UnsafeList inside a singleton ComponentData fit better for your use case? I’ve never touched those Unsafe collections, but they are blitables so they can be used inside ComponentData, so they look like a good alternative to create collections shared between systems.
Marking an unwritable type as writable for the sake of tracking an instance of a different type, neither of which directly reference each other, is an excellent way to make you code difficult to trace and vulnerable to bugs. There’s nothing stopping you from doing things how you do them today, nor is there anything stopping you from modifying the Entities package to get the behavior you want. But I and others appreciate Unity recognizing this less ideal practice and not recommending people to use this approach to solve this kind of problem.
So you mean having a component on each entity that holds a reference to the same UnsafeList? That would probably work, allthough it is not really necessary for each component to have an own reference, the compiler is loosing the information that he is operating on the same array for all entities all the time then, does that make a difference? But this is an interesting idea, I did not know about UnsafeList before, thanks
Ok, I missunderstood it a bit when I read it the first time, the lookup method confused me, but it is only for main thread access right. But I still have to manually handle all the dependencies, which is a lot of boilerplate, it is just a suggestion about where I should store the dependency job handle.
What I would like to have is an automatic system, where I only need to tag systems somehow if they read or write from a container. Then automatically determine the jobs to not run in parallel and have race conditions. I don’t think that is a bad practice or very complicated.
And this kind of thing is even already implented in the ECS, I would be happy if there would be a way for me to use it.
For completeness, this is the problem I need the nativearrays for:
I have a 2D tilemap with a lot of simulation stuff happening (Water Flow, Subsurface humidity). The simulation needs to also happen when the player is not looking, since the game should simulate a small ecosysstem. So each tile has to have some data stored for it somewhere. And the simulation needs to access neighboring tiles all the time.
I could have chunk entities, say for 4x4 tiles that then contain the water and humidity data for 16 tiles. But since I need to query the neighbors very often I would need to check if the current tile is on the border of the current chunk all the time. If it is, I need to find the other, chunk, call GetComponentData etc. So a lot of branching and GetComponentData calls.
In my case all I need to store is the index of the tile in their respective NativeArrays. Also I store the indexes of the neighbors. Then I can always just query the neighbor information, never need to do if(tile.x == chunk.x || tile.x == chunk.x + chunkWidth || tile.y ....) at all or call GetComponentData.
I even can choose the data layout of the NativeArrays myself, to have nearby tiles be nearby in the arrays too, so the neighborhood lookups are very local.
This all makes this approach very attractive to me, the only thing I have to do is manually manage the dependencies between the systems when they read/write to the arrays. I am happy to hear alternative approaches that still maintain the advantages I have mentioned.
Unity does not provide a way to do this automatically. Sure they automatically track components, which you can abuse as you have been doing. But I suggest writing your own mechanism to do this that meets your needs, as this will make your code more explicit and debuggable. It is totally possible to get such a mechanism to feel really native to ECS by directly manipulating the Dependency property of SystemBase. Whenever you retrieve the NativeContainer, you combine Dependency with the stored JobHandles. And you can subclass SystemBase with an abstract class that seals OnUpdate and exposes a new abstract OnUpdate that it calls. After the new OnUpdate runs, the base OnUpdate can capture Dependency and write it to the NativeContainer’s dependencies.
For proof of how clean you can make this, I am including some code from a game I am working on (this code is working fine atm). What you need to know is that CollisionLayer is a struct of NativeContainers that I have multiple of and want to track job dependencies of each individually between systems. As you will see from this code, the only JobHandle interactions I have are with Dependency and CompleteDependency() which are both part of SystemBase. Everything else is handled silently by SubSystem and the EntityManager extension methods (you’d likely want to make these methods part of your system base class instead of tying it to the EntityManager like I did).