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:
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.
EntityCommandBuffer was recorded in OctreeTestSystem and played back in Unity.Entities.EndSimulationEntityCommandBufferSystem.
at (wrapper managed-to-native) Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrowNoEarlyOut_Injected(Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle&)
at Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) [0x00000] in <fdd4f5823e2a41e8be8d5dcbd0bfd5b1>:0
at Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) [0x00021] in <fdd4f5823e2a41e8be8d5dcbd0bfd5b1>:0
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
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
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
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
Unity.Entities.EntityCommandBufferSystem.FlushPendingBuffers (System.Boolean playBack) (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/EntityCommandBufferSystem.cs:286)
Unity.Entities.EntityCommandBufferSystem.OnUpdate () (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/EntityCommandBufferSystem.cs:188)
Unity.Entities.ComponentSystem.Update () (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystem.cs:109)
Unity.Entities.ComponentSystemGroup.UpdateAllSystems () (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystemGroup.cs:445)
UnityEngine.Debug:LogException(Exception)
Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/Stubs/Unity/Debug.cs:19)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystemGroup.cs:450)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystemGroup.cs:398)
Unity.Entities.ComponentSystem:Update() (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ComponentSystem.cs:109)
Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at Library/PackageCache/com.unity.entities@0.11.0-preview.7/Unity.Entities/ScriptBehaviourUpdateOrder.cs:192)
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:
using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Mathematics.math;
public class OctreeTestSystem : SystemBase
{
NativeHashMap<ulong, OctreeNode> octreeStructure;
NativeArray<int3> nodeOffsets;
const int maxDepth = 7;
const int rootNodeSize = 128;
const int chunkSize = 64;
private EntityQuery octreeQuery;
int3[] offsets =
{
int3(0),
int3(0, 0, 1),
int3(0, 1, 0),
int3(0, 1, 1),
int3(1, 0, 0),
int3(1, 0, 1),
int3(1, 1, 0),
int3(1, 1, 1)
};
protected override void OnCreate()
{
int hashMapSize = (int)((pow(8, maxDepth + 1) - 1) / 7);
octreeStructure = new NativeHashMap<ulong, OctreeNode>(hashMapSize, Allocator.Persistent);
nodeOffsets = new NativeArray<int3>(8, Allocator.Persistent);
octreeQuery = EntityManager.CreateEntityQuery(typeof(octreeNeedsInit), typeof(octreeNodeBufferElement));
nodeOffsets.CopyFrom(offsets);
}
protected override void OnUpdate()
{
if (octreeQuery.CalculateEntityCount() == 0)
{
return;
}
NativeArray<Entity> octrees = octreeQuery.ToEntityArray(Allocator.Persistent);
Entity octree = octrees[0]; //TODO: Distance prioritization
octrees.Dispose();
octreeStructure.Clear();
FillLinearOctreeJob fillOctreeJob = new FillLinearOctreeJob
{
maxDepth = maxDepth,
rootNodeSize = rootNodeSize,
octreeStructure = octreeStructure,
leafNodes = EntityManager.GetBuffer<octreeNodeBufferElement>(octree),
nodeOffsets = nodeOffsets,
chunkSize = chunkSize
};
JobHandle fillOctreeHandle = fillOctreeJob.Schedule(this.Dependency);
removeNeedsInit removeNeedsInitJob = new removeNeedsInit
{
ecb = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>().CreateCommandBuffer(),
entity = octree
};
JobHandle removeNeedsInitHandle = removeNeedsInitJob.Schedule(fillOctreeHandle);
//EntityManager.RemoveComponent(octree, typeof(octreeNeedsInit));
this.Dependency = removeNeedsInitHandle;
}
protected override void OnDestroy()
{
octreeStructure.Dispose();
nodeOffsets.Dispose();
}
struct removeNeedsInit : IJob
{
public EntityCommandBuffer ecb;
public Entity entity;
public void Execute()
{
ecb.RemoveComponent(entity, typeof(octreeNeedsInit));
}
}
}
And:
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
using Unity.Entities;
using UnityEngine;
using static Unity.Mathematics.math;
[BurstCompile]
public struct FillLinearOctreeJob : IJob
{
[WriteOnly] public NativeHashMap<ulong, OctreeNode> octreeStructure;
[ReadOnly] public NativeArray<int3> nodeOffsets;
[WriteOnly] public DynamicBuffer<octreeNodeBufferElement> leafNodes;
public int maxDepth;
public int rootNodeSize;
public int chunkSize;
public void Execute()
{
ulong rootNodeCode = 1;
OctreeNode node;
node.locCode = rootNodeCode;
node.hasChild = 1;
addNode(node, float3(0, 0, 0), rootNodeSize);
}
int getNodeTreeDepth(ulong locCode)
{
return (int)log2(locCode)/3;
}
void addNode(OctreeNode node, float3 pos, int size)
{
octreeStructure.Add(node.locCode, node);
shouldDivide(ref node, float3(4096, 4096, 4096), pos, size);
if ((getNodeTreeDepth(node.locCode) < maxDepth) && (node.hasChild == 1))
{
for(uint i = 0; i < 8; i++)
{
int newSize = size / 2;
addNode(createChildNode(node.locCode, i), pos + (newSize * nodeOffsets[(int)i] * chunkSize), newSize);
}
}
else
{
octreeNodeBufferElement leafNode;
leafNode.value = node;
leafNodes.Add(leafNode);
}
}
OctreeNode createChildNode(ulong parentLocCode, uint child)
{
ulong childLocCode = (parentLocCode << 3) + child;
OctreeNode childNode;
childNode.locCode = childLocCode;
childNode.hasChild = 0;
return childNode;
}
void shouldDivide(ref OctreeNode node, float3 cameraPos, float3 nodePos, int nodeSize)
{
float3 octreeMiddle = nodePos + float3((nodeSize / 2) * chunkSize);
float d = distance(cameraPos, nodePos + octreeMiddle);
if(d < (chunkSize * nodeSize))
{
node.hasChild = 1;
} else
{
node.hasChild = 0;
}
}
}
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.