Search Unity

  1. Unity 2018.3 is now released.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Want more efficiency in your development work? Sign up to receive weekly tech and creative know-how from Unity experts.
    Dismiss Notice
  4. Build games and experiences that can load instantly and without install. Explore the Project Tiny Preview today!
    Dismiss Notice
  5. Nominations have been announced for this years Unity Awards. Celebrate the wonderful projects made by your peers this year and get voting! Vote here!
    Dismiss Notice
  6. Want to provide direct feedback to the Unity team? Join the Unity Advisory Panel.
    Dismiss Notice
  7. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice

API usability

Discussion in 'Entity Component System and C# Job system' started by Adam-Mechtley, Sep 4, 2018.

  1. wobes

    wobes

    Joined:
    Mar 9, 2013
    Posts:
    504
    I was talking about replacing Array.Empty. Sorry, didn't get you that time. Now I see what do you mean.
     
  2. Ofx360

    Ofx360

    Joined:
    Apr 30, 2013
    Posts:
    48
    Hmm, i'm not getting errors when i did this:

    Code (CSharp):
    1. ComponentGroup updateGroup;
    2.  
    3. protected override void OnCreateManager()
    4. {
    5.     updateGroup = GetComponentGroup(new EntityArchetypeQuery()
    6.     {
    7.         All = new ComponentType[] { typeof(Position) },
    8.         Any = new ComponentType[] { typeof(HitBox), typeof(HitBoxType) }
    9.     });
    10. }
     
  3. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    15
    My apologies, I posted a while ago with some suggestions and said that I would post a full repo of where I was going with it, then I disappeared. I got drawn towards other projects, sorry. Would it still be useful for me to put that up? I'd have to tidy up the code and throw a README together which I would be happy to do, but it may be too late to be worth discussing more radical changes now?

    To recap, the API I was proposing was basically a re-work of an old design for my own C# ECS framework. It relies heavily on generics and allows systems to look something a little like the following, with component read/write permissions being compile-time errors. This example system may contain embarrassing bugs.

    Code (CSharp):
    1.  
    2. class SnapNearestAgentToCursor : ComponentSystem
    3. {
    4.     // Only runs when the system is first enabled. Sets up phases, which run every frame.
    5.     // Phases are minimal bits of code to which dependencies are scoped.
    6.     // Jobs are completed when a phase needs them to be, rather than the whole system.
    7.     // This allows for (slightly) more overlap.
    8.     protected override void OnEnable(InitialisationContext setup)
    9.     {
    10.         var inputSystem = this.FetchSystem<InputSystem>();
    11.         float2 cursorPos;
    12.         NativeArray<EntityKey> nearest = new NativeArray<EntityKey>(1);
    13.         // Phase with no component dependencies.
    14.         setup.Phase().Build(() =>
    15.         {
    16.             cursorPos = inputSystem.GetProjectedCursorPos();
    17.             nearest[0] = EntityKey.Null;
    18.         });
    19.         // Any dependencies that are writing to the relevant entities' Position component get completed before this.
    20.         setup.Phase().WithGroup<Included<AgentTag>, Included<Moveable>, Readable<Position>>().Build(agentsGroup =>
    21.         {
    22.             float nearestDistSqr = float.PositiveInfinity;
    23.             foreach (var agent in agentsGroup.Entities)
    24.             {
    25.                 var distSqr = (agent.Read<Position>().pos - cursorPos).LengthSquared;
    26.                 if (distSqr < nearestDistSqr)
    27.                 {
    28.                     nearest[0] = agent.Entity;
    29.                     nearestDistSqr = distSqr;
    30.                 }
    31.             }
    32.         });
    33.         // Dependencies reading from only the single relevant entity's Position component get completed before this.
    34.         setup.Phase().WithGroup<Included<AgentTag>, Included<Moveable>, Writeable<Position>>(from: nearest).Build(agentsGroup =>
    35.         {
    36.             foreach (var agent in agentsGroup.Entities) agent.Write(new Position(cursorPos));
    37.         });
    38.     }
    39. }
    40.  
     
    Last edited: Nov 14, 2018
    noio likes this.
  4. simsoll

    simsoll

    Joined:
    Aug 28, 2017
    Posts:
    1
    I think the data-oriented design of the ECS is really nice. However, I have a few suggestions on how to create ComponentSystems in a simpler way. My main pain points are
    • The use of inheritance which leads to “protected override void” in every method signatur, which can be hard to grasp for a beginner. I havn’t read all things related to the current ECS implementation, so I don’t know if using inheritance somehow is the key to “make it work”.
    • OnUpdate has a lot of boilerplate with setting filters, getting data out of the group, define shared data used across all items in the group and iterating over each item in the group.
    • You are forced to thinking in “groups”, but I think it’s more intuitive and simple to think in term of each how each entity should be updated and then let Unity do the handling of groups behind the scenes.
    My solution to these pain points are under the assumption that you will only use data for index “i” when updating item “i”. I havn’t seen any examples that breaks this assumption, so I think it’s safe to assume.

    Instead of inheriting from ComponentSystem when creating a ComponentSystem I instead suggest to implement the following interface

    Code (CSharp):
    1. public interface IComponentSystem<TEntity, TSharedData>
    2. {
    3.     public ISharedComponentData Filter();
    4.     public TSharedData SharedData();
    5.     public void OnUpdate(TEntity entity, TSharedData sharedData);
    6. }
    An implementation of this interface could look like the following

    Code (CSharp):
    1. public struct PlayerMovementEntity
    2. {
    3.     [ReadOnly]
    4.     public Rigidbody Rigidbodies;
    5.  
    6.     // made-up class, should implement IComponentData
    7.     public InputComponentData Input;
    8. }
    9.  
    10. public struct SharedData
    11. {
    12.     public Time DeltaTime;
    13. }
    14.  
    15. public struct SharedGrouping : ISharedComponentData
    16. {
    17.     public int Group;
    18. }
    19.  
    20. public class PlayerMovementSystem : IComponentSystem<PlayerMovementEntity, SharedData>
    21. {
    22.     public ISharedComponentData Filter()
    23.     {
    24.         return new SharedGrouping { Group = 1 };
    25.     }
    26.  
    27.     public SharedData SharedData()
    28.     {
    29.         return new SharedData {DeltaTime = Time.deltaTime};
    30.     }
    31.  
    32.  
    33.     public PlayerMovementEntity OnUpdate(PlayerMovementEntity entity, SharedData sharedData)
    34.     {
    35.         // do something with the entity using deltaTime and return it
    36.     }
    37. }
    Behind the scenes Unity could take implementations of IComponentSystem and do all the wirering to make the system work. Something like the following

    Code (CSharp):
    1. public class ComponentSystemManager<TEntity, TSharedData> : ComponentSystem
    2. {
    3.     // somehow inject a component system
    4.     IComponentSystem<TEntity, TSharedData> componentSystem;
    5.  
    6.     // made up Group class
    7.     Group<TEntity> group;
    8.  
    9.     protected override void OnCreateManager(int capacity)
    10.     {
    11.         group = GetComponentGroup(typeof(TEntity));
    12.     }
    13.  
    14.     protected override void OnUpdate()
    15.     {
    16.         group.SetFilter(this.componentSystem.Filter());
    17.      
    18.         var entities = group.GetEntities();
    19.         var sharedData = this.componentSystem.SharedData();
    20.  
    21.         for (int i = 0; i < entities.Length; i++)
    22.         {
    23.             entities[i] = this.componentSystem.OnUpdate(entities[i], sharedData);
    24.         }
    25.     }
    26. }
    In the above suggestion it’s not possible to exclude entities that contain a particular component (SubtractiveComponent does that at the moment when specifying the group). Instead I suggest extending the filter to handle such needs.

    Furthermore, you could add more interfaces for implementing simpler systems that don't need filters and shared data, eg.

    Code (CSharp):
    1. public interface ISimpleComponentSystem<TEntity>
    2. {
    3.     public void OnUpdate(TEntity entity);
    4. }
     
    Last edited: Nov 14, 2018
  5. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    164
    Your solution is very similar to Inject API. I see two main draw backs. First it would need reflection to figure out needed components and right now systems have access to entity manager from it's base class. JobProcessComponentData and JobProcessComponentDataWithEntity have very simple to use API.
     
    noio likes this.
  6. Xerioz

    Xerioz

    Joined:
    Aug 13, 2013
    Posts:
    57
    Would be nice to have something like:
    Code (CSharp):
    1. [UpdateFrequency(10, PlayerLoop.Update)]
    2. public class UpdateGroup {}
    3.  
    4. [UpdateInGroup(typeof(UpdateGroup), order: 100)]
    5. class MySystem : ComponentSystem
    Instead of the current awkward UpdateBefore/After api
     
    Jes28 likes this.
  7. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    814
    Hi, if barriers in Entity Debugger displays all the incoming commands from all instances of its ECB that would be very useful. (To optimize MinimumChunkSize or debug command ordering problems etc.) Currently it is just time taken to run the barrier and left me wonder why it is taking so long.
     
  8. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    814
    Also should be great if you rename StreamBinaryWriter to FileBinaryWriter and make a new StreamBinaryWriter that accept Stream as a constructor? I wish to add my own encryption and compression as a chain of stream before finally giving to it.
     
    Jes28 and elcionap like this.
  9. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    6,633
    Could you get an ECS system down to one line of code?

    e.g.

    Code (CSharp):
    1. MoveSystem : ECS { Vector3 Move( Vector3 pos, forward, float speed) { return pos + forward * speed * Time.deltaTime; }}
     
  10. LurkingNinjaDev

    LurkingNinjaDev

    Joined:
    Jan 20, 2015
    Posts:
    1,940
    You can write your Skyrim-sized game in one .cs file also... but why would you do that?
     
    noio, 5argon and Antypodish like this.
  11. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    6,633
    It's an extreme example of removing all of the boilerplate code from ECS, in an attempt to make it easier to use.
     
  12. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    814
    It just boils down to is it a C# or not. If we are making a new scrpting language then of course anything is possible, yes you could. But is it flexible enough for just about anything? That's a merit in sticking to C#, an established programming language. But if we are sticking to C#? It would need stay in C#'s rule at the same time as going towards the desired design. Things like "protected override", etc. you are trying to avoid, that make things works.

    I think in C# "1 line" support is at its best by expression body => operator, so definitely not in class level. At best it would be expression-bodied method as a system/as a job.