Disposing of NativeArrays that are passed into Jobs

I am getting the infamous “A Native Collection has not been disposed, resulting in a memory leak. Enable Full StackTraces to get more details.” error, and I’m fairly sure I know why, but I 'm not sure how to fix it. I’m working on a jobified unit selection system, where I build a native array of Entities to pass into the job. Afterwards I dispose of the array. But I do not dispose of copy of the array inside the job. And quite frankly, I’m not sure how I would manage to. I’m using the array as a list which holds the currently selected units. This is necessary because I switch which units are being selected based on a “hostility-priority gauge”. The player can select, either their own units, the enemy units, or neutral units, but never a combination of the three. I’ll post the job for reference.

 public struct SelectionJob : IJobForEachWithEntity<SelectableComponent, Translation>
    {

        public EntityCommandBuffer.Concurrent buffer;
        public float3 lowerLeftPosition;
        public float3 upperRightPosition;
        public int selectingHostileLevel;//set to 0
        public int numberOfCurrentSelected;//set to 0
        public NativeArray<Entity> currentSelectedTemp;
        public float3 camPos;
        public float4x4 camProjMaxtrix;
        public float3 camUp;
        public float3 camRight;
        public float3 camForward;
        public float screenWidth;
        public float screenHeight;
        public int myID;
        public NativeArray<bool> teamMatrix;
        public void Execute(Entity entity, int index, ref SelectableComponent selectionData, ref Translation translation)
        {
            float2 entityPosition = ConvertWorldToScreenCoordinates(translation.Value, camPos, camProjMaxtrix, camUp, camRight, camForward, screenWidth, screenHeight, 1);
            if (entityPosition.x >= lowerLeftPosition.x &&
               entityPosition.y >= lowerLeftPosition.y &&
               entityPosition.x <= upperRightPosition.x &&
               entityPosition.y <= upperRightPosition.y)
            {
                int ownerID = selectionData.ownerID;
                int ownerHostilityGauge = DeterminTeam(ownerID, myID, teamMatrix);

                if (ownerHostilityGauge == selectingHostileLevel)
                {

                    buffer.AddComponent<SelectedComponent>(index, entity);
                    if (selectingHostileLevel != 1)
                    {
                        currentSelectedTemp[numberOfCurrentSelected] = entity;
                        numberOfCurrentSelected++;
                    }
                }
                else if (selectingHostileLevel != 1)
                {
                    if ((selectingHostileLevel == 0 && ownerHostilityGauge != 0) || (selectingHostileLevel == -1 && ownerHostilityGauge == 1))
                    {
                        selectingHostileLevel = ownerHostilityGauge;

                        int length = numberOfCurrentSelected;
                        for (int i = 0; i < length; i++)
                        {
                            buffer.RemoveComponent<SelectedComponent>(index, currentSelectedTemp[i]);
                        }

                        numberOfCurrentSelected = 0;

                        buffer.AddComponent<SelectedComponent>(index, entity);
                        if (selectingHostileLevel != 1)
                        {
                            currentSelectedTemp[numberOfCurrentSelected] = entity;
                            numberOfCurrentSelected++;
                        }
                    }
                }
            }
        }


        int DeterminTeam(int unitTeam, int myID, NativeArray<bool> teamMatrix)
        {
             //var teamMatrix = sceneManager.playerList[myID].playerTeamMatrix;

            if (myID == unitTeam)
            {
                return 1;
            }
            else if (teamMatrix[unitTeam])
            {
                return -1;
            }
            else if (!teamMatrix[unitTeam])
            {
                return 0;
            }

            return 0;

        }

        public static float2 ConvertWorldToScreenCoordinates(float3 point, float3 cameraPos, float4x4 camProjMatrix, float3 camUp, float3 camRight, float3 camForward, float pixelWidth, float pixelHeight, float scaleFactor)
        {
            /*
            * 1 convert P_world to P_camera
            */
            float4 pointInCameraCoodinates = ConvertWorldToCameraCoordinates(point, cameraPos, camUp, camRight, camForward);


            /*
            * 2 convert P_camera to P_clipped
            */
            float4 pointInClipCoordinates = math.mul(camProjMatrix, pointInCameraCoodinates);

            /*
            * 3 convert P_clipped to P_ndc
            * Normalized Device Coordinates
            */
            float4 pointInNdc = pointInClipCoordinates / pointInClipCoordinates.w;


            /*
            * 4 convert P_ndc to P_screen
            */
            float2 pointInScreenCoordinates;
            pointInScreenCoordinates.x = pixelWidth / 2.0f * (pointInNdc.x + 1);
            pointInScreenCoordinates.y = pixelHeight / 2.0f * (pointInNdc.y + 1);


            // return screencoordinates with canvas scale factor (if canvas coords required)
            return pointInScreenCoordinates / scaleFactor;
        }

        private static float4 ConvertWorldToCameraCoordinates(float3 point, float3 cameraPos, float3 camUp, float3 camRight, float3 camForward)
        {
            // translate the point by the negative camera-offset
            //and convert to Vector4
            float4 translatedPoint = new float4(point - cameraPos, 1f);

            // create transformation matrix
            float4x4 transformationMatrix = float4x4.identity;
            transformationMatrix.c0 = new float4(camRight.x, camUp.x, -camForward.x, 0);
            transformationMatrix.c1 = new float4(camRight.y, camUp.y, -camForward.y, 0);
            transformationMatrix.c2 = new float4(camRight.z, camUp.z, -camForward.z, 0);

            float4 transformedPoint = math.mul(transformationMatrix, translatedPoint);

            return transformedPoint;
        }


    }

If anyone has any ideas on how to dispose of this implementation of native array, your help will be appreciated.

On another error note, This job runs before the selection job

 [BurstCompile][RequireComponentTag(typeof(SelectedComponent))]//if no shift is down, run this Job
    public struct RemovedSelectedJob : IJobForEachWithEntity<Translation>
    {
        public EntityCommandBuffer.Concurrent buffer;
        public void Execute(Entity entity, int index, [ReadOnly] ref Translation trans)
        {
            buffer.RemoveComponent<SelectedComponent>(index, entity);
        }
    }

and is giving me a “Cannot find the field TypeInfos required for supporting TypeManager intrinsics in burst” error at line buffer.RemoveComponent<SelectedComponent>(index, entity);, so can I not use the buffer with burst? or am I just using it incorrectly? if the former, will that be fixed at some point? is there a better way to do selection?

Use

[DeallocateOnJobCompletion] public NativeArray<bool> teamMatrix;

Also burst is not yet compatible with adding or removing components, try running it without burst and see what it says

1 Like

Okay, Thanks! That is what I was looking for. Sadly it did not help, as a matter of fact, it made things worse. So I guess my next question is what is the path to find ThreadsafeLinearAllocator.cpp to enable full stack trace?

Can you show part of the code in the system that schedule the SelectionJob?

1 Like

Sure! This is in the OnUpdate method.

 if (Input.GetMouseButtonUp(0))
        {
            sceneManager.selectionAreaTransform.gameObject.SetActive(false);
            float3 endPosition = Input.mousePosition;
            isSelecting = false;
            if (CheckChatBoxMode()) { return nullHandle; }
          
            float3 lowerLeftPosition = new float3(math.min(graphicStartPosition.x, endPosition.x), math.min(graphicStartPosition.y, endPosition.y), 0);
            float3 UpperRightPosition = new float3(math.max(graphicStartPosition.x, endPosition.x), math.max(graphicStartPosition.y, endPosition.y), 0);

            if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift))
            {
                var removeSelectedJob = new RemovedSelectedJob
                {
                    buffer = buffer.CreateCommandBuffer().ToConcurrent()
                }.Schedule(this, inputDeps);
                removeSelectedJob.Complete();
            }

            float selectionAreaMinSize = 5f;
            float selectionAreaSize = math.distance(lowerLeftPosition, UpperRightPosition);

            Entity clickedEntity = Entity.Null;

            if (selectionAreaSize < selectionAreaMinSize)
            {
                clickedEntity = DetermineClickedEntity(sceneManager.groundLayer);
                var tempBuffer = buffer.CreateCommandBuffer();
                tempBuffer.AddComponent<SelectedComponent>(clickedEntity);
            }
            else
            {
///////////////////////////////////////////////This is where the job is scheduled///////////////////////////////////////////
                NativeArray<Entity> currentSelectedTemp = new NativeArray<Entity>(1000, Allocator.TempJob);
                NativeArray<bool> teamMatrixTemp = new NativeArray<bool>(sceneManager.PlayerTeamMatrixOutput(sceneManager.me.playerID), Allocator.TempJob);
                var selectionJob = new SelectionJob
                {
                    buffer = buffer.CreateCommandBuffer().ToConcurrent(),
                    lowerLeftPosition = lowerLeftPosition,
                    upperRightPosition = UpperRightPosition,
                    selectingHostileLevel = 0,
                    numberOfCurrentSelected=0,
                    currentSelectedTemp = currentSelectedTemp,
                    camPos = mainCam.transform.position,
                    camProjMaxtrix = mainCam.projectionMatrix,
                    camUp = mainCam.transform.up,
                    camRight = mainCam.transform.right,
                    camForward = mainCam.transform.forward,
                    screenWidth = mainCam.pixelWidth,
                    screenHeight = mainCam.pixelHeight,
                    myID = sceneManager.me.playerID,
                    teamMatrix = teamMatrixTemp
                }.Schedule(this, inputDeps);
                currentSelectedTemp.Dispose();
                teamMatrixTemp.Dispose();
                selectionJob.Complete();

                return selectionJob;

            }

        }

You are disposing your NativeArrays before you job get executed! I’m not sure, but I think the error in your first post is a cascade effect of another error you are getting because your job is using a disposed array.

You should either :

  1. move your Dispose() after selectionJob.Complete()
  2. remove the Dispose() and selectionJob.Complete() and add [DeallocateOnJobCompletion] before the NativeArray in your job.

That being said, you should try to never call Complete() on JobHandle (if possible), because this is blocking the main thread until the job get executed.

1 Like

Okay, this is my new job input

 public EntityCommandBuffer.Concurrent buffer;
        public float3 lowerLeftPosition;
        public float3 upperRightPosition;
        public int selectingHostileLevel;//set to 0
        public int numberOfCurrentSelected;//set to 0
        [DeallocateOnJobCompletion] public NativeArray<Entity> currentSelectedTemp;
        public float3 camPos;
        public float4x4 camProjMaxtrix;
        public float3 camUp;
        public float3 camRight;
        public float3 camForward;
        public float screenWidth;
        public float screenHeight;
        public int myID;
        [DeallocateOnJobCompletion] public NativeArray<bool> teamMatrix;

and I removed the job.complete() and disposes. I’m still getting the same error. I think enabling the full stack trace would at least tell me where my issues are. Any idea where that file is?

Your code should look something like this. Keep the [DeAllocateOnJobCompletion] in the job itself on native arrays.

NativeArray<Entity> currentSelectedTemp = new NativeArray<Entity>(1000, Allocator.TempJob);
NativeArray<bool> teamMatrixTemp = new NativeArray<bool>(sceneManager.PlayerTeamMatrixOutput(sceneManager.me.playerID), Allocator.TempJob);
                var selectionJob = new SelectionJob
                {
                    buffer = buffer.CreateCommandBuffer().ToConcurrent(),
                    lowerLeftPosition = lowerLeftPosition,
                    upperRightPosition = UpperRightPosition,
                    selectingHostileLevel = 0,
                    numberOfCurrentSelected=0,
                    currentSelectedTemp = currentSelectedTemp,
                    camPos = mainCam.transform.position,
                    camProjMaxtrix = mainCam.projectionMatrix,
                    camUp = mainCam.transform.up,
                    camRight = mainCam.transform.right,
                    camForward = mainCam.transform.forward,
                    screenWidth = mainCam.pixelWidth,
                    screenHeight = mainCam.pixelHeight,
                    myID = sceneManager.me.playerID,
                    teamMatrix = teamMatrixTemp
                }.Schedule(this, inputDeps);

               buffer.AddJobHandleForProducer(selectionJob)
                return selectionJob;
1 Like

That certainly helped! There are still aparently a couple issues. One is I am calling the removeSelected job before the SelectionJob, and it doesn’t like that I’m not return a jobhandle. Any way to handle this without creating a new system? Here is my whole OnUpdate Method, seeing as its apparently much worse than I thought (I recently jobified the code, although it was working fine on the main thread).

protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        JobHandle nullHandle = new JobHandle { };

        if (Input.GetMouseButtonDown(0))//reset the start position of the selection area
        {
            sceneManager.selectionAreaTransform.gameObject.SetActive(true);
            graphicStartPosition = Input.mousePosition;
            sceneManager.selectionAreaTransform.position = graphicStartPosition;
            isSelecting = true;

        }

        if (Input.GetMouseButton(0))//keep resizing the area while the mouse is held down
        {

            float3 graphicSelectionAreaSize = (float3)Input.mousePosition - graphicStartPosition;
            sceneManager.selectionAreaTransform.localScale = graphicSelectionAreaSize;
            if (CheckChatBoxMode()) { return nullHandle; }
        }

        if (Input.GetMouseButtonUp(0))
        {
            sceneManager.selectionAreaTransform.gameObject.SetActive(false);
            float3 endPosition = Input.mousePosition;
            isSelecting = false;
            if (CheckChatBoxMode()) { return nullHandle; }
         
            float3 lowerLeftPosition = new float3(math.min(graphicStartPosition.x, endPosition.x), math.min(graphicStartPosition.y, endPosition.y), 0);
            float3 UpperRightPosition = new float3(math.max(graphicStartPosition.x, endPosition.x), math.max(graphicStartPosition.y, endPosition.y), 0);

            if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift))
            {
                var removeSelectedJob = new RemovedSelectedJob
                {
                    buffer = buffer.CreateCommandBuffer().ToConcurrent()
                }.Schedule(this, inputDeps);
                buffer.AddJobHandleForProducer(removeSelectedJob);
               // return removeSelectedJob;
            }

            float selectionAreaMinSize = 5f;
            float selectionAreaSize = math.distance(lowerLeftPosition, UpperRightPosition);

            Entity clickedEntity = Entity.Null;

            if (selectionAreaSize < selectionAreaMinSize)
            {
                clickedEntity = DetermineClickedEntity(sceneManager.groundLayer);
                var tempBuffer = buffer.CreateCommandBuffer();
                tempBuffer.AddComponent<SelectedComponent>(clickedEntity);
            }
            else
            {

                NativeArray<Entity> currentSelectedTemp = new NativeArray<Entity>(1000, Allocator.TempJob);
                NativeArray<bool> teamMatrixTemp = new NativeArray<bool>(sceneManager.PlayerTeamMatrixOutput(sceneManager.me.playerID), Allocator.TempJob);
                var selectionJob = new SelectionJob
                {
                    buffer = buffer.CreateCommandBuffer().ToConcurrent(),
                    lowerLeftPosition = lowerLeftPosition,
                    upperRightPosition = UpperRightPosition,
                    selectingHostileLevel = 0,
                    numberOfCurrentSelected=0,
                    currentSelectedTemp = currentSelectedTemp,
                    camPos = mainCam.transform.position,
                    camProjMaxtrix = mainCam.projectionMatrix,
                    camUp = mainCam.transform.up,
                    camRight = mainCam.transform.right,
                    camForward = mainCam.transform.forward,
                    screenWidth = mainCam.pixelWidth,
                    screenHeight = mainCam.pixelHeight,
                    myID = sceneManager.me.playerID,
                    teamMatrix = teamMatrixTemp
                }.Schedule(this, inputDeps);
                buffer.AddJobHandleForProducer(selectionJob);


                return selectionJob;

            }

        }



        return nullHandle;
    }

just return inputDeps if the criteria for job scheduling isnt met. no need for the null handle.

also full stack tracing is in the menu toolbar: jobs>leak detection> full stack tracing

1 Like

Try something like this. This will create the proper dependencies required.

protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
    if (Input.GetMouseButtonDown(0))//reset the start position of the selection area
        {
            sceneManager.selectionAreaTransform.gameObject.SetActive(true);
            graphicStartPosition = Input.mousePosition;
            sceneManager.selectionAreaTransform.position = graphicStartPosition;
            isSelecting = true;
        }
        if (Input.GetMouseButton(0))//keep resizing the area while the mouse is held down
        {
            float3 graphicSelectionAreaSize = (float3)Input.mousePosition - graphicStartPosition;
            sceneManager.selectionAreaTransform.localScale = graphicSelectionAreaSize;
            if (CheckChatBoxMode()) { return inputDeps; }
        }
        if (Input.GetMouseButtonUp(0))
        {
            sceneManager.selectionAreaTransform.gameObject.SetActive(false);
            float3 endPosition = Input.mousePosition;
            isSelecting = false;
            if (CheckChatBoxMode()) { return inputDeps; }
        
            float3 lowerLeftPosition = new float3(math.min(graphicStartPosition.x, endPosition.x), math.min(graphicStartPosition.y, endPosition.y), 0);
            float3 UpperRightPosition = new float3(math.max(graphicStartPosition.x, endPosition.x), math.max(graphicStartPosition.y, endPosition.y), 0);
            if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift))
            {
                var removeSelectedJob = new RemovedSelectedJob
                {
                    buffer = buffer.CreateCommandBuffer().ToConcurrent()
                };
                inputDeps = removeSelectedJob.Schedule(inputDeps);
                buffer.AddJobHandleForProducer(removeSelectedJob);
            }
            float selectionAreaMinSize = 5f;
            float selectionAreaSize = math.distance(lowerLeftPosition, UpperRightPosition);
            Entity clickedEntity = Entity.Null;
            if (selectionAreaSize < selectionAreaMinSize)
            {
                clickedEntity = DetermineClickedEntity(sceneManager.groundLayer);
                var tempBuffer = buffer.CreateCommandBuffer();
                tempBuffer.AddComponent<SelectedComponent>(clickedEntity);
            }
            else
            {
                NativeArray<Entity> currentSelectedTemp = new NativeArray<Entity>(1000, Allocator.TempJob);
                NativeArray<bool> teamMatrixTemp = new NativeArray<bool>(sceneManager.PlayerTeamMatrixOutput(sceneManager.me.playerID), Allocator.TempJob);
                var selectionJob = new SelectionJob
                {
                    buffer = buffer.CreateCommandBuffer().ToConcurrent(),
                    lowerLeftPosition = lowerLeftPosition,
                    upperRightPosition = UpperRightPosition,
                    selectingHostileLevel = 0,
                    numberOfCurrentSelected=0,
                    currentSelectedTemp = currentSelectedTemp,
                    camPos = mainCam.transform.position,
                    camProjMaxtrix = mainCam.projectionMatrix,
                    camUp = mainCam.transform.up,
                    camRight = mainCam.transform.right,
                    camForward = mainCam.transform.forward,
                    screenWidth = mainCam.pixelWidth,
                    screenHeight = mainCam.pixelHeight,
                    myID = sceneManager.me.playerID,
                    teamMatrix = teamMatrixTemp
                };
                inputDeps = removeSelectedJob.Schedule(inputDeps);
                buffer.AddJobHandleForProducer(selectionJob);

            }
        }
        return inputDeps;
    }
1 Like

That did it! Okay, thank you for clearing that up for me. Sadly I still have another issue. I’m receiving a “IndexOutOfRangeException: Index 2 is out of restricted IJobParallelFor range [518…2] in ReadWriteBuffer.” error when I select units that are not owned by the player. The error is thrown from the DeterminTeam() method in the selection job.
Code below

 public struct SelectionJob : IJobForEachWithEntity<SelectableComponent, Translation>
    {

        public EntityCommandBuffer.Concurrent buffer;
        public float3 lowerLeftPosition;
        public float3 upperRightPosition;
        public int selectingHostileLevel;//set to 0
        public int numberOfCurrentSelected;//set to 0
        [DeallocateOnJobCompletion] public NativeArray<Entity> currentSelectedTemp;
        public float3 camPos;
        public float4x4 camProjMaxtrix;
        public float3 camUp;
        public float3 camRight;
        public float3 camForward;
        public float screenWidth;
        public float screenHeight;
        public int myID;
        [DeallocateOnJobCompletion] public NativeArray<bool> teamMatrix;
        public void Execute(Entity entity, int index, [ReadOnly] ref SelectableComponent selectionData, [ReadOnly] ref Translation translation)
        {
            float2 entityPosition = ConvertWorldToScreenCoordinates(translation.Value, camPos, camProjMaxtrix, camUp, camRight, camForward, screenWidth, screenHeight, 1);
            if (entityPosition.x >= lowerLeftPosition.x &&
               entityPosition.y >= lowerLeftPosition.y &&
               entityPosition.x <= upperRightPosition.x &&
               entityPosition.y <= upperRightPosition.y)
            {
                int ownerID = selectionData.ownerID;
                int ownerHostilityGauge = DeterminTeam(ownerID, myID, teamMatrix);

                if (ownerHostilityGauge == selectingHostileLevel)
                {

                    buffer.AddComponent<SelectedComponent>(index, entity);
                    if (selectingHostileLevel != 1)
                    {
                        currentSelectedTemp[numberOfCurrentSelected] = entity;
                        numberOfCurrentSelected++;
                    }
                }
                else if (selectingHostileLevel != 1)
                {
                    if ((selectingHostileLevel == 0 && ownerHostilityGauge != 0) || (selectingHostileLevel == -1 && ownerHostilityGauge == 1))
                    {
                        selectingHostileLevel = ownerHostilityGauge;

                        int length = numberOfCurrentSelected;
                        for (int i = 0; i < length; i++)
                        {
                            buffer.RemoveComponent<SelectedComponent>(index, currentSelectedTemp[i]);
                        }

                        numberOfCurrentSelected = 0;

                        buffer.AddComponent<SelectedComponent>(index, entity);
                        if (selectingHostileLevel != 1)
                        {
                            currentSelectedTemp[numberOfCurrentSelected] = entity;
                            numberOfCurrentSelected++;
                        }
                    }
                }
            }
        }


        int DeterminTeam(int unitTeam, int myID, NativeArray<bool> teamMatrix)
        {
             //var teamMatrix = sceneManager.playerList[myID].playerTeamMatrix;

            if (myID == unitTeam)
            {
                //teamMatrix.Dispose();
                return 1;
            }
            else if (teamMatrix[unitTeam])
            {
               // teamMatrix.Dispose();
                return -1;
            }
            else if (!teamMatrix[unitTeam])
            {
                //teamMatrix.Dispose();
                return 0;
            }
            //teamMatrix.Dispose();
            return 0;

        }

        public static float2 ConvertWorldToScreenCoordinates(float3 point, float3 cameraPos, float4x4 camProjMatrix, float3 camUp, float3 camRight, float3 camForward, float pixelWidth, float pixelHeight, float scaleFactor)
        {
            /*
            * 1 convert P_world to P_camera
            */
            float4 pointInCameraCoodinates = ConvertWorldToCameraCoordinates(point, cameraPos, camUp, camRight, camForward);


            /*
            * 2 convert P_camera to P_clipped
            */
            float4 pointInClipCoordinates = math.mul(camProjMatrix, pointInCameraCoodinates);

            /*
            * 3 convert P_clipped to P_ndc
            * Normalized Device Coordinates
            */
            float4 pointInNdc = pointInClipCoordinates / pointInClipCoordinates.w;


            /*
            * 4 convert P_ndc to P_screen
            */
            float2 pointInScreenCoordinates;
            pointInScreenCoordinates.x = pixelWidth / 2.0f * (pointInNdc.x + 1);
            pointInScreenCoordinates.y = pixelHeight / 2.0f * (pointInNdc.y + 1);


            // return screencoordinates with canvas scale factor (if canvas coords required)
            return pointInScreenCoordinates / scaleFactor;
        }

        private static float4 ConvertWorldToCameraCoordinates(float3 point, float3 cameraPos, float3 camUp, float3 camRight, float3 camForward)
        {
            // translate the point by the negative camera-offset
            //and convert to Vector4
            float4 translatedPoint = new float4(point - cameraPos, 1f);

            // create transformation matrix
            float4x4 transformationMatrix = float4x4.identity;
            transformationMatrix.c0 = new float4(camRight.x, camUp.x, -camForward.x, 0);
            transformationMatrix.c1 = new float4(camRight.y, camUp.y, -camForward.y, 0);
            transformationMatrix.c2 = new float4(camRight.z, camUp.z, -camForward.z, 0);

            float4 transformedPoint = math.mul(transformationMatrix, translatedPoint);

            return transformedPoint;
        }


    }

Is there any way to multithread this? or create a switch by some other means?
Essentially what I’m doing is I look at the hostility level of the selected unit (either neutral (0), Enemy (-1) or friendly (1)), if it is neutral, I add it to a List currentlySelected. Then if an entity in the selection area is an enemy, I use the currentlySelected list to do a lookup on all the neutral entities I was selecting this frame, and remove their selected component. Then I start only adding Enemy units. Repeat this for Enemy-> Friendly. The problem arises that the list I am using (which is a native array) needs to be shared across jobs. I suppose my question would then be, “is there a different way to do this?”