Search Unity

I get the impression that ECS means I have to write way more code to do the same thing?

Discussion in 'Entity Component System' started by Arowx, Jun 7, 2018.

  1. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    I have to break down objects into processes and their data, miniaturising the processes and data needed by them.

    So high level abstractions that can make programming easier and fun e.g. Ork.Attack(Enemy);

    The ECS version (pseudo code):
    1. CombatManager.AddJob(Attack(ref attacker, ref defender));
    2. HealthManager.AddJob(UpdateHealth(ref attacker));
    3. AnimationManager.AddJob(AttackAnim(ref attacker));
    4. HealthManager.AddJob(UpdateHealth(ref defender));
    5. AnimationManager.AddJob(DefenceAnim(ref attacker));
    6. ParticleManger.AddJob(AttackFX(ref attacker));
    7. ParticleManger.AddJob(DefendFX(ref attacker));
    The updside of ECS is once I have a working framework it should be much more portable between projects, which garners the question will Unity have a default set of template ECS systems the way it has API calls and Monobehaviours in the old version?

    PS: How much more code is needed by ECS than Monobehaviours?
     
    TooManySugar and Torietron like this.
  2. Afonso-Lage

    Afonso-Lage

    Joined:
    Jul 8, 2012
    Posts:
    70
    ECS is meant to solve problems that not include write less code, btw it includes reuse code. If you are programing as a hobby (like me), you should stick with whatever is more fun to you.

    But even if you write more? code, you spend more time thinking on how to solve problems on your games mechanis and logic. When you write a system you are already working with any entity that has that component data and you write it just once.

    If you have a CombatSystem and you decided that your Ork's little pet will need to attack, you just add an attack component to it. Doesn't need to implement function, change classes and break all existing code. I think this one of the best part of ECS. Changed your mind and Ork's little demon should not attack anymore? Ok, just remove component or even don't add.
     
  3. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    The thing is this Ork Demon will also need an attack animation, sfx, particle fx, death/damage, animaiton, sfx/fx as well as stats and other information. In the old system most of these things you would build into a couple of components e.g. it could have a combat component to provide it with attack and defence capabilities.

    The old component based system provided larger grained component features just like the ECS system. But the ECS system you will need to add lots of micro systems just to provide the ability to bring in an Ork Demon.

    Lots of the code for these micro systems which are just basically structs that need a System or Manager to process them require a lot more work for the programmer/developer designer.

    For instance how well do these systems scale when you develop your projects complexity. What about chaining ECSJobs into action/reaction event chains that need to run in sequence and can branch e.g. Behaviour trees or Combat outcomes.

    Or quite simply lots of micro systems that need to interact vs fewer objects/components that require a magnitude less interactions.

    For example imagine a 3 element system where the elements all need to interact with each other you have 3 links, at 6 elements you have 6 then you have 15 connections (hint: it's n * (n-1) / 2)

    How many ECS Systems will you need in your game vs How many objects/components.

    For example in spaceship example provided by Unity...

    Wow possible bad example (complexity too low maybe)? 12 scripts for the classic* 13 for the Pure ECS?

    OK what about the code.... Here is the classic shooting code

    Code (CSharp):
    1. // Shooting
    2.             var playerPos = player.GetComponent<Transform2D>().Position;
    3.  
    4.             var state = GetComponent<EnemyShootState>();
    5.  
    6.             state.Cooldown -= Time.deltaTime;
    7.          
    8.             if (state.Cooldown <= 0.0)
    9.             {
    10.                 state.Cooldown = TwoStickBootstrap.Settings.enemyShootRate;
    11.                 var position = GetComponent<Transform2D>().Position;
    12.  
    13.                 ShotSpawnData spawn = new ShotSpawnData()
    14.                 {
    15.                     Position = position,
    16.                     Heading = math.normalize(playerPos - position),
    17.                     Faction = TwoStickBootstrap.Settings.EnemyFaction
    18.                 };
    19.                 ShotSpawnSystem.SpawnShot(spawn);
    20.             }
    Compare that to the new EnemyShoot Code!

    Code (CSharp):
    1. class EnemyShootSystem : JobComponentSystem
    2.     {
    3.         public struct Data
    4.         {
    5.             public int Length;
    6.             [ReadOnly] public ComponentDataArray<Position2D> Position;
    7.             public ComponentDataArray<EnemyShootState> ShootState;
    8.         }
    9.  
    10.         [Inject] private Data m_Data;
    11.  
    12.         public struct PlayerData
    13.         {
    14.             public int Length;
    15.             [ReadOnly] public ComponentDataArray<Position2D> Position;
    16.             [ReadOnly] public ComponentDataArray<PlayerInput> PlayerInput;
    17.         }
    18.  
    19.         [Inject] private PlayerData m_Player;
    20.         [Inject] private ShotSpawnBarrier m_ShotSpawnBarrier;
    21.  
    22.         // [ComputeJobOptimization]
    23.         // This cannot currently be burst compiled because CommandBuffer.SetComponent() accesses a static field.
    24.         struct SpawnEnemyShots : IJob
    25.         {
    26.             public float2 PlayerPos;
    27.             public float DeltaTime;
    28.             public float ShootRate;
    29.             public float ShotTtl;
    30.             public float ShotEnergy;
    31.             public EntityArchetype ShotArchetype;
    32.  
    33.             [ReadOnly] public ComponentDataArray<Position2D> Position;
    34.             public ComponentDataArray<EnemyShootState> ShootState;
    35.  
    36.             public EntityCommandBuffer CommandBuffer;
    37.  
    38.             public void Execute()
    39.             {
    40.                 for (int i = 0; i < ShootState.Length; ++i)
    41.                 {
    42.                     var state = ShootState[i];
    43.  
    44.                     state.Cooldown -= DeltaTime;
    45.                     if (state.Cooldown <= 0.0)
    46.                     {
    47.                         state.Cooldown = ShootRate;
    48.  
    49.                         ShotSpawnData spawn;
    50.                         spawn.Shot.TimeToLive = ShotTtl;
    51.                         spawn.Shot.Energy = ShotEnergy;
    52.                         spawn.Position = Position[i];
    53.                         spawn.Heading = new Heading2D {Value = math.normalize(PlayerPos - Position[i].Value)};
    54.                         spawn.Faction = Factions.kEnemy;
    55.  
    56.                         CommandBuffer.CreateEntity(ShotArchetype);
    57.                         CommandBuffer.SetComponent(spawn);
    58.                     }
    59.  
    60.                     ShootState[i] = state;
    61.                 }
    62.             }
    63.         }
    64.  
    65.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    66.         {
    67.             if (m_Data.Length == 0 || m_Player.Length == 0)
    68.                 return inputDeps;
    69.  
    70.             return new SpawnEnemyShots
    71.             {
    72.                 PlayerPos = m_Player.Position[0].Value,
    73.                 DeltaTime = Time.deltaTime,
    74.                 ShootRate = TwoStickBootstrap.Settings.enemyShootRate,
    75.                 ShotTtl = TwoStickBootstrap.Settings.enemyShotTimeToLive,
    76.                 ShotEnergy = TwoStickBootstrap.Settings.enemyShotEnergy,
    77.                 Position = m_Data.Position,
    78.                 ShootState = m_Data.ShootState,
    79.                 CommandBuffer = m_ShotSpawnBarrier.CreateCommandBuffer(),
    80.                 ShotArchetype = TwoStickBootstrap.ShotSpawnArchetype,
    81.             }.Schedule(inputDeps);
    82.         }
    83.     }
    84. }
    So on top of the potential rising complexity from needing more micro components and systems to process them, there is a lot of boiler plate code that you need to use with the ECS/Job system.

    Maybe it's early days but could Unity not make ECS/Jobs an easier API/system to use.

    It also seems like we could be losing a lot of things we take for granted with the a component OO system e.g. inheritance and aggregation.

    What if Unity wrote a OOP to ECS converter or better yet compiler, where it strips a Monobehaviour class down into a set of ECS systems?

    Sounds complex but think about it Monobehaviour is a game engine component with most of the bits we use in games in it so code written on top of it that uses the Unity API could be easily analysed for it's use of the different gaming systems the could be broken down to ECS systems.

    You could gain the advantages of writing coherent OOP component code and Unity could generate all that boild plate ECS code under the hood, just as it does when it compiles our code to IL or Native.

    *The Classic Example has some scripts that are IMHO redundant and micro components e.g. MoveSpeed, Transform2D, EnemyShotState, Faction. So 9 Classic to 13 ECS or complexity factor 36 vs 78.
     
    Last edited: Jun 8, 2018
    pachash and noio like this.
  4. Afonso-Lage

    Afonso-Lage

    Joined:
    Jul 8, 2012
    Posts:
    70
    Again, ECS is not meant to write less code, it's meant to have better code. Sorry but this seems to be a kind of "convince me to use ECS" post, which I don't think is usefull.

    Your OOP code will run on a single thread, while your ECS code will run at multiple cores, so it is parallel by default. What if you have to have more lines of codes? This seems to be a nice trade-off, write more lines to get performance by default, parallelism, network-ready data and so on. Note that many of these boilerplate code can be solved with a template file, shotcuts and many more things.
     
    vexe, mannyhams, rudypoo and 4 others like this.
  5. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Code (CSharp):
    1. using System.ComponentModel.Design;
    2. using System.Runtime.CompilerServices;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Mathematics;
    7. using Unity.Transforms2D;
    8.  
    9. namespace TwoStickPureExample
    10. {
    11.  
    12.     /// <summary>
    13.     /// Assigns out damage from shots colliding with entities of other factions.
    14.     /// </summary>
    15.     class ShotDamageSystem : JobComponentSystem
    16.     {
    17.         struct Players
    18.         {
    19.             public int Length;
    20.             public ComponentDataArray<Health> Health;
    21.             [ReadOnly] public ComponentDataArray<Position2D> Position;
    22.             [ReadOnly] public ComponentDataArray<PlayerInput> PlayerMarker;
    23.         }
    24.  
    25.         [Inject] Players m_Players;
    26.  
    27.         struct Enemies
    28.         {
    29.             public int Length;
    30.             public ComponentDataArray<Health> Health;
    31.             [ReadOnly] public ComponentDataArray<Position2D> Position;
    32.             [ReadOnly] public ComponentDataArray<Enemy> EnemyMarker;
    33.         }
    34.  
    35.         [Inject] Enemies m_Enemies;
    36.  
    37.         /// <summary>
    38.         /// All player shots.
    39.         /// </summary>
    40.         struct PlayerShotData
    41.         {
    42.             public int Length;
    43.             public ComponentDataArray<Shot> Shot;
    44.             [ReadOnly] public ComponentDataArray<Position2D> Position;
    45.             [ReadOnly] public ComponentDataArray<PlayerShot> PlayerShotMarker;
    46.         }
    47.         [Inject] PlayerShotData m_PlayerShots;
    48.  
    49.         /// <summary>
    50.         /// All enemy shots.
    51.         /// </summary>
    52.         struct EnemyShotData
    53.         {
    54.             public int Length;
    55.             public ComponentDataArray<Shot> Shot;
    56.             [ReadOnly] public ComponentDataArray<Position2D> Position;
    57.             [ReadOnly] public ComponentDataArray<EnemyShot> EnemyShotMarker;
    58.         }
    59.         [Inject] EnemyShotData m_EnemyShots;
    60.  
    61.         [ComputeJobOptimization]
    62.         struct CollisionJob : IJobParallelFor
    63.         {
    64.             public float CollisionRadiusSquared;
    65.  
    66.             public ComponentDataArray<Health> Health;
    67.             [ReadOnly] public ComponentDataArray<Position2D> Positions;
    68.  
    69.             [NativeDisableParallelForRestriction]
    70.             public ComponentDataArray<Shot> Shots;
    71.  
    72.             [NativeDisableParallelForRestriction]
    73.             [ReadOnly] public ComponentDataArray<Position2D> ShotPositions;
    74.  
    75.             public void Execute(int index)
    76.             {
    77.                 float damage = 0.0f;
    78.  
    79.                 float2 receiverPos = Positions[index].Value;
    80.  
    81.                 for (int si = 0; si < Shots.Length; ++si)
    82.                 {
    83.                     float2 shotPos = ShotPositions[si].Value;
    84.                     float2 delta = shotPos - receiverPos;
    85.                     float distSquared = math.dot(delta, delta);
    86.                     if (distSquared <= CollisionRadiusSquared)
    87.                     {
    88.                         var shot = Shots[si];
    89.  
    90.                         damage += shot.Energy;
    91.  
    92.                         // Set the shot's time to live to zero, so it will be collected by the shot destroy system
    93.                         shot.TimeToLive = 0.0f;
    94.  
    95.                         Shots[si] = shot;
    96.                     }
    97.                 }
    98.  
    99.                 var h = Health[index];
    100.                 h.Value = math.max(h.Value - damage, 0.0f);
    101.                 Health[index] = h;
    102.             }
    103.         }
    104.  
    105.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    106.         {
    107.             var settings = TwoStickBootstrap.Settings;
    108.  
    109.             if (settings == null)
    110.                 return inputDeps;
    111.  
    112.             var enemiesVsPlayers = new CollisionJob
    113.             {
    114.                 ShotPositions = m_EnemyShots.Position,
    115.                 Shots = m_EnemyShots.Shot,
    116.                 CollisionRadiusSquared = settings.playerCollisionRadius * settings.playerCollisionRadius,
    117.                 Health = m_Players.Health,
    118.                 Positions = m_Players.Position,
    119.             }.Schedule(m_Players.Length, 1, inputDeps);
    120.  
    121.             var playersVsEnemies = new CollisionJob
    122.             {
    123.                 ShotPositions = m_PlayerShots.Position,
    124.                 Shots = m_PlayerShots.Shot,
    125.                 CollisionRadiusSquared = settings.enemyCollisionRadius * settings.enemyCollisionRadius,
    126.                 Health = m_Enemies.Health,
    127.                 Positions = m_Enemies.Position,
    128.             }.Schedule(m_Enemies.Length, 1, enemiesVsPlayers);
    129.  
    130.             return playersVsEnemies;
    131.         }
    132.     }
    133. }
    134.  
    OK Above is the rather verbose code to trigger damage when shots hit and reduce the hit ships health by an amount or health -= damage;

    Three things to consider here...

    One: ECS is just functional programming with structs so is C# even the best programming language for this type of coding, we are adding lots of Attributes and wrapper code when maybe another language could work better with ECS?

    If you look at the Pure Twin Stick Shooter Example it has a ShotDamageSystem and a RemoveDeadSystem, something that would have been a few lines of code in an OnCollisionEnter() function is now two separate systems weighing in at 133 + 64 = 197 lines of code.

    Two: Imagine introducing different weapon damage types as the example is space based imagine we want 3 different weapon types missiles (M) with a blast radius, photon shots(the default S) and blasters bolts (B photon energy).

    So we can have 2 damage types kinetic and energy with any weapon type.

    And as we have grown our range of weapon types we add appropriate defences shields (d), armour (a)and reflective armour (r).

    So different damage types are reduced by different amounts depending on the defences and the shields/armour are reduced damaged themselves before we hit health.

    How would our ECS systems cope with this raised level of complexity as we now have a potential of 9 damage scenarios before we take health damage?

    B->a
    B->d
    B->r
    M->a
    M->d
    M->r
    S->a
    S->d
    S->r

    Also our designer wants 9 different sets of particle and sound fx for the damage as well as effects for when the shields/armour is destroyed.

    And they will want to be able to change them in editor!

    Three: How well does ECS provide sequential control, in games we have actions that trigger events that trigger effects it's "[UpdateAfter(typeof(ShotSpawnSystem))]" attributes don't really give you fine grained control of the order of things e.g. Can you lock down your ECS systems to a:
    A -> B -> C -> D
    sequence for all things in the game how long before you start needing
    earlyC -> A -> B -> C -> D -> lateA -> lateB

    Now imagine a game with a big set of ECS systems to manage it's complexity would it have tens of systems or even over a hundred ECS systems running to maximise the throughput and control the sequence of events at a fine enough grain to provide an amazing experience e.g. effects and animations happen the same frame as their trigger event.

    IMHO the impacts of ECS almost guarantee complexity as every interaction within the game world between what would have been two components or even a function in one component is now an ECS system.
     
    krzysie7 and noio like this.
  6. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    For me the a lot of design friction and the most difficult to overcome is to let go of the sequential this-then-that mindset in order to use ECS. Even more so when you are porting your existing code, it is tempting to design a system while thinking where in "the chain" it would be.

    But I found that the correct mindset is to rather think "what does it needs" in order to start running. You don't care about execution order when putting Before/After but that will ultimately becomes an execution order. The produced order might not be what you expected, but the behaviour is correct.

    For example my game I used to have a flow like : calculate clickable things position this frame -> get input -> check against those things -> take action, all hand coded in old style.

    Right now I code them in System in terms of requirement rather than execution ordering (checking action requires input processed + position calculated, etc.) I got an unexpected result that I do not have in my head : the get input system runs the first. But is the behaviour what I wanted anyways? Yes.

    The ordering is optimized for parallelism, so it is actually harmful to think about a concrete ordering of systems and try to force them that way. (e.g. Saying I want to insert system Z in between A and B, similar to what you described) In my actual game I already got a lot of unexpected ordering when checking in the Profiler timeline view, but ultimately all of that results in the correct behaviour that I want.
     
    SolidAlloy, abixbg_unity and rudypoo like this.
  7. Goldseeker

    Goldseeker

    Joined:
    Apr 2, 2013
    Posts:
    42
    If the argument was OOP vs ECS it may havebenn something to discuss, but if i understand correctly you compare ECS with monobehavior based component system which just doesn't scale up to something bigger than early prototype. It is easier to setup and use than both proper OOP and ECS, but it becomes unmaintainable really quick
     
    starikcetin likes this.
  8. Afonso-Lage

    Afonso-Lage

    Joined:
    Jul 8, 2012
    Posts:
    70
    Also, let me point something that you don't see very often when ppl are talking about benefits of ECS: Isolation of Systems. I'm developing a system that gets the object the player is targeting (simple raycast), but there is no physics for ECS yet, so I had to do my own and after developing it, I saw on entity debugger it was taking 20ms to finish (I'm very bad at physics), eating most part of my frame time, so I start to refactoring it, using jobs, getting help from Burst compiler until I got 0,5ms per frame.

    The way ECS is built you know where the problems can be, since only systems contain logic, so only systems can eat your frame time. Since all systems can run isolately, you have a nice a bealtiful performance track tool right out of the box, you don't need profile for most cases. Also, when I open my inventory, which I don't need the object it is point to, I just disable this system and reenable when inventory is closed.

    This can be done for all systems, just because the way of developing things in ECS.
     
  9. avvie

    avvie

    Joined:
    Jan 26, 2014
    Posts:
    74
    my little bit to this is that the more i learn the less i write. I might have systems ready from another experiment. no need to change a thing. just drag and drop. super easy
    At start i had the same look on it because of IComponentData and stuff. Even then the final code, considering what unity does behind the scenes must be less than in ecs than in mono. Its just that in mono you get to drag and drop components. I say must, because the announcement about the small runtimes that can go down to 45kb require pure ecs, instead of the 45mb that monobehavior needs.

    Apart from the paradigm shift, personally i cant find a thing that isnt better to do in ecs. even things that wouldnt require C# jobs, they perform better in ecs than in monobehaviors
     
    Afonso-Lage likes this.
  10. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    In all honesty I've found that in the long term while I might be writing a little more boilerplate code, I am writing systems that are a lot clearer in it's intent and the logic that I want to apply. Most of the time when attempting something a little longer winded than just a small jam game I end up with a great ball of spaghetti because of all the dependencies I need to create in order to enforce the behaviour I want. I don't want unreadable code that obfuscating what behaviour is present so I think the extra boilerplate is well worth it. It's also forcing me to really think about the minimum that is required for something to be considered a system.
     
    starikcetin and PhilSA like this.
  11. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Even the ECS teams docs state they need to do more work to make the code simpler!

    I've had this conversation before with another developer who used a managed code approach in Unity very similar to ECS in a lot of ways.

    https://forum.unity.com/threads/how...-my-unorthodox-way.413419/page-3#post-2702280
     
    Last edited: Jun 8, 2018
    noio likes this.
  12. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,129
  13. KitsuneX89

    KitsuneX89

    Joined:
    Dec 12, 2014
    Posts:
    2
    If you just took the time to watch one of their videos... all your questions and speculations would be answered. Yes it introduces a (slightly) different way of writing code, a style you might not be used to (it is still C# tho..). However the consideration and motivation for doing this, is performance and re-usability.. Which show impressive numbers, especially when using Burst compiler. I also don't understand how this would be so much more complex, the workflow is very similar to the current workflow.
     
  14. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    OK ECS gives lots of performance but for something that basically chops up what was OOP code into structs then takes their functions and runs them over an array of those structs it seems like a lot of code for a for next loop.

    Code (CSharp):
    1. public struct BitOfObject {
    2.    public int oneBit;
    3.    public float anotherBit;
    4. }
    5.  
    6. public ObjectBits bits[];
    7.  
    8. public OnUpdate() {
    9.    foreach(BitOfObject bit in bits)
    10.    {
    11.       //DoASingleOOPActionHere;
    12.    }
    13. }
    14.  
    Now if Unity could get ECS code this concise and easy to understand that would be amazing but its nowhere near this simple yet.

    Isn't there a more concise way to do this e.g. a lot of programming languages develop more advanced features to make doing repetitive things like this easier for the programmer e.g. Templates/Generics.

    What if Unity allowed an old school OOP object to be ECS'ed by the build process e.g. an [ECS] tag and Unity takes the object and its functions and divides it up into structs used by the functions and builds a complete ECS subsystem that is compiled and run.

    Benefits we can write old school quick and easy OOP and get ECS performance.
     
  15. Afonso-Lage

    Afonso-Lage

    Joined:
    Jul 8, 2012
    Posts:
    70
    I think that's impossible. OOP vs ECS is not about syntax, but about logic. In order to use ECS you need to change how you see the world as a programmer and you cannot expect Unity to rework your logic, since this is what make you a programmer and what make Unity an engine.

    You can expect Unity to improve the current state of ECS by reducing the amount of boilerplate code.

    I don't want to bring a discussiong about OOP vs Data Oriented, because for me it's like discussing if earth is flat, but please note that OOP vs Data Oriented isn't nothing new, for years many devs communities are trying to get away from OOP, be it for Data Oriented or Functional Programming, but almost any experienced develop agrees that for modern computer, with many cores, OOP isn't the best approach.
     
    StarFluke and avvie like this.
  16. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    I'm also very weary of using something like templates to generate code. Quite often it's hiding what should've been a code smell. My view is that one should be writing the boilerplate as it helps in understanding how to work with the framework, but to also be looking at ways to decrease the amount of boilerplate. If this is just generated you're never going to feel the pain of the architecture and there won't be effort put into thinking about a way to improve it.
     
  17. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    What if we invented a component all the dataItems in a component are stored in an array and a foreachprocess does the actions on the data?

    Code (CSharp):
    1. component {
    2.    dataitem ABitOfData;
    3.    dataitem AnotherBitOfData;
    4.    dataStore[]  dataArray;
    5.  
    6.    foreachprocess {
    7.       //Do some processing on each piece of data in dataArray
    8.    }
    9. }
    Arguably you would not need the dataStore as it would be built into the component.

    The idea here is to bring the way ECS works into the programming language therefore making it easier to work with for programmers.
     
    noio likes this.
  18. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Is it sounds like `IJobProcessComponentData<T1,T2,...>` ? In each Execute it works like foreachprocess that you described. It comes with `ref` so you could freely change values and have them saved.

    I have made a lib over Unity's ECS that uses generic to influence what's injected. (E7ECS) The idea is that if you have [Inject] in your abstract superclass as anything that is not private (protected/private protected/public), it could be carried over to the subclass. Then you can make <T> go into that struct in your abstract class. Maybe you can exploit this to help reduce the amount of code you write.

    For example my game currently I can do this :

    Code (CSharp):
    1. public class PlayAllAudioSystem : MonoCS<AudioSource>
    2. {
    3.     protected override void OnUpdate()
    4.     {
    5.         foreach(AudioSource aSource in MonoComponents)
    6.         {
    7.             aSource.Play();
    8.         }
    9.     }
    10. }
    MonoCS take the generic T to inject a `ComponentData<T>` and put them in IEnumerable<T> MonoComponents.
     
    noio likes this.
  19. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    ECS kind of forces you to work more structured, so you need to set up a basis for your system which might take a bit more time than just coding away. But once you have the basics of your game down it should become a lot easier to create things on top of that.
     
  20. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    I think my main issue is I'm going to be writing and re-writing lots of tiny structs and foreach loops with a lot of additional boiler plate code and attributes that currently are just confusing to get to grips with.

    If Unity could update the C# compiler with a new set of tokens dedicated to doing Jobs and ECS that could make the whole process a breeze.

    Also looking at the Space Shooter example why have one file with structs and another with the System that uses them, could we not have micro component OOP where the structs are included with the main system that uses them?

    It's like going back to C/C++ with header files and c/cpp files.

    I also think a lot of people will find this approach confusing as it does not work as well with the editor as the old system, e.g. we are not seeing Entities in the IDE other than the Debug viewer. Hopefully this will improve greatly as the system is improved.

    Actually as the ECS system is so similar in structure e.g. struct/foreach would it be an ideal system to implement via visual development tools as they could do most of the boilerplate in the background and let the developers focus more easily on the mechanics of the game?
     
    FROS7 likes this.
  21. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    Systems and Components are still kind of separate, multiple Systems can use the same Component etc.
    If you want to put Components in the same file as their respective Systems you can just do that.

    Well, it is a different approach and it is just a lot less visually and more data driven way of doing things. ECS just work a lot different than classical "OOP" style programming. I don't think everyone will adopt to it and I'm not sure everyone even should.

    It's still kind of a beta in my eyes and I'm looking forward to a whole lot more things. Better visualization in the Editor is one thing but I'm also looking for more functional things. Something like Entitas Reactive System where you can get callbacks when a component is added or removed etc. And for them to open it up a bit more and make it more flexible, currently you can't put any kind of Collection, not even primitive arrays, into IComponentData.

    Over time, as it becomes the core of most Unity internal systems, these Components will probably become similarly supported as Monobehaviours are right now.
     
    Sluggy and optimise like this.
  22. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    In my personal view I've come to think of components as my data schema, almost the database of the game that I'm building. This is done through careful consideration of the types of data that needs to be represented and how to define small groupings so that we don't just end up with a bunch of primitive type values to represent the data.

    Systems can then go forth and provide a good explanation of the type of operations it's performing on the data as well as on what data it's performing. So instead of trying to abstract our data to the point where we're building objects that constantly need to have their data mapped to something, we now end up with a relatively flat interaction between game logic and the data that drives that logic.
     
  23. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
  24. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    If they could work on the verbosity of the API like Entitas that would be a great boon to developers IMHO...

    Code (CSharp):
    1. var entities = context.GetEntities(Matcher<GameEntity>.AllOf(GameMatcher.Position, GameMatcher.Velocity));
    2. foreach(var e in entities) {
    3.     var pos = e.position;
    4.     var vel = e.velocity;
    5.     e.ReplacePosition(pos.value + vel.value);
    6. }
    vs

    Code (CSharp):
    1. using Unity.Entities;
    2. //using Unity.Transforms2D;
    3. using Unity.Transforms;
    4. using Unity.Mathematics;
    5.  
    6. using UnityEngine;
    7.  
    8. namespace ECSSpaceInvaders
    9. {
    10.  
    11.     public class EnemyMoveSystem : ComponentSystem {
    12.  
    13.         public struct Data
    14.         {
    15.             public int Length;
    16.             public ComponentDataArray<Position> Position;          
    17.             public ComponentDataArray<Heading> Heading;
    18.             public ComponentDataArray<MoveSpeed> MoveSpeed;
    19.             public ComponentDataArray<Enemy> Enemy;
    20.         }
    21.  
    22.         [Inject] private Data m_Data;
    23.  
    24.         protected override void OnUpdate()
    25.         {
    26.             var settings = SpaceInvadersBootstrap.Settings;
    27.             Rect r = settings.playfield;
    28.  
    29.             r.xMin *= 0.95f;
    30.             r.xMax *= 0.95f;
    31.  
    32.             float dt = Time.deltaTime;
    33.  
    34.             float3 move = dt * settings.invaderMoveDirection * settings.enemySpeed;
    35.             float3 position;
    36.  
    37.             for (int index = 0; index < m_Data.Length; ++index)
    38.             {
    39.                 position = m_Data.Position[index].Value;              
    40.  
    41.                 position += move;
    42.  
    43.                 if (position.x > r.xMax)
    44.                 {
    45.                     position.x = r.xMax;
    46.                     settings.invaderMoveDirection.x = -1f;
    47.                 }
    48.  
    49.                 if (position.x < r.xMin)
    50.                 {
    51.                     position.x = r.xMin;
    52.                     settings.invaderMoveDirection.x = 1f;
    53.                 }
    54.  
    55.                 m_Data.Position[index] = new Position{ Value = position };                              
    56.             }
    57.  
    58.             SpaceInvadersBootstrap.Settings.invaderMoveDirection = settings.invaderMoveDirection;
    59.         }
    60.     }
    61. }
    Compared to setting up the data used then writing an Execute function and a JobHandle wrapper and all those [Inject] and [ReadOnly] and [NativeDisableParallelForRestriction] this is definitely the way Unity ECS should go.
     
    Last edited: Jun 21, 2018
  25. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    Well, despite those two examples clearly having different scopes to begin with so it's hardly a fair comparison.

    You do know this exists?
    Code (CSharp):
    1.     [BurstCompile]
    2.     struct MovementJob : IJobProcessComponentData<Position, Velocity> {
    3.         public float deltaTime;
    4.  
    5.         public void Execute(ref Position position, [ReadOnly]ref Velocity velocity) {
    6.             rotation.Value = position.Value + velocity.Value * deltaTime;
    7.         }
    8.     }
    Sure, you still have to set up the job and hand it the deltatime and it's still not as dynamic yet, but it's not as bad as you make it seem.
     
    Last edited: Jun 21, 2018
  26. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    The Unity approach to structuring component layout is a lot more performant then most other ECS systems. So you have to take that into account.

    It's fairly simple to add an abstraction yourself so you can in effect call entity.component. Not the ECS entity mind you but a struct you create that looks and acts like a more traditional ECS entity. Here is a copy and paste of what it looks like in a system. In OnUpdate I just create a new AgentSteeringEntity for each ECS entity. You could vary this and put the IComponent fields in your system as well.

    You have all sorts of tools to add the abstractions you need to make your systems clean. And IMO that is better left up to us anyways, because how you structure this can change system to system. I have other systems where I don't do the below, because the logic is such I don't know which components I need to pull out up front. But where I do this cleans things up nicely.


    Code (csharp):
    1.  
    2. public struct AgentSteeringEntity
    3.     {
    4.         public CombatEntityId CombatEntityId;
    5.         public Position Position;
    6.         public FactionId FactionId;
    7.         public Weapon Weapon;
    8.         public GroupInfo GroupInfo;
    9.         public CombatStats CombatStats;
    10.         public CombatTarget CombatTarget;
    11.         public DamageSummary DamageSummary;
    12.         public MoveDestination MoveDestination;
    13.  
    14.         public AgentSteeringEntity(AgentSteeringGroup group, int index)
    15.         {
    16.             CombatEntityId = group.CombatEntityIds[index];
    17.             Position = group.Positions[index];
    18.             FactionId = group.FactionIds[index];
    19.             GroupInfo = group.GroupInfos[index];
    20.             CombatStats = group.CombatStats[index];
    21.             CombatTarget = group.CombatTargets[index];
    22.             DamageSummary = group.DamageSummaries[index];
    23.             MoveDestination = group.MoveDestinations[index];
    24.             Weapon = group.Weapons[index];
    25.         }
    26.     }
    27.  
    28.     public struct AgentSteeringGroup
    29.     {
    30.         [ReadOnly]
    31.         public ComponentDataArray<GroupInfo> GroupInfos;
    32.         [ReadOnly]
    33.         public ComponentDataArray<CombatStats> CombatStats;
    34.         [ReadOnly]
    35.         public ComponentDataArray<MoveDestination> MoveDestinations;
    36.         [ReadOnly]
    37.         public ComponentDataArray<DamageSummary> DamageSummaries;
    38.         [ReadOnly]
    39.         public ComponentDataArray<CombatEntityId> CombatEntityIds;
    40.         [ReadOnly]
    41.         public ComponentDataArray<Position> Positions;
    42.         [ReadOnly]
    43.         public ComponentDataArray<FactionId> FactionIds;
    44.         [ReadOnly]
    45.         public ComponentDataArray<Weapon> Weapons;
    46.         [ReadOnly]
    47.         public ComponentDataArray<CombatTarget> CombatTargets;
    48.  
    49.         public EntityArray Entities;
    50.  
    51.         public int Length;
    52.  
    53.     }
    54.  
     
  27. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Another trick you can do for single value components to make them more elegant is using implicit operators. So Position can be used directly as a float3 in most cases.

    Code (csharp):
    1.  
    2. public struct Position : IComponentData
    3.     {
    4.         public float3 Value;
    5.  
    6.         public static implicit operator float3(Position position)
    7.         {
    8.             return position.Value;
    9.         }
    10.     }
    11.  
     
    starikcetin, noio, Cynicat and 2 others like this.
  28. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Now this is the kind of examples we need how to make ECS work for you and not feel as thought your are doing loads of work just to update an entities position in your game.
     
  29. just a small correction:

    just to update tens (or hundreds) of thousands of entities positions in your game in a few milliseconds...

    And BTW, really? In the age of modern IDE we are arguing about code verbosity? ROFL.
     
    sand_lantern likes this.
  30. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    Be careful with that though, all it does is allow you to omit the ".Value" in position.Value when using it in an assignment or when wanting to pass the float3 value. It does not allow you to set anything to it more easily and the float that is being used is a value copy of the Value, not in any way bound to the Component.
    It really just saves you from manually assigning the float3 value to a new variable or typing ".Value".
     
  31. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Also try code snippet to live with verbose code. It is the first time in more than 7 years of coding that I am compelled to use one. For example with VSCode I have these snippet :

    Code (JavaScript):
    1. {
    2.     // Place your snippets for csharp here. Each snippet is defined under a snippet name and has a prefix, body and
    3.     // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    4.     // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
    5.     // same ids are connected.
    6.     // Example:
    7.     // "Print to console": {
    8.     //     "prefix": "log",
    9.     //     "body": [
    10.     //         "console.log('$1');",
    11.     //         "$2"
    12.     //     ],
    13.     //     "description": "Log output to console"
    14.     // }
    15.     "Protected Override": {
    16.         "prefix": "po",
    17.         "body": [
    18.             "protected override $0",
    19.         ]
    20.     },
    21.     "Native Array Kit": {
    22.         "prefix": "naark",
    23.         "body": [
    24.             "NativeArray<int> $0naar;",
    25.             "protected override void OnCreateManager(int capacity)",
    26.             "{",
    27.             "    naar = new NativeArray<int>(1, Allocator.Persistent);",
    28.             "}",
    29.             "",
    30.             "protected override void OnDestroyManager()",
    31.             "{",
    32.             "    naar.Dispose();",
    33.             "}",
    34.         ]
    35.     },
    36.     "On Create - Destroy": {
    37.         "prefix": "oncredes",
    38.         "body": [
    39.             "protected override void OnCreateManager(int capacity)",
    40.             "{",
    41.             "}",
    42.             "",
    43.             "protected override void OnDestroyManager()",
    44.             "{",
    45.             "}",
    46.         ]
    47.     },
    48.     "Native Array": {
    49.         "prefix": "naar",
    50.         "body": [
    51.             "NativeArray<int> $0naar;",
    52.             "naar = new NativeArray<int>(1, Allocator.Persistent);",
    53.             "naar.Dispose();",
    54.         ]
    55.     },
    56.     "Job PCD": {
    57.         "prefix": "jobpcd",
    58.         "body": [
    59.             "protected override JobHandle OnUpdate(JobHandle inputDeps)",
    60.             "{",
    61.             "    var job = new Job()",
    62.             "    {",
    63.             "    };",
    64.             "    return job.Schedule(this, inputDeps);",
    65.             "}",
    66.             "",
    67.             "struct Job : IJobProcessComponentData<$0>",
    68.             "{",
    69.             "    public void Execute(ref )",
    70.             "    {",
    71.             "    }",
    72.             "}",
    73.         ]
    74.     },
    75.     "Job Parallel For": {
    76.         "prefix": "jobpara",
    77.         "body": [
    78.             "protected override JobHandle OnUpdate(JobHandle inputDeps)",
    79.             "{",
    80.             "    var job = new Job()",
    81.             "    {",
    82.             "    };",
    83.             "    return job.Schedule(len, 1, inputDeps);",
    84.             "}",
    85.             "",
    86.             "struct Job : IJobParallelFor",
    87.             "{",
    88.             "    public void Execute(int i)",
    89.             "    {$0",
    90.             "    }",
    91.             "}",
    92.         ]
    93.     },
    94.     "Job": {
    95.         "prefix": "job",
    96.         "body": [
    97.             "protected override JobHandle OnUpdate(JobHandle inputDeps)",
    98.             "{",
    99.             "    var job = new Job()",
    100.             "    {",
    101.             "    };",
    102.             "    return job.Schedule(inputDeps);",
    103.             "}",
    104.             "",
    105.             "struct Job : IJob",
    106.             "{",
    107.             "    public void Execute()",
    108.             "    {$0",
    109.             "    }",
    110.             "}",
    111.         ]
    112.     },
    113.     "Using Job": {
    114.         "prefix": "usejob",
    115.         "body": [
    116.             "using Unity.Entities;",
    117.             "using Unity.Mathematics;",
    118.             "using Unity.Collections;",
    119.             "using Unity.Jobs;",
    120.             "using UnityEngine.Experimental.PlayerLoop;",
    121.         ],
    122.     },
    123.     "Debug Log": {
    124.         "prefix": "dbl",
    125.         "body": [
    126.             "Debug.Log($\"$0\");"
    127.         ],
    128.     },
    129.     "Read Only Component Data Array": {
    130.         "prefix": "rocda",
    131.         "body": [
    132.             "[ReadOnly] public ComponentDataArray<$0> datas;"
    133.         ],
    134.     },
    135.     "Write Only Component Data Array": {
    136.         "prefix": "wocda",
    137.         "body": [
    138.             "[WriteOnly] public ComponentDataArray<$0> datas;"
    139.         ],
    140.     },
    141.     "Component Data Array": {
    142.         "prefix": "cda",
    143.         "body": [
    144.             "public ComponentDataArray<$0> datas;"
    145.         ],
    146.     },
    147.     "Read Only Component Array": {
    148.         "prefix": "roca",
    149.         "body": [
    150.             "[ReadOnly] public ComponentArray<$0> components;"
    151.         ],
    152.     },
    153.     "Write Only Component Array": {
    154.         "prefix": "woca",
    155.         "body": [
    156.             "[WriteOnly] public ComponentArray<$0> components;"
    157.         ],
    158.     },
    159.     "Component Array": {
    160.         "prefix": "ca",
    161.         "body": [
    162.             "public ComponentArray<$0> components;"
    163.         ],
    164.     },
    165.     "Injection Group": {
    166.         "prefix": "injectgroup",
    167.         "body": [
    168.             "struct InjectedGroup ",
    169.             "{$0",
    170.             "}",
    171.             "[Inject] InjectedGroup injectedGroup;",
    172.         ],
    173.     },
    174.     "Entity Array": {
    175.         "prefix": "entiarr",
    176.         "body": [
    177.             "[ReadOnly] public EntityArray entities;",
    178.         ],
    179.     },
    180.     "Length": {
    181.         "prefix": "len",
    182.         "body": [
    183.             "public int Length;",
    184.         ],
    185.     },
    186.     "Readonly Public": {
    187.         "prefix": "rop",
    188.         "body": [
    189.             "[ReadOnly] public $0",
    190.         ],
    191.     },
    192.     "Entity Command Buffer": {
    193.         "prefix": "enticom",
    194.         "body": [
    195.             "public EntityCommandBuffer command;",
    196.         ],
    197.     },
    198.     "Barrier System": {
    199.         "prefix": "barr",
    200.         "body": [
    201.             "public class Barrier$0 : BarrierSystem { }",
    202.             "[Inject] Barrier barrier;",
    203.         ],
    204.     },
    205.     "Get Barrier into a job": {
    206.         "prefix": "combar",
    207.         "body": [
    208.             "command = barrier.CreateCommandBuffer(),",
    209.         ],
    210.     },
    211.     "Update Before": {
    212.         "prefix": "ube",
    213.         "body": [
    214.             "[UpdateBefore(typeof($0))]",
    215.         ],
    216.     },
    217.     "Update After": {
    218.         "prefix": "uaf",
    219.         "body": [
    220.             "[UpdateAfter(typeof($0))]",
    221.         ],
    222.     },
    223. }
    From 1 month of usage the most used being rocda -> [ReadOnly] public ComponentDataArray<$0> datas; followed by po -> protected override and injectgroup
     
    Last edited: Jun 21, 2018
    EduardoLauerCD and starikcetin like this.
  32. :D
    Maybe I'm too biased because I work as a Java developer in my day job, so I'm used to it already.

    BTW, as far as I remember, they said that first they develop the system and then they will work on the integration to make it better (I think it will mean that they try to make it less verbose too).
     
  33. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    I haven't played around with it yet, but ref returns might make this more interesting.

    And all approaches like this have caveats including the previous example which adds overhead. Overhead I'm ok with in logic in OnUpdate, not so much in a job in a tight loop or parallel for. I'd rather have the default be the most performant like it is now and choose when I think it's ok to add more abstractions.
     
  34. BL1TZ

    BL1TZ

    Joined:
    May 22, 2015
    Posts:
    4
    Okay, so ECS gives me an impression of the MVC framework.

    Some benefits are:
    - A clear separation of concerns
    - Faster performance due to parallelism
    - Reusable code

    Lets say I have thousands of snowflakes which all share the same behaviour (they fall down, while rotating, but every snowflake does so at different speeds), then I can totally see the benefit of the ECS system, No Problem!

    There are some things, however, unclear to me (cuz I dont understand **** what [Injection] and that kind of stuff even means). I seem to have a lot of trouble understanding the ECS conventions and how it should be used in systems which differ from your basic snowflake.

    Lets say I have a forest scene, I put some animals in there, some of them are birds, some of them are deers and some of them are bears. All of those animals have their own behaviour which suits their race, but all of them have different implementations from each other:
    - 1 bear decides to hunt a deer straight on.
    - 1 bear decides to just walk around.
    - 1 bear deides to hunt a deer while sneaking

    - 1 bird decides to fly around.
    - 1 bird decides to make a sound.

    - 1 deer decides to run away (from the attacking bear).
    - 1 deer is drinking at a pool of water.

    With our old system, I would just simply write behaviour for all of our animals and implement the different ways of doing specific tasks (hunting, walking, flying, etc).

    the real problem for me starts when I have to convert this kind of system towards and ECS driven system.

    How would I go about and implement such a system using ECS? Compared to our old system, currently I wouldnt see a benefit of going through the trouble of creating this system in ECS, because 1) I dont underrstand the conventions of ECS yet and 2) If I would write this system, it would take me significantly more time!

    Could someone help me out or explain to me what I'm missing here?
     
    fantasicle0 and Le-Tuan-Son like this.
  35. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Think of ECS as atomic actions:
    • All of the animals move - Movement System.
    • Some of the animals make a noise or less noise when sneaking. - Sound System.
    • Some of the animals hunt.- Follow System.
    • All of the animals can see - Sense System.
    • All of the animals can hear. - Hear System.
    • All of the animals can drink water - Drink/Health System.
    • All of the animals have a Behavioural System that would trigger the other systems.
    Note you nearly always end up with way more atomic ECS systems that are often only a few lines of code or the ECS boilerplate problem.

    In theory adding a new animal with a new behaviour would just be adding new data to the systems and maybe a new system e.g. Attack / Defend / Eat / Flee / Mate / Spawn.

    Also this is why you should make ECS systems as atomic as possible as a slightly complex system would require more work to maintain and add features/behaviours across a whole set of systems.

    The benefits are the same basic atomic system that moves your snowflakes can move your animals. Or once you have a movement system it should work for all moving entities in your game.

    I think the Behavioural system in my example would possibly become a complex system as it would have different logic depending on the animal type, possibly an enum and switch case for bear/bird/deer then for state transition walk/drink/hunt/run/alarm call... which could be a problem or is it good to have one complex system that manages behaviours e.g. FSM like?
     
    Last edited: Dec 9, 2018
    Kirsche, BL1TZ and Antypodish like this.
  36. Le-Tuan-Son

    Le-Tuan-Son

    Joined:
    Jan 13, 2016
    Posts:
    21
    I agree with BL1TZ.
    Old system is much more easier to implement and much more easier to understand. Especially for a character with complex behaviour.

    And 1 year later, when I need to update some behaviour of my character. With the old system, it will be much easier to understand the behaviour flow of my character.

    Will only use ECS if I really need improve game performance and for some simple system!
     
  37. Deleted User

    Deleted User

    Guest

    When it gets going ECS will probably be more modular than you expect without a consistent need for memory shuffling (internally managed) due to instantiated memory that doesn't get disposed until the garbage collection cycle.

    Old system is easy, yeah because its been taught and its been the expected norm for Unity or other engines since time began. New system is also easy, it just requires more support right now.
    In the future you could probably expect ECS to completely replace Monobehaviour, but you would still program it the old way. Far in the future though, talking like 2030.

    If you have written much C++ (COM probably closer) you would not really be fussed about the whole boilerplate side of this, because its not that bad in comparison.
     
    xVergilx and hippocoder like this.
  38. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    That's very clever! How did I not think of that before?

    It introduces a possibility of confusion in the future if you want to add more data members to the component, then you would need to refactor all usages. But still, pretty useful.
     
  39. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    You should put this on Github.
     
  40. sledgeman

    sledgeman

    Joined:
    Jun 23, 2014
    Posts:
    389
    I think unity´s next-gen visualScripting tool will be coupled with ECS ! Accessibility to all ...
     
  41. nsxdavid

    nsxdavid

    Joined:
    Apr 6, 2009
    Posts:
    476
    @sledgeman I can't imagine that the visual programming feature will really make this relateable to the masses. The one thing ECS requires is a really firm understanding of WHY you are doing something in what would other wise seem to be an obtuse way. OOP, for all it's faults, is something one can more easily grasp. In fact, the whole idea of OOP is to try and metaphorically model "real-world" concepts in code.

    I think ECS has some serious maintainability problems. And perhaps even more debugging problems. It does allow for one to isolate systems somewhat (where dependencies allow), and in theory each system is doing only a very small thing. But if you cannot just read your code and know how it flows, I think you are setting yourself up for a nightmare trying to bring more people onto a project or returning to code you have not touched in a while.

    ECS seems great for some very specialized situations, but we cannot pretend there isn't a serious cost to doing this for the developer. Like anything that is perfomance-first, we are trading off something to achieve it. Be it memory, or clarity... whatever the case is. And ECS is no different.

    So using ECS end-to-end in a game seems to be one of those things you better think about really carefully.
     
    Sluggy likes this.
  42. No one told you should do that. Ever.
     
  43. nsxdavid

    nsxdavid

    Joined:
    Apr 6, 2009
    Posts:
    476
    Ever discussion of doing "Pure ECS" for a game is telling people that's a thing to do. Plus it becomes increasingly hard to do it partially. Hybrid ECS might be a solution, except as more systems are converted to DOTS, it slowly closes the door. I'm not sure how to use, say, Hybrid mode with the new Unity Physics. You can't use a GameObject Entity on a GameObject that has Unity Physics components, you have to use Convert To Enity. Maybe there is a way to do it, but it's certainly not obvious.
     
  44. Nothing is obvious, that's why it's called 'preview'. When it will be ready, you will have the how-to to follow how to do it. But a lot of Unity engineer, including Joachim and Mike stated multiple times that they don't think ECS/DOD is an answer for everything. Which means you will have to decide for yourself when you use it and what for. As for the unity systems, it will be as transparent as today. Do you recognize that the transforms and hierarchies are using the DOTS technology? No.
     
  45. nsxdavid

    nsxdavid

    Joined:
    Apr 6, 2009
    Posts:
    476
    The idea here is to comment on what we see today so that it might guide what is to come. Just 'shut up and wait' is hardly useful in that regard. And I didn't grasp what your final sentence meant at all.

    In any case, I'm going to have to double down here because even if there is an admission that ECS isn't right for everything, that doesn't square well with the roadmap that suggests everything will transition to it. If Unity Physics is an example of what that might look like, then it suggests Hybrid ECS might not be the way things really evolve.

    If one needs, say, stateless Physics, then they are going to end up in an ECS rabbit hole, or at least it seems that way. Or maybe I'm missing something, every well could be.
     
  46. Transforms and the hierarchy-handling (parent-child connection between transforms) are using DOTS technology for a while now, even before the DOTS announcement. Still you're using them from OOP and from regular components. So sometimes the underlying DOTS is transparent for you, so you shouldn't be intimidated by DOTS or the ECS. We will have tools (like the converter) to handle the data pass-through between these. At least as of now. The how and such are questions for tomorrow.
    And since your entire argument came from the visual scripting concerns, I answered in that regard: they can do things transparently, they will be able to build a VS solution so you won't have to handle the ECS/DOD on the very bottom level. They're promising higher level components, so you don't have to think in DOD at all, you can concentrate on your gameplay on whatever you want to do in your game.

    If you need Unity Physics, you will have to have DOTS and you will have to have some kind of translation. But we already have one example: the conversion tool. But if you wish to remain in OOP world, you can use the PhysX, they will upgrade it to the latest and they will take care of it, if you don't want to take care the translation.

    And yes, my argument was that you don't have to move EVERYTHING to DOTS and you don't have to start to apply it to everything. And now, you are sliding to other questions, comments. Whatever.
     
  47. nsxdavid

    nsxdavid

    Joined:
    Apr 6, 2009
    Posts:
    476
    No, I cannot use PhysX because I need stateless. The automatic translation doesn't work in hybrid mode. Maybe this is just a missing piece that's coming later, but it's hard to know what is intended at this point.

    But I still assert that a visual abstraction of DOTs is a super heavy lift. Expressing game play in DOTs is substantially difficult once you get beyond trivial examples relative to the old-school OOP style, with so much of it being boiler plate and added complexity that obfuscates the readability and intent of the code. Not to mention the debugging issues.

    And I have no problem with that, so long as it's just something we do when we need to go for maximum performance. But the idea seems to be that this is the way to do everything (despite your denial). I say this because when the question of performance (or battery drain) is raised, DOTs is the answer given. Sure enough this will do the trick, but at considerable development cost.

    Hopefully the hybrid approach will be first class. It's the only way I see this working in the end.
     
  48. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    The same is true in GameObject land too. Once you get past grabbing components and modifying transforms and playing sound and launching particle systems, and you start to work on larger games where you need real control, you start to have to intelligently set ground rules for sizes of Monobehaviours, draw lines for which Monobehaviour is responsible for what, have managers do manager-y things, and get them all hooked up and communicating with each other. And once you do, it becomes extremely painful to modify anything without the whole chain events breaking because one component made an assumption about another component already having its ScriptableObject initialized, but that second component got moved to a spawner pool with delayed initialization and now there's a NullReferenceException causing more chaos. Sure, the debugging tools might be more polished, but the code is still pretty obfuscated and difficult to fix. Eventually I expect ECS will have the ability to drop any frame's state into the debugger, and step through system by system (with breakpoints inside the system if required) and let you look at the before and after state of all your entities with each system to pinpoint the problem with perfect reproduce-ability.

    As for your use case, I don't think it is a use case Unity anticipated. The main issue you have is that the Unity.Physics components require ConvertToEntity in order to work properly in the inspector. If I were in your position, I would just copy those Monobehaviours into your project, rename the classes, and remove the attribute. From there you can manually set up your entities for physics and link them up in whatever way you feel like.
     
  49. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    The hard part of DOTS is you still have to reason about concurrency. It prevents race conditions in a brute force manner, it doesn't design them out you have to do that. Job component systems are the only part of DOTS that does this all for you right now, and that's only if you are only using entities/components.

    It's still better then no safety checks by far don't get me wrong. But that's half the battle. Creating large complex games having to ad hoc reason about it like we have to now for a lot of stuff. The oh just have one system have a dependency on the job handle of another. That kind of thing won't cut it long term. But it's early so I think we will see more patterns come out to solve that eventually.
     
  50. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Dependencies in JobComponent system are create based on data access (Read write access of component types), not based on simply passing you the previous systems dependencies. The idea very much is that you should be able to just use JobComponentSystem as is, trust that it generates you good dependencies. Write a lot of parallel for jobs in your systems of course, but thats it.

    In the Unite austin nordeus demo, the game was quite heavy on simulation code and the approach got us to around 95% multicore utilization. There wasn't anything special we did with the dependencies beyond just using JobComponentSystem as far as i remember.

     
    Hanoke, Zoey_O and FROS7 like this.