Unity 2022.3.18f1
Entities 1.2.0
Collections 2.4.0
Let me explain the issue.
We have 2 Systems and 1 Job - WriterSystem, ReaderSystem & ReadFromListJob. The ReaderSystem updates after the WriterSystem and executes the Job ReadFromListJob.
On the first 3 frames the safety system allows me to write to the NativeList from the WriterSystem, however on the 4. frame and later it throws an InvalidOperationException, which states “The previously scheduled job ReaderSystem:ReadFromListJob reads from the Unity.Collections.NativeList”
As far as I am aware, scheduling a Job locks the safety handle of the respective Collection and after the Job completes it releases the safety handle of the said Collection. Furthermore, trying to access a Collection from the main thread, which has its safety handle locked will throw an Exception, which absolutely makes sense.
Thus, how is it possible that the WriterSystem, which always updates before the ReaderSystem (+ ReadFromListJob) is not allowed to write to the NativeList? ReadFromListJob completes before the next frame, so the safety handle should be released and thus allowing writing to the Collection.
Personally, I don’t know how to exactly check which Jobs or how many Jobs still have locked the safety handle. I tried using the static functions of AtomicSafetyHandle but wasn’t able to find a way.
The only way to prevent this conundrum is by applying the NativeDisableContainerSafetyRestriction Attribute on the Collection inside the ReadFromListJob or state.CompleteDependency() in the WriterSystem, which in my opinion are not elegant solutions.
I am curious if anyone else has/had this problem and how they have dealt with it.
EDIT:
As tertle mentioned below, completing the Dependency of the whole State isn’t a preferable solution and suggested to only complete the dependency on the Singleton Entity. I have adapted the code to include this solution.
[UpdateAfter(typeof(WriterSystem))]
[CreateAfter(typeof(WriterSystem))]
[UpdateInGroup(typeof(ReaderWriterSystemGroup))]
public partial struct ReaderSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<SingletonTestCollection>();
}
public void OnDestroy(ref SystemState state) { }
public void OnUpdate(ref SystemState state)
{
var testList = SystemAPI.GetSingleton<SingletonTestCollection>().testList;
state.Dependency = new ReadFromListJob
{
list = testList
}.Schedule(state.Dependency);
}
private struct ReadFromListJob : IJob
{
//[NativeDisableContainerSafetyRestriction]
[ReadOnly]
public NativeList<int> list;
public void Execute()
{
UnityEngine.Debug.Log($"Reading {list.Length} items.");
}
}
}
[UpdateBefore(typeof(ReaderSystem))]
[CreateBefore(typeof(ReaderSystem))]
[UpdateInGroup(typeof(ReaderWriterSystemGroup))]
public partial struct WriterSystem : ISystem
{
private int _iterationCount;
private EntityQuery _clearCollectionSafety;
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<SingletonTestCollection>();
state.EntityManager.CreateSingleton( new SingletonTestCollection { testList = new NativeList<int>(32, Allocator.Persistent) });
_iterationCount = 0;
_clearCollectionSafety = SystemAPI.QueryBuilder().WithAllRW<SingletonTestCollection>().Build();
}
public void OnDestroy(ref SystemState state)
{
state.Dependency = SystemAPI.GetSingletonRW<SingletonTestCollection>().ValueRW.testList.Dispose(state.Dependency);
}
public void OnUpdate(ref SystemState state)
{
//state.CompleteDependency();
//_clearCollectionSafety.CompleteDependency();
var testList = SystemAPI.GetSingletonRW<SingletonTestCollection>().ValueRW.testList;
UnityEngine.Debug.Log("Adding from Writer System. Iteration: " + ++_iterationCount);
testList.Add(0);
}
}
public struct SingletonTestCollection : IComponentData
{
public NativeList<int> testList;
}
public partial class ReaderWriterSystemGroup : ComponentSystemGroup { }