Search Unity

UpdateAfter only if the previous system've been ran

Discussion in 'Data Oriented Technology Stack' started by felipin, Nov 27, 2019.

  1. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    40
    Is there some way to do a system only after other system running?
     
  2. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    204
    Labeling the second system with [UpdateAfter(typeof(FirstSystem))] makes it run after the first system has been *updated*, but that's probably not what you are looking for. If you want the second system to run after the first system has *completed*, have the first system store the final JobHandle in a public field called FinalJobHandle, and in the second system, make sure to combine the FinalJobHandle with inputDeps. Make sure to have the UpdateAfter label as well. Now the first system will update AND complete before the second system is ran
     
  3. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    553
    That is not how the systems should run.
    Data should drive systems lifetime.
    What you want to do is make previous system to produce the data for the next system.
     
    Vacummus likes this.
  4. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    40
    My project has spell entites that can affect other entities, for each component,
    SpellComponent
    , that should be able to affect another component,
    TargetComponent
    , should exists one system
    DirectSpellSystem<TSpellComponent, TTargetComponent>
    , this system is resposible for cast and break spells and it runs only if a spell entity have been created (casted) or destroyed (broken) and I'd like to implement a system that updates a second component of a entity when a spell affects one of its component, this is what I got:

    Code (CSharp):
    1. [GenerateAuthoringComponent]
    2. public struct MovementSpeedBonus : ISpell<MovementSpeedBonus>
    3. {
    4.    public int Add;
    5.    public int Multiply;
    6.  
    7.    public void Break(ref MovementSpeedBonus Target)
    8.    {
    9.       Target.Add -= Add;
    10.       Target.Multiply -= Multiply;
    11.    }
    12.  
    13.    public void Cast(ref MovementSpeedBonus Target)
    14.    {
    15.       Target.Add += Add;
    16.       Target.Multiply += Multiply;
    17.    }
    18. }
    19.  
    20. [GenerateAuthoringComponent]
    21. public struct MovementSpeed : IComponentData
    22. {
    23.    public float Current;
    24.    public float Base;
    25. }
    26.  
    27.  
    28. [UpdateAfter(typeof(MovementSpeedBonusSpellSystem))]
    29. public class MovementSpeedSystem : JobComponentSystem
    30. {
    31.    protected override JobHandle OnUpdate(JobHandle InputDeps)
    32.    {
    33.       return Entities
    34.          .WithBurst()
    35.          .WithChangeFilter<MovementSpeedBonus>()
    36.          .ForEach((ref MovementSpeed MovementSpeed, ref MovementSpeedBonus MovementSpeedBonus) =>
    37.          {
    38.             MovementSpeed.Current = (MovementSpeed.Base * (1 + MovementSpeedBonus.Multiply / 100f)) + MovementSpeedBonus.Add / 100f;
    39.          }).Schedule(InputDeps);
    40.    }
    41. }
    42.  
    43. public class MovementSpeedBonusSpellSystem : DirectSpellSystem<MovementSpeedBonus, MovementSpeedBonus> { }
    But as the project will have a bunch systems like this, it's not that good to have these always running, when there's no need. That's why Im looking for a way to do it.
     
  5. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    553
    These systems only run if there is data for them to run on. On top of that you can take advantage of the [ChangedFilter] attribute to filter out unchanged chunks.
     
  6. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    40
    Yep, but they run when not needed, MovementSpeedSystem will always run (because the main character in the game will have MovementSpeed and MovementSpeedBonus), in a scenario where exists 10 systems like this and their queries are valid, it can means unnecessary 0.50-0.40ms, so if there's a way to avoid it, then it'll be great
     
  7. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    127
    Sounds like what you are looking for here is the Event Tag Pattern. A Event Tag component is a component that only lives for one update cycle. Your MoveSpeedBonus component should be an event tag component that gets added by your DirectSpellSystem when it's time to cast or break. Then you'll need a new system (maybe called CleanUpMoveSpeedBonusSystem) that removes the MoveSpeedBonus component from the entity. Finally, set your MovementSpeedSystem to run before CleanUpMoveSpeedBonusSystem so it performs its operation before the component is removed. This will insure that your MovementSpeedSystem only runs when a spell has been cast.

    Also, I would challenge you to avoid using inheritance, like the way you are using DirectSpellSystem. Not sure what problem you are trying solve with inheritance, but inheritance is one of the tightest forms of coupling, and you'd be better off avoiding it, unless you enjoy playing with concrete (which is what your code will feel like after using inheritance). I would encourage you to look into more Data Oriented ways solve the problem you are trying to solve with OOP.
     
    Last edited: Nov 27, 2019
    SenseEater likes this.
  8. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    40
    Tag component wouldn't be a great choice in the scenario I wish to use it, I'm looking for performance, a scenario where characters can be complex entites (too many data components, but enough to support ~30 entities per chunk) and let's assume a range spell caster that casts a spell to every single enemy entity in a specific area and there's a area that contains 200 enemies, spells can affect more than one component, so changing the archetype of 200 complex enemies 2-4 times at one frame would be worse than my current concept, where only spell entites change their archetypes what is not a big deal because they're simple entities that have at most 8 data components.

    And MovementSpeedBonus cannot be a tag, because it's the sum of all active bonus the entity received, spells can be used by anything (items, power-ups, abilities, etc.) e.g. when you buy a item, it creates a spell that gives you attributes e.g. %10 Movement Speed or +5 Movement Speed and if a spell was casted on a entity it must be able to be broken later (beacuse the ability expired or a item was sold). That's why it have to have a component like MovementSpeedBonus, because if we evalutated MovementSpeed directly, it will impossible to broke it because there's no way to know how a spell affected it in a set of spell with unordered casting/breaking events.

    UPDATE: I found a solution, instead of evaluate it later in another system, I'm evaluating it while casting/breaking. I dont like these methods inside IComponentData, but burst can inline they and as far as I know burst cannot do it with FunctionPointer

    Code (CSharp):
    1. [GenerateAuthoringComponent]
    2. public struct MovementSpeed : IComponentData
    3. {
    4.    public float Current;
    5.    public float Base;
    6.    public int AddBonus;
    7.    public int MultiplyBonus;
    8.  
    9.    public void Evaluate()
    10.    {
    11.       Current = (Base * (1 + MultiplyBonus / 100f) + AddBonus / 100f);
    12.    }
    13. }
    14.  
    15. public class MovementSpeedBonusSpellSystem : DirectSpellSystem<MovementSpeedBonus, MovementSpeed> { }
    16.  
    17. [GenerateAuthoringComponent]
    18. public struct MovementSpeedBonus : ISpell<MovementSpeed>
    19. {
    20.    public int Add;
    21.    public int Multiply;
    22.  
    23.    public void Break(ref MovementSpeed Target)
    24.    {
    25.       Target.AddBonus -= Add;
    26.       Target.MultiplyBonus -= Multiply;
    27.       Target.Evaluate();
    28.    }
    29.  
    30.    public void Cast(ref MovementSpeed Target)
    31.    {
    32.       Target.AddBonus += Add;
    33.       Target.MultiplyBonus += Multiply;
    34.       Target.Evaluate();
    35.    }
    36. }
    37.  
     
    Last edited: Nov 27, 2019
  9. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    127
    Have you looked into the Entity Event pattern? Instead of having Break and Cast functions inside the MovementSpeedBonus (which is really confusing btw), you could instead create an entity event that holds information about the spell that is being casted (or broken) and on what entity it is being applied on. Something like this:

    Code (CSharp):
    1. public struct CastMovementSpeedBonusEvent: IComponentData {
    2.     public int add, Multiply;
    3.     public Entity target;
    4. }
    Then you can have a system that queries for this and updates the MovementSpeed and MovementSpeedBonus components. After that system has run, you will need a system that destroys this entity event. This would avoid the performance issues with dealing with archetypes changes, and you won't have systems that update unnecessarily (such as the update issues you were having with the MovementSpeedSystem). Plus, it's a pattern that lends to more architecturally sound code.
     
  10. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    40
    how to avoid break/cast methods? I have to use them inside a job and by using interface burst can inline them.

    I'll try to explain how this system is supposed to be used, when you wanna change an attribute from an entity in the game, you have to create a spell entity, this spell entity can be dynamic (that's why I cannot use it as event entity), e.g. a spell from a character's item that intensifies by time, as spell is a entity is possible to create a system that intensifies it by time and add a (DynamicSpell) to the spell entity what will update (break previous value and cast the new one) the spell if its target or value change. Every spell has at least a DirectSpellTarget (Entity) and a SpellComponent (like MovementSpeedBonus).

    It's possible to assign a spell to a item (entity) by using LinkedEntityGroup or sth like this, then when you create(buy) a item the spell can affect its owner, then when the item is destroyed(sell) the spell will be broken. Another feature is use a Lifetime component that destroy the entity after a certain amount of time, so if a spell have to be broken in n seconds, just add a Lifetime component to it. Beside that, as spell is a entity you can destroy it concurrent by using EBC.
    Also the work needed to introduce a new spell is minimal, e.g.:

    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. [assembly: RegisterGenericComponentType(typeof(DirectSpellState<AttackPower, AttackPower>))]
    4.  
    5. [GenerateAuthoringComponent]
    6. public struct AttackPower : ISpell<AttackPower> {
    7.    public int Value;
    8.  
    9.    public void Break(ref AttackPower Target) => Target.Value -= Value;
    10.  
    11.    public void Cast(ref AttackPower Target) => Target.Value += Value;
    12. }
    13.  
    14. public class AttackPowerSpellSystem : DirectSpellSystem<AttackPower, AttackPower> { }
    15.  

    Basically, spell as a persistent entity has a lot of flexibility, right now I'm implementing spells stacking :D
     
  11. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    172
    I’m still confused as to why your system is running when it shouldn’t. Are you saying it’s running even when MovementSpeedBonus has not been changed (or touched by a system which could potentially change it)?
     
  12. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    40
    I'd thought so, until I tested it and figured out that ChangeFilter doesn't work like this, if the query (ignore SetFilter) is valid, then the system is gonna run. That's why I created this thread, beacuse before I thought that changefilter only validates the query when a previous system queried the component in write mode.

    Is this a bug?
     
unityunity