Search Unity

Systems not respecting update order

Discussion in 'Entity Component System' started by Guedez, Apr 16, 2019.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    This is my system group order declarations:
    Code (CSharp):
    1. public class GroupEffectInitializeAverages {}
    2. [UpdateAfter(typeof(GroupEffectInitializeAverages))]
    3. public class GroupEffectTakeBeforeAverage {}
    4. [UpdateAfter(typeof(GroupEffectTakeBeforeAverage))]
    5. public class GroupEffectMultiplyTargets {}
    6. [UpdateAfter(typeof(GroupEffectMultiplyTargets))]
    7. public class GroupEffectCountMatches {}
    8. [UpdateAfter(typeof(GroupEffectCountMatches))]
    9. public class GroupEffectApplyEffects {}
    10. [UpdateAfter(typeof(GroupEffectApplyEffects))]
    11. public class GroupEffectTakeAfterAverage {}
    12. [UpdateAfter(typeof(GroupEffectTakeAfterAverage))]
    13. public class GroupEffectApplyAverageDifference {}
    This is the console:
    Code (CSharp):
    1. Create Averager entities: 221
    2. Run bonus: Entity Index: 337 Version: 2, 221
    3. Run average: Entity Index: 340 Version: 1, 221
    4. Run average: Entity Index: 341 Version: 1, 221
    5. Run measure total: Entity Index: 4 Version: 2, 221
    These are the systems that should be running in order (only the last one posted here is running in the wrong order, it should be going in between the before and after calculate average systems, it is running as the second system). The QuickSystem classes are from here: forum.unity.com/threads/easy-362-systems-for-lazy-people.643021

    Code (CSharp):
    1. [UpdateInGroup(typeof(GroupEffectInitializeAverages))]
    2. public class RecalculateResultValues : QuickSystemFilter1Shared<EffectsEffectActivated, FinalItemReference> {
    3.     protected override void OnUpdate(FinalItemReference s) {
    4.         Debug.Log("Create Averager entities: " + Time.frameCount);
    5.         Entity en = PostUpdateCommands.CreateEntity();
    6.         PostUpdateCommands.AddComponent<NutrientProcessor.Nutrients>(en, default);
    7.         PostUpdateCommands.AddComponent<TasteProcessor.Tastes>(en, default);
    8.         PostUpdateCommands.AddComponent<AromaProcessor.Aroma>(en, default);
    9.         PostUpdateCommands.AddComponent<CompositionProcessor.Composition>(en, default);
    10.         PostUpdateCommands.AddComponent<CookPropertiesProcessor.CookProperties>(en, default);
    11.         PostUpdateCommands.AddComponent<ItemInstance>(en, default);
    12.         Entity en2 = PostUpdateCommands.Instantiate(en);
    13.         PostUpdateCommands.AddComponent<CalculateBeforeAverage>(en, default);
    14.         PostUpdateCommands.AddComponent<CalculateAfterAverage>(en2, default);
    15.  
    16.         PostUpdateCommands.AddComponent(en, new ResultRefence { en = en });
    17.         PostUpdateCommands.AddComponent(en2, new ResultRefence { en = en });
    18.  
    19.         PostUpdateCommands.AddComponent<PendingAverageRecalculation>(s.result, default);
    20.         PostUpdateCommands.AddComponent(s.result, new PreviousAverage { en = en });
    21.         PostUpdateCommands.AddComponent(s.result, new CurrentAverage { en = en2 });
    22.     }
    23. }
    Code (CSharp):
    1. public abstract class CalculateAverageBaseSystem<T, E, S> : QuickSystemFilter1Entity<T, E, S> where E : struct, IComponentData where S : struct, IComponentData {
    2.     protected ComponentGroup helperGroup;
    3.     protected IAccessor<float>[] toAverage;
    4.     protected MethodInfo[] methodsGet;
    5.     protected MethodInfo[] methodsSet;
    6.     protected override void OnCreateManager() {
    7.         base.OnCreateManager();
    8.         helperGroup = GetComponentGroup(typeof(FinalItemReference), typeof(CookingCookStatus));
    9.         toAverage = new IAccessor<float>[]  {
    10.             new NutrientProcessor.Nutrients(),
    11.             new TasteProcessor.Tastes(),
    12.             new CookPropertiesProcessor.CookProperties(),
    13.             new CompositionProcessor.Composition(),
    14.             new AromaProcessor.Aroma(),
    15.             new ItemInstance()
    16.         };
    17.         MethodInfo methodInfoGet = EntityManager.GetType().GetMethod("GetComponentData");
    18.         MethodInfo methodInfoSet = EntityManager.GetType().GetMethod("SetComponentData");
    19.         methodsGet = new MethodInfo[toAverage.Length];
    20.         methodsSet = new MethodInfo[toAverage.Length];
    21.         for (int i = 0; i < toAverage.Length; i++) {
    22.             IAccessor<float> acc = toAverage[i];
    23.             methodsGet[i] = methodInfoGet.MakeGenericMethod(acc.GetType());
    24.             methodsSet[i] = methodInfoSet.MakeGenericMethod(acc.GetType());
    25.             //methods[i].Invoke(em, new object[] { result, acc });
    26.         }
    27.     }
    28. }
    29.  
    30. public class CalculateAverageSystem<T> : CalculateAverageBaseSystem<T, ResultRefence, ItemInstance> {
    31.     protected override void OnUpdate(Entity self, ref ResultRefence s, ref ItemInstance instance) {
    32.         Utils.BreakPoint();
    33.         Debug.Log("Run average: " + self+", "+ Time.frameCount);
    34.         helperGroup.SetFilter(new FinalItemReference() { result = s.en });
    35.         object[] argsGet = new object[1];
    36.         object[] argsSet = new object[2];
    37.         float total = 0;
    38.         for (int i = 0; i < toAverage.Length; i++) {
    39.             IAccessor<float> acc = toAverage[i];
    40.             for (int j = 0; j < acc.Length; j++) {
    41.                 acc[j] = 0;
    42.             }
    43.         }
    44.         ForEach((Entity e, ref ItemInstance inst) => {
    45.             argsGet[0] = e;
    46.             argsSet[0] = e;
    47.             total += inst.Amount;
    48.             for (int i = 0; i < toAverage.Length; i++) {
    49.                 IAccessor<float> acc = toAverage[i];
    50.                 IAccessor<float> fromEnt = (IAccessor<float>)methodsGet[i].Invoke(EntityManager, argsGet);
    51.                 for (int j = 0; j < acc.Length; j++) {
    52.                     acc[j] += fromEnt[j];
    53.                 }
    54.             }
    55.         }, helperGroup);
    56.         for (int i = 0; i < toAverage.Length; i++) {
    57.             IAccessor<float> acc = toAverage[i];
    58.             for (int j = 0; j < acc.Length; j++) {
    59.                 acc[j] /= total;
    60.             }
    61.             argsSet[0] = self;
    62.             argsSet[1] = acc;
    63.             methodsSet[i].Invoke(EntityManager, argsSet);
    64.         }
    65.         instance.Quality = total;
    66.     }
    67.  
    68. }
    69.  
    70. [UpdateInGroup(typeof(GroupEffectTakeBeforeAverage))]
    71. public class CalculateBeforeAverageSystem : CalculateAverageSystem<CalculateBeforeAverage> {
    72. }
    73. [UpdateInGroup(typeof(GroupEffectTakeAfterAverage))]
    74. public class CalculateAfterAverageSystem : CalculateAverageSystem<CalculateAfterAverage> {
    75. }
    Code (CSharp):
    1. [UpdateInGroup(typeof(GroupEffectApplyAverageDifference))]
    2. public class CalculateAverageDifferenceSystem : CalculateAverageBaseSystem<PendingAverageRecalculation, PreviousAverage, CurrentAverage> {
    3.     protected override void OnUpdate(Entity self, ref PreviousAverage p, ref CurrentAverage c) {
    4.         object[] argsGet = new object[1];
    5.         object[] argsSet = new object[2];
    6.         Debug.Log("Run measure total: " + self + ", " + Time.frameCount);
    7.         argsSet[0] = self;
    8.         Entity previous = p.en;
    9.         Entity current = c.en;
    10.         for (int i = 0; i < toAverage.Length; i++) {
    11.             argsGet[0] = self;
    12.             IAccessor<float> acc = (IAccessor<float>)methodsGet[i].Invoke(EntityManager, argsGet);
    13.             argsGet[0] = previous;
    14.             IAccessor<float> previousAcc = (IAccessor<float>)methodsGet[i].Invoke(EntityManager, argsGet);
    15.             argsGet[0] = current;
    16.             IAccessor<float> previouscurrent = (IAccessor<float>)methodsGet[i].Invoke(EntityManager, argsGet);
    17.             for (int j = 0; j < acc.Length; j++) {
    18.                 float dif = previouscurrent[j] - previousAcc[j];
    19.                 if (dif > 0.01 || dif < -0.01) {
    20.                     acc[j] += dif;
    21.                     Debug.Log("Changed: " + acc.GetType() + "/" + j + " change: " + dif);
    22.                 }
    23.             }
    24.             argsSet[1] = acc;
    25.             methodsSet[i].Invoke(EntityManager, argsSet);
    26.         }
    27.         PostUpdateCommands.RemoveComponent<PreviousAverage>(self);
    28.         PostUpdateCommands.RemoveComponent<CurrentAverage>(self);
    29.         PostUpdateCommands.RemoveComponent<PendingAverageRecalculation>(self);
    30.         PostUpdateCommands.DestroyEntity(previous);
    31.         PostUpdateCommands.DestroyEntity(current);
    32.     }
    33.  
    34. }
    Code (CSharp):
    1. [UpdateInGroup(typeof(GroupEffectApplyEffects))]
    2. public abstract class TransferValues<V, E, T> : QuickSystemFilter4Entity<EffectsEffectActivated, V, EffectTargetAttribute, E, EffectTarget, EffectCostAndValue> where V : struct, IComponentData where E : struct, IComponentData where T : struct, IComponentData, IAccessor<float> {
    3.  
    4.     public delegate float InLoopAction(float value, ref T accessor, byte b);
    5.  
    6.     protected override void OnUpdate(Entity e, ref EffectTarget target, ref EffectCostAndValue cost) {
    7.         T accessor = EntityManager.GetComponentData<T>(target.entity);
    8.         Excecute(e, cost, ref accessor);
    9.         EntityManager.SetComponentData<T>(target.entity, accessor);
    10.         PostUpdateCommands.DestroyEntity(e);
    11.     }
    12.  
    13.     public abstract void Excecute(Entity e, EffectCostAndValue cost, ref T accessor);
    14.  
    15.     public float DoTransferAvailable(ref T accessor, float available, DynamicBuffer<byte> buffer, InLoopAction newMethod) {
    16.         float total = 0;
    17.         int count = buffer.Length;
    18.         int safeguard = 10;
    19.         do {
    20.             float value = available / count;
    21.             foreach (byte b in buffer.AsNativeArray()) {
    22.                 float val = newMethod(value, ref accessor, b);
    23.                 available -= val;
    24.                 total += val;
    25.             }
    26.         } while (available > 0 && safeguard-- > 0);
    27.         return total;
    28.     }
    29.  
    30.     public float DoTransfer(EffectCostAndValue cost, ref T accessor, DynamicBuffer<byte> buffer, InLoopAction newMethod) {
    31.         float total = 0;
    32.         int count = buffer.Length;
    33.         float value = cost.value;
    34.         foreach (byte b in buffer.AsNativeArray()) {
    35.             float val = newMethod(value, ref accessor, b);
    36.             total += val;
    37.         }
    38.         return total;
    39.     }
    40.  
    41.     public DynamicBuffer<byte> GetBuffer<T>(Entity e) where T : struct, IBufferElementData {
    42.         return EntityManager.GetBuffer<T>(e).Reinterpret<byte>();
    43.     }
    44.  
    45.     public float TransferReduceConstant(float value, ref T accessor, byte b) {
    46.         float v = accessor[b];
    47.         float cval = Mathf.Min(v, value);
    48.         accessor[b] = v - cval;
    49.         return cval;
    50.     }
    51.     public float TransferReducePercent(float value, ref T accessor, byte b) {
    52.         float v = accessor[b];
    53.         float cval = Mathf.Min(v, v * value);
    54.         accessor[b] = v - cval;
    55.         return cval;
    56.     }
    57.     public float TransferIncreaseConstant(float value, ref T accessor, byte b) {
    58.         float v = accessor[b];
    59.         float cval = Mathf.Min(Cookbook.maxValue - v, value);
    60.         accessor[b] = v + cval;
    61.         return cval;
    62.     }
    63.     public float TransferIncreasePercent(float value, ref T accessor, byte b) {
    64.         float v = accessor[b];
    65.         float cval = Mathf.Min(Cookbook.maxValue - v, v * value);
    66.         accessor[b] = v + cval;
    67.         return cval;
    68.     }
    69. }
    70.  
    71. [UpdateInGroup(typeof(GroupEffectApplyEffects))]
    72. public abstract class BonusValuesConstant<E, T> : TransferValues<EffectsBonusConstant, E, T> where E : struct, IComponentData where T : struct, IComponentData, IAccessor<float> {
    73.     public override void Excecute(Entity e, EffectCostAndValue cost, ref T accessor) {
    74.         Debug.Log("Run bonus: " + e + ", " + Time.frameCount);
    75.         DoTransfer(cost, ref accessor, GetBuffer<EffectTargetAttribute>(e), TransferIncreaseConstant);
    76.     }
    77. }
    78.  
    79. [UpdateInGroup(typeof(GroupEffectApplyEffects))]
    80. public class BonusValuesConstantNutrients : BonusValuesConstant<EffectsAffectsNutrients, NutrientProcessor.Nutrients> {
    81. }
    82. [UpdateInGroup(typeof(GroupEffectApplyEffects))]
    83. public class BonusValuesConstantTastes : BonusValuesConstant<EffectsAffectsTastes, TasteProcessor.Tastes> {
    84. }
    85. [UpdateInGroup(typeof(GroupEffectApplyEffects))]
    86. public class BonusValuesConstantAroma : BonusValuesConstant<EffectsAffectsAroma, AromaProcessor.Aroma> {
    87. }
    88. [UpdateInGroup(typeof(GroupEffectApplyEffects))]
    89. public class BonusValuesConstantComposition : BonusValuesConstant<EffectsAffectsComposition, CompositionProcessor.Composition> {
    90. }
    91. [UpdateInGroup(typeof(GroupEffectApplyEffects))]
    92. public class BonusValuesConstantCookProperties : BonusValuesConstant<EffectsAffectsCookProperties, CookPropertiesProcessor.CookProperties> {
    93. }
    94. [UpdateInGroup(typeof(GroupEffectApplyEffects))]
    95. public class BonusValuesConstantQuality : BonusValuesConstant<EffectsAffectsQuality, ItemInstance> {
    96. }
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    They respecting update order. UpdateAfter not mean “This system/group run after this Immediately” it runs after, but it can run everywhere after, even in end of frame (if no other dependencies which can affect on order of this group) in your case you should put this two group inside other group which runs after TakeBeforeAfter, and change EffectCountMatches order to this group, or you can use UpdateBefore in addition with update after for correction of order.
     
  3. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    They are not. A member of GroupEffectApplyEffects (BonusValuesConstantNutrients("Run bonus" Debug.log)) updated before a member of GroupEffectTakeBeforeAverage (CalculateBeforeAverageSystem("Run average" debug.log))
    According to the order declaration, that should be impossible, since they can only run after GroupEffectCountMatches which can only run after GroupEffectMultiplyTargets which can only run after GroupEffectTakeBeforeAverage
    If A > B > C > D then A > D, it can't be any other way, they are indeed ignoring the run order.

    Could this be happening because no systems ran in GroupEffectCountMatches or GroupEffectMultiplyTargets?
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Exactly.

    (A > B) (B > C) (C > D) then A > D is only true if A B C D all exist

    if C doesn't exist then it's just

    A > B, D

    D can execute anywhere. You have not specified that D has any dependency on A or B, only C.
     
  5. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I’ve run into this same confusion, which took quite a while to figure out.

    There’s currently no way to know if a given system didn’t run on a given frame, unless you have an idea of which system, and you set a breakpoint. So debugging issues with system order can be quite confusing.

    I think tertle’s comment highlights this- people are using different words to describe the states of a system. When does it “exist”, vs. “run”, vs. “update”, vs. “check to see if it should update”? Which of those affect the “UpdateAfter” attributes?

    Those are all answerable questions, but right now it involves digging through the source. It’s not as intuitive as it hopefully will be later.

    I’ve defaulted to making Groups for everything.

    Or better yet, just call system updates explicitly (don’t forget the ShouldUpdate checks).
     
    Last edited: Apr 18, 2019
  6. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Now I looking from PC, do you edited you OP message? Coz when I looked from mobile I saw:
    Code (CSharp):
    1. public class GroupEffectInitializeAverages {}
    2. [UpdateAfter(typeof(GroupEffectInitializeAverages))]
    3. public class GroupEffectTakeBeforeAverage {}
    4. [UpdateAfter(typeof(GroupEffectTakeBeforeAverage))]
    5. public class GroupEffectMultiplyTargets {}
    6. [UpdateAfter(typeof(GroupEffectMultiplyTargets))]
    7. public class GroupEffectCountMatches {}
    8. [UpdateAfter(typeof(GroupEffectCountMatches))]
    9. public class GroupEffectApplyEffects {}
    10. [UpdateAfter(typeof(GroupEffectApplyEffects))]
    11. public class GroupEffectTakeAfterAverage {}
    12. [UpdateAfter(typeof(GroupEffectTakeBeforeAverage))] //<----
    13. public class GroupEffectApplyAverageDifference {}
    And this is why I told about two groups merging and changing order of GroupEffectCountMatches, coz I think you have two parallel groups. :) But maybe I just misslook some thing, cause it's without any spaces and very uncomfortable to read from mobile (and from PC too).
     
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I guess it makes as much sense as it does not at all. At least I know how to fix it now
    Do declaring the groups as BarrierSystems chance anything?