Search Unity

Issue with using EntityCommandBuffer within SystemBase

Discussion in 'Entity Component System' started by Matt_De_Boss_Developer, Jun 18, 2020.

  1. Matt_De_Boss_Developer

    Matt_De_Boss_Developer

    Joined:
    Oct 17, 2014
    Posts:
    46
    So let me preface this entire thing by stating that I have no idea what the hell I am doing.

    Okay, so I am attempting to create an Octree system within my game that creates and octree with leaf nodes. Each leaf node is a chunk that must be meshed based on the voxels that fall within its bounds.

    Now with that context, let me explain my actual issue.

    My job acts on entities that contain the "octreeNeedsInit" component attached to them. So one might thing: "Hey, chuck a ForEach in there and you're good". Well, I have reservations about doing this because there may be multiple octrees within the player's view distance due to there being multiple voxel bodies (like a moon within proximity of the planet the player is on). Because of this, the processing time of actually constructing these octrees may go over the time I want each frame to fall within.

    Here is my first question: Does Entities.ForEach limit the number of iterations it does based on how long it takes each iteration to execute and then save the next iterations for the next frame?

    After creating a Job that fills a Hashmap with octree nodes and adds leaf nodes to the entitie's dynamic buffer within the SystemBase class, I want to remove the component "octreeNeedsInit" so that I don't keep adding to the Hashmap. However, I keep getting this error:

    Code (CSharp):
    1. ArgumentException: The previously scheduled job OctreeTestSystem:removeNeedsInit writes to the Unity.Entities.EntityCommandBuffer removeNeedsInit.ecb. You must call JobHandle.Complete() on the job OctreeTestSystem:removeNeedsInit, before you can write to the Unity.Entities.EntityCommandBuffer safely.
    2. EntityCommandBuffer was recorded in OctreeTestSystem and played back in Unity.Entities.EndSimulationEntityCommandBufferSystem.
    3.   at (wrapper managed-to-native) Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrowNoEarlyOut_Injected(Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle&)
    4.   at Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) [0x00000] in <fdd4f5823e2a41e8be8d5dcbd0bfd5b1>:0
    5.   at Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) [0x00021] in <fdd4f5823e2a41e8be8d5dcbd0bfd5b1>:0
    6.   at Unity.Entities.EntityCommandBuffer.EnforceSingleThreadOwnership () [0x00015] in C:\Users\mattb\Desktop\Radon Unity Tests\Library\PackageCache\com.unity.entities@0.11.0-preview.7\Unity.Entities\EntityCommandBuffer.cs:893
    7.   at Unity.Entities.EntityCommandBuffer.PlaybackInternal (Unity.Entities.EntityDataAccess* mgr) [0x00000] in C:\Users\mattb\Desktop\Radon Unity Tests\Library\PackageCache\com.unity.entities@0.11.0-preview.7\Unity.Entities\EntityCommandBuffer.cs:1275
    8.   at Unity.Entities.EntityCommandBuffer.Playback (Unity.Entities.EntityManager mgr) [0x00000] in C:\Users\mattb\Desktop\Radon Unity Tests\Library\PackageCache\com.unity.entities@0.11.0-preview.7\Unity.Entities\EntityCommandBuffer.cs:1260
    9.   at Unity.Entities.EntityCommandBufferSystem.FlushPendingBuffers (System.Boolean playBack) [0x0004b] in C:\Users\mattb\Desktop\Radon Unity Tests\Library\PackageCache\com.unity.entities@0.11.0-preview.7\Unity.Entities\EntityCommandBufferSystem.cs:220
    10.  
    11. Unity.Entities.EntityCommandBufferSystem.FlushPendingBuffers (System.Boolean playBack) (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/EntityCommandBufferSystem.cs:286)
    12. Unity.Entities.EntityCommandBufferSystem.OnUpdate () (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/EntityCommandBufferSystem.cs:188)
    13. Unity.Entities.ComponentSystem.Update () (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystem.cs:109)
    14. Unity.Entities.ComponentSystemGroup.UpdateAllSystems () (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystemGroup.cs:445)
    15. UnityEngine.Debug:LogException(Exception)
    16. Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/Stubs/Unity/Debug.cs:19)
    17. Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystemGroup.cs:450)
    18. Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystemGroup.cs:398)
    19. Unity.Entities.ComponentSystem:Update() (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystem.cs:109)
    20. Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ScriptBehaviourUpdateOrder.cs:192)
    21.  
    The same error occurs when it comes to the DynamicBuffer whenever I don't use the second job to remove the component.

    Here is my code:

    Code (CSharp):
    1.  
    2. using System.Runtime.InteropServices;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Mathematics;
    7. using UnityEngine;
    8. using static Unity.Mathematics.math;
    9.  
    10. public class OctreeTestSystem : SystemBase
    11. {
    12.     NativeHashMap<ulong, OctreeNode> octreeStructure;
    13.    
    14.     NativeArray<int3> nodeOffsets;
    15.  
    16.  
    17.     const int maxDepth = 7;
    18.  
    19.     const int rootNodeSize = 128;
    20.  
    21.     const int chunkSize = 64;
    22.  
    23.     private EntityQuery octreeQuery;
    24.    
    25.     int3[] offsets =
    26.     {
    27.         int3(0),
    28.         int3(0, 0, 1),
    29.         int3(0, 1, 0),
    30.         int3(0, 1, 1),
    31.         int3(1, 0, 0),
    32.         int3(1, 0, 1),
    33.         int3(1, 1, 0),
    34.         int3(1, 1, 1)
    35.  
    36.     };
    37.  
    38.     protected override void OnCreate()
    39.     {
    40.         int hashMapSize = (int)((pow(8, maxDepth + 1) - 1) / 7);
    41.  
    42.         octreeStructure = new NativeHashMap<ulong, OctreeNode>(hashMapSize, Allocator.Persistent);
    43.         nodeOffsets = new NativeArray<int3>(8, Allocator.Persistent);
    44.         octreeQuery = EntityManager.CreateEntityQuery(typeof(octreeNeedsInit), typeof(octreeNodeBufferElement));
    45.        
    46.         nodeOffsets.CopyFrom(offsets);
    47.     }
    48.  
    49.     protected override void OnUpdate()
    50.     {
    51.  
    52.  
    53.         if (octreeQuery.CalculateEntityCount() == 0)
    54.         {
    55.             return;
    56.         }
    57.        
    58.        
    59.         NativeArray<Entity> octrees = octreeQuery.ToEntityArray(Allocator.Persistent);
    60.         Entity octree = octrees[0]; //TODO: Distance prioritization
    61.         octrees.Dispose();
    62.        
    63.        
    64.        
    65.         octreeStructure.Clear();
    66.        
    67.        
    68.        
    69.         FillLinearOctreeJob fillOctreeJob = new FillLinearOctreeJob
    70.         {
    71.             maxDepth = maxDepth,
    72.             rootNodeSize = rootNodeSize,
    73.             octreeStructure = octreeStructure,
    74.             leafNodes = EntityManager.GetBuffer<octreeNodeBufferElement>(octree),
    75.             nodeOffsets = nodeOffsets,
    76.             chunkSize = chunkSize
    77.         };
    78.  
    79.         JobHandle fillOctreeHandle = fillOctreeJob.Schedule(this.Dependency);
    80.  
    81.         removeNeedsInit removeNeedsInitJob = new removeNeedsInit
    82.         {
    83.             ecb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>().CreateCommandBuffer(),
    84.             entity = octree
    85.         };
    86.  
    87.         JobHandle removeNeedsInitHandle = removeNeedsInitJob.Schedule(fillOctreeHandle);
    88.        
    89.         //EntityManager.RemoveComponent(octree, typeof(octreeNeedsInit));
    90.        
    91.         this.Dependency = removeNeedsInitHandle;
    92.  
    93.  
    94.     }
    95.  
    96.  
    97.     protected override void OnDestroy()
    98.     {
    99.         octreeStructure.Dispose();
    100.        
    101.         nodeOffsets.Dispose();
    102.     }
    103.  
    104.     struct removeNeedsInit : IJob
    105.     {
    106.         public EntityCommandBuffer ecb;
    107.         public Entity entity;
    108.        
    109.         public void Execute()
    110.         {
    111.             ecb.RemoveComponent(entity, typeof(octreeNeedsInit));
    112.            
    113.         }
    114.     }
    115. }
    116.  

    And:

    Code (CSharp):
    1.  
    2. using Unity.Mathematics;
    3. using Unity.Jobs;
    4. using Unity.Collections;
    5.  
    6. using Unity.Burst;
    7. using Unity.Entities;
    8. using UnityEngine;
    9. using static Unity.Mathematics.math;
    10.  
    11. [BurstCompile]
    12. public struct FillLinearOctreeJob : IJob
    13. {
    14.  
    15.     [WriteOnly] public NativeHashMap<ulong, OctreeNode> octreeStructure;
    16.    
    17.    
    18.     [ReadOnly] public NativeArray<int3> nodeOffsets;
    19.  
    20.     [WriteOnly] public DynamicBuffer<octreeNodeBufferElement> leafNodes;
    21.  
    22.     public int maxDepth;
    23.     public int rootNodeSize;
    24.     public int chunkSize;
    25.  
    26.    
    27.  
    28.     public void Execute()
    29.     {
    30.  
    31.         ulong rootNodeCode = 1;
    32.         OctreeNode node;
    33.         node.locCode = rootNodeCode;
    34.         node.hasChild = 1;
    35.  
    36.         addNode(node, float3(0, 0, 0), rootNodeSize);
    37.  
    38.        
    39.     }
    40.  
    41.     int getNodeTreeDepth(ulong locCode)
    42.     {
    43.         return (int)log2(locCode)/3;
    44.        
    45.        
    46.        
    47.     }
    48.  
    49.  
    50.     void addNode(OctreeNode node, float3 pos, int size)
    51.     {
    52.         octreeStructure.Add(node.locCode, node);
    53.         shouldDivide(ref node, float3(4096, 4096, 4096), pos, size);
    54.  
    55.         if ((getNodeTreeDepth(node.locCode) < maxDepth) && (node.hasChild == 1))
    56.         {
    57.             for(uint i = 0; i < 8; i++)
    58.             {
    59.                 int newSize = size / 2;
    60.                 addNode(createChildNode(node.locCode, i), pos + (newSize * nodeOffsets[(int)i] * chunkSize), newSize);
    61.  
    62.             }
    63.         }
    64.         else
    65.         {
    66.             octreeNodeBufferElement leafNode;
    67.             leafNode.value = node;
    68.             leafNodes.Add(leafNode);
    69.         }
    70.        
    71.     }
    72.  
    73.     OctreeNode createChildNode(ulong parentLocCode, uint child)
    74.     {
    75.         ulong childLocCode = (parentLocCode << 3) + child;
    76.  
    77.         OctreeNode childNode;
    78.         childNode.locCode = childLocCode;
    79.         childNode.hasChild = 0;
    80.         return childNode;
    81.  
    82.  
    83.     }
    84.  
    85.  
    86.     void shouldDivide(ref OctreeNode node, float3 cameraPos, float3 nodePos, int nodeSize)
    87.     {
    88.  
    89.         float3 octreeMiddle = nodePos + float3((nodeSize / 2) * chunkSize);
    90.  
    91.         float d = distance(cameraPos, nodePos + octreeMiddle);
    92.  
    93.         if(d < (chunkSize * nodeSize))
    94.         {
    95.             node.hasChild = 1;
    96.         } else
    97.         {
    98.             node.hasChild = 0;
    99.         }
    100.  
    101.  
    102.     }
    103.  
    104.  
    105.  
    106. }
    107.  
    To end this post, thank you for being such a great forum! I really do appreciate the people who camp this forum and answers people's questions.
     
  2. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    982
    You may have forgotten to call AddJobHandleForProducer().
    Code (CSharp):
    1. var commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    2.  
    3. removeNeedsInit removeNeedsInitJob = new removeNeedsInit {
    4.     ecb = commandBufferSystem.CreateCommandBuffer(),
    5.     entity = octree
    6. };
    7. JobHandle removeNeedsInitHandle = removeNeedsInitJob.Schedule(fillOctreeHandle);
    8.  
    9. commandBufferSystem.AddJobHandleForProducer(removeNeedsInitHandle);
     
    deus0 likes this.
  3. Matt_De_Boss_Developer

    Matt_De_Boss_Developer

    Joined:
    Oct 17, 2014
    Posts:
    46
    You, good sir, are the goat. I can't believe I forgot to do this! Thank you so much!

    In terms of how I setting this System up, are there any improvements in terms of structure that could maybe make things run how Unity intended?
     
  4. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    982
    How you did it is fine. Here are some few nitpicks:
    • You might want to cache EndSimulationEntityCommandBufferSystem in OnCreate() so you don't call World.GetOrCreateSystem() on every frame.
    • That lone octree entity could probably be implemented as a singleton instead.
     
  5. Matt_De_Boss_Developer

    Matt_De_Boss_Developer

    Joined:
    Oct 17, 2014
    Posts:
    46
    I will definitely cache the Ecb. However, the lone entity is just a test at the moment. There will be multiple octrees in the future. This is why I clear the hashmap in order to make room for the new octree being constructed (after this, the leaf nodes are passed to the octree entity for the next step, which is generating voxel data and then meshing). Thank you for the tips!
     
  6. deus0

    deus0

    Joined:
    May 12, 2015
    Posts:
    256
    Hey, I passed by this forum because i ran into the same issue.

    Is the octree implementation for voxel data faster then a byte array? (Currently using byte arrays)

    Also, damn ECS is mad fast. I'm getting so much results already. Then I switched my systems to use command buffers. And wow.

    On a side note, is there any way to push mesh data in a job? Or Use cameras in pure ecs yet? :)
     
  7. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Camera's should not use Convert and Destroy, but instead use Convert and Inject. So unfortunately, no, cameras cannot be read from/written to inside of a jobified/bursted Entities.ForEach. You might be better off working on other aspects of your game project while waiting for other Unity features to join ECS. For example, the Unity.Physics package is quite mature and you won't lose much work to future breaking changes by working on your game's physics
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Not true. Add HYBRID_ENTITIES_CAMERA_CONVERSION to your Scripting Define Symbols.
     
    Abbrew likes this.