Job schedule and race condition

Hello everyone
I have two systems: one that moves objects with MovementComponent and another that sets a new target to them when you click rightMouseButton on the terrain. So the first system (MovementSystem) uses a job in its workflow. Considering that MovementSystem only reads the MovementComponent (where target and speed values are stored) and TargetingSystem (==MouseTargetingSystem in the error) writes to that component, I put the UpdateBefore(typeof(MovementSystem)) attribute for the TargetingSystem and thought it must be okay. But when I click the rightMouseButton often (not every time, but often) there is an error:

As I understand, it says that somehow it was created a race condition between my two systems. But how if only one is writing there. Moreover it writes info there in the main thread, so MovementSystem dont update until TargetingSystem end its work. If I add a moveJobHandle.Complete() in the MovementSystem it works fine without any errors. But I still do not understand why it is the case. Moreover I thought that UnityDOTS automatically adds a state.Dependency-s things everywhere it is needed to be. Isnt it the dependency which UnityDOTS should have processed by itself? If no, then I also do not understand this automatics (it was mentioned in official Unity sample video - 10:50 timing

Here is my code:

[BurstCompile]
public partial struct MovementSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<MovementComponent>();
       
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var moveJob = new MovementJob { time = SystemAPI.Time.DeltaTime, collisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().CollisionWorld };
        moveJob.Schedule();
    }

    [BurstCompile]
    public void OnDestroy(ref SystemState state)
    {
       
    }
}

[BurstCompile]
public partial struct MovementJob : IJobEntity
{
    public float time;
    public CollisionWorld collisionWorld;
    public void Execute(ref LocalTransform transform, in MovementComponent movementComponent)
    {
        float3 tempDir = movementComponent.target - transform.Position;
        tempDir.y = 0;
        float3 dir = math.normalize(tempDir);

        if (math.distancesq(movementComponent.target, transform.Position) < 1f)
            return;

        CollisionFilter filter = new CollisionFilter
        {
            BelongsTo = ~0u,
            CollidesWith = 1u << 7,
            GroupIndex = 0
        };
        float3 originPointDistance = transform.Position;
        originPointDistance.y -= 1f;
        var pointDistanceInput = new PointDistanceInput { Filter = filter, Position = originPointDistance, MaxDistance = 1.5f };
        if (collisionWorld.CalculateDistance(pointDistanceInput, out DistanceHit closestHit))
        {
            transform.Position.y = closestHit.Position.y + 1;
            transform.Rotation = quaternion.LookRotation(transform.Forward(), closestHit.SurfaceNormal);
           
        }
        else
        {
            Debug.Log("ERROR: Terrain under unit is not found");
        }
       
        transform.Position += dir * time * movementComponent.speed;
    }
}
[UpdateBefore(typeof(MovementSystem))]
public partial struct MouseTargetingUnits : ISystem
{
    public void OnCreate (ref SystemState state)
    {
        state.RequireForUpdate<MovementComponent>();
    }

    public void OnUpdate(ref SystemState state)
    {
        if (Mouse.current.rightButton.wasPressedThisFrame && CheckCursorOnTerrain(out Unity.Physics.RaycastHit newTarget))
        {
            foreach (var movement in SystemAPI.Query<RefRW<MovementComponent>>())
            {
                movement.ValueRW.target = newTarget.Position;
            }
        }
    }

    private bool CheckCursorOnTerrain(out Unity.Physics.RaycastHit newTarget)
    {
        var collisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().CollisionWorld;
        UnityEngine.Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.value);
        RaycastInput raycastInput = new RaycastInput
        {
            Start = ray.origin,
            End = ray.GetPoint(1000f),
            Filter = new CollisionFilter
            {
                BelongsTo = ~0u,
                CollidesWith = 1u << 7,
                GroupIndex = 0
            }
        };
        newTarget = new Unity.Physics.RaycastHit();
        return collisionWorld.CastRay(raycastInput, out newTarget);
    }
}

And also an additional small question. If I schedule the MovementJob without BurstCompiling it runs on the main thread, if I got it right in the Profiler. So does it mean that using worker threads is possible only with BurstCompiling?

The issue is singletons & main thread access.
Singleton access for the collections inside it is more quirky and doesn’t handle well automatically.

Try modifying MouseTargetingUnits main thread access into IJobEntity (e.g. pass data from the camera as a ray to the job and process in a job), or complete the dependency before idiomatic foreach loop.

See the example with main thread access:
https://docs.unity3d.com/Packages/com.unity.physics@1.0/manual/physics-singletons.html

Well, adding the state.CompleteDependency() helps, but for that effect it must be before the if (…), not before idiomatic foreach loop:

public void OnUpdate(ref SystemState state)
{
    state.CompleteDependency();
    if (Mouse.current.rightButton.wasPressedThisFrame && CheckCursorOnTerrain(out Unity.Physics.RaycastHit newTarget))
    {
        foreach (var movement in SystemAPI.Query<RefRW<MovementComponent>>())
        {
            movement.ValueRW.target = newTarget.Position;
        }
    }
}

In that occasion the problem is gone. But I just wonder why. It is quite obvious for me now that the race condition happens not in the MovementComponent (because even if I comment the idiomatic foreach loop, problem is the same until I add a state.CompleteDependency()), but somewhere in the Unity.Physics. But where and why?
And the second question is: why does I have to CompleteDependency in the MouseTargetingUnits, when it updates before the MovementSystem?

Yep, that’s cause CheckCursorOnTerrain attempts to access physics data.

I haven’t used Unity.Physics for a long time. So this is pure speculation.
General logic tells me its due to undefined behaviour that may occur if data is attempted to be read during some other job modifying it.

Basically, if you treat PhysicsWorldSingleton as a bunch of native collections - it kinda makes sense not to attempt to read or write during other [potential] jobs.

My best guesses are:
Jobs aren’t scheduled right away and against each other. Which makes order undefined.
Basically, job system internally may change order of execution of jobs that aren’t depend on each other. Which makes it unsafe for the access [in theory].
– or –
It assumes previous frame jobs may run into this frame and its unsafe for that reason.
– or –
Something else.

  1. The issue is in CheckCursorOnTerrain. That is why the CompleteDependency has to happen before the if.
    For accessing singletons in the main thread you have to manually sync. That is to be able to schedule jobs using native container from a singleton without sync points. It’s not beautiful yet but it solves a lot of performance issues.

To access the singleton from the main thread you should call:
state.EntityManager.CompleteDependencyBeforeRO<PhysicsWorldSingleton>()

  1. MovementSystem only registers read only access on the singleton which leads to it getting scheduled in parallel with other RO physics work. This is still catched by the native containers safety checks but doesn’t log exact err

Simplest fix: add
SystemAPI.GetSingletonRW<PhysicsWorldSingleton>();
to MovementSystem.OnCreate. This adds a write dependency on the singleton to MovementSystem and should prevent any collisions with physics jobs.

Both of these looks logical for me. But if the first one is right, then why in the MovementSystem it only registers RO access? It does collisionWorld.CalculateDistance in the scheduled job which is similar to collisionWorld.CastRay in the way of accessing data, as I think. I suppose both of them are writing some data in the Physics data, dont they?
And isn`t this:

will be much less efficient. As I got from the Unity`s sample videos and documentation, not giving RW access means much better perfomance, am I right?

And also I didnt understand this part... For scheduling jobs without a sync point, we need to manually sync?.. Just dont understand…

Oh, you are right, the MoveJob only needs read access but doesn’t declare that. You have to add [ReadOnly] to the CollisionWorld field.

If you would schedule a job you would put Dependency in which would resolve ordering. But you access the collision world from the main thread here not using the Dependency so you are responsible for waiting for jobs that have write access. This is a special case for GetSingleton. It does not wait for jobs automatically as it’s is often used to access the included native containers which have there own safety handles. In contrast GetComponent automatically waits for all writers.

Take a look at ‘Dependency Completion’ in the docs:
https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/components-singleton.html

Okay, now I understand it much-much better, thanks.

But still I dont understand, why using CollisionWorld field can create RaceConditions. It is a struct, we just copy it to local variable, dont we? So how two different fields and so then two different copies can write to or read the same piece of data?
Moreover it is additional frustrating thing: here

is written that
“Call (SystemBase|SystemAPI|EntityQuery).GetSingleton() to retrieve it if read-only access to physics simulation data is sufficient, or (SystemBase|SystemAPI|EntityQuery).GetSingletonRW() if read-write access is required. In addition to returning a PhysicsWorldSingleton, these calls will also ensure that the jobs using the retrieved singleton will not run into a race condition.”
So as I understand it is said here that GetSingleton has safety checks as well. I suppose I misunderstood something, but it seems for me to be the contrary to the information in ‘Dependency Completion’ in the link you`ve given to me…

Also this it gives me an error if I try to do this. It says that “modifier readonly cannot be used for this element” or that “attributes are not suitable in this context”. I don`t really understand why it happens, but it is :frowning:

Using CollisionWorld struct itself isn’t the problem, that is copied, but it includes NativeContainers. eg:

    public struct CollisionWorld : ICollidable, IAspectQueryable, IDisposable
    {
        [NoAlias] private NativeArray<RigidBody> m_Bodies;    // storage for all the rigid bodies
        [NoAlias] internal Broadphase Broadphase;             // bounding volume hierarchies around subsets of the rigid bodies
        [NoAlias] public NativeParallelHashMap<Entity, int> EntityBodyIndexMap;

These containers reference data and keep track on who is currently allowed to use. When you pass a CollisionWorld to a job the safety system will check that every native container within CollisionWorld is safe in this context, meaning: every job accessing any of the containers in a conflicting way must be contained in the Dependancy otherwise a race condition would be possible. GetSingleton ensures that all jobs from other systems accessing this singleton will get added to your Dependency → using jobs is safe.
The difference between [ReadOnly] and not readonly is that data may only be writable by a single writer at any time but multiple readers are fine → ReadOnly allows more parallelization.

These calls add the singleton type to the system dependencies. So as long as you are using the native containers embedded in the singleton through jobs everything is fine. When using them in the main thread you have to explictly wait for all read jobs accessing it.

You must use the [ReadOnly] (Unity.Collections.ReadOnly) attribute not the readonly modifier.

Its a “native” container of native containers. Internal native containers aren’t safe in this case.
Data isn’t copied. Reference to the data is copied.

If simplified:

struct FakeContainer {
   void* ContainerDataPtr;
   void* OtherContainerDataPtr;
}

Think of the native containers as a “reference” type. They contain a pointer to the actual allocated data memory block. Meaning if you copy a pointer - it still points to the same location.

I think its more of an issues of GetSingleton / GetSingletonRW, since no extra dependecies are combined and manual dependency management is required as a result.

It should manage dependencies automatically, but it doesn’t due to extra hack UT added.
Manual most likely does not reflect this.

I’ve encountered this behaviour a while ago, and it seems intentional in order to be able to fetch native containers that reside inside components without stalling jobs.
See:
https://discussions.unity.com/t/925323/8

I now better understand all of these in details, thanks… But I still do not have a complete picture… It may seem that I am a silly one, but I am a newbie and I really don`t understand :frowning: So sorry if my questions are dumb, or if I must have already understood everything - I am really trying to do this…

  1. So I will try to generalize what is happening and in this generalization I will show the confusing moments.


So here is the picture from the Profiler. Here I can see that the MouseTargetingUnits system is the first one in each frame. Then it goes the MovementSystem which schedule MovementJob and after this jobs is run.
In the MouseTargetingUnits I get a copy of CollisionWorld, which is a bunch of NativeArrays with references on some Physics things. Then the collisionWorld.CastRay does some read/write magic in these Physics things. And only after finishing all of these MovementSystem is started. It also gets a copy of CollisionWorld, but just puts it in the job and schedule it. Next job starts its work in the worker thread and does similar read/write magic in Physics things (because collisionWorld.CalculateDistance is called in the MovementJob) And it seems to me like it shouldnt be any errors through this workflow. But error happens, and it happens in the very start of this chain of actions. And that is something I dont understand. How it can be a race condition between MouseTargetingUnits and MovementJob when they even do not work simultaneously. Just a note that as an expirement I also tried to add ```World.DefaultGameObjectInjectionWorld.EntityManager.CompleteDependencyBeforeRO<PhysicsWorldSingleton>();``` before the getting the singleton of PhysicsWorldSingleton and coping the collision world through it. But it didnt help. Also I tried to give a [ReadOnly] to both variables of CollisionWorld, but it didn`t help as well (moreover I am not sure why it should - see the next point).

  1. What about the [ReadOnly]. I dont understand a little if it should help to fix this problem with race condition OR it will just give a little boost for perfomance because of the fact that ECS will know that I dont plan to change anything in CollisionWorld copy? If the first variant is right, then I do not understand how? During the CastRay or CalculateDistance NativeArrays are not changed (or at least they are changed in the copies of the CollisionWorld as it is a struct which we have got by value). Instead of that when CastRay happens, I suppose, it changes Physics things accessed by reading the refs from these NativeArrays. So attribute [ReadOnly] will not prevent accessing the Physics things in these arrays and safety checks will not see that problem (Job dependencies | Entities | 1.0.16 - as I got it right, here is the info about this in ‘Override the default order’)

  2. And also I faced some frustrating question while was looking in the Profiler: in most of frames it is the same picture as on the previous picture. But there are also frames like this one:


    So there is not MovementJob at all in this frame. And I really have no idea why, when it is called each frame in the MovementSystem

Safety system doesn’t not “analyze” current logic. Its a bunch of checks against all possible unsafe cases.
Meaning if one condition fails - it will just throw an exeception. It may be a false positive. It might not.
At the same time - its better to detect an issue early than to silently fail and receive a deadlock in a build in some rare scenario. Highly likely nothing is added as dependency even on RO singleton access, because of singleton quirks. Check what’s actually being generated upon singleton access.

For the container in a component & singletons case, I had to manual add dependencies.
E.g. to store a field of JobHandle inside the component with the dependency, combine it with system dependency on read and write access each time.

As to how to handle physics in this case (since I don’t think they provide a manual dependency to schedule against) - its better to ask on the specific subforum here.

Most likely no entities to process (no MovementComponent) → no matching queries → fast exit.

Can you please give a hint how to view the generated code? I remember you said that you dont know how to do it in the VS, but at least how to do it in the Rider - I cant find this option anywhere in settings

No, my scene is pretty simple, no entities are deleted or disabled there - so the MovementComponent always exists…

Do you mean that I should reask this question on Physics subforum? Because I still can`t see the general picture, I asked in the last post…

There’s a “DOTS” header at the struct / class of the system. Clicking it will display “Show Generated Code”.
Alternatively, place cursor at systems name → Alt+Enter → Show Generated Code;

Something else then. Hard to tell without actual seing changes to the code and what happens during that specific frame.

No harm in trying.

There is no such thing here…
9251049--1293477--upload_2023-8-26_0-25-35.png
9251049--1293480--upload_2023-8-26_0-26-38.png
(clicking on “ECS system” does nothing)

If you want here is a .unitypackage of scene. I understand that it may be too much, but just in case. (state.CompleteDependency() is uncommented here, because it was dedicated for another thread as can be seen from the name, so error is not occuring)

9250311–1293303–TerrainSyncProblem.unitypackage (96.7 KB)

Older version perhaps?

On 2023.2.1 it looks like this:
9251943--1293714--upload_2023-8-26_13-7-1.png

I’ve just recreated project from package, so it seems Profiler messes around with job scheduling. I’d say its a Profiler bug. Basically, job shows under Profiler instead of Jobs like this:
9251943--1293726--upload_2023-8-26_13-16-27.png

To simplify life a bit for yourself, when working with jobs, make sure to enable Show Flow Events.
Then, when you select the system in the Timeline, it will show a line towards each job generated by the system.
Easier to track down visually what’s going on:
9251943--1293741--upload_2023-8-26_13-51-11.png

[ReadOnly] attribute mentioned in this thread is Unity.Collections one. Not default System.ComponentModel one.

As for the how to avoid calling into dependency completion - Chain jobs:

  • Use a separate job to detect terrain / collision hits;
  • Apply target in a different job.

Here’s an example how to do it (including proper ReadOnlyAttribute usage):

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Profiling;
using RaycastHit = Unity.Physics.RaycastHit;

namespace Controls {
   [UpdateBefore(typeof(MovementSystem))]
   public partial struct MouseTargetingUnits : ISystem {
      public void OnCreate(ref SystemState state) {
         state.RequireForUpdate<PhysicsWorldSingleton>();
         state.RequireForUpdate<MovementComponent>();
      }

      public void OnUpdate(ref SystemState state) {
         if (!Mouse.current.rightButton.wasPressedThisFrame) return;
 
         Profiler.BeginSample("MouseTargetingUnitsSystem");

         UnityEngine.Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.value);
         PhysicsWorldSingleton singleton = SystemAPI.GetSingleton<PhysicsWorldSingleton>();
         NativeReference<TargetHitResult> hitResult = new NativeReference<TargetHitResult>(Allocator.TempJob);

         // Chain jobs one after another to avoid calling dependency completion
         state.Dependency = new DetectTerrainHit
                            {
                               CollisionWorld = singleton.CollisionWorld,
                               Input = new RaycastInput
                                       {
                                          Start = ray.origin,
                                          End = ray.GetPoint(1000f),
                                          Filter = new CollisionFilter
                                                   {
                                                      BelongsTo = ~0u,
                                                      CollidesWith = 1u << 7,
                                                      GroupIndex = 0
                                                   }
                                       },
                               Result = hitResult
                            }.Schedule(state.Dependency);

         // TODO Apply targets onto only selected units by specifying query as a Schedule parameter
         state.Dependency = new ApplyMovementTargetJob
                            {
                               Result = hitResult,
                            }.Schedule(state.Dependency);

         hitResult.Dispose(state.Dependency);
 
         Profiler.EndSample();
      }

      [BurstCompile]
      public partial struct DetectTerrainHit : IJob {
         [ReadOnly]
         public CollisionWorld CollisionWorld;

         [ReadOnly]
         public RaycastInput Input;

         public NativeReference<TargetHitResult> Result;

         public void Execute() {
            bool hasHit = CollisionWorld.CastRay(Input, out RaycastHit result);
            Result.Value = new TargetHitResult
                           {
                              HasHit = hasHit,
                              Position = result.Position
                           };
         }
      }

      [BurstCompile]
      public partial struct ApplyMovementTargetJob : IJobEntity {
         [ReadOnly]
         public NativeReference<TargetHitResult> Result;

         public void Execute(ref MovementComponent moveData) {
            TargetHitResult hit = Result.Value;
    
            // TODO this can be done with ArchetypeChunk iteration +
            // a separate query [Like a selected units query]
            // to do a fast exit without iterating all chunks at all if nothing is hit at all
            // but for simplicity, I've used IJobEntity. It shouldn't really matter for counts <10k and potentially more
            // on decent hardware. Otherwise use ArchetypeChunk iteration + IJob instead
            if (!hit.HasHit) return;
    
            moveData.target = hit.Position;
         }
      }
   }

   public struct TargetHitResult {
      public bool HasHit;
      public float3 Position;
   }
}

And in profiler you’ll see something like this:
9251943--1293735--upload_2023-8-26_13-43-6.png

Granted, its not optimal to check if something was hit per entity, but all workload is offloaded to the jobs & there’s no main thread stalling / forced dependency completion.

Once you get used to IJobEntity & IJobs, there’s a better way to do this by fast exiting before archetype iteration starts inside the job. However, its a way more code (as it requires to write Archetype → Chunk → Entity iteration manually, plus example of it in manual is messed up). So optimize it later once you’re more used to jobs & entities memory layout. Or don’t, if doesn’t become an issue. Like for example selected units counts in less than some value.

One more gotcha you might run into - IJob will codegen will not include dependencies for this case:

new Job().Schedule();

Will codegen into this:

new Job().Schedule();

And not:

state.Dependency = new Job().Schedule(state.Dependency);

So make sure to include dependencies where needed for IJobs. Its implemented like that so you could run arbitrary IJobs that do not depend on anything without inserting jobs into the current chain. Something that can be easily missed.

For IJobEntity its not neccessary, but I’ve added it to show what’s going on.

9251943--1293738--upload_2023-8-26_13-50-47.png

1 Like

Wow! Thank you a lot for such help. It is extremely useful!

Only some clarifies left to ask:
1.

Do you mean here something like this (it is from the official video tutorial):
9252948--1293987--upload_2023-8-27_0-49-59.png
If it is, then why this is faster than idiomatic foreach (which lies besides IJobEntity I suppose)? In this official tutorial it is even said that idiomatic foreach becomes something very similar to this after codegen magic?
I suppose after understanding this question I will also get why IJob is faster than IJobEntity (as I do not at the moment :()
Here is the official tutorial I am talking about if needed (timing - 22:20):
https://www.youtube.com/watch?v=P6_3L7RTcm0

We use [ReadOnly] only for some optimizations which are done automaticly by the Unity, right? So ideally we should write this attribute everywhere, when we dont change any data, right? If so, should we also use it for Systems fields or properties? And should we also use it for some simple data types such as integer or string or smth like that?

  1. Isnt it too overheading to put such easy commands in separate jobs? The thing is that somewhere in the official tutorials (dont remember where exactly) it was said that if we give a job too easy task it is less effective than doing it on main thread. Because it will be spent much more time on creating job, than on processing. Isn`t it so?

  2. Can we do something like this to stop unnecesary searches of MovementComponents?

state.Dependency = new DetectTerrainHit
        {
            CollisionWorld = singleton.CollisionWorld,
            Input = new RaycastInput
            {
                Start = ray.origin,
                End = ray.GetPoint(1000f),
                Filter = new CollisionFilter
                {
                    BelongsTo = ~0u,
                    CollidesWith = 1u << 7,
                    GroupIndex = 0
                }
            },
            Result = hitResult
        }.Schedule(state.Dependency);
        if (hitResult.Value.HasHit)
            // TODO Apply targets onto only selected units by specifying query as a Schedule parameter
            state.Dependency = new ApplyMovementTargetJob
            {
                Result = hitResult,
            }.Schedule(state.Dependency);

It seems to me a little more convenient way than messing up with ArchetypeChunks. Or your suggetion is more effective? Or my suggetion is wrong? Or there is another reason why it is better to manualy create Chunks Query and iterate over it?

Everything else is pretty clear for me now, so again big thanks.
Just for a note:

For generated code that was the problem, you were right (I was on 2022 version)

And these hints and of course your beautiful code were extremely helpful too! Again, again and again thank you for all your help!

1 Like

IJobEntity iterates over all entities that match its query. IJob + Archetype iteration allows you to stop job execution without iterating all entities if nothing is hit by the raycast. That’s why it would be faster on large number of entities.
But don’t do it on main thread.

No. Adding ReadOnly attribute to the native collection will mark it respectively for the safety system. Meaning different jobs could read safely from the same collection in parallel without triggering safety. If you attempt to write anything to the collection that is potentially read in parallel - safety will throw an exception (just like in this case).

Here’s the thing:

  1. Entities 1.x+ allows to BurstCompile scheduling. Which makes cost of scheduling drastically less or near non-existent for ISystems. This wasn’t the case for 0.51-. Plus, since 2022.2, UT did a great job optimizing job system as well.
  2. By not touching Physics data on the main thread you’re preventing other potential stalls of other parallel jobs that you might write in the future. Also, no Physics jobs are stalled this way.

Small jobs usually grow with extra conditions and such, but biggest pro in removing job stalls & keeping main thread free to continue execution. Generally good idea is to reduce all workload on the main thread to a minimum, and offload processing to the jobs / separate threads.
Less time spent on main thread → more jobs can be scheduled, more data can be processed.

Jobs do not [or may not] run instantly. Or scheduled instantly. Sometimes they’re even re-ordered.
Its not safe to read results, because job might not be complete or being scheduled at all.
So it won’t work, or will fail on random devices at random time. Otherwise you’d have to complete the first job raycast hit job before access which makes it useless.

1 Like

Thank you a lot! Now everything is clear.

I will try to summurize everything a little. Just for myself and maybe for those, who will read this thread in the future:
The problem in my situation was that safety checks were alarming that two copies of CollisionWorlds are trying to change the data of Physics native collections. Even though there is a high possibility that it is a fake alarm (because 2nd collisionWorld isnt even copied at the moment when 1st collisionWorld ends his work. And why error was pointing on the 1st collisionWorld usage - that might be a safety check bug or smth like this, I suppose). So the easiest solution is to put a sync point (via state.CompleteDependency()) before the place which error is pointing at. But in fact it isnt a good solution, because this way main thread and jobs are stalled (we should create as less sync points as possible). So the better solution is to put 1st collsionWorld usage in the job (as in the Entities 1.x creating a job is almost free). That way we will create a Dependency chain for the collisionWorlds, so safety checks wont be cursing us for that anymore.
Also very useful info:

  1. ArchetypeChunk iteration is basically the same thing as idiomatic foreach, but because it is low level, we can modify it in a way we need.
  2. [ReadOnly] attribute (which is from Unity.Collections) should be placed before every native collection (NativeArray or some struct). Because this way ECS knows that we are planning only to read from this collection and gives us an access to it as soon as nothing is writing into it (if we don`t add this attribute, ECS gives an access only after nothing is writing into it or reading it).
  3. Singletons are quirky. They are adding a dependency after them, but not checking it before using. Because of that using them in main thread without a sync point is not possible. So a better way is to GetSingleton in the Jobs (when we are writing new MyJob {mySingleton = SystemAPI.GetSingleton()}, we are getting singleton in the constructor of MyJob), because that way jobs are looking after dependencies, so it is not a problem that singleton doesn`t do that.
  4. NativeReference is an extremely useful thing :slight_smile: (for some reason it wasn`t mentioned in any tutorials I read or watched)
1 Like