Confustion with sharing NativeCollection between different SystemBase systems.

I’m confused about sharing a NativeArray (or other NativeCollections) between multiple SystemBase systems.

In other threads I read that a common practice is to make a public property for the Native Collection and then access it in other systems with GetOrCreateSystem<>(), and also that I need to keep track of the dependencies manually.

However when I’ve tried to set that up I get this error.

error DC0001: Entities.ForEach Lambda expression uses field '_gridOccupationMap'. Either assign the field to a local outside of the lambda expression and use that instead, or use .WithoutBurst() and .Run()

This seems like a major drawback and I haven’t seen anyone else mention it.

Can someone tell me what I’m doing wrong here? How do I make a public property and also assign it to a local? Am I stuck .WithoutBurst and .Run for all my systems that are the owner of shared NativeCollections?

using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;

namespace Cynics
{

    public class GridOccupationSystem : SystemBase
    {
        private NativeArray<Entity> _gridOccupationMap;

        public NativeArray<Entity> GridOccupationMap => _gridOccupationMap;

        private EndSimulationEntityCommandBufferSystem _endSimulationEntityCommandBufferSystem;
        
        private int _width;

        private int _gridSize;
        
        
        protected override void OnCreate()
        {
            base.OnCreate();

            var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

            var gridDefinition = entityManager.CreateEntityQuery(ComponentType.ReadOnly<GridDefinition>())
                .GetSingleton<GridDefinition>();


            _width = gridDefinition.width;
            _gridSize = gridDefinition.width * gridDefinition.height;

            _gridOccupationMap =
                new NativeArray<Entity>(_gridSize, Allocator.Persistent);

            _endSimulationEntityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();

        }

        public JobHandle GetDependency()
        {
            return Dependency;
        }

        public void AddDependency(JobHandle inputDependency)
        {
            Dependency = JobHandle.CombineDependencies(Dependency, inputDependency);
        }

        public Entity EntityAtIndex(int index)
        {
            if (index < 0 || index > _gridSize)
                return Entity.Null;

            return _gridOccupationMap[index];
        }

        public Entity EntityAtXY(int x, int y)
        {
            var index = GridMappingHelper.ArrayIndexFromGridLocation(x, y, _width);
            
            if (index < 0 || index > _gridSize)
                return Entity.Null;

            return _gridOccupationMap[index];
        }
        
        public bool IsOccupied(int index)
        {
            if (index < 0 || index > _gridSize)
                return false;

            return _gridOccupationMap[index] != Entity.Null;
        }
        
        protected override void OnUpdate()
        {
            var ecb = _endSimulationEntityCommandBufferSystem.CreateCommandBuffer();

            var gridDefinition = GetSingleton<GridDefinition>();
            var gridSize = gridDefinition.width * gridDefinition.height;
            
            Entities.WithAll<Tag_AddToOccupationGrid>().ForEach((Entity e, int entityInQueryIndex, in GridLocation gridLocation) =>
            {
                
                var index = GridMappingHelper.ArrayIndexFromGridLocation(gridLocation.x, gridLocation.y, gridDefinition.width);

                if (index < 0 || index > gridSize)
                    return;

                this._gridOccupationMap[index] = e;
                
                ecb.RemoveComponent<Tag_AddToOccupationGrid>(e);
                
            }).Schedule();
            
            Entities.WithAll<Tag_RemoveFromOccupationGrid>().ForEach((Entity e, int entityInQueryIndex, in GridLocation gridLocation) =>
            {
                
                var index = GridMappingHelper.ArrayIndexFromGridLocation(gridLocation.x, gridLocation.y, gridDefinition.width);

                if (index < 0 || index > gridSize)
                    return;

                this._gridOccupationMap[index] = Entity.Null;
                
                ecb.RemoveComponent<Tag_RemoveFromOccupationGrid>(e);

            }).Schedule();
        }
    }
}

If I understand your problem currently, you should just make a local copy of the collection variable straight before the Entities.ForEach(). Just like this:

var localGridOccupationMap = _gridOccupationMap;

And then use localGridOccupationMap inside of the Entities.ForEach(). If you want to call functions inside of the Entities.ForEach() where the collection i used, the function needs to be static and you should just include the collection as a parameter.

1 Like