Search Unity

Schedule Job Error and Utility AI

Discussion in 'Entity Component System' started by Dnalsi, Jul 10, 2019.

  1. Dnalsi

    Dnalsi

    Joined:
    Feb 11, 2014
    Posts:
    9
    Hi ! I am trying to create an AI based on the Utility AI from Dave Mark (here). To simplify my question, I create a test to illustrate my problem.
    Code (CSharp):
    1. using NUnit.Framework;
    2. using System;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5.  
    6. namespace Tests
    7. {
    8.     [Category("ECS Test")]
    9.     public class SimpleFilterJobTests
    10.     {
    11.         World World;
    12.         EntityManager Manager;
    13.  
    14.         [SetUp]
    15.         public void SetUp()
    16.         {
    17.             World = new World("Test World");
    18.             Manager = World.EntityManager;
    19.         }
    20.  
    21.         public void TearDown()
    22.         {
    23.             if (World != null)
    24.             {
    25.                 World.Dispose();
    26.                 World = null;
    27.                 Manager = null;
    28.             }
    29.         }
    30.  
    31.         [Test]
    32.         public void AssertJobPass()
    33.         {
    34.             Assert.Pass();
    35.         }
    36.  
    37.         public struct SharedValue : ISharedComponentData, IEquatable<SharedValue>
    38.         {
    39.             public int Value;
    40.  
    41.             public bool Equals(SharedValue other)
    42.             {
    43.                 return Value == other.Value;
    44.             }
    45.  
    46.             public override int GetHashCode()
    47.             {
    48.                 return Value;
    49.             }
    50.         }
    51.  
    52.         public struct SimpleValue : IComponentData
    53.         {
    54.             public float Value;
    55.         }
    56.  
    57.         public struct SimpleValueJob : IJobForEach<SimpleValue>
    58.         {
    59.             public void Execute(ref SimpleValue simple)
    60.             {
    61.                 simple.Value = 10;
    62.             }
    63.         }
    64.  
    65.         [Test]
    66.         public void TestScheduleOnTwoSeperateChunk()
    67.         {
    68.             for (int i = 0; i < 10; i++)
    69.             {
    70.                 var e = Manager.CreateEntity();
    71.                 Manager.AddComponent(e, typeof(SimpleValue));
    72.                 Manager.AddSharedComponentData(e, new SharedValue { Value = 1 });
    73.             }
    74.  
    75.             for (int i = 0; i < 10; i++)
    76.             {
    77.                 var e = Manager.CreateEntity();
    78.                 Manager.AddComponent(e, typeof(SimpleValue));
    79.                 Manager.AddSharedComponentData(e, new SharedValue { Value = 2 });
    80.             }
    81.  
    82.             var query = Manager.CreateEntityQuery(typeof(SharedValue), typeof(SimpleValue));
    83.  
    84.             query.SetFilter(new SharedValue { Value = 1 });
    85.             var handle1 = new SimpleValueJob().Schedule(query);
    86.  
    87.             query.SetFilter(new SharedValue { Value = 2 });
    88.             var handle2 = new SimpleValueJob().Schedule(query);
    89.  
    90.             JobHandle.CompleteAll(ref handle1, ref handle2);
    91.         }
    92.     }
    93. }
    94.  

    And the error, I get.

    TestScheduleOnTwoSeperateChunk (1.281s)
    ---
    System.InvalidOperationException : The previously scheduled job SimpleFilterJobTests:SimpleValueJob writes to the NativeArray SimpleValueJob.Iterator. You are trying to schedule a new job SimpleFilterJobTests:SimpleValueJob, which writes to the same NativeArray (via SimpleValueJob.Iterator). To guarantee safety, you must include SimpleFilterJobTests:SimpleValueJob as a dependency of the newly scheduled job.
    ---
    at Unity.Entities.JobForEachExtensions.Schedule (System.Void* fullData, Unity.Collections.NativeArray`1[T] prefilterData, System.Int32 unfilteredLength, System.Int32 innerloopBatchCount, System.Boolean isParallelFor, System.Boolean isFiltered, Unity.Entities.JobForEachExtensions+JobForEachCache& cache, System.Void* deferredCountData, Unity.Jobs.JobHandle dependsOn, Unity.Jobs.LowLevel.Unsafe.ScheduleMode mode) [0x00066] in D:\Project\VoxelWar\Library\PackageCache\com.unity.entities@0.0.12-preview.33\Unity.Entities\IJobForEach.cs:451
    at Unity.Entities.JobForEachExtensions.ScheduleInternal_C[T] (T& jobData, Unity.Entities.ComponentSystemBase system, Unity.Entities.EntityQuery query, System.Int32 innerloopBatchCount, Unity.Jobs.JobHandle dependsOn, Unity.Jobs.LowLevel.Unsafe.ScheduleMode mode) [0x0007c] in D:\Project\VoxelWar\Library\PackageCache\com.unity.entities@0.0.12-preview.33\Unity.Entities\IJobForEach.gen.cs:1510
    at Unity.Entities.JobForEachExtensions.Schedule[T] (T jobData, Unity.Entities.EntityQuery query, Unity.Jobs.JobHandle dependsOn) [0x00020] in D:\Project\VoxelWar\Library\PackageCache\com.unity.entities@0.0.12-preview.33\Unity.Entities\IJobForEach.gen.cs:1152
    at Tests.SimpleFilterJobTests.TestScheduleOnTwoSeperateChunk () [0x00190] in D:\Project\VoxelWar\Assets\Tests\AI\Behaviour\SimpleFilterJobTests.cs:93
    at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
    at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <1f0c1ef1ad524c38bbc5536809c46b48>:0

    1. If I understand, I should make my second JobHandle depends from the first job. But why ? If I understand SharedComponentData, the first and second job should read on 2 separates chunks, so they shouldn't have a problem to write in the NativeArray ?

    Now for the context like I say I want to maximize the use of ECS in my Utility System. I know I will have random access but I can still multi-thread my code and use the burst compiler, so I am thinking it's worth it.

    My data is structure like that. I have DecisionMaker which have Decisions which have Considerations. Each considerations and decisions need to give a score. All decisions are unique but, considerations are fixed and there score can be reuse in the same DecisionMaker. So if I have 2 decision that need to consider its health, I can score HealthConsideration one time for 2 decisions. Each decision score is multiply and compensate on one ComponentData and the highest of all decisions in all DecisionMaker is the winner.

    One solution, it's to use one attribute by DecisionMaker

    Code (CSharp):
    1. using NUnit.Framework;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Mathematics;
    6.  
    7. namespace Tests
    8. {
    9.     [Category("VoxelWar")]
    10.     public class AgentBrainTests
    11.     {
    12.         World World;
    13.         EntityManager Manager;
    14.  
    15.         [SetUp]
    16.         public void SetUp()
    17.         {
    18.             World = new World("Test World");
    19.             Manager = World.EntityManager;
    20.  
    21.             for (int i = 0; i < 10; i++)
    22.             {
    23.                 var e = Manager.CreateEntity();
    24.                 Manager.AddComponentData(e, new TestHealth { Value = 10f });
    25.                 Manager.AddComponent(e, typeof(TestHealthScore));
    26.                 Manager.AddComponent(e, typeof(TestHighestDecisionScore));
    27.                 Manager.AddComponent(e, typeof(TestDecisionScore));
    28.                 Manager.AddComponent(e, typeof(TestDecisionMaker1));
    29.             }
    30.  
    31.             for (int i = 0; i < 5; i++)
    32.             {
    33.                 var e = Manager.CreateEntity();
    34.                 Manager.AddComponentData(e, new TestHealth { Value = 5f });
    35.                 Manager.AddComponentData(e, new TestDistance { Value = 25f });
    36.                 Manager.AddComponent(e, typeof(TestHealthScore));
    37.                 Manager.AddComponent(e, typeof(TestDistanceScore));
    38.                 Manager.AddComponent(e, typeof(TestHighestDecisionScore));
    39.                 Manager.AddComponent(e, typeof(TestDecisionScore));
    40.                 Manager.AddComponent(e, typeof(TestDecisionMaker2));
    41.                 Manager.AddComponent(e, typeof(TestDecisionMaker1));
    42.             }
    43.         }
    44.  
    45.         public void TearDown()
    46.         {
    47.             if (World != null)
    48.             {
    49.                 World.Dispose();
    50.                 World = null;
    51.                 Manager = null;
    52.             }
    53.         }
    54.  
    55.         // A Test behaves as an ordinary method
    56.         [Test]
    57.         public void AgentBrainTestsSimplePasses()
    58.         {
    59.             Assert.Pass();
    60.         }
    61.  
    62.         public struct TestDecisionMaker1 : IComponentData { }
    63.         public struct TestDecisionMaker2 : IComponentData { }
    64.  
    65.         public struct TestHighestDecisionScore : IComponentData { public float Value; }
    66.         public struct TestDecisionScore : IComponentData { public float Value; }
    67.  
    68.         public struct TestHealthScore : IComponentData { public float Value; }
    69.         public struct TestHealth : IComponentData { public float Value; }
    70.  
    71.         public struct TestDistanceScore : IComponentData { public float Value; }
    72.         public struct TestDistance : IComponentData { public float Value; }
    73.  
    74.         public class TestBarrier : EntityCommandBufferSystem { }
    75.  
    76.         public interface ITestConsideration
    77.         {
    78.             void Init(EntityManager manager, EntityQuery query);
    79.             JobHandle Score(EntityQuery query);
    80.             JobHandle Merge(EntityQuery query, JobHandle inputDeps);
    81.         }
    82.  
    83.         public class TestHealthConsideration : ITestConsideration
    84.         {
    85.             struct HealthJob : IJobForEach<TestHealthScore, TestHealth>
    86.             {
    87.                 public void Execute(ref TestHealthScore c0, [ReadOnly] ref TestHealth c1)
    88.                 {
    89.                     c0.Value = c1.Value / 10;
    90.                 }
    91.             }
    92.  
    93.             struct MergeHealth : IJobForEachWithEntity<TestDecisionScore, TestHealthScore>
    94.             {
    95.                 public void Execute(Entity entity, int index, ref TestDecisionScore c0, [ReadOnly] ref TestHealthScore c1)
    96.                 {
    97.                     c0.Value *= c1.Value;
    98.                 }
    99.             }
    100.  
    101.             public void Init(EntityManager manager, EntityQuery query)
    102.             {
    103.                 manager.AddComponent(query, typeof(TestHealthScore));
    104.             }
    105.  
    106.             public JobHandle Score(EntityQuery query)
    107.             {
    108.                 return new HealthJob().Schedule(query);
    109.             }
    110.  
    111.             public JobHandle Merge(EntityQuery query, JobHandle inputDeps)
    112.             {
    113.                 return new MergeHealth().Schedule(query, inputDeps);
    114.             }
    115.         }
    116.  
    117.         public class TestDistanceConsideration : ITestConsideration
    118.         {
    119.             struct DistanceJob : IJobForEach<TestDistanceScore, TestDistance>
    120.             {
    121.                 public void Execute(ref TestDistanceScore c0, [ReadOnly] ref TestDistance c1)
    122.                 {
    123.                     c0.Value = c1.Value / 50;
    124.                 }
    125.             }
    126.  
    127.             struct MergeDistance : IJobForEachWithEntity<TestDecisionScore, TestDistanceScore>
    128.             {
    129.                 public void Execute(Entity entity, int index, ref TestDecisionScore c0, [ReadOnly] ref TestDistanceScore c1)
    130.                 {
    131.                     c0.Value *= c1.Value;
    132.                 }
    133.             }
    134.  
    135.             public void Init(EntityManager manager, EntityQuery query)
    136.             {
    137.                 manager.AddComponent(query, typeof(TestDistanceScore));
    138.             }
    139.  
    140.             public JobHandle Score(EntityQuery query)
    141.             {
    142.                 return new DistanceJob().Schedule(query);
    143.             }
    144.  
    145.             public JobHandle Merge(EntityQuery query, JobHandle inputDeps)
    146.             {
    147.                 return new MergeDistance().Schedule(query, inputDeps);
    148.             }
    149.         }
    150.  
    151.         public struct TestCompareHighestDecisionScore : IJobForEach<TestHighestDecisionScore, TestDecisionScore>
    152.         {
    153.             public void Execute(ref TestHighestDecisionScore c0, ref TestDecisionScore c1)
    154.             {
    155.                 c0.Value = math.max(c0.Value, c1.Value);
    156.             }
    157.         }
    158.  
    159.         [Test]
    160.         public void AgentBrainDecisionTagComponent()
    161.         {
    162.             ITestConsideration[] considerations = new ITestConsideration[]
    163.             {
    164.                 new TestHealthConsideration(),
    165.                 new TestDistanceConsideration()
    166.             };
    167.  
    168.             EntityQueryDesc[] decisions = new EntityQueryDesc[]
    169.             {
    170.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestHealth), typeof(TestDistance) } },
    171.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestHealth) } },
    172.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestDistance) } }
    173.             };
    174.             EntityQueryDesc[] decisionScores = new EntityQueryDesc[]
    175.             {
    176.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestHealthScore), typeof(TestDistanceScore), typeof(TestHealth), typeof(TestDistance) } },
    177.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestHealthScore), typeof(TestHealth) } },
    178.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestDistanceScore), typeof(TestDistance) } }
    179.             };
    180.  
    181.             int[][] decisionConsiderations = new int[][]
    182.             {
    183.                 new int[] { 1, 0 },
    184.                 new int[] { 0 },
    185.                 new int[] { 1 }
    186.             };
    187.  
    188.             int[] sortedDecision = new int[] { 1, 0, 2 };
    189.             int[] sortedDecisionMaker = new int[] { 0, 1, 1 };
    190.             EntityQueryDesc[] decisionMakers = new EntityQueryDesc[]
    191.             {
    192.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestDecisionMaker1), typeof(TestHighestDecisionScore), typeof(TestDecisionScore), typeof(TestHealthScore), typeof(TestHealth) } },
    193.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestDecisionMaker2), typeof(TestDecisionMaker1), typeof(TestHighestDecisionScore), typeof(TestDecisionScore), typeof(TestHealthScore), typeof(TestDistanceScore), typeof(TestHealth), typeof(TestDistance) } }
    194.             };
    195.  
    196.             EntityQuery[] queries = new EntityQuery[decisionMakers.Length];
    197.             queries[0] = Manager.CreateEntityQuery(decisionMakers[0]);
    198.             queries[1] = Manager.CreateEntityQuery(decisionMakers[1]);
    199.  
    200.             Assert.AreEqual(15, queries[0].CalculateLength());
    201.             Assert.AreEqual(5, queries[1].CalculateLength());
    202.  
    203.             for (int i = 0; i < decisions.Length; i++)
    204.             {
    205.                 EntityQuery query = queries[sortedDecisionMaker[i]];
    206.  
    207.                 JobHandle handle = default;
    208.                 int[] consIndice = decisionConsiderations[sortedDecision[i]];
    209.                 for (int j = 0; j < consIndice.Length; j++)
    210.                 {
    211.                     var consideration = considerations[consIndice[j]];
    212.  
    213.                     JobHandle inputDeps = consideration.Score(query);
    214.                     inputDeps = consideration.Merge(query, JobHandle.CombineDependencies(handle, inputDeps));
    215.                     handle = JobHandle.CombineDependencies(handle, inputDeps);
    216.                 }
    217.                 handle = new TestCompareHighestDecisionScore().Schedule(query, handle);
    218.                 handle.Complete();
    219.             }
    220.  
    221.             using (var scores = queries[0].ToComponentDataArray<TestHealthScore>(Allocator.TempJob))
    222.             {
    223.                 for (int i = 0; i < scores.Length; i++)
    224.                 {
    225.                     Assert.IsTrue(scores[i].Value == 1f || scores[i].Value == 0.5f);
    226.                 }
    227.             }
    228.  
    229.             Assert.Pass();
    230.         }
    231.     }
    232. }
    233.  

    The problem with this solution is that I need to hardcode my DecisionMaker struct. I want to let my designer and player create decision outside the game. So what I can do, it's create like 100 DecisionMaker struct, which represent one index and could access in a big switch or array and use the unique index of the DecisionMaker to tag my entity. Which I feel is kind a bad practice to be honest. I looked at the generation of ComponentType and there doesn't seem to be a way to create a fake ComponentType at runtime or something like that.

    So my second solution is to use SharedComponentData to seperate create something like above.

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using NUnit.Framework;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using Unity.Jobs;
    8. using Unity.Mathematics;
    9. using UnityEngine;
    10. using UnityEngine.TestTools;
    11.  
    12. namespace Tests
    13. {
    14.     [Category("VoxelWar")]
    15.     public class AgentBrainSharedTests
    16.     {
    17.         World World;
    18.         EntityManager Manager;
    19.  
    20.         [SetUp]
    21.         public void SetUp()
    22.         {
    23.             World = new World("Test World");
    24.             Manager = World.EntityManager;
    25.  
    26.             for (int i = 0; i < 10; i++)
    27.             {
    28.                 var e = Manager.CreateEntity();
    29.                 Manager.AddComponentData(e, new TestHealth { Value = 10f });
    30.                 Manager.AddComponent(e, typeof(TestHealthScore));
    31.                 Manager.AddComponent(e, typeof(TestHighestDecisionScore));
    32.                 Manager.AddComponent(e, typeof(TestDecisionScore));
    33.                 Manager.AddSharedComponentData(e, new TestBrainChunk { Value = 1 });
    34.             }
    35.  
    36.             for (int i = 0; i < 5; i++)
    37.             {
    38.                 var e = Manager.CreateEntity();
    39.                 Manager.AddComponentData(e, new TestHealth { Value = 5f });
    40.                 Manager.AddComponentData(e, new TestDistance { Value = 25f });
    41.                 Manager.AddComponent(e, typeof(TestHealthScore));
    42.                 Manager.AddComponent(e, typeof(TestDistanceScore));
    43.                 Manager.AddComponent(e, typeof(TestHighestDecisionScore));
    44.                 Manager.AddComponent(e, typeof(TestDecisionScore));
    45.                 Manager.AddSharedComponentData(e, new TestBrainChunk { Value = 2 });
    46.             }
    47.         }
    48.  
    49.         public void TearDown()
    50.         {
    51.             if (World != null)
    52.             {
    53.                 World.Dispose();
    54.                 World = null;
    55.                 Manager = null;
    56.             }
    57.         }
    58.  
    59.         // A Test behaves as an ordinary method
    60.         [Test]
    61.         public void AgentBrainTestsSimplePasses()
    62.         {
    63.             Assert.Pass();
    64.         }
    65.  
    66.         public struct TestBrainChunk : ISharedComponentData, IEquatable<TestBrainChunk>
    67.         {
    68.             public int Value;
    69.  
    70.             public bool Equals(TestBrainChunk other)
    71.             {
    72.                 return other.Value == Value;
    73.             }
    74.  
    75.             public override int GetHashCode()
    76.             {
    77.                 return Value;
    78.             }
    79.         }
    80.  
    81.         public struct TestHighestDecisionScore : IComponentData { public float Value; }
    82.         public struct TestDecisionScore : IComponentData { public float Value; }
    83.  
    84.         public struct TestHealthScore : IComponentData { public float Value; }
    85.         public struct TestHealth : IComponentData { public float Value; }
    86.  
    87.         public struct TestDistanceScore : IComponentData { public float Value; }
    88.         public struct TestDistance : IComponentData { public float Value; }
    89.  
    90.         public class TestBarrier : EntityCommandBufferSystem { }
    91.  
    92.         public interface ITestConsideration
    93.         {
    94.             void Init(EntityManager manager, EntityQuery query);
    95.             JobHandle Score(EntityQuery query);
    96.             JobHandle Merge(EntityQuery query, JobHandle inputDeps);
    97.         }
    98.  
    99.         public class TestHealthConsideration : ITestConsideration
    100.         {
    101.             struct HealthJob : IJobForEach<TestHealthScore, TestHealth>
    102.             {
    103.                 public void Execute(ref TestHealthScore c0, [ReadOnly] ref TestHealth c1)
    104.                 {
    105.                     c0.Value = c1.Value / 10;
    106.                 }
    107.             }
    108.  
    109.             struct MergeHealth : IJobForEachWithEntity<TestDecisionScore, TestHealthScore>
    110.             {
    111.                 public void Execute(Entity entity, int index, ref TestDecisionScore c0, [ReadOnly] ref TestHealthScore c1)
    112.                 {
    113.                     c0.Value *= c1.Value;
    114.                 }
    115.             }
    116.  
    117.             public void Init(EntityManager manager, EntityQuery query)
    118.             {
    119.                 manager.AddComponent(query, typeof(TestHealthScore));
    120.             }
    121.  
    122.             public JobHandle Score(EntityQuery query)
    123.             {
    124.                 return new HealthJob().Schedule(query);
    125.             }
    126.  
    127.             public JobHandle Merge(EntityQuery query, JobHandle inputDeps)
    128.             {
    129.                 return new MergeHealth().Schedule(query, inputDeps);
    130.             }
    131.         }
    132.  
    133.         public class TestDistanceConsideration : ITestConsideration
    134.         {
    135.             struct DistanceJob : IJobForEach<TestDistanceScore, TestDistance>
    136.             {
    137.                 public void Execute(ref TestDistanceScore c0, [ReadOnly] ref TestDistance c1)
    138.                 {
    139.                     c0.Value = c1.Value / 50;
    140.                 }
    141.             }
    142.  
    143.             struct MergeDistance : IJobForEachWithEntity<TestDecisionScore, TestDistanceScore>
    144.             {
    145.                 public void Execute(Entity entity, int index, ref TestDecisionScore c0, [ReadOnly] ref TestDistanceScore c1)
    146.                 {
    147.                     c0.Value *= c1.Value;
    148.                 }
    149.             }
    150.  
    151.             public void Init(EntityManager manager, EntityQuery query)
    152.             {
    153.                 manager.AddComponent(query, typeof(TestDistanceScore));
    154.             }
    155.  
    156.             public JobHandle Score(EntityQuery query)
    157.             {
    158.                 return new DistanceJob().Schedule(query);
    159.             }
    160.  
    161.             public JobHandle Merge(EntityQuery query, JobHandle inputDeps)
    162.             {
    163.                 return new MergeDistance().Schedule(query, inputDeps);
    164.             }
    165.         }
    166.  
    167.         public struct TestCompareHighestDecisionScore : IJobForEach<TestHighestDecisionScore, TestDecisionScore>
    168.         {
    169.             public void Execute(ref TestHighestDecisionScore c0, ref TestDecisionScore c1)
    170.             {
    171.                 c0.Value = math.max(c0.Value, c1.Value);
    172.             }
    173.         }
    174.  
    175.         [Test]
    176.         public void AgentBrainDecisionSharedComponent()
    177.         {
    178.             ITestConsideration[] considerations = new ITestConsideration[]
    179.             {
    180.                 new TestHealthConsideration(),
    181.                 new TestDistanceConsideration()
    182.             };
    183.  
    184.             EntityQueryDesc[] decisions = new EntityQueryDesc[]
    185.             {
    186.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestHealth), typeof(TestDistance) } },
    187.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestHealth) } },
    188.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestDistance) } }
    189.             };
    190.             EntityQueryDesc[] decisionScores = new EntityQueryDesc[]
    191.             {
    192.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestHealthScore), typeof(TestDistanceScore), typeof(TestHealth), typeof(TestDistance) } },
    193.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestHealthScore), typeof(TestHealth) } },
    194.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestDistanceScore), typeof(TestDistance) } }
    195.             };
    196.  
    197.             int[][] decisionConsiderations = new int[][]
    198.             {
    199.                 new int[] { 1, 0 },
    200.                 new int[] { 0 },
    201.                 new int[] { 1 }
    202.             };
    203.  
    204.             int[] sortedDecision = new int[] { 1, 0, 2 };
    205.             int[] sortedDecisionMaker = new int[] { 0, 1, 1 };
    206.             EntityQueryDesc[] decisionMakers = new EntityQueryDesc[]
    207.             {
    208.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestBrainChunk), typeof(TestHighestDecisionScore), typeof(TestDecisionScore), typeof(TestHealthScore), typeof(TestHealth) } },
    209.                 new EntityQueryDesc { All = new ComponentType[] { typeof(TestBrainChunk), typeof(TestHighestDecisionScore), typeof(TestDecisionScore), typeof(TestHealthScore), typeof(TestDistanceScore), typeof(TestHealth), typeof(TestDistance) } }
    210.             };
    211.  
    212.             EntityQuery[] queries = new EntityQuery[decisionMakers.Length];
    213.             queries[0] = Manager.CreateEntityQuery(decisionMakers[0]);
    214.             queries[1] = Manager.CreateEntityQuery(decisionMakers[1]);
    215.  
    216.             Dictionary<int, List<int>> brainChunkDecisionMaker = new Dictionary<int, List<int>>();
    217.             brainChunkDecisionMaker.Add(0, new List<int> { 1, 2 });
    218.             brainChunkDecisionMaker.Add(1, new List<int> { 2 });
    219.  
    220.             List<int> keys = new List<int>();
    221.             NativeList<JobHandle> values = new NativeList<JobHandle>(10, Allocator.Temp);
    222.             try
    223.             {
    224.                 for (int i = 0; i < decisions.Length; i++)
    225.                 {
    226.                     EntityQuery query = queries[sortedDecisionMaker[i]];
    227.                     brainChunkDecisionMaker.TryGetValue(sortedDecisionMaker[i], out List<int> makerIndice);
    228.  
    229.                     int[] consIndice = decisionConsiderations[sortedDecision[i]];
    230.                     for (int index = 0; index < makerIndice.Count; index++)
    231.                     {
    232.                         int valueIndex = FindOrCreateIndexOf(makerIndice[index], ref keys, ref values);
    233.                         query.SetFilter(new TestBrainChunk { Value = makerIndice[index] });
    234.  
    235.                         for (int j = 0; j < consIndice.Length; j++)
    236.                         {
    237.                             JobHandle inputDeps = considerations[consIndice[j]].Score(query);
    238.                             inputDeps = considerations[consIndice[j]].Merge(query, JobHandle.CombineDependencies(inputDeps, values[valueIndex]));
    239.                             values[valueIndex] = new TestCompareHighestDecisionScore().Schedule(query, inputDeps);
    240.                         }
    241.                     }
    242.  
    243.                     JobHandle.CompleteAll(values);
    244.                     values.Clear();
    245.                     keys.Clear();
    246.                 }
    247.             }
    248.             finally
    249.             {
    250.                 values.Dispose();
    251.             }
    252.  
    253.             queries[0].ResetFilter();
    254.             queries[1].ResetFilter();
    255.  
    256.             queries[0].SetFilter(new TestBrainChunk { Value = 1 });
    257.             using (var scores = queries[0].ToComponentDataArray<TestHealthScore>(Allocator.TempJob))
    258.             {
    259.                 for (int i = 0; i < scores.Length; i++)
    260.                 {
    261.                     Assert.AreEqual(1f, scores[i].Value);
    262.                 }
    263.             }
    264.  
    265.             queries[0].SetFilter(new TestBrainChunk { Value = 2 });
    266.             using (var scores = queries[0].ToComponentDataArray<TestHealthScore>(Allocator.TempJob))
    267.             {
    268.                 for (int i = 0; i < scores.Length; i++)
    269.                 {
    270.                     Assert.AreEqual(0.5f, scores[i].Value);
    271.                 }
    272.             }
    273.  
    274.             queries[1].SetFilter(new TestBrainChunk { Value = 2 });
    275.             using (var scores = queries[0].ToComponentDataArray<TestHealthScore>(Allocator.TempJob))
    276.             {
    277.                 for (int i = 0; i < scores.Length; i++)
    278.                 {
    279.                     Assert.AreEqual(0.5f, scores[i].Value);
    280.                 }
    281.             }
    282.         }
    283.  
    284.         private int FindOrCreateIndexOf(int item, ref List<int> keys, ref NativeList<JobHandle> values)
    285.         {
    286.             int index = keys.IndexOf(item);
    287.             if (index == -1)
    288.             {
    289.                 index = keys.Count;
    290.                 keys.Add(item);
    291.                 values.Add(default);
    292.             }
    293.  
    294.             return index;
    295.         }
    296.     }
    297. }
    298.  

    So in my example BrainChunk has either the value 1 or 2. The value 1 represent the DecisionMaker1 and the value 2 represent DecisionMaker1 and DecisionMaker2. I keep in memory in brainChunkDecisionMaker where every DecisionMaker1 is and in what chunk, and sefilter by this value. Maybe you see now my comparison with my first issue. It give me the same error, because even if I am on different chunk I cannot access the same NativeArray for the same job

    2. So my question is, is it normal behaviour or a bug ? And if normal, what should I do ? My next idea was to create my own chunk or get my chunks from my EntityQuery. Else I thought maybe to read a dll that my tool create which have a attribute custom for each DecisionMaker and bind to a unique index. It would need to be recompile because type need to be know at compilation. It could be an Ok solution for my designer but not for my player.

    3. Another question in the same context. This system need to run in another World to keep my fps stable. I found the EntityManager.MoveEntitiesFrom to move the data of All my agents on another EntityManager. One thing I want to do in my system is to keep a cache of my precedent consideration score. So every update, I want to sync my entity from one world to the other. Is it possible or a good idea ?

    4. Last question, I keep my raycast in a DynamicBuffer to reuse for other consideration, so I create my own collector to add the value directly in my DynamicBuffer. Because the DynamicBuffer has a internal max buffer I would like to stop the query when the DynamicBuffer is full. I saw that in an ICollector.Add you need to return a bool. If I return false, does the collector stop its query and if not is there another way to create this behavior ?

    Thank you.
     
    Last edited: Jul 10, 2019
    siggigg and florianhanke like this.
  2. Dnalsi

    Dnalsi

    Joined:
    Feb 11, 2014
    Posts:
    9
    Another solution would be to manage all the score outside EntityManager. With that no more problem with job Schedule and Sync value. I would need to find a way to align EntityManager chunks and my score chunks and process both at same time in an IJobParallelFor

    Still curious about why I can't query 2 jobs when I filter them and process them in 2 separate chunks.
     
    Last edited: Jul 12, 2019
  3. Dnalsi

    Dnalsi

    Joined:
    Feb 11, 2014
    Posts:
    9
    Hey ! I made some progress and thought some people could be interested.

    First thing first, I did some performance tests that schedule 2 jobs which write to 2 different part of an array. One test has one jobs depends on the other and another test has no dependency. The result is that both go at approximately the same speed. My theory is that the Job Scheduling system can see when there is no collision and schedule accordingly. At least, I think...

    Anyway, I wandered from my initial solution. My initial solution was to schedule every considerations and removing entity with IJobParallelForFilter. At first, I chose this solution because I wanted to minimize my cache miss, but after some thinking I realize it was not a good solution. Using SharedComponent and IJobParallelForFilter for my jobs would create small chunk of entity, so each jobs would work with 10 entities. So overall it was a lot of work on the mainthread for not a lot of performance gain. Anyway, after some research and rereading this post, I realize what people was proposing. Creating a big job that process all entities, one at a time. It was a lot easier to write because it was just following the implementation of Dave in a job. This is my implementation of Utility AI using ECS:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Mathematics;
    7. using Unity.Physics;
    8. using Unity.Physics.Systems;
    9. using Unity.Transforms;
    10. using VoxelWar.AI.Navigation;
    11. using VoxelWar.Generic;
    12. using VoxelWar.Helper;
    13.  
    14. namespace VoxelWar.AI.Behaviour
    15. {
    16.     readonly struct ActorContext
    17.     {
    18.         public readonly Entity Entity;
    19.         public readonly Entity MyTarget;
    20.         public readonly float3 Position;
    21.         public readonly ActorDecision LastDecision;
    22.  
    23.         public ActorContext(Entity entity, Entity myTarget, float3 position, ActorDecision decision)
    24.         {
    25.             Entity = entity;
    26.             MyTarget = myTarget;
    27.             Position = position;
    28.             LastDecision = decision;
    29.         }
    30.     }
    31.  
    32.     struct BrainContext
    33.     {
    34.         public int Decision;
    35.  
    36.         public float Self;
    37.         public float Factor;
    38.  
    39.         public NativeList<float> Targets;
    40.         public NativeList<Entity> Identities;
    41.         public NativeList<DistanceHit> Hits;
    42.     }
    43.  
    44.     public class AIBehaviourSystem : JobComponentSystem
    45.     {
    46.         [BurstCompile]
    47.         struct ActorBrainScoreJob : IJobForEachWithEntity_EBCCC<ActorBrain, Translation, ActorDecision, ActorTarget>
    48.         {
    49.             [ReadOnly] public CollisionWorld CollisionWorld;
    50.  
    51.             [ReadOnly] public NativeArray<DecisionEvaluator> Evaluators;
    52.             [ReadOnly] public NativeArray<Consideration> Considerations;
    53.  
    54.             [ReadOnly] public ComponentDataFromEntity<Health> Healths;
    55.             [ReadOnly] public ComponentDataFromEntity<Translation> Positions;
    56.  
    57.             const float momentum = 0.04f;
    58.  
    59.             public void Execute(Entity entity, int index, DynamicBuffer<ActorBrain> decisions,
    60.                 [ReadOnly] ref Translation myPosition, ref ActorDecision myDecision, [ReadOnly] ref ActorTarget myTarget)
    61.             {
    62.                 var actor = new ActorContext(entity, myTarget.Value, myPosition.Value, myDecision);
    63.  
    64.                 var brain = new BrainContext
    65.                 {
    66.                     Targets = new NativeList<float>(Allocator.Temp),
    67.                     Identities = new NativeList<Entity>(Allocator.Temp),
    68.                     Hits = new NativeList<DistanceHit>(Allocator.Temp)
    69.                 };
    70.  
    71.                 var highest = new ActorDecision
    72.                 {
    73.                     Decision = -1,
    74.                     Score = float.MinValue,
    75.                     Target = Entity.Null
    76.                 };
    77.  
    78.                 for (int dec = 0; dec < decisions.Length; dec++)
    79.                 {
    80.                     brain.Decision = decisions[dec].Decision;
    81.  
    82.                     var evaluator = Evaluators[brain.Decision];
    83.                     if (evaluator.Weight < highest.Score)
    84.                         break;
    85.  
    86.                     if (evaluator.Context == EvaluatorContext.Self)
    87.                     {
    88.                         EvaluateSelfDecision(evaluator, actor, ref highest, ref brain);
    89.                     }
    90.                     else
    91.                     {
    92.                         EvaluateMultipleDecision(evaluator, actor, ref highest, ref brain);
    93.                     }
    94.                 }
    95.  
    96.                 brain.Targets.Dispose();
    97.                 brain.Identities.Dispose();
    98.                 brain.Hits.Dispose();
    99.  
    100.                 myDecision = highest;
    101.             }
    102.  
    103.             private void EvaluateSelfDecision(in DecisionEvaluator evaluator, in ActorContext actor, ref ActorDecision highest, ref BrainContext brain)
    104.             {
    105.                 var considerations = evaluator.Considerations;
    106.  
    107.                 brain.Self = evaluator.Weight + math.select(0, momentum, brain.Decision == actor.LastDecision.Decision) * evaluator.Weight;
    108.                 brain.Factor = 1f - (1f / considerations.Length);
    109.  
    110.                 for (int c = 0; c < considerations.Length && brain.Self > highest.Score; c++)
    111.                 {
    112.                     ScoreConsideration(Considerations[considerations.Start + c], actor, ref brain);
    113.                 }
    114.  
    115.                 bool newHighest = brain.Self > highest.Score;
    116.                 highest.Score = math.select(highest.Score, brain.Self, newHighest);
    117.                 highest.Decision = math.select(highest.Decision, brain.Decision, newHighest);
    118.                 highest.Target = newHighest ? Entity.Null : highest.Target;
    119.             }
    120.  
    121.             private void EvaluateMultipleDecision(in DecisionEvaluator evaluator, in ActorContext actor, ref ActorDecision highest, ref BrainContext brain)
    122.             {
    123.                 FilterLayer layer = FilterLayer.Default;
    124.                 switch (evaluator.Context)
    125.                 {
    126.                     case EvaluatorContext.Allies: layer = FilterLayer.Allies; break;
    127.                     case EvaluatorContext.Enemies: layer = FilterLayer.Enemies; break;
    128.                 }
    129.  
    130.                 CollisionWorld.CalculateDistance(new PointDistanceInput
    131.                 {
    132.                     Position = actor.Position,
    133.                     MaxDistance = 50f,
    134.                     Filter = CollisionFilterUtils.Interact(layer)
    135.                 }, ref brain.Hits);
    136.  
    137.                 if (brain.Hits.Length != 0)
    138.                 {
    139.                     for (int i = 0; i < brain.Hits.Length; i++)
    140.                     {
    141.                         var entity = CollisionWorld.Bodies[brain.Hits[i].RigidBodyIndex].Entity;
    142.                         brain.Targets.Add(1f + math.select(0, momentum, brain.Decision == actor.LastDecision.Decision && actor.LastDecision.Target == entity));
    143.                         brain.Identities.Add(entity);
    144.                     }
    145.  
    146.                     var considerations = evaluator.Considerations;
    147.  
    148.                     brain.Self = evaluator.Weight + math.select(0, momentum, brain.Decision == actor.LastDecision.Decision);
    149.                     brain.Factor = 1f - (1f / considerations.Length);
    150.  
    151.                     for (int c = 0; c < considerations.Length && brain.Targets.Length != 0; c++)
    152.                     {
    153.                         ScoreConsideration(Considerations[considerations.Start + c], actor, ref brain);
    154.  
    155.                         RemoveLowScoreTargets(highest.Score, ref brain);
    156.                     }
    157.  
    158.                     for (int i = 0; i < brain.Targets.Length; i++)
    159.                     {
    160.                         float score = brain.Self * brain.Targets[i];
    161.                         bool newHighest = score > highest.Score;
    162.                         highest.Score = math.select(highest.Score, score, newHighest);
    163.                         highest.Decision = math.select(highest.Decision, brain.Decision, newHighest);
    164.                         highest.Target = newHighest ? brain.Identities[i] : highest.Target;
    165.                     }
    166.  
    167.                     brain.Targets.Clear();
    168.                     brain.Identities.Clear();
    169.                 }
    170.  
    171.                 brain.Hits.Clear();
    172.             }
    173.  
    174.             private void ScoreConsideration(in Consideration consideration, in ActorContext actor, ref BrainContext brain)
    175.             {
    176.                 switch (consideration.Type)
    177.                 {
    178.                     case ConsiderationType.MyHealth:
    179.                         {
    180.                             brain.Self = ScoreHealth(brain.Self, actor.Entity, consideration.Curve, brain.Factor);
    181.                         }
    182.                         break;
    183.                     case ConsiderationType.MyTargetHealth:
    184.                         {
    185.                             brain.Self = ScoreHealth(brain.Self, actor.MyTarget, consideration.Curve, brain.Factor);
    186.                         }
    187.  
    188.                         break;
    189.                     case ConsiderationType.HealthOf:
    190.                         {
    191.                             for (int i = 0; i < brain.Targets.Length; i++)
    192.                             {
    193.                                 brain.Targets[i] = ScoreHealth(brain.Targets[i], brain.Identities[i], consideration.Curve, brain.Factor);
    194.                             }
    195.                         }
    196.  
    197.                         break;
    198.                     case ConsiderationType.MyDistanceToTarget:
    199.                         {
    200.                             var range = consideration.Params.ToRange();
    201.  
    202.                             for (int i = 0; i < brain.Targets.Length; i++)
    203.                             {
    204.                                 float3 targetPos = CollisionWorld.Bodies[brain.Hits[i].RigidBodyIndex].WorldFromBody.pos;
    205.                                 float input = (math.distance(actor.Position, targetPos) - range.Min) / range.Distance;
    206.  
    207.                                 brain.Targets[i] = Compensate(brain.Targets[i] * CurveSolver.Solve(input, consideration.Curve), ref brain.Factor);
    208.                             }
    209.                         }
    210.  
    211.                         break;
    212.                     case ConsiderationType.MyDistanceToMyTarget:
    213.                         {
    214.                             var range = consideration.Params.ToRange();
    215.                             float input = (math.distance(actor.Position, Positions[actor.MyTarget].Value) - range.Min) / range.Distance;
    216.  
    217.                             brain.Self = Compensate(brain.Self * CurveSolver.Solve(input, consideration.Curve), ref brain.Factor);
    218.                         }
    219.  
    220.                         break;
    221.                     case ConsiderationType.MyDistanceToFixTarget:
    222.                         {
    223.                             var target = consideration.Params.ToTarget();
    224.                             var range = consideration.Params.ToRange();
    225.                             float input = (math.distance(actor.Position, Positions[target.Value].Value) - range.Min) / range.Distance;
    226.  
    227.                             brain.Self = Compensate(brain.Self * CurveSolver.Solve(input, consideration.Curve), ref brain.Factor);
    228.                         }
    229.  
    230.                         break;
    231.                     case ConsiderationType.DistanceToMyTarget:
    232.                         {
    233.                             var range = consideration.Params.ToRange();
    234.  
    235.                             for (int i = 0; i < brain.Targets.Length; i++)
    236.                             {
    237.                                 float3 targetPos = CollisionWorld.Bodies[brain.Hits[i].RigidBodyIndex].WorldFromBody.pos;
    238.                                 float input = (math.distance(Positions[actor.MyTarget].Value, targetPos) - range.Min) / range.Distance;
    239.  
    240.                                 brain.Targets[i] = Compensate(brain.Targets[i] * CurveSolver.Solve(input, consideration.Curve), ref brain.Factor);
    241.                             }
    242.                         }
    243.  
    244.                         break;
    245.                     case ConsiderationType.DistanceToFixTarget:
    246.                         {
    247.                             var target = consideration.Params.ToTarget();
    248.                             var range = consideration.Params.ToRange();
    249.  
    250.                             for (int i = 0; i < brain.Targets.Length; i++)
    251.                             {
    252.                                 float3 targetPos = CollisionWorld.Bodies[brain.Hits[i].RigidBodyIndex].WorldFromBody.pos;
    253.                                 float input = (math.distance(actor.Position, targetPos) - range.Min) / range.Distance;
    254.  
    255.                                 brain.Targets[i] = Compensate(brain.Targets[i] * CurveSolver.Solve(input, consideration.Curve), ref brain.Factor);
    256.                             }
    257.                         }
    258.  
    259.                         break;
    260.                 }
    261.             }
    262.  
    263.             private float ScoreHealth(float score, Entity target, in Curve curve, float factor)
    264.             {
    265.                 if (!Healths.Exists(target))
    266.                     return 0;
    267.                  
    268.                 var health = Healths[target];
    269.                 float input = health.Value / health.Max;
    270.  
    271.                 return Compensate(score * CurveSolver.Solve(input, curve), ref factor);
    272.             }
    273.  
    274.             private static void RemoveLowScoreTargets(float highestScore, ref BrainContext brain)
    275.             {
    276.                 int i = 0;
    277.                 //Remove any target who are under the highest decision score
    278.                 while (i < brain.Targets.Length)
    279.                 {
    280.                     if (brain.Targets[i] * brain.Self < highestScore)
    281.                     {
    282.                         brain.Targets.RemoveAtSwapBack(i);
    283.                         brain.Hits.RemoveAtSwapBack(i);
    284.                         brain.Identities.RemoveAtSwapBack(i);
    285.                     }
    286.                     else
    287.                     {
    288.                         i++;
    289.                     }
    290.                 }
    291.             }
    292.  
    293.             private static float Compensate(float score, ref float factor)
    294.             {
    295.                 return score + (((1.0f - score) * factor) * score);
    296.             }
    297.         }
    298.  
    299.         struct ActorBrainResponseJob : IJobForEachWithEntity<ActorDecision, Translation>
    300.         {
    301.             public EntityCommandBuffer.Concurrent CommandBuffer;
    302.  
    303.             [ReadOnly] public NativeArray<Decision> Responses;
    304.  
    305.             [ReadOnly] public ComponentDataFromEntity<Health> Healths;
    306.             [ReadOnly] public ComponentDataFromEntity<Translation> Positions;
    307.  
    308.             public void Execute(Entity entity, int index, [ReadOnly] ref ActorDecision brain, [ReadOnly] ref Translation myPosition)
    309.             {
    310.                 var response = Responses[brain.Decision];
    311.  
    312.                 switch (response.Type)
    313.                 {
    314.                     case DecisionType.StayInPlace:
    315.                         CommandBuffer.RemoveComponent(index, entity, typeof(Moving));
    316.                         break;
    317.                     case DecisionType.MoveToEntity:
    318.                         CommandBuffer.AddComponent(index, entity, new Moving());
    319.                         CommandBuffer.SetComponent(index, entity, new MoveTo { Target = Positions[response.Params.Entity].Value });
    320.                         break;
    321.                     case DecisionType.FleeToSafeSpot:
    322.                         float3 heading = Positions[brain.Target].Value - myPosition.Value;
    323.                         heading.y = 0;
    324.  
    325.                         float3 target = myPosition.Value + -heading * 5f;
    326.                         CommandBuffer.AddComponent(index, entity, new Moving());
    327.                         CommandBuffer.SetComponent(index, entity, new MoveTo { Target = target });
    328.                         break;
    329.                     case DecisionType.FollowEntity:
    330.                         CommandBuffer.AddComponent(index, entity, new Moving());
    331.                         CommandBuffer.SetComponent(index, entity, new MoveTo { Target = Positions[brain.Target].Value });
    332.                         break;
    333.                 }
    334.             }
    335.         }
    336.  
    337.         public EvaluatorBacklog Backlog;
    338.  
    339.         public EndInitializationEntityCommandBufferSystem EndInitializationSystem;
    340.  
    341.         public BuildPhysicsWorld PhysicSystem;
    342.  
    343.         protected override void OnCreate()
    344.         {
    345.             PhysicSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
    346.  
    347.             EndInitializationSystem = World.GetOrCreateSystem<EndInitializationEntityCommandBufferSystem>();
    348.         }
    349.  
    350.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    351.         {
    352.             var healthsFromEntity = GetComponentDataFromEntity<Health>(true);
    353.             var positionsFromEntity = GetComponentDataFromEntity<Translation>(true);
    354.  
    355.             inputDeps = new ActorBrainScoreJob
    356.             {
    357.                 CollisionWorld = PhysicSystem.PhysicsWorld.CollisionWorld,
    358.              
    359.                 Evaluators = Backlog.Evaluators,
    360.                 Considerations = Backlog.Considerations,
    361.  
    362.                 Healths = healthsFromEntity,
    363.                 Positions = positionsFromEntity
    364.             }.Schedule(this, inputDeps);
    365.  
    366.             inputDeps = new ActorBrainResponseJob
    367.             {
    368.                 CommandBuffer = EndInitializationSystem.CreateCommandBuffer().ToConcurrent(),
    369.  
    370.                 Responses = Backlog.Responses,
    371.  
    372.                 Healths = healthsFromEntity,
    373.                 Positions = positionsFromEntity
    374.             }.Schedule(this, inputDeps);
    375.  
    376.             EndInitializationSystem.AddJobHandleForProducer(inputDeps);
    377.  
    378.             return inputDeps;
    379.         }
    380.  
    381.         protected override void OnDestroy()
    382.         {
    383.             Backlog.Dispose();
    384.         }
    385.  
    386.         public void SetEvaluator(List<DecisionEvaluator> evaluators, List<Decision> responses, List<Consideration> considerations)
    387.         {
    388.             Backlog = new EvaluatorBacklog
    389.             {
    390.                 Evaluators = new NativeArray<DecisionEvaluator>(evaluators.ToArray(), Allocator.Persistent),
    391.                 Considerations = new NativeArray<Consideration>(considerations.ToArray(), Allocator.Persistent),
    392.                 Responses = new NativeArray<Decision>(responses.ToArray(), Allocator.Persistent)
    393.             };
    394.         }
    395.     }
    396.  
    397.  
    398. }

    I made a demo to showcase the system



    A list of thing I want to change and add right now are:
    -Remove all NativeList allocation in job
    -Cache the raycast between entity
    -Add a Influence Map

    Feedback are welcome and if you made your own Utility system, I would like to know what you did
     
    Last edited: Aug 9, 2019
    florianhanke likes this.