Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Dependencies, Deallocation, and Add Component [SOLVED]

Discussion in 'Entity Component System' started by bellicapax, Jul 7, 2018.

  1. bellicapax

    bellicapax

    Joined:
    Oct 5, 2016
    Posts:
    14
    Hi!

    I'm trying to implement a physics system with the ECS & Job Systems.

    Right now only these scripts exist:

    Systems
    • SpaceMembershipSystem
    • SpaceMembershipBarrier
    • PhysicsSystem
    Components
    • PhysicsObject : empty ISharedComponentData
    ComponentGroups
    • MembershipDebugGroup
    Jobs
    • SpaceMembershipJob
    • MembershipDebugJob
    SpaceMembershipSystem
    The SpaceMembershipSystem is a JobComponentSystem that takes requests to add or remove an entity from the space and adds them to a collection. In its OnUpdate() it creates a command buffer from the SpaceMembershipBarrier. Inside the SpaceMembershipJob it uses the buffer to Add a PhysicsObject component for each add request and Remove the same for each remove request.
    I'm using two NativeArrays inside the SpaceMembershipJob to hold references to the entities and the request types (as bytes).

    My first question is must I call Complete() on this SpaceMembershipJob so that I can Dispose() of these NativeArrays or is there another way to do that? It seems if I just return the handle, they don't get automatically deallocated.

    My second question is can I not use an IJobParallelFor to add or remove components on entities? It seemed to not let me.

    Both the SpaceMembershipSystem and the SpaceMembershipBarrier are marked as UpdateBefore(PhysicsSystem). I'm honestly not sure which one matters more or if they both do.

    PhysicsSystem
    The PhysicsSystem is a JobComponentSystem that currently only Injects the MembershipDebugGroup and schedules a new MembershipDebugJob in OnUpdate. The group consists of the SharedComponentDataArray<PhysicsObject>, an EntityArray, and Length. The MembershipDebugJob is an IJobParallelFor that debugs the Entities in the EntityArray which is passed in via the injected group in the system. (I am passing in the inputDeps)

    My third question is how do I get this MembershipDebugJob to actually run and not get deallocated via the add component (I assume) in the other system? I want to just schedule it without calling Complete() (which does work) so that when I have real physics computations, they have until the next OnUpdate() to run.

    It did work when I had the add and remove job and the barrier system inside the PhysicsSystem and then only had one system, but it wouldn't add or remove until the next frame. In the current system if I force both jobs to complete the component gets added on the same frame so the PhysicsSystem can access it in the group (as I want).

    Any insights or help would be greatly appreciated!
     
    Last edited: Jul 7, 2018
  2. Tudor_n

    Tudor_n

    Joined:
    Dec 10, 2009
    Posts:
    359
    Hi,

    In order:
    1. Try using the [DeallocateOnJobCompletion] tag in the last job in the dependency chain. I sometimes have a cleanup job specifically for this. A better pattern might exist, but I don't know it. ( I have similar problems with containers that don't support this tag, like NativeList, and it is a PITA )
    2. Check EntityCommandBuffer.Concurrent
    3. This depends too much on your setup, existing code and goals for me to give a straight answer now. ( it's also way too early for that )
     
    Last edited: Jul 7, 2018
    bellicapax likes this.
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,635
    Question 1

    You can automatically dispose of collections after a job is complete using [DeallocateOnJobCompletion]

    If you happen to have multiple of the same job running sequentially, which is something I happened to run into today, my solution was to simply add 1 more job at the end of the chain to dispose of them.

    Code (CSharp):
    1.         [BurstCompile]
    2.         private struct DeallocatePointsJob : IJob
    3.         {
    4.             [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<int2> FogPoints;
    5.          
    6.             public void Execute()
    7.             {
    8.                 /* NOOP */
    9.             }
    10.         }
    Question 2

    No, setup a barrier system

    Inject it into the system, create a buffer and use that to add components after the parallel job is done.

    Code (CSharp):
    1.     public class FaceBarrierSystem : BarrierSystem
    2.     {
    3.     }
    4.  
    5.     public class FaceCullingSystem : JobComponentSystem
    6.     {
    7.         [Inject] private FaceBarrierSystem _faceBarrierSystem;
    8.      
    9.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    10.         {
    11.             var buffer = _faceBarrierSystem.CreateCommandBuffer();
    12.          
    13.             var getVisibleFacesJob = new GetVisibleFacesJob
    14.             {
    15.                 CommandBuffer = buffer.Concurrent
    16.  
    17.             ....        
    And use that to add / remove components (after the job completes)
     
    bellicapax likes this.
  4. bellicapax

    bellicapax

    Joined:
    Oct 5, 2016
    Posts:
    14
    Thank you both for the suggestions to 1 and 2. They are working well! I am still lost on how to deal with 3. It seems I can run my additions in a barrier system (but not in a job) and it works ok, but I want to run them in a job :/
     
  5. bellicapax

    bellicapax

    Joined:
    Oct 5, 2016
    Posts:
    14
    OK! I got it working!

    tl;dr:
    • The job to add and remove components must be scheduled within the same system as the one manipulating those entities. (I.e., I had to get rid of the SpaceMembershipSystem and put its logic into the PhysicsSystem)
    • I had to call JobHandle.ScheduleBatchedJobs() If someone could really explain what this method does fully I would super appreciate that. I've read the description but can't wrap my head around it.

    In case anyone is curious what this looks like, here are my 5 scripts that simply add, remove, and debug entities. The performance hit is really the debugging.

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.BEPUphysics;
    3. using Unity.BEPUphysics.Components;
    4. using Unity.Entities;
    5. using System.Collections.Generic;
    6.  
    7. public class PhysicsTest : MonoBehaviour
    8. {
    9.     private const int MAX_CHANGES_PER_FRAME = 100;
    10.     private EntityManager _entityManager;
    11.     private List<Entity> _entities = new List<Entity>();
    12.  
    13.     private void Awake()
    14.     {
    15.         _entityManager = World.Active.GetOrCreateManager<EntityManager>();
    16.         var e = _entityManager.CreateEntity();
    17.         _entityManager.AddSharedComponentData(e, new PhysicsObject { });
    18.     }
    19.  
    20.     private void Update()
    21.     {
    22.         var val = Random.value;
    23.         if (val > 0.75f)
    24.         {
    25.             var changes = Random.Range(0, MAX_CHANGES_PER_FRAME);
    26.             for (int i = 0; i < changes; i++)
    27.             {
    28.                 var e = _entityManager.CreateEntity();
    29.                 _entities.Add(e);
    30.                 PhysicsSystem.Add(e);
    31.             }
    32.         }
    33.         else if (val < 0.25f && _entities.Count > 0)
    34.         {
    35.             var changes = Mathf.Min(_entities.Count, Random.Range(0, MAX_CHANGES_PER_FRAME));
    36.             for (int i = 0; i < changes; i++)
    37.             {
    38.                 var index = Random.Range(0, _entities.Count);
    39.                 var e = _entities[index];
    40.                 PhysicsSystem.Remove(e);
    41.                 _entities.RemoveAt(index);
    42.             }
    43.         }
    44.     }
    45. }
    46.  

    Code (CSharp):
    1. using Unity.BEPUphysics.Jobs;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Collections;
    5.  
    6. namespace Unity.BEPUphysics
    7. {
    8.     public class PhysicsSystem : JobComponentSystem
    9.     {
    10.         [Inject] private MembershipDebugGroup _membershipDebugGroup;
    11.  
    12.         protected override void OnCreateManager(int capacity)
    13.         {
    14.             _entitiesWithMembershipChanges = new NativeList<Entity>(Allocator.Persistent);
    15.             _requestedMembershipChanges = new NativeList<byte>(Allocator.Persistent);
    16.         }
    17.  
    18.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    19.         {
    20.             var membershipHandle = ScheduleMembershipChanges(inputDeps);
    21.             var handle = new MembershipDebugJob
    22.             {
    23.                 EntityArray = _membershipDebugGroup.EntityArray,
    24.             }.Schedule(_membershipDebugGroup.Length, 64, membershipHandle);
    25.             JobHandle.ScheduleBatchedJobs();
    26.             return handle;
    27.         }
    28.  
    29.         protected override void OnDestroyManager()
    30.         {
    31.             _entitiesWithMembershipChanges.Dispose();
    32.             _requestedMembershipChanges.Dispose();
    33.         }
    34.  
    35.         #region Membership
    36.  
    37.         [Inject] private PhysicsMembershipBarrier _physicsMembershipBarrier;
    38.         private static NativeList<Entity> _entitiesWithMembershipChanges;
    39.         private static NativeList<byte> _requestedMembershipChanges;
    40.  
    41.         public static void Add(Entity entity)
    42.         {
    43.             TryAddRequest(entity, true);
    44.         }
    45.  
    46.         public static void Remove(Entity entity)
    47.         {
    48.             TryAddRequest(entity, false);
    49.         }
    50.  
    51.         // This currently has no way to know if you've ever previously added a component
    52.         private static void TryAddRequest(Entity entity, bool add)
    53.         {
    54.             byte desired = (byte)(add ? 1 : 0);
    55.             if (_entitiesWithMembershipChanges.Contains(entity))
    56.             {
    57.                 var i = _entitiesWithMembershipChanges.IndexOf(entity);
    58.                 var request = _requestedMembershipChanges[i];
    59.                 if (request == desired)
    60.                     return;
    61.                 else
    62.                 {
    63.                     _entitiesWithMembershipChanges.RemoveAtSwapBack(i);
    64.                     _requestedMembershipChanges.RemoveAtSwapBack(i);
    65.                 }
    66.             }
    67.             else
    68.             {
    69.                 _entitiesWithMembershipChanges.Add(entity);
    70.                 _requestedMembershipChanges.Add(desired);
    71.             }
    72.         }
    73.  
    74.         private JobHandle ScheduleMembershipChanges(JobHandle inputDeps)
    75.         {
    76.             JobHandle membershipHandle = inputDeps;
    77.             if (_entitiesWithMembershipChanges.Length > 0)
    78.             {
    79.                 EntityCommandBuffer.Concurrent buffer = _physicsMembershipBarrier.CreateCommandBuffer();
    80.                 var membershipJob = new PhysicsMembershipJob
    81.                 {
    82.                     CommandBuffer = buffer,
    83.                     Entities = new NativeArray<Entity>(_entitiesWithMembershipChanges, Allocator.TempJob),
    84.                     Adds = new NativeArray<byte>(_requestedMembershipChanges, Allocator.TempJob),
    85.                 };
    86.                 membershipHandle = membershipJob.Schedule(_entitiesWithMembershipChanges.Length, 32, inputDeps);
    87.                 _entitiesWithMembershipChanges.Clear();
    88.                 _requestedMembershipChanges.Clear();
    89.             }
    90.             return membershipHandle;
    91.         }
    92.  
    93.         #endregion
    94.     }
    95.  
    96.     public class PhysicsMembershipBarrier : BarrierSystem { }
    97. }
    98.  

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.BEPUphysics.Components;
    5.  
    6. namespace Unity.BEPUphysics.Jobs
    7. {
    8.     public struct PhysicsMembershipJob : IJobParallelFor
    9.     {
    10.         [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<Entity> Entities;
    11.         [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<byte> Adds;
    12.         public EntityCommandBuffer.Concurrent CommandBuffer;
    13.  
    14.         public void Execute(int index)
    15.         {
    16.             if (Adds[index] == 1)
    17.             {
    18.                 CommandBuffer.AddSharedComponent(Entities[index], new PhysicsObject { });
    19.             }
    20.             else
    21.             {
    22.                 CommandBuffer.RemoveComponent<PhysicsObject>(Entities[index]);
    23.             }
    24.         }
    25.     }
    26. }

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.BEPUphysics.Components;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5. using Unity.Collections;
    6.  
    7. namespace Unity.BEPUphysics.Jobs
    8. {
    9.     public struct MembershipDebugJob : IJobParallelFor
    10.     {
    11.         [ReadOnly] public EntityArray EntityArray;
    12.  
    13.         public void Execute(int index)
    14.         {
    15.             Debug.LogFormat("Entity index: {0} version: {1}", EntityArray[index].Index, EntityArray[index].Version);
    16.         }
    17.     }
    18.  
    19.     public struct MembershipDebugGroup
    20.     {
    21.         [ReadOnly] public SharedComponentDataArray<PhysicsObject> PhysicsObjectArray;
    22.         public EntityArray EntityArray;
    23.         public readonly int Length;
    24.     }
    25. }
    26.  

    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. namespace Unity.BEPUphysics.Components
    4. {
    5.     /// <summary>
    6.     /// Simply a marker that the Physics system should process this Entity
    7.     /// </summary>
    8.     public struct PhysicsObject : ISharedComponentData { }
    9. }
    10.