Some help with iterating over entities and Compute Shader

So basically, I have a ComponentSystem that takes all chunks within the world that have a component called “ChunkInfo”, then I grab the first one that needs data from a compute shader (this means that its chunkProgress value is 0). After this, I run a compute shader with a few arguments, and wait for the compute shader to complete by not running another iteration until the request for data is finished. CODE BELOW

So here are my question with how I have implemented this:

  • Would it be better to add a component flag instead of having a value within a component that I check? My thinking for this is that then the entityQuery wouldn’t have to iterate over chunks that already have their compute shader run.
  • Is there a better way to run compute shaders and wait for them? Because thus far, it looks like a freaking mess how it is.
  • Am I changing the component data for each Entity in the most efficient way? So far, I have been creating a new component that I then use SetComponentData on.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
using UnityEngine.Rendering;
using Unity.Mathematics;
using Unity.Burst;

[BurstCompile]
public class VoxelGenerationSystem : ComponentSystem
{
    EntityQuery chunksQuery; //and entity query that will grab all the chunks currently in the world

    ComputeShader voxelDataGenerator = Resources.Load<ComputeShader>("GenerateVoxelData"); //the object for the compute shader
    ComputeBuffer voxelBuffer; //the actual compute buffer that we allcoate for the compute shader

    AsyncGPUReadbackRequest request; //this is the object for the Async request that will fetch our voxel data from the compute shader

    int GenerateVoxelDataKernel; //just the integer ID for the kernel in the compute shader

    bool hasDispatched = false; //checks whether the compute shader is currently creating voxel data for a chunk, it prevents more than one compute shader running at the same time

    int chunkID = -1; //used to save the index to the chunk we are grabbing voxel data for so that whenever the next time the system is run, it can access it

    NativeArray<Entity> chunks; //keeps an up to date list of all chunks in the world

    protected override void OnCreate()
    {
        chunksQuery = World.Active.EntityManager.CreateEntityQuery(typeof(ChunkInfo)); //initialize the chunk query to grab everything with "ChunkInfo" on it

        voxelBuffer = new ComputeBuffer(17 * 17 * 17, sizeof(float)); //intialize the compute buffer with a size that is +1 in each direction (more info on this later)

        GenerateVoxelDataKernel = voxelDataGenerator.FindKernel("GenerateVoxelData"); //finds the Kernel ID
        voxelDataGenerator.SetBuffer(GenerateVoxelDataKernel, "VoxelData", voxelBuffer); //sets the buffer of the compute shader using our allocated compute buffer

    }




    protected override void OnUpdate()
    {


        if (hasDispatched == false) //only ryn this step if the compute shader is done so that the next chunk can be provided with its voxel data
        {

            chunks = chunksQuery.ToEntityArray(Allocator.Persistent); //update the current list of all chunks


            for (int i = 0; i < chunks.Length; i++) //iterate over all chunks so that we are only grabbing chunks that dont already have their voxel data created by the compute shader
            {
                if (World.Active.EntityManager.GetComponentData<ChunkInfo>(chunks[i]).chunkProgress == 0) //0 means that it has no voxel data
                {
                    chunkID = i;
                    break;
                }
                else
                {
                    chunkID = -1;
                }
            }

            if (chunkID == -1) //this means that no chunk was found that needed voxel data, so we just deallocate the chunks array
            {
                chunks.Dispose();
                return;
            }

            Entity chunk = chunks[chunkID]; //grabs the chunk that needs the voxel data


            ChunkInfo chunkInfo = World.Active.EntityManager.GetComponentData<ChunkInfo>(chunk); //grabs the chunk info so that we can read the dimensions of the chunk, the location of the chunks, and can also change the chunk status

            voxelDataGenerator.SetInt("SizeX", chunkInfo.size.x + 1);
            voxelDataGenerator.SetInt("SizeY", chunkInfo.size.y + 1);
            voxelDataGenerator.SetInt("SizeZ", chunkInfo.size.z + 1);

            voxelDataGenerator.SetInt("x", chunkInfo.pos.x);
            voxelDataGenerator.SetInt("y", chunkInfo.pos.y);
            voxelDataGenerator.SetInt("z", chunkInfo.pos.z);



            voxelDataGenerator.Dispatch(GenerateVoxelDataKernel, (int)math.ceil((chunkInfo.size.x + 1) / 1), (int)math.ceil((chunkInfo.size.y + 1) / 1), (int)math.ceil((chunkInfo.size.z + 1) / 1));
            hasDispatched = true; //the gpu is now doing its thing, so lets wait for it to finish before going onto the next chunk
            request = AsyncGPUReadback.Request(voxelBuffer); //setting the request in motion, and we can now use this to check if the gpu is done

        }


        if (request.done) //if the gpu is done with its shit, then lets copy the new data into the buffer of the chunk
        {

            if (request.hasError == true) //if the request has something wrong with it, then just throw an error
            {
                throw new System.NotImplementedException();
            }



            DynamicBuffer<float> voxelDynamicBuffer = World.Active.EntityManager.GetBuffer<VoxelData>(chunks[chunkID]).Reinterpret<float>(); //fetches the voxel buffer of the chunk we are copying data into
            voxelDynamicBuffer.CopyFrom(request.GetData<float>()); //copy that shit from the compute buffer into the buffer of the chunk

            ChunkInfo chunkInfo = World.Active.EntityManager.GetComponentData<ChunkInfo>(chunks[chunkID]); //fetch the ChunkInfo from the chunk so that we can change it to 1
            chunkInfo.chunkProgress = 1; //set it to 1

            World.Active.EntityManager.SetComponentData(chunks[chunkID], chunkInfo); //set the component

            hasDispatched = false;
            chunks.Dispose();
        }
    }
}

It was a little confusing at first given Unity has its own concept of a Chunk.

I would think using a component flag is faster than actually getting component data (for if chunkProgress == 0). And besides, the way its set up now, the query is still iterating over completed ChunkInfos anyway. It’d let you ditch lines 50-75 and more, I think.

Yeah looking at it now, it is pretty confusing. The chunks refer to the voxel chunks that make up a terrain system. And I definitely agree with you. Using a component flag would not only simplify my code by a significant amount, but it would also split my entities into their archetypes (actual ECS chunks) which is sort of the entire point of ECS.