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

Feedback [Feature Request] Wait for job to finish without blocking the main thread

Discussion in 'Entity Component System' started by Guedez, Mar 25, 2020.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Edit:
    What: Please create a method in which we can, in the same frame, execute code in main thread and schedule jobs -> wait for the jobs to finish without blocking the main thread -> execute more code in the main thread and maybe even schedule more jobs -> repeat.
    How: Maybe a type of system whose Update is an Iterator like a Coroutine or
    Job.WithCode().ScheduleInMainThread()

    Why: Some things can only be done in the main thread, like scheduling more jobs based on the results, sometimes the number and types of said jobs can change depending on results, or even calling abstract methods or basically anything involving reference types. If any of these needs to be done with the result of a job, you basically need to wait for the next frame, and that might invalidate the previous job results, since structural changes can happen between frames, so it's not always possible to simply wait for the next frame

    Old post: I have an example of system that have two such sync points.
    I have no idea how to solve this with the available current tools.
    So I'd love for some sort of
    OnUpdate
    that supports
    yield return JobHandle
    .
    So I can schedule my jobs, wait for them to finish, do the rest of the stuff that needs to be done
     
    Last edited: Mar 29, 2020
  2. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    825
    Sorry but is
    Code (CSharp):
    1. Dependency.Complete();
    not what you're looking for?
     
  3. Curlyone

    Curlyone

    Joined:
    Mar 15, 2018
    Posts:
    41
    Imo, let Unity handle dependincies, you can chain jobs, if your 2nd job depends on 1st job, 2nd job wont start until 1st job is finished, can help more if you give us some example of your case
     
  4. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    That locks the main thread for the duration, which is what I am currently doing since there are no other options currently
    The first part needs to do things that are not allowed in a job, and the second job needs the result of such operations to be properly scheduled
    For instance, the second job requires a couple of
    NativeArrays
    , whose sizes needs to be bigger or equal to the total of entities, whose size we only know after finishing the first part, that depends on
    CreateArchetypeChunkArray 
    or
    CreateArchetypeChunkArrayAsync
    , either is equally slow if you can't process them in a job.
    Being able to
    yield return JobHandle
    would probably make
    CreateArchetypeChunkArrayAsync
    be as good as instant
    The relevant code:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Threading;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using Unity.Jobs;
    8. using UnityEngine;
    9.  
    10. public abstract class ChunkFilterSystem : JobComponentSystem {
    11.     public abstract JobHandle ScheduleJob(int JobIndex, EntityQuery Query, NativeSlice<ArchetypeChunk> ChunkList, NativeSlice<int> chunkTotals, int EntityCount, JobHandle InputDeps);
    12.     public abstract void PrepareForScheduleJobs(int TotalEntityCount, int JobCount);
    13.     public abstract bool FilterChunk(ArchetypeChunk chunk);
    14.     public abstract EntityQuery[] GetQueries();
    15.  
    16.     private NativeArray<ArchetypeChunk> ChunkList;
    17.     private NativeArray<int> TotalPerChunk;
    18.     private List<DeferedJobCall> calls;
    19.     List<NativeArray<ArchetypeChunk>> chunksList;
    20.     protected override void OnCreate() {
    21.         base.OnCreate();
    22.         ChunkList = new NativeArray<ArchetypeChunk>(8, Allocator.Persistent);
    23.         TotalPerChunk = new NativeArray<int>(8, Allocator.Persistent);
    24.         calls = new List<DeferedJobCall>();
    25.         chunksList = new List<NativeArray<ArchetypeChunk>>();
    26.     }
    27.  
    28.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    29.         int total = 0;
    30.         int i = 0;
    31.         int totalEnt = 0;
    32.         int totaltotalEnt = 0;
    33.         calls.Clear();
    34.         EntityQuery[] queries = GetQueries();
    35.  
    36.         foreach (EntityQuery query in queries) {
    37.             total += query.CalculateChunkCount();
    38.         }
    39.         Utils.RecreateIfSmaller(ref ChunkList, total);
    40.         Utils.RecreateIfSmaller(ref TotalPerChunk, total);
    41.         total = 0;
    42.         JobHandle comb = new JobHandle();
    43.         chunksList.Clear();
    44.         foreach (EntityQuery query in queries) {
    45.             chunksList.Add(query.CreateArchetypeChunkArrayAsync(Allocator.TempJob, out JobHandle wait));
    46.             comb = JobHandle.CombineDependencies(comb, wait);
    47.         }
    48.         comb.Complete();
    49.         /// - This will be counting the number of entities, so that I can properly schedule the next jobs
    50.         int j = 0;
    51.         foreach (EntityQuery query in queries) {
    52.             NativeArray<ArchetypeChunk> chunks = chunksList[j++];
    53.             foreach (ArchetypeChunk chunk in chunks) {
    54.                 if (FilterChunk(chunk)) {
    55.                     int index = total + i;
    56.                     ChunkList[index] = chunk;
    57.                     TotalPerChunk[index] = totalEnt;
    58.                     i++;
    59.                     totalEnt += chunk.Count;
    60.                 }
    61.             }
    62.             if (i > 0) {
    63.                 calls.Add(new DeferedJobCall() { // no job is scheduled if this fails
    64.                     Query = query,
    65.                     ChunkList = ChunkList.Slice(total, i),
    66.                     ChunkTotals = TotalPerChunk.Slice(total, i),
    67.                     EntityCount = totalEnt
    68.                 });
    69.             }
    70.             chunks.Dispose();
    71.             totaltotalEnt += totalEnt;
    72.             total += i;
    73.             i = 0;
    74.             totalEnt = 0;
    75.         }
    76.         /// - This is where the total amount of entities is used, it will allocate my NativeArrays so that they are big enough for the jobs
    77.         PrepareForScheduleJobs(totaltotalEnt, calls.Count);
    78.         /// - These are the jobs that will use the native arrays that needs to be big enough
    79.         int JobIndex = 0;
    80.         JobHandle combined = inputDeps;
    81.         foreach (DeferedJobCall call in calls) {
    82.             JobHandle.CombineDependencies(ScheduleJob(JobIndex++, call.Query, call.ChunkList, call.ChunkTotals, call.EntityCount, combined), combined);
    83.         }
    84.         return combined;
    85.     }
    86.  
    87.     protected override void OnDestroy() {
    88.         base.OnDestroy();
    89.         if (ChunkList.IsCreated) {
    90.             ChunkList.Dispose();
    91.         }
    92.         if (TotalPerChunk.IsCreated) {
    93.             TotalPerChunk.Dispose();
    94.         }
    95.     }
    96.  
    97.     internal struct DeferedJobCall {
    98.         internal EntityQuery Query;
    99.         internal NativeSlice<ArchetypeChunk> ChunkList;
    100.         internal NativeSlice<int> ChunkTotals;
    101.         internal int EntityCount;
    102.     }
    103. }
    104.  
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    NativeList.AsDeferredJobArray exists for this purpose. You can resize the lists in a job that runs after the first job but before the second.
     
  6. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    As @DreamingImLatios said - AsDeferredJobArray is exactly for that purpose with IJobParallelForDeffer. You always can add intermediate IJob in addition (just as example it can be any job types, depends on case) with remapping some hash maps to deferred lists, or other stuff. But 90% of work you always can move from main thread and have minimum amount of synch points where you applying structural changes.
     
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    I am pretty sure my use case is falling on the 10% of work you can't move from the main thread.
    In pseudo code:
    1 For each EntityQuery
    2 Get all chunks that match EntityQuery (0.6 ms minimum, and async needs .Complete() that takes the same amount)
    3 Iterate over the chunks, filter them by the value of a ChunkComponentData, add the unfiltered out chunks to a list, remember when each EntityQuery starts and ends
    4 For each chunk in the new list, count how many entities each chunk have, and the total entity count for this EntityQuery, also save how many entities have been gone through each before chunk
    5 Resize the NativeArrays that will collect data from the chunks to ensure it is large enough using the sum of totals values figured out in step 4
    6 IJobParallelFor through the chunk, fill the arrays with the needed data
    7 for each EntityQuery, Graphics.DrawMeshInstanced using a NativeSlice of step 5 regarding all the entities for that EntityQuery

    Step 5 and 7 absolutely cannot be moved out of the main thread. One can circumvent this by executing step 7 as the first thing (so you render the last frame in the start of the current frame, thus removing the need to lock the main thread to wait for the jobs).

    If I could Yield return before 5 and 7, everything would work fine. Currently I have to JobHandle.Complete() before 5 and render 7 at the start of the next frame.

    TLDR:
    The objective is to collect data from entities that are 'InFrustrum', which is dictated by the value of their corresponding ChunkComponentData, into a giant NativeArray that is sliced to render using Graphics.DrawMeshInstanced. If you think it's possible to do without those two blocking steps (5 for ensuring the size of the native array and 7 to perform Graphics.DrawMeshInstanced(Which last I checked needs to be done from Main Thread)), please do tell me how, even if in pseudo code only.

    OBS: Since NativeLists cannot handle concurrency, they are not a solution to getting rid of step 5, unless they automatically resize on list[index out of bounds]=value and indexing can be used in parallel, since I do know exactly where each entity goes in the native array after all of that counting
     
  8. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    And use them as deferred arrays
    Before update system always completes its dependencies. I'm doing DrawMeshInstancedIndirect right at the start of OnUpdate. Other part of OnUpdate collects matrices and other things etc. inside jobs which Scheduled at the end of OnUpdate. And result of their job applying at start of next OnUpdate.

    Frame Start -> ... -> This system dependencies completed internally -> OnUpdate Start-> DMII -> Schedule Data processing jobs -> OnUpdate End -> ... -> Frame End -------- repeat
     
    vildauget likes this.
  9. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Is there a way to insert a abstract method inside a job? Make an abstract job that only needs to implement a little piece? The whole point of this is to make a job that can filter chunks, so I really need this
        public abstract bool FilterChunk(ArchetypeChunk chunk);
    to be extendable, this is used in step 3
     
  10. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Scratch all that,
    frameIndex.AsDeferredJobArray()
    does not work at all. Somehow my frameIndex NativeList<float> is both
    frameIndex.IsCreated
    and
    Length = System.InvalidOperationException: The NativeArray has been deallocated, it is not allowed to access it
    at the same time
    For some reason something somewhere decided it was appropriate to go around Disposing my arrays and I have no way to know what/why. A breakpoint on NativeList.Dispose is not being very helpful
    Edit: Seems to have nothing to do with
    frameIndex.AsDeferredJobArray()
    ,
    frameIndex.AsArray()
    had the same result.
    Might be
    IJobNativeMultiHashMapVisitKeyValue


    Edit 2: Removing [BurstCompile] and fixing bugs, none which were related with Dispose(), somehow fixed this, letting me add [BurstCompile] again
     
    Last edited: Mar 28, 2020
  11. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    A̶l̶t̶h̶o̶u̶g̶h̶ ̶i̶t̶ ̶s̶e̶e̶m̶s̶ ̶t̶o̶ ̶b̶e̶ ̶g̶e̶n̶e̶r̶a̶t̶i̶n̶g̶ ̶s̶o̶m̶e̶ ̶j̶u̶n̶k̶ ̶d̶a̶t̶a̶ ̶a̶n̶d̶ ̶a̶r̶t̶i̶f̶a̶c̶t̶s̶, especially on the first frames, some stuff keeps blinking left and right and is pretty much bugged somehow, this is what I've managed to do.

    Edit: The artifacts were caused by a double i++ on the main loop, leaving whatever data was in every odd number instance for rendering, the old code also had the same bug, but for some reason never displayed the artifacts

    The performance is also just as bad, about 1-2ms for 3̶0̶k 20k entities, past that my GPU can't handle. At least this time is not for
    handle.Complete()
    reasons

    If anyone somehow stumbles on this thread via google, here is how I've implemented
    AsDeferredJobArray()

    I will revert this and eagerly await for
    yield return JobHandle
    . Being able to not use it 90% of the time is no good excuse to wave way a much easier and clearer way to do things. I can only hope it's not impossible

    Unless I totally misunderstood something and implemented it all wrong, this was not worth the effort, I got a new bug and no performance improvement, got much more complex, and now I can't have my abstract method to filter the chunks

    Code (CSharp):
    1. using AnimationInstancing;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using Unity.Burst;
    6. using Unity.Collections;
    7. using Unity.Entities;
    8. using Unity.Jobs;
    9. using Unity.Mathematics;
    10. using Unity.Rendering;
    11. using Unity.Transforms;
    12. using UnityEngine;
    13. using static Unity.Mathematics.math;
    14. using static UpdateFrustrumCulling;
    15.  
    16. [UpdateBefore(typeof(UpdateFrustrumCulling))]
    17. public class BuildRenderInstancedAnimationBlock : ChunkFilterSystem {
    18.  
    19.     [BurstCompile]
    20.     struct BuildRenderInstancedAnimationBlockJob : IJobNativeMultiHashMapVisitKeyValue<int, IndexedArchetypeChunk> {
    21.         public NativeSlice<int> TotalPerChunk;
    22.  
    23.         public NativeArray<Matrix4x4> arrayMat;
    24.         public NativeArray<float> frameIndex;
    25.  
    26.         [ReadOnly] public ArchetypeChunkComponentType<ECSAnimationInstance> ECSAnimationInstance;
    27.         [ReadOnly] public ArchetypeChunkComponentType<LocalToWorld> LocalToWorld;
    28.         internal NativeArray<int> NativeParams;
    29.         internal NativeArray<int> CountMap;
    30.  
    31.         public void Execute(ArchetypeChunk chunk, int TotalSoFar) {
    32.             var ECSAnimationInstance = chunk.GetNativeArray(this.ECSAnimationInstance);
    33.             var LocalToWorld = chunk.GetNativeArray(this.LocalToWorld);
    34.             for (var i = 0; i < chunk.Count; i++) {
    35.                 int internali = TotalSoFar + i;
    36.                 arrayMat[internali] = (Matrix4x4)LocalToWorld[i].Value;
    37.                 ECSAnimationInstance eCSAnimationInstance = ECSAnimationInstance[i];
    38.                 frameIndex[internali] = eCSAnimationInstance.curFrame;
    39.             }
    40.         }
    41.  
    42.         public void ExecuteNext(int key, IndexedArchetypeChunk value) {
    43.             Execute(value.chunk, TotalPerChunk[value.skip + value.index]);
    44.         }
    45.     }
    46.  
    47.     private NativeArray<int> CountMap;
    48.     private NativeList<ArchetypeChunk> ChunksToIterate;
    49.     private NativeList<Matrix4x4> arrayMat;
    50.     private NativeList<float> frameIndex;
    51.     private NativeList<JobHandle> jobs;
    52.  
    53.     private EntityQuery[] Groups;
    54.     private Dictionary<int, AnimationInstancingMgr.VertexCache> vertexCachePool;
    55.     private List<ECSInstancedAnimationData> indexes;
    56.     private List<ECSInstancedAnimationData> indexes2;
    57.     public Camera camera;
    58.  
    59.  
    60.     ArchetypeChunkComponentType<InFrustrum> InFrustrum;
    61.     ArchetypeChunkComponentType<ECSAnimationInstance> ECSAnimationInstance;
    62.     ArchetypeChunkComponentType<LocalToWorld> LocalToWorld;
    63.  
    64.     protected override void OnCreate() {
    65.         base.OnCreate();
    66.         camera = Camera.main;
    67.         vertexCachePool = AnimationInstancingMgr.Instance.vertexCachePool;
    68.         indexes = new List<ECSInstancedAnimationData>();
    69.         indexes2 = new List<ECSInstancedAnimationData>();
    70.         jobs = new NativeList<JobHandle>(64, Allocator.TempJob);
    71.         Utils.RecreateIfSmaller(ref arrayMat, 10);
    72.         Utils.RecreateIfSmaller(ref frameIndex, 10);
    73.     }
    74.  
    75.     protected override JobHandle OnUpdate(JobHandle inputDependencies) {
    76.         int i = 0;
    77.         int total = 0;
    78.         if (!frameIndex.IsCreated) {
    79.             frameIndex = new NativeList<float>(10, Allocator.Persistent);
    80.         }
    81.         if (CountMap.IsCreated) {
    82.             foreach (ECSInstancedAnimationData prefab in indexes.Skip(1)) {
    83.                 if (!vertexCachePool.ContainsKey(prefab.VertexCache)) {
    84.                     i++;
    85.                     continue;
    86.                 }
    87.                 if (CountMap.Length <= i) {
    88.                     break;
    89.                 }
    90.                 int count = CountMap[i++];
    91.                 if (count == 0) {
    92.                     continue;
    93.                 }
    94.                 AnimationInstancingMgr.VertexCache cache = vertexCachePool[prefab.VertexCache];
    95.                 AnimationInstancingMgr.MaterialBlock mblock = cache.instanceBlockList[prefab.MaterialIdentifier];
    96.                 Bounds bounds1 = new Bounds(camera.transform.position, Vector3.one * 25);
    97.                 FPSDisplay.ExtraText = "" + count;
    98.                 AnimationInstancingMgr.RenderPackage(mblock.ECSPackage, cache, prefab.AnimationTextureIndex, AnimationInstancingMgr.Instance.UseInstancing, count, total,
    99.                     arrayMat, frameIndex, bounds1);
    100.  
    101.                 total += count;
    102.             }
    103.         }
    104.         indexes2.Clear();
    105.         EntityManager.GetAllUniqueSharedComponentData(indexes2);
    106.         if (indexes.Count != indexes2.Count || !indexes.SequenceEqual(indexes2)) {
    107.             Groups = new EntityQuery[indexes2.Count - 1];
    108.             for (i = 1; i < indexes2.Count; i++) {
    109.                 EntityQuery m_Group = Groups[i - 1] = GetEntityQuery(ComponentType.ReadOnly<ECSInstancedAnimationData>(),
    110.                 ComponentType.ReadOnly<ECSAnimationInstance>(),
    111.                 ComponentType.ReadOnly<LocalToWorld>(),
    112.                 ComponentType.ReadOnly<WorldRenderBounds>(),
    113.                 ComponentType.ChunkComponentReadOnly<InFrustrum>());
    114.                 m_Group.SetSharedComponentFilter(indexes2[i]);
    115.             }
    116.         }
    117.         Utils.RecreateIfSmaller(ref CountMap, Groups.Length);
    118.         List<ECSInstancedAnimationData> tmp = indexes2;
    119.         indexes2 = indexes;
    120.         indexes = tmp;
    121.         InFrustrum = GetArchetypeChunkComponentType<InFrustrum>(true);
    122.         ECSAnimationInstance = GetArchetypeChunkComponentType<ECSAnimationInstance>(true);
    123.         LocalToWorld = GetArchetypeChunkComponentType<LocalToWorld>(true);
    124.  
    125.         return base.OnUpdate(inputDependencies);
    126.     }
    127.  
    128.     public override JobHandle PrepareForScheduleJobs(NativeArray<int> nativeparams, JobHandle inputDeps) {
    129.         NativeList<Matrix4x4> arrayMat1 = arrayMat;
    130.         NativeList<float> frameIndex1 = frameIndex;
    131.         return Job.WithCode(() => {
    132.             int getTotal = GetTotalEntities(nativeparams);
    133.             int getTotalJobs = GetTotalJobs(nativeparams);
    134.             if (arrayMat1.Length < getTotal) {
    135.                 arrayMat1.Resize(getTotal * 2, NativeArrayOptions.UninitializedMemory);
    136.                 frameIndex1.Resize(getTotal * 2, NativeArrayOptions.UninitializedMemory);
    137.             }
    138.         }).Schedule(inputDeps);
    139.     }
    140.  
    141.     public override bool FilterChunk(ArchetypeChunk chunk) {
    142.         return chunk.GetChunkComponentData(InFrustrum).Value;
    143.     }
    144.  
    145.     public override EntityQuery[] GetQueries() {
    146.         return Groups;
    147.     }
    148.  
    149.     public override JobHandle ScheduleJob(int JobIndex, EntityQuery Query, NativeMultiHashMap<int, IndexedArchetypeChunk> ChunkList, NativeArray<int> chunkTotals, NativeArray<int> nativeparams, JobHandle InputDeps) {
    150.         NativeArray<int> countMap = CountMap;
    151.         Job.WithCode(() => {
    152.             countMap[JobIndex] = nativeparams[JobIndex * 3 + 2];
    153.         }).Schedule(InputDeps);
    154.         BuildRenderInstancedAnimationBlockJob job = new BuildRenderInstancedAnimationBlockJob();
    155.         job.arrayMat = arrayMat.AsDeferredJobArray();
    156.         job.frameIndex = frameIndex.AsDeferredJobArray();
    157.         job.NativeParams = nativeparams;
    158.         job.CountMap = CountMap;
    159.         job.TotalPerChunk = chunkTotals;
    160.         job.ECSAnimationInstance = ECSAnimationInstance;
    161.         job.LocalToWorld = LocalToWorld;
    162.         // job.bounds = bounds;
    163.         return job.Schedule(ChunkList, 64, InputDeps);
    164.     }
    165.  
    166.     protected override void OnStopRunning() {
    167.         base.OnDestroy();
    168.     }
    169.     protected override void OnDestroy() {
    170.         base.OnDestroy();
    171.         if (CountMap.IsCreated) {
    172.             CountMap.Dispose();
    173.         }
    174.         if (arrayMat.IsCreated) {
    175.             arrayMat.Dispose();
    176.         }
    177.         if (frameIndex.IsCreated) {
    178.             frameIndex.Dispose();
    179.         }
    180.         if (jobs.IsCreated) {
    181.             jobs.Dispose();
    182.         }
    183.     }
    184. }
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Threading;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using Unity.Jobs;
    8. using static UpdateFrustrumCulling;
    9. using UnityEngine;
    10.  
    11. public abstract class ChunkFilterSystem : JobComponentSystem {
    12.     public struct IndexedArchetypeChunk {
    13.         public int index;
    14.         public int skip;
    15.         public ArchetypeChunk chunk;
    16.     }
    17.     public abstract JobHandle ScheduleJob(int JobIndex, EntityQuery Query, NativeMultiHashMap<int, IndexedArchetypeChunk> ChunkList, NativeArray<int> chunkTotals, NativeArray<int> nativeparams, JobHandle InputDeps);
    18.     public abstract JobHandle PrepareForScheduleJobs(NativeArray<int> nativeparams, JobHandle InputDeps);
    19.     public abstract bool FilterChunk(ArchetypeChunk chunk);
    20.     public abstract EntityQuery[] GetQueries();
    21.  
    22.     private NativeMultiHashMap<int, IndexedArchetypeChunk> ChunkList;
    23.     private NativeArray<int> TotalPerChunk;
    24.     List<NativeArray<ArchetypeChunk>> chunksList;
    25.     protected override void OnCreate() {
    26.         base.OnCreate();
    27.         ChunkList = new NativeMultiHashMap<int, IndexedArchetypeChunk>(8, Allocator.Persistent);
    28.         TotalPerChunk = new NativeArray<int>(8, Allocator.Persistent);
    29.         chunksList = new List<NativeArray<ArchetypeChunk>>();
    30.     }
    31.  
    32.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    33.         int total = 0;
    34.         int i = 0;
    35.         EntityQuery[] queries = GetQueries();
    36.         foreach (EntityQuery query in queries) {
    37.             total += query.CalculateChunkCount();
    38.         }
    39.         if (ChunkList.IsCreated) {
    40.             ChunkList.Clear();
    41.         }
    42.         Utils.RecreateIfSmaller(ref ChunkList, total);
    43.         Utils.RecreateIfSmaller(ref TotalPerChunk, total);
    44.         total = 0;
    45.         JobHandle comb = inputDeps;
    46.         chunksList.Clear();
    47.         foreach (EntityQuery query in queries) {
    48.             chunksList.Add(query.CreateArchetypeChunkArrayAsync(Allocator.TempJob, out JobHandle wait));
    49.             comb = JobHandle.CombineDependencies(comb, wait);
    50.         }
    51.         int j = 0;
    52.         NativeArray<int> nativeparams = new NativeArray<int>(queries.Length * 3, Allocator.TempJob);
    53.         foreach (EntityQuery query in queries) {
    54.             int jj = j;
    55.             NativeArray<ArchetypeChunk> chunks = chunksList[j++];
    56.             NativeMultiHashMap<int, IndexedArchetypeChunk> chunkList = ChunkList;
    57.             NativeArray<int> totalPerChunk = TotalPerChunk;
    58.             //Func<ArchetypeChunk, bool> lambda = (chunk) => FilterChunk(chunk);
    59.             ArchetypeChunkComponentType<InFrustrum> InFrustrum = InFrustrum = GetArchetypeChunkComponentType<InFrustrum>(true);
    60.             comb = JobHandle.CombineDependencies(comb, Job.WithCode(() => {
    61.  
    62.                 int ji = 0;
    63.                 int jtotal = 0;
    64.                 int totalEnt = 0;
    65.                 if (jj > 0) {
    66.                     jtotal = nativeparams[jj * 3 - 3];
    67.                 }
    68.                 int len = chunks.Length;
    69.                 for (int l = 0; l < len; l++) {
    70.                     ArchetypeChunk chunk = chunks[l];
    71.                     if (chunk.GetChunkComponentData(InFrustrum).Value) {
    72.                         int index = jtotal + ji;
    73.                         chunkList.Add(jj, new IndexedArchetypeChunk() { index = ji, skip = jtotal, chunk = chunk });
    74.                         totalPerChunk[index] = totalEnt;
    75.                         ji++;
    76.                         totalEnt += chunk.Count;
    77.                     }
    78.                 }
    79.                 jtotal += ji;
    80.                 nativeparams[jj * 3 + 0] = jtotal;
    81.                 nativeparams[jj * 3 + 1] = ji;
    82.                 nativeparams[jj * 3 + 2] = totalEnt;
    83.                 //if (i > 0) {
    84.                 //    calls.Add(new DeferedJobCall() { // no job is scheduled if this fails
    85.                 //        Query = query,
    86.                 //        ChunkList = ChunkList.Slice(total, i),
    87.                 //        ChunkTotals = TotalPerChunk.Slice(total, i),
    88.                 //        EntityCount = totalEnt
    89.                 //    });
    90.                 //}
    91.             }).Schedule(comb));
    92.             chunks.Dispose(comb);
    93.         }
    94.  
    95.         /// - This is where the total amount of entities is used, it will allocate my NativeArrays so that they are big enough for the jobs
    96.         comb = PrepareForScheduleJobs(nativeparams, comb);
    97.         /// - These are the jobs that will use the native arrays that needs to be big enough
    98.         int JobIndex = 0;
    99.         foreach (EntityQuery query in queries) {
    100.             comb = JobHandle.CombineDependencies(ScheduleJob(JobIndex++, query, ChunkList, TotalPerChunk, nativeparams, comb), comb);
    101.         }
    102.         nativeparams.Dispose(comb);
    103.         //Job.WithDeallocateOnJobCompletion(nativeparams).Schedule(comb);
    104.         return comb;
    105.     }
    106.  
    107.     protected override void OnDestroy() {
    108.         base.OnDestroy();
    109.         if (ChunkList.IsCreated) {
    110.             ChunkList.Dispose();
    111.         }
    112.         if (TotalPerChunk.IsCreated) {
    113.             TotalPerChunk.Dispose();
    114.         }
    115.     }
    116.  
    117.     //internal struct DeferedJobCall {
    118.     //    internal EntityQuery Query;
    119.     //    internal NativeSlice<ArchetypeChunk> ChunkList;
    120.     //    internal NativeSlice<int> ChunkTotals;
    121.     //    internal int EntityCount;
    122.     //}
    123.  
    124.     public static int GetTotalJobs(NativeArray<int> nativeparams) {
    125.         int count = nativeparams.Length;
    126.         int total = 0;
    127.         for (int i = 0; i < count; i += 3) {
    128.             total += nativeparams[i] != 0 ? 1 : 0;
    129.         }
    130.         return total;
    131.     }
    132.  
    133.     public static int GetTotalEntities(NativeArray<int> nativeparams) {
    134.         int count = nativeparams.Length;
    135.         int total = 0;
    136.         for (int i = 0; i < count; i += 3) {
    137.             total += nativeparams[i + 2];
    138.         }
    139.         return total;
    140.     }
    141. }
    142.  
     
    Last edited: Mar 28, 2020
  12. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Then what is the reason?

    I would be surprised if this happened. It is not really "Performance by Default".
     
    eizenhorn likes this.
  13. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    I am keeping the new implementation in a backup file for things like this
    The Profile Start/End are as follows: https://pastebin.com/YiuX0tK3 https://pastebin.com/GTqGsbj9
    Result: https://imgur.com/a/lfSWCB1
    So it's mostly
    Code (CSharp):
    1.  
    2.         EntityManager.GetAllUniqueSharedComponentData(indexes2);
    3.         if (indexes.Count != indexes2.Count || !indexes.SequenceEqual(indexes2)) {
    4.             Groups = new EntityQuery[indexes2.Count - 1];
    5.             for (i = 1; i < indexes2.Count; i++) {
    6.                 EntityQuery m_Group = Groups[i - 1] = GetEntityQuery(ComponentType.ReadOnly<ECSInstancedAnimationData>(),
    7.                 ComponentType.ReadOnly<ECSAnimationInstance>(),
    8.                 ComponentType.ReadOnly<LocalToWorld>(),
    9.                 ComponentType.ReadOnly<WorldRenderBounds>(),
    10.                 ComponentType.ChunkComponentReadOnly<InFrustrum>());
    11.                 m_Group.SetSharedComponentFilter(indexes2[i]);
    12.             }
    13.         }
    I've been thinking of ways of caching this, but I have no idea how to know if GetAllUniqueSharedComponentData changed or not before actually doing so. I wouldn't be suprised if SequenceEquals takes just as long as making new EntityQueries, but I don't have any better solution for that.
    Second place is DrawMesh, which is as should be and I doubt I can make it faster before we get jobs that write straight into the GPU, which is under development as I gather.

    I am not sure how to properly profile the jobs, and my estimation of '1-2ms' was from whatever the Entity Debugger tells me, which was 1.4-1.6~ sometimes 1 sometimes 2.

    It's good to know that most of the time is on the jobs and not the main thread, but this implementation don't let me implement the chunk filtering due to 'no jobs with instances' restriction, so unless I can get that working somehow, there is little use for it. I will try with some "Gimme a struct that implements this interface" abstract method and and see if burst lets me use that

    Update: Does not work because Job.WithCode also throws error DC0025: Entities.ForEach cannot be used in system ChunkFilterSystem`1 as Entities.ForEach in generic system types are not supported.

    Not sure why it wouldn't be, it is certainly way easier than what I had to do to get what I have right now. I can only hope it can be done and be just as performant, but there is no way for me to know. Maybe it would lead to determinism issues
     
    Last edited: Mar 28, 2020
  14. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    You already can write in to GPU memory directly by ComputeBuffer Begin\EndWrite
     
  15. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Is it a 2020.1 beta thing? I don't see it here (2019.3.4f1)
     
  16. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    Yes it's added in 2020.1 by Hybrid Renderer team to engine core.
     
  17. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Can you resize a
    ComputeBuffer 
    inside a job? Otherwise that would require the
    yield return JobHandle
    or locking the main thread until I get the entity count
     
  18. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    This is not proven to work on mobile, which is what I thing @Guedez is trying to ensure.

    Sorry. But this does not mean anything to me. If you can upload the actual profiler data (binary file) of your old, new, and "ideal performance but doesn't work correctly" scenarios, I can give you a lot more insight as to what the problems might be.
     
  19. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    Where?
     
  20. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
  21. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    Last edited: Mar 28, 2020
  22. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    I never stated I cared about mobile.
    I really want a
    yield return JobHandle
    because it would be extremely simple to work with. Hopefully it can be done while maintaining determinism and being performant, but if it's not, then that's that.
     
  23. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Sorry. I was confusing you with the other person who was rewriting the Hybrid Renderer because it had problems on mobile.

    The await/async state machine and the C# job system are fundamentally different and cannot really be made to interop cleanly. You can think of await/async as a futures tree that uses threading primitives during an await to alert completion. In contrast, the C# job system uses a DAG (dependency combination support) and is capable of telling to the main thread to contribute on work when necessary. If you really need the call-stack back-peddling of async/await for getting your job dependencies correct (I would argue you probably have architected your code wrong), you can use IEnumerator<JobHandle> and yield return (you might have to wrap JobHandle in some other struct) to get a similar effect.
     
  24. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    The thing is, I know very little of the inner workings, when I say
    yield return JobHandle
    is because that's the way I know how to "stop now, go do something else, and continue from here at a later time" from Coroutines. I wouldn't care if it's scheduling a main thread lambda(without restrictions), calling OnUpdate multiple times with a state parameter, or a method that runs just like a coroutine, so long I get to run code in the main thread right after a JobHandle finished(rather than having to wait for the next frame), all is fine
    Unless there is a typo in the quoted part, the system supports it, we only need a way to actually do it.
     
  25. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Call Complete()? Or do exactly what I said with IEnumerator<JobHandle> which requires no additional support from Unity.

    I suspect the optimization you desire may require a higher level of technical understanding than what you choose to operate at.
     
  26. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Don't
    Complete()
    freezes the main thread for the duration?
    If every implementation of
    ChunkFilterSystem
    (That I made) (that I plan on using a lot), have a 0.6ms main thread blocking
    Complete()
    every single frame regardless of amount of entities it will be handling, it will get out of hand pretty fast. Which is why I would like a
    Complete()
    (that does not block the main thread which was proposed as
    yield return JobHandle
    since I don't know a much about the inner workings).
    I believe it does block the main thread because that's how I interpreted the profiler data, so if it does not, then I guess the issue is solved
     
  27. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    It does block the main thread and tells the main thread to instead try helping out completing the jobs (something await/async can't do). Awaiting a task can also block the main thread.
    You might be desiring JobHandle.ScheduleBatchedJobs so that you can do main thread work while jobs scheduled in the same system run.
     
  28. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    No, I want to use the job result in the main thread because some things you can only do in the main thread, like executing abstract methods on the result of the job, and then run more jobs, and then display the results of those jobs.
    Since I need to do this at two point in the system, I can't just wait for the next frame twice
     
  29. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    So what main thread work do you want to happen while those jobs are running?
     
  30. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    other systems
     
  31. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Well for one, using async for this use case would break determinism, so yet another reason why I doubt Unity will implement this. It sounds like you need to break up your system if it is a problem. Can I ask why you are main-thread bound in the first place that you are trying to make the main thread do dedicated main thread work rather than help crunch through these rendering jobs?
     
  32. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Because EntityQuery.CreateArchetypeChunkArray takes a while, and I need to work on the result of that in the main thread (calling an abstract method)
    I will have potentially 10+ different systems that will do completely different things with the result but all of them start with asking for EntityQuery.CreateArchetypeChunkArray. If all of them lock the main thread, the whole frame will be waiting for EntityQuery.CreateArchetypeChunkArray
     
  33. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    And you can't just pass this array and the JobHandle as arguments into the abstract method?

    Sorry, but I really do not understand your thought process nor what you are trying to do. I will gladly answer any explicit questions or look at profiler capture binaries, but otherwise I think this is a problem you are just going to have to wrestle with on your own.
     
  34. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Here goes the explicit question:
    How do I create an abstract system, which have a abstract method that takes an ArchetypeChunk and returns a boolean, then schedules jobs for the chunks that returned true in the abstract method? Without .Complete and using EntityQuery.CreateArchetypeChunkArrayAsync
     
  35. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    The abstract system does this? That would mean that it would schedule a job based on its own result and all of its peers. That doesn't make any sense.
     
  36. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    By using .Complete, yeah, I can do that, because the abstract method is called on the main thread and then I schedule the jobs. Which is why I so much want to be able to main thread -> schedule things -> main thread -> schedule more things on the same frame
     
  37. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    I think there's more than just an abstract class involved here because an abstract class with an abstract function can't collect multiple of its own return value to schedule a job in said abstract function unless it is recursive.
     
  38. BrendonSmuts

    BrendonSmuts

    Joined:
    Jun 12, 2017
    Posts:
    86
    This is still not a specific problem. What is the operation you are trying to perform and the result you are trying to produce. My gut tells me, based off the numerous problems you face, that your fundamental approach is flawed and there is a method to achieving what you need that is “ECS friendly”
     
  39. BrendonSmuts

    BrendonSmuts

    Joined:
    Jun 12, 2017
    Posts:
    86
    Alternatively an approach to doing more main thread work while the job is in progress is to split your logic into two systems, one with all the work up until the JobHandle you are dependent on, the second system consumes that JobHandle to call the complete before doing it’s dependent work. In between those two systems you can shove any number of non-dependent systems that can perform and schedule their own work.
     
  40. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Draw all meshes in given chunks, the chunks are to be filtered using an abstract method so I can reuse the chunk filtering part of the code in other systems. The current system implementing the chunk filtering code is taking a ChunkComponentData and checking it's value to decide if the chunk stays or goes away.
    I currently have two implementations
    1 The non main thread waiting implementation can't call abstract methods
    2 The version that can call the abstract method but needs to JobHandle.Complete(), which locks the main thread for about 0.6ms for each implementation of the system, which I plan on having plenty of

    The source of the non locking is available above, this is the source of the locking version that can call abstract methods:

    Maybe there is a misunderstanding? Here is the source

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Threading;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using Unity.Jobs;
    8. using UnityEngine;
    9.  
    10. public abstract class ChunkFilterSystem : JobComponentSystem {
    11.     public abstract JobHandle ScheduleJob(int JobIndex, EntityQuery Query, NativeSlice<ArchetypeChunk> ChunkList, NativeSlice<int> chunkTotals, int EntityCount, JobHandle InputDeps);
    12.     public abstract void PrepareForScheduleJobs(int TotalEntityCount, int JobCount);
    13.     public abstract bool FilterChunk(ArchetypeChunk chunk);
    14.     public abstract EntityQuery[] GetQueries();
    15.  
    16.     private NativeArray<ArchetypeChunk> ChunkList;
    17.     private NativeArray<int> TotalPerChunk;
    18.     private List<DeferedJobCall> calls;
    19.     List<NativeArray<ArchetypeChunk>> chunksList;
    20.     protected override void OnCreate() {
    21.         base.OnCreate();
    22.         ChunkList = new NativeArray<ArchetypeChunk>(8, Allocator.Persistent);
    23.         TotalPerChunk = new NativeArray<int>(8, Allocator.Persistent);
    24.         calls = new List<DeferedJobCall>();
    25.         chunksList = new List<NativeArray<ArchetypeChunk>>();
    26.     }
    27.  
    28.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    29.         int total = 0;
    30.         int i = 0;
    31.         int totalEnt = 0;
    32.         int totaltotalEnt = 0;
    33.         calls.Clear();
    34.         EntityQuery[] queries = GetQueries();
    35.  
    36.         foreach (EntityQuery query in queries) {
    37.             total += query.CalculateChunkCount();
    38.         }
    39.         Utils.RecreateIfSmaller(ref ChunkList, total);
    40.         Utils.RecreateIfSmaller(ref TotalPerChunk, total);
    41.         total = 0;
    42.         JobHandle comb = new JobHandle();
    43.         chunksList.Clear();
    44.         foreach (EntityQuery query in queries) {
    45.             chunksList.Add(query.CreateArchetypeChunkArrayAsync(Allocator.TempJob, out JobHandle wait));
    46.             comb = JobHandle.CombineDependencies(comb, wait);
    47.         }
    48.         comb.Complete();
    49.         /// - This will be counting the number of entities, so that I can properly schedule the next jobs
    50.         int j = 0;
    51.         foreach (EntityQuery query in queries) {
    52.             NativeArray<ArchetypeChunk> chunks = chunksList[j++];
    53.             foreach (ArchetypeChunk chunk in chunks) {
    54.                 if (FilterChunk(chunk)) {
    55.                     int index = total + i;
    56.                     ChunkList[index] = chunk;
    57.                     TotalPerChunk[index] = totalEnt;
    58.                     i++;
    59.                     totalEnt += chunk.Count;
    60.                 }
    61.             }
    62.             if (i > 0) {
    63.                 calls.Add(new DeferedJobCall() { // no job is scheduled if this fails
    64.                     Query = query,
    65.                     ChunkList = ChunkList.Slice(total, i),
    66.                     ChunkTotals = TotalPerChunk.Slice(total, i),
    67.                     EntityCount = totalEnt
    68.                 });
    69.             }
    70.             chunks.Dispose();
    71.             totaltotalEnt += totalEnt;
    72.             total += i;
    73.             i = 0;
    74.             totalEnt = 0;
    75.         }
    76.         /// - This is where the total amount of entities is used, it will allocate my NativeArrays so that they are big enough for the jobs
    77.         PrepareForScheduleJobs(totaltotalEnt, calls.Count);
    78.         /// - These are the jobs that will use the native arrays that needs to be big enough
    79.         int JobIndex = 0;
    80.         JobHandle combined = inputDeps;
    81.         foreach (DeferedJobCall call in calls) {
    82.             JobHandle.CombineDependencies(ScheduleJob(JobIndex++, call.Query, call.ChunkList, call.ChunkTotals, call.EntityCount, combined), combined);
    83.         }
    84.         return combined;
    85.     }
    86.  
    87.     protected override void OnDestroy() {
    88.         base.OnDestroy();
    89.         if (ChunkList.IsCreated) {
    90.             ChunkList.Dispose();
    91.         }
    92.         if (TotalPerChunk.IsCreated) {
    93.             TotalPerChunk.Dispose();
    94.         }
    95.     }
    96.  
    97.     internal struct DeferedJobCall {
    98.         internal EntityQuery Query;
    99.         internal NativeSlice<ArchetypeChunk> ChunkList;
    100.         internal NativeSlice<int> ChunkTotals;
    101.         internal int EntityCount;
    102.     }
    103. }
    104.  
    Code (CSharp):
    1. using AnimationInstancing;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using Unity.Burst;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using Unity.Jobs;
    8. using Unity.Mathematics;
    9. using Unity.Rendering;
    10. using Unity.Transforms;
    11. using UnityEngine;
    12. using static Unity.Mathematics.math;
    13. using static UpdateFrustrumCulling;
    14.  
    15. [UpdateBefore(typeof(UpdateFrustrumCulling))]
    16. public class BuildRenderInstancedAnimationBlock : ChunkFilterSystem {
    17.  
    18.     [BurstCompile]
    19.     struct BuildRenderInstancedAnimationBlockJob : IJobParallelFor {
    20.         public NativeSlice<ArchetypeChunk> ChunkSlice;
    21.         public NativeSlice<int> TotalPerChunk;
    22.  
    23.         [NativeDisableParallelForRestriction] public NativeArray<Matrix4x4> arrayMat;
    24.         [NativeDisableParallelForRestriction] public NativeArray<float> frameIndex;
    25.  
    26.         [ReadOnly] public ArchetypeChunkComponentType<ECSAnimationInstance> ECSAnimationInstance;
    27.         [ReadOnly] public ArchetypeChunkComponentType<LocalToWorld> LocalToWorld;
    28.  
    29.         public void Execute(ArchetypeChunk chunk, int TotalSoFar) {
    30.             var ECSAnimationInstance = chunk.GetNativeArray(this.ECSAnimationInstance);
    31.             var LocalToWorld = chunk.GetNativeArray(this.LocalToWorld);
    32.             for (var i = 0; i < chunk.Count; i++) {
    33.                 int internali = TotalSoFar + i;
    34.                 arrayMat[internali] = (Matrix4x4)LocalToWorld[i].Value;
    35.                 ECSAnimationInstance eCSAnimationInstance = ECSAnimationInstance[i];
    36.                 frameIndex[internali] = eCSAnimationInstance.curFrame;
    37.             }
    38.         }
    39.  
    40.         public void Execute(int index) {
    41.             Execute(ChunkSlice[index], TotalPerChunk[index]);
    42.         }
    43.     }
    44.  
    45.     private NativeArray<int> CountMap;
    46.     private NativeArray<ArchetypeChunk> ChunksToIterate;
    47.     private NativeArray<Matrix4x4> arrayMat;
    48.     private NativeArray<float> frameIndex;
    49.     private NativeList<JobHandle> jobs;
    50.  
    51.     private EntityQuery[] Groups;
    52.     private Dictionary<int, AnimationInstancingMgr.VertexCache> vertexCachePool;
    53.     private BuildRenderInstancedAnimationBlockJob[] jobCache;
    54.     private List<ECSInstancedAnimationData> indexes;
    55.     private List<ECSInstancedAnimationData> indexes2;
    56.     public Camera camera;
    57.  
    58.  
    59.     ArchetypeChunkComponentType<InFrustrum> InFrustrum;
    60.     ArchetypeChunkComponentType<ECSAnimationInstance> ECSAnimationInstance;
    61.     ArchetypeChunkComponentType<LocalToWorld> LocalToWorld;
    62.  
    63.     protected override void OnCreate() {
    64.         base.OnCreate();
    65.         camera = Camera.main;
    66.         vertexCachePool = AnimationInstancingMgr.Instance.vertexCachePool;
    67.         indexes = new List<ECSInstancedAnimationData>();
    68.         indexes2 = new List<ECSInstancedAnimationData>();
    69.         jobs = new NativeList<JobHandle>(64, Allocator.TempJob);
    70.         Utils.RecreateIfSmaller(ref arrayMat, 10);
    71.         Utils.RecreateIfSmaller(ref frameIndex, 10);
    72.     }
    73.  
    74.     protected override JobHandle OnUpdate(JobHandle inputDependencies) {
    75.         int i = 0;
    76.         int total = 0;
    77.         foreach (ECSInstancedAnimationData prefab in indexes.Skip(1)) {
    78.             if (!vertexCachePool.ContainsKey(prefab.VertexCache)) {
    79.                 i++;
    80.                 continue;
    81.             }
    82.             if (CountMap.Length <= i) {
    83.                 break;
    84.             }
    85.             int count = CountMap[i++];
    86.             if (count == 0) {
    87.                 continue;
    88.             }
    89.             AnimationInstancingMgr.VertexCache cache = vertexCachePool[prefab.VertexCache];
    90.             AnimationInstancingMgr.MaterialBlock mblock = cache.instanceBlockList[prefab.MaterialIdentifier];
    91.             Bounds bounds1 = new Bounds(camera.transform.position, Vector3.one * 25);
    92.             FPSDisplay.ExtraText = "" + count;
    93.             AnimationInstancingMgr.RenderPackage(mblock.ECSPackage, cache, prefab.AnimationTextureIndex, AnimationInstancingMgr.Instance.UseInstancing, count, total,
    94.                 arrayMat, frameIndex, bounds1);
    95.  
    96.             total += count;
    97.         }
    98.         indexes2.Clear();
    99.         EntityManager.GetAllUniqueSharedComponentData(indexes2);
    100.         if (indexes.Count != indexes2.Count || !indexes.SequenceEqual(indexes2)) {
    101.             Groups = new EntityQuery[indexes2.Count - 1];
    102.             for (i = 1; i < indexes2.Count; i++) {
    103.                 EntityQuery m_Group = Groups[i - 1] = GetEntityQuery(ComponentType.ReadOnly<ECSInstancedAnimationData>(),
    104.                 ComponentType.ReadOnly<ECSAnimationInstance>(),
    105.                 ComponentType.ReadOnly<LocalToWorld>(),
    106.                 ComponentType.ReadOnly<WorldRenderBounds>(),
    107.                 ComponentType.ChunkComponentReadOnly<InFrustrum>());
    108.                 m_Group.SetSharedComponentFilter(indexes2[i]);
    109.             }
    110.         }
    111.         List<ECSInstancedAnimationData> tmp = indexes2;
    112.         indexes2 = indexes;
    113.         indexes = tmp;
    114.         InFrustrum = GetArchetypeChunkComponentType<InFrustrum>(true);
    115.         ECSAnimationInstance = GetArchetypeChunkComponentType<ECSAnimationInstance>(true);
    116.         LocalToWorld = GetArchetypeChunkComponentType<LocalToWorld>(true);
    117.  
    118.         return base.OnUpdate(inputDependencies);
    119.     }
    120.  
    121.     public override void PrepareForScheduleJobs(int TotalEntityCount, int JobCount) {
    122.         Utils.RecreateIfSmaller(ref arrayMat, TotalEntityCount);
    123.         Utils.RecreateIfSmaller(ref frameIndex, TotalEntityCount);
    124.         Utils.RecreateIfSmaller(ref CountMap, JobCount);
    125.  
    126.         if (jobCache == null || jobCache.Length < JobCount) {
    127.             jobCache = new BuildRenderInstancedAnimationBlockJob[JobCount * 2];
    128.         }
    129.     }
    130.  
    131.     public override bool FilterChunk(ArchetypeChunk chunk) {
    132.         return chunk.GetChunkComponentData(InFrustrum).Value;
    133.     }
    134.  
    135.     public override EntityQuery[] GetQueries() {
    136.         return Groups;
    137.     }
    138.  
    139.     public override JobHandle ScheduleJob(int JobIndex, EntityQuery Query, NativeSlice<ArchetypeChunk> ChunkList, NativeSlice<int> chunkTotals, int EntityCount, JobHandle InputDeps) {
    140.         CountMap[JobIndex] = EntityCount;
    141.         BuildRenderInstancedAnimationBlockJob job = jobCache[JobIndex];
    142.         job.arrayMat = arrayMat;
    143.         job.frameIndex = frameIndex;
    144.         job.ChunkSlice = ChunkList;
    145.         job.TotalPerChunk = chunkTotals;
    146.         job.ECSAnimationInstance = ECSAnimationInstance;
    147.         job.LocalToWorld = LocalToWorld;
    148.         // job.bounds = bounds;
    149.         return job.Schedule(ChunkList.Length, 64, InputDeps);
    150.     }
    151.  
    152.     protected override void OnStopRunning() {
    153.         base.OnDestroy();
    154.     }
    155.     protected override void OnDestroy() {
    156.         base.OnDestroy();
    157.         if (CountMap.IsCreated) {
    158.             CountMap.Dispose();
    159.         }
    160.         if (arrayMat.IsCreated) {
    161.             arrayMat.Dispose();
    162.         }
    163.         if (frameIndex.IsCreated) {
    164.             frameIndex.Dispose();
    165.         }
    166.         if (jobs.IsCreated) {
    167.             jobs.Dispose();
    168.         }
    169.     }
    170. }
    How exactly can I chain systems? I don't think just [UpdateAfter(typeof(MyOtherSystem))] will wait for the last job handle to finish, will it? I think the second system will run immediately and just give me the job handle, I would end up just moving where the JobHandle.Complete lies rather than removing it
     
  41. BrendonSmuts

    BrendonSmuts

    Joined:
    Jun 12, 2017
    Posts:
    86
    If you want the job to potentially span over multiple frames and don’t care at what frame you continue from them you can rather fire off the first phase of work in that system, store the job handle and exit out the OnUpdate. When the next frame comes around you can inspect the jobhandle to check if it has completed and continue your work or otherwise exit then OnUpdate again and wait another frame.
     
  42. BrendonSmuts

    BrendonSmuts

    Joined:
    Jun 12, 2017
    Posts:
    86
    There are too many red flags here to really give you a good solution to your issue. I still can’t tell what problem you’re trying to solve. I can tell your solution to whatever it is means that you need a generic method to filter chunks but what problem that is trying to address is beyond me. From what I can see right now you have no good reason to try come up with a method of generic chunk filtering when, as it stands, to you have one system that needs that filtering. Save yourself the headache and filter it explicitly in your mesh system. You’ll probably find you

    a) have no real problem that needs solving
    b) have not investigated your problem sufficiently to formulate a proper solution
    c) have insufficient domain knowledge to architect a suitable solution
    d) discover the cost of solving your problem doesn’t cover the benefit

    All of these points will be answered for you by simply putting this aside for now and doing thing the “dumb” way.
     
  43. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Won't the
    CreateArchetypeChunkArrayAsync
    be invalid by then?
     
  44. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    I wonder how many times I will have to repeat myself that I want to execute code in the main thread after scheduling jobs without blocking the main thread while I wait for those previously scheduled jobs to finish. Which is impossible now, and therefore this thread, from the very first post, was a feature request, that somehow got hijacked into "lets not request the feature, let's instead try to solve it somehow instead" which I seriously tried to, but it's impossible

    I've edited the first post to be as explicit as possible about what I want/need

    I really love when someone suggests that the solution to your problem is not solving your problem
    The reason I want the filtering to happen in an abstract method, is to be able to reuse code, so I don't have to copy paste the whole filtering every time I need it
     
    Last edited: Mar 29, 2020
    Egad_McDad, R0man and iamarugin like this.
  45. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Yes there was. You are calling the abstract method multiple times from a concrete method in the abstract class. By the way, if you are sharing code with more than 200 lines and have a question related to only specific pieces, it is a good idea to remove the other pieces and replace them with
    // ... Doing some other work that generally does such and such on such and such
    Maintain class and method signatures that encapsulate the important parts so that the context isn't lost.

    With that said, you can solve this with a delegation pattern. In the base abstract class, try something like this:
    Code (CSharp):
    1. //Overriders should schedule a concrete instance of FilterChunksJob with a unique implementation of FilterChunks and return the JobHandle.
    2.     protected abstract JobHandle FilterChunks(FilterChunksJobData jobData, JobHandle inputDeps);
    3.  
    4.     public interface IFilterChunks
    5.     {
    6.         bool FilterChunk(ArchetypeChunk chunk);
    7.     }
    8.  
    9.     protected struct FilterChunksJobData
    10.     {
    11.         public NativeArray<int> integerCounts; //total, i, totalEnt, ect
    12.         public NativeArray<ArchetypeChunk> chunkList; //Why are you calling an array a list?
    13.         public NativeArray<int> totalPerChunk;
    14.         public NativeArray<ArchetypeChunk> chunks; //chunkList vs chunks?
    15.     }
    16.    
    17.     protected struct FilterChunksJob<T> : IJob where T : struct, IFilterChunks
    18.     {
    19.         public FilterChunksJobData data;
    20.         public T filter;
    21.  
    22.         public void Execute()
    23.         {
    24.             for (int i = 0; i < data.chunks.Length; i++)
    25.             {
    26.                 var chunk = data.chunks[i];
    27.                 if (filter.FilterChunk(chunk))
    28.                 {
    29.                     //You get the idea?
    30.                 }
    31.             }
    32.         }
    33.     }
     
    Guedez likes this.
  46. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Oh, I've tried using generics to solve this before, but the generic type was on the system rather than on the job, the generics on the system would throw an error complaining about "generic jobs can't Entities.ForEach".
    This might work, I will try it out later
     
  47. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Turns out
    NativeList.AsParallelWriter()
    is a thing, works wonderfully, I can just
    Add 
    (although restricted to
    AddNoResize
    )without having to know exactly where the data needs to go, so no more counting chunks, and the only issue to be solved now is not being able to know exactly how many entities I will need space for so I just ensure the worst case scenario (all entities at once in frustrum), which is only an issue for the CPU memory, since the GPU memory is only allocated on the next frame, so I already know the entity count by then. I wish I knew about it sooner

    I still want my feature request, just because this specific problem does not need it anymore does not mean I will stop wanting to schedule main thread work

    Not the same thing being rendered, since I tested
    NativeList.AsParallelWriter
    on what would be the second system to extend
    ChunkFilterSystem
    :
    Code (CSharp):
    1. using System;
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Transforms;
    7. using UnityEngine;
    8. public class DrawCropTile : JobComponentSystem {
    9.  
    10.     public NativeList<Matrix4x4>[] Matrix4x4List;
    11.     public NativeList<TileRendertData>[] TileRendertDataList;
    12.  
    13.     public ComputeBuffer[] Matrix4x4Buffers;
    14.     public ComputeBuffer[] TileRendertDataBuffers;
    15.     public ComputeBuffer[] ArgsBuffers;
    16.     public uint[][] ArgsArray;
    17.  
    18.     public Material[] MaterialArray;
    19.  
    20.     Mesh[] Meshes;
    21.  
    22.     EntityQuery[] m_Group;
    23.     EndSimulationEntityCommandBufferSystem entityCommandBufferSystem;
    24.  
    25.     Camera camera;
    26.     protected override void OnCreate() {
    27.         base.OnCreate();
    28.         camera = Camera.main;
    29.         entityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    30.  
    31.         Mesh center = Resources.Load<Mesh>("Center");
    32.         Mesh cornerIn = Resources.Load<Mesh>("Inner_Corner");
    33.         Mesh cornerOut = Resources.Load<Mesh>("Outer_Corner");
    34.         Mesh side = Resources.Load<Mesh>("Side");
    35.  
    36.         m_Group = new EntityQuery[4];
    37.         m_Group[0] = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>(),
    38.             ComponentType.ReadOnly<TileRendertData>(),
    39.             ComponentType.ReadOnly<TileMesh>(),
    40.             ComponentType.ChunkComponentReadOnly<InFrustrum>());
    41.         m_Group[1] = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>(),
    42.             ComponentType.ReadOnly<TileRendertData>(),
    43.             ComponentType.ReadOnly<TileMesh>(),
    44.             ComponentType.ChunkComponentReadOnly<InFrustrum>());
    45.         m_Group[2] = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>(),
    46.             ComponentType.ReadOnly<TileRendertData>(),
    47.             ComponentType.ReadOnly<TileMesh>(),
    48.             ComponentType.ChunkComponentReadOnly<InFrustrum>());
    49.         m_Group[3] = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>(),
    50.             ComponentType.ReadOnly<TileRendertData>(),
    51.             ComponentType.ReadOnly<TileMesh>(),
    52.             ComponentType.ChunkComponentReadOnly<InFrustrum>());
    53.  
    54.         //m_Group[0].SetSharedComponentFilter(new TileMesh() { Value = center });
    55.         //m_Group[1].SetSharedComponentFilter(new TileMesh() { Value = cornerIn });
    56.         //m_Group[2].SetSharedComponentFilter(new TileMesh() { Value = cornerOut });
    57.         //m_Group[3].SetSharedComponentFilter(new TileMesh() { Value = side });
    58.  
    59.         Meshes = new Mesh[4];
    60.         Meshes[0] = center;
    61.         Meshes[1] = cornerIn;
    62.         Meshes[2] = cornerOut;
    63.         Meshes[3] = side;
    64.  
    65.         Material original = Resources.Load<Material>("TileMaterial");
    66.  
    67.         Matrix4x4List = new NativeList<Matrix4x4>[4];
    68.         TileRendertDataList = new NativeList<TileRendertData>[4];
    69.         Matrix4x4Buffers = new ComputeBuffer[4];
    70.         TileRendertDataBuffers = new ComputeBuffer[4];
    71.         ArgsBuffers = new ComputeBuffer[4];
    72.         MaterialArray = new Material[4];
    73.         ArgsArray = new uint[4][];
    74.         for (int i = 0; i < 4; i++) {
    75.             Matrix4x4Buffers[i] = new ComputeBuffer(256, 64);
    76.             TileRendertDataBuffers[i] = new ComputeBuffer(256, 8);
    77.             Matrix4x4List[i] = new NativeList<Matrix4x4>(256, Allocator.Persistent);
    78.             TileRendertDataList[i] = new NativeList<TileRendertData>(256, Allocator.Persistent);
    79.             MaterialArray[i] = UnityEngine.Object.Instantiate(original);
    80.             ArgsBuffers[i] = new ComputeBuffer(5, 4, ComputeBufferType.IndirectArguments);
    81.             ArgsArray[i] = new uint[5];
    82.  
    83.             uint[] ArgsArrayI = ArgsArray[i];
    84.             Mesh MeshI = Meshes[i];
    85.             ArgsArrayI[0] = (uint)MeshI.GetIndexCount(0);
    86.             ArgsArrayI[2] = (uint)MeshI.GetIndexStart(0);
    87.             ArgsArrayI[3] = (uint)MeshI.GetBaseVertex(0);
    88.  
    89.             MaterialArray[i].SetBuffer("Matrices", Matrix4x4Buffers[i]);
    90.             MaterialArray[i].SetBuffer("Heights", TileRendertDataBuffers[i]);
    91.         }
    92.     }
    93.  
    94.     [BurstCompile]
    95.     struct UpdateTileModelJob : IJobChunk {
    96.         public NativeList<Matrix4x4>.ParallelWriter Matrix4x4Writer;
    97.         public NativeList<TileRendertData>.ParallelWriter TileRendertDataWriter;
    98.         [ReadOnly] public ArchetypeChunkComponentType<LocalToWorld> LocalToWorld;
    99.         [ReadOnly] public ArchetypeChunkComponentType<TileRendertData> TileRendertData;
    100.         [ReadOnly] public ArchetypeChunkComponentType<InFrustrum> InFrustrum;
    101.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    102.             var LocalToWorld = chunk.GetNativeArray(this.LocalToWorld);
    103.             var TileRendertData = chunk.GetNativeArray(this.TileRendertData);
    104.             if (chunk.GetChunkComponentData(InFrustrum).Value) {
    105.                 for (var i = 0; i < chunk.Count; i++) {
    106.                     Matrix4x4Writer.AddNoResize((Matrix4x4)LocalToWorld[i].Value);
    107.                     TileRendertDataWriter.AddNoResize(TileRendertData[i]);
    108.                 }
    109.             }
    110.         }
    111.     }
    112.  
    113.     protected override JobHandle OnUpdate(JobHandle inputDependencies) {
    114.         Bounds bounds1 = new Bounds(camera.transform.position, Vector3.one * 25);
    115.         JobHandle combined = inputDependencies;
    116.         for (int i = 0; i < 4; i++) {
    117.             int count = Matrix4x4List[i].Length;
    118.             if (count > 0 && i == 0) {
    119.                 if (Matrix4x4Buffers[i].count < count) {
    120.                     Matrix4x4Buffers[i].Dispose();
    121.                     TileRendertDataBuffers[i].Dispose();
    122.                     Matrix4x4Buffers[i] = new ComputeBuffer(256, 64);
    123.                     TileRendertDataBuffers[i] = new ComputeBuffer(256, 8);
    124.                     MaterialArray[i].SetBuffer("Matrices", Matrix4x4Buffers[i]);
    125.                     MaterialArray[i].SetBuffer("Heights", TileRendertDataBuffers[i]);
    126.                 }
    127.                 Matrix4x4Buffers[i].SetData(Matrix4x4List[i].AsArray(), 0, 0, count);
    128.                 TileRendertDataBuffers[i].SetData(TileRendertDataList[i].AsArray(), 0, 0, count);
    129.  
    130.                 uint[] ArgsArrayI = ArgsArray[i];
    131.  
    132.                 ArgsArrayI[1] = (uint)count;
    133.                 ArgsBuffers[i].SetData(ArgsArrayI);
    134.  
    135.                 Graphics.DrawMeshInstancedIndirect(Meshes[i], 0, MaterialArray[i], bounds1, ArgsBuffers[i]);
    136.  
    137.                 Matrix4x4List[i].Clear();
    138.                 TileRendertDataList[i].Clear();
    139.             } else {
    140.                 Matrix4x4List[i].Clear();
    141.                 TileRendertDataList[i].Clear();
    142.             }
    143.  
    144.             int worstcasecount = m_Group[i].CalculateEntityCount();
    145.             if (worstcasecount >= Matrix4x4List[i].Capacity) {
    146.                 Matrix4x4List[i].ResizeUninitialized(worstcasecount * 2);
    147.                 TileRendertDataList[i].ResizeUninitialized(worstcasecount * 2);
    148.             }
    149.             var job = new UpdateTileModelJob();
    150.             job.Matrix4x4Writer = Matrix4x4List[i].AsParallelWriter();
    151.             job.TileRendertDataWriter = TileRendertDataList[i].AsParallelWriter();
    152.             job.LocalToWorld = GetArchetypeChunkComponentType<LocalToWorld>();
    153.             job.TileRendertData = GetArchetypeChunkComponentType<TileRendertData>();
    154.             job.InFrustrum = GetArchetypeChunkComponentType<InFrustrum>();
    155.             combined = JobHandle.CombineDependencies(combined, job.Schedule(m_Group[i], inputDependencies));
    156.         }
    157.         return combined;
    158.     }
    159.  
    160.     protected override void OnDestroy() {
    161.         base.OnDestroy();
    162.         if (Matrix4x4List[0].IsCreated) {
    163.             for (int i = 0; i < 4; i++) {
    164.                 Matrix4x4List[i].Dispose();
    165.                 TileRendertDataList[i].Dispose();
    166.                 Matrix4x4Buffers[i].Dispose();
    167.                 TileRendertDataBuffers[i].Dispose();
    168.                 ArgsBuffers[i].Dispose();
    169.             }
    170.         }
    171.     }
    172. }
     
  48. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Sync points are how ECS deals with concurrency. Vs say where you would use concurrent collections that on every update have to use interlocked/spinwait. The pattern is quite common actually even outside of games, but part of the design is you have to have a primary synchronization point. A place where you wait for jobs dependent on structural changes to complete before making those changes. And almost every design that does this uses a main thread construct for scheduling and waiting. ECS is simply a variant of a well established pattern.

    The drawback of this approach if you want to call it that is it requires explicit design on the part of the developer. You have to limit sync points, schedule jobs so that they always have enough time to complete. It's jobs that you didn't give enough time to that are forcing the main thread to wait. And the fix is give them enough time.

    Right now this takes some rather careful planning and Unity still has a lot of work to do of it's own to make it easier on developers. And they are like with the hybrid renderer refactor, but it's going to be a while before you don't have to plan very very carefully to avoid extended waits when using a lot of DOTS features.
     
    R0man likes this.
  49. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    So it's more realistic to expect better tools than more or customized sync points?
    For instance, a better more robust EntityQuery.CountEntities() so that I don't have to manually do it?

    I've seen that they plan on adding a Chunks.ForEach(), which would probably solve tons of problems

    OBS: How about scheduling main thread work? It would be just a lambda that runs in the main thread with no restrictions, or that would be no different than jobs scheduling jobs, which is prohibited?