Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Feedback Is the current ECS API easy and intuitive enough?

Discussion in 'Entity Component System' started by apkdev, May 13, 2021.

  1. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    Even though for a while I have been working full time on a project that partially utilizes ECS, I still consider myself an inexperienced outsider to this forum, especially seeing the amazing projects everyone is working on, or even managed to ship already. I am not sure whether this makes me less or more qualified to start this thread, but I'm doing it anyway.

    The following is a genuine question that has bugged me for a while, and I'm interested in learning what other people think, especially the programmers more experienced with ECS prototyping. Given that threads in this subforum tends to get pretty heated, I'd like to ask everyone to make sure the discussion stays civil and constructive.

    Do you think the current ECS API's design is easy and intuitive enough for widespread community adoption in the long term?

    Over the years we've seen multiple iterations of the ECS API. Currently, the design focuses on readability and prototyping speed without significant performance overhead. I consider this to be the an excellent direction. Daily development experience is just as important to me as performance, and boy, do I love performance. It looks like the dust is starting to settle, and - as far as I understand - we're likely to start seeing a lesser number of large, impactful changes to the overall programming workflow.

    However, having written some production ECS code, it still sometimes feels like the API is working against me. Of course, I DO realize every single change at this point probably involves months of work for someone. But it could also possibly lower the bar of entry to an endless number of developers.

    I am personally mostly satisfied with the direction ECS is taking. However, I consider myself to be a pretty experienced C#/Unity programmer, and it has still taken me multiple failed attempts (separated by a few weeks or months) to understand ECS enough to be able to produce working prototypes. This is fine, I'll manage. My problem is that, in the future, I think I'll have a hard time convincing other people to get started with ECS at all. I've personally witnessed several experienced Unity developers try and give up on ECS.

    I don't have much code that I'm able/allowed to share, so I'm basing my examples on this blog post.

    Code (CSharp):
    1. EndSimulationEntityCommandBufferSystem endSimulationEcbSystem;
    2.  
    3. protected override void OnCreate()
    4.   => endSimulationEcbSystem
    5.     = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    6.  
    7. protected override void OnUpdate()
    8. {
    9.   var endSimulationCommands = endSimulationEcbSystem.CreateCommandBuffer().ToConcurrent();
    10.   var time = Time.DeltaTime;
    11.  
    12.   Entities
    13.     .WithName("UnitJob")
    14.     .ForEach(
    15.       (Entity entity, int entityInQueryIndex, in Translation translation, in UnitComponent unit) =>
    16.       {
    17.         var newTrans = translation;
    18.         var tower = GetComponent<TowerComponent>(unit.ParentTower);
    19.         var towerTranslation = GetComponent<Translation>(unit.ParentTower).Value;
    20.  
    21.         float fractionInFormation = unit.Index / (float) tower.UnitCount;
    22.         float3 target = tower.Formation switch
    23.         {
    24.           TowerFormation.Circle => PointOnCircle(tower.Radius, fractionInFormation, towerTranslation),
    25.           TowerFormation.Rect => PointOnRect(towerTranslation, tower.Radius, fractionInFormation),
    26.           _ => newTrans.Value,
    27.         };
    28.  
    29.         float3 delta = target - newTrans.Value;
    30.         float frameSpeed = unit.Speed * time;
    31.         newTrans.Value = math.length(delta) > frameSpeed
    32.           ? newTrans.Value + math.normalize(delta) * frameSpeed
    33.           : target;
    34.  
    35.         endSimulationCommands.SetComponent(entityInQueryIndex, entity, newTrans);
    36.       })
    37.     .Schedule();
    38.  
    39.   // this part is easy to forget and the method name isn't helpful
    40.   endSimulationEcbSystem.AddJobHandleForProducer(Dependency);
    41. }
    42.  
    Some of my thoughts:
    - The entire
    EndSimulationEntityCommandBufferSystem
    and
    EntityCommandBuffer
    thing feels not just verbose, but like literal boilerplate. This feels very unfriendly to quick prototyping scenarios. AFAIK this can't be omitted in certain scenarios, eg. when performing structural changes.
    -
    int entityInQueryIndex
    requires that the parameter is named correctly. This felt unexpected when I first tried using this (eg. I couldn't shorten it to
    int index
    ). Can't we just get an
    entity.GetInQueryIndex()
    extension? I think this is a comparably tiny problem in practice, though.

    I've written a stripped-down version of this system. Please keep in mind that while I'm showing an example of what I think the code could kinda look like, this is obviously not a concrete proposal. More like back-of-the-napkin code.

    Code (CSharp):
    1. protected override void OnUpdate()
    2. {
    3.   var endSimulationCommands = GetCommandBuffer<EndSimulationCommandBuffer>();
    4.   var time = Time.DeltaTime;
    5.  
    6.   Entities
    7.     .WithName("UnitJob")
    8.     .ForEach(
    9.       (Entity entity, in Translation translation, in UnitComponent unit) =>
    10.       {
    11.         var newTrans = translation;
    12.         var tower = GetComponent<TowerComponent>(unit.ParentTower);
    13.         var towerTranslation = GetComponent<Translation>(unit.ParentTower).Value;
    14.  
    15.         float fractionInFormation = unit.Index / (float) tower.UnitCount;
    16.         float3 target = tower.Formation switch
    17.         {
    18.           TowerFormation.Circle => PointOnCircle(tower.Radius, fractionInFormation, towerTranslation),
    19.           TowerFormation.Rect => PointOnRect(towerTranslation, tower.Radius, fractionInFormation),
    20.           _ => newTrans.Value,
    21.         };
    22.  
    23.         float3 delta = target - newTrans.Value;
    24.         float frameSpeed = unit.Speed * time;
    25.         newTrans.Value = math.length(delta) > frameSpeed
    26.           ? newTrans.Value + math.normalize(delta) * frameSpeed
    27.           : target;
    28.  
    29.         // EntityInQueryIndex could be a struct to make this strongly typed and harder to get wrong
    30.         // If it also contained the Entity struct, we could omit the Entity parameter altogether
    31.         endSimulationCommands.SetComponent(entity.GetInQueryIndex(), entity, newTrans);
    32.       })
    33.     .Schedule();
    34. }
    35.  

    Is this a huge difference? In practice, yes. Currently doing simple things requires a ton of code that, frankly, I can't even type without a reference. Having to copy-paste anything at all feels like a red flag to me. It also feels like MonoBehaviour land never had this much boilerplate. It's currently a hard sell for seasoned Unity programmers. But at the same time, it feels like we're nearly there.

    //edit: I've updated the thread with samples that better represent the current API. Sorry for the confusion. I was working with an earlier Entities version and I wasn't up to date.

    I'll try to update the thread as I come up with more examples, but in the meanwhile, please feel free to provide your own.

    So, anyway, is it just me?
     
    Last edited: May 14, 2021
  2. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    677
    Two things.

    You don't actually need the ComponentDataFromEntity when using Entities.ForEach, just use Get/SetComponent instead (it will generate proper code for you).
    Code (CSharp):
    1.     EndSimulationEntityCommandBufferSystem endSimulationEcbSystem;
    2.    
    3.     protected override void OnCreate()
    4.       => endSimulationEcbSystem
    5.         = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    6.    
    7.     protected override void OnUpdate()
    8.     {
    9.       var endSimulation = endSimulationEcbSystem.CreateCommandBuffer().ToConcurrent();
    10.       var time = Time.DeltaTime;
    11.    
    12.       Entities
    13.         .WithName("UnitJob")
    14.         .ForEach(
    15.           (Entity entity, int entityInQueryIndex, in Translation translation, in UnitComponent unit) =>
    16.           {
    17.             var newTrans = translation;
    18.             var tower = GetComponent<TowerComponent>(unit.ParentTower);
    19.             var towerTranslation = GetComponent<Translation>(unit.ParentTower).Value;
    20.    
    21.             float fractionInFormation = unit.Index / (float) tower.UnitCount;
    22.             float3 target = tower.Formation switch
    23.             {
    24.               TowerFormation.Circle => PointOnCircle(tower.Radius, fractionInFormation, towerTranslation),
    25.               TowerFormation.Rect => PointOnRect(towerTranslation, tower.Radius, fractionInFormation),
    26.               _ => newTrans.Value,
    27.             };
    28.    
    29.             float3 delta = target - newTrans.Value;
    30.             float frameSpeed = unit.Speed * time;
    31.             if (math.length(delta) < frameSpeed)
    32.               newTrans.Value = target;
    33.             else
    34.               newTrans.Value += math.normalize(delta) * frameSpeed;
    35.    
    36.             endSimulation.SetComponent(entityInQueryIndex, entity, newTrans);
    37.           })
    38.         .Schedule();
    39.    
    40.       endSimulationEcbSystem.AddJobHandleForProducer(Dependency);
    41.     }
    And if you are talking about prototyping, I think we could have some fairness here, the code you sent is already optimized with multi-threading, here is a more "prototype-looking" code:
    Code (CSharp):
    1.     protected override void OnUpdate()
    2.     {
    3.       var time = Time.DeltaTime;
    4.    
    5.       Entities
    6.         .WithName("UnitJob")
    7.         .ForEach(
    8.           (Entity entity, in Translation translation, in UnitComponent unit) =>
    9.           {
    10.             var newTrans = translation;
    11.             var tower = GetComponent<TowerComponent>(unit.ParentTower);
    12.             var towerTranslation = GetComponent<Translation>(unit.ParentTower).Value;
    13.    
    14.             float fractionInFormation = unit.Index / (float) tower.UnitCount;
    15.             float3 target = tower.Formation switch
    16.             {
    17.               TowerFormation.Circle => PointOnCircle(tower.Radius, fractionInFormation, towerTranslation),
    18.               TowerFormation.Rect => PointOnRect(towerTranslation, tower.Radius, fractionInFormation),
    19.               _ => newTrans.Value,
    20.             };
    21.    
    22.             float3 delta = target - newTrans.Value;
    23.             float frameSpeed = unit.Speed * time;
    24.             if (math.length(delta) < frameSpeed)
    25.               newTrans.Value = target;
    26.             else
    27.               newTrans.Value += math.normalize(delta) * frameSpeed;
    28.    
    29.             EntityManager.SetComponent(entity, newTrans);
    30.           })
    31.         .WithStructuralChanges().Run();
    32.     }
    so in the end, this is the actual boilterplate required:

    Code (CSharp):
    1.  
    2.     protected override void OnUpdate()
    3.     {
    4.       Entities
    5.         .WithName("UnitJob")
    6.         .ForEach(
    7.           (Entity entity, in Translation translation, in UnitComponent unit) =>
    8.           {
    9.             entityManager.SetComponent(entity, newTrans);
    10.           })
    11.         .WithStructuralChanges().Run();
    12.     }
    13.  
     
    apkdev likes this.
  3. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    Thanks! I didn't realize that. This certainly makes things much easier. I'd still say an
    entity.GetComponent
    API would make things both more intuitive for newcomers, and more readable overall. This is a bit of a philosophical issue for some, though.

    But that would completely ruin the performance, wouldn't it (no multithreading, no Burst)? I think "performance by default" is approach fundamentally what we should stick to.

    For example, on my specific project, this wouldn't work even for prototyping (dynamic simulation on a rather large scene). The level is next to unplayable with Burst and job threads disabled. Essentially, I'd need to rewrite my code right away before I commit it and push it to the repo, and that's more work than just writing optimized code from the beginning.
     
    brunocoimbra likes this.
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    Your poll and questions are extremely biased. Whether or not the current API is "good enough" isn't exclusively dependent on whether the boilerplate is reduced. Intuitiveness, optimizability, extensibility, and flexibility are all factors.

    And my answer to that is "no". Entities still has a long ways to go. Game Object Conversion and Subscenes have really bad workflows for code prototyping (it is not fundamentally flawed, but rather there's some state management issues in the implementation that need to be fixed). Transforms can be clunky to work with and also slow in complex hierarchies. There's a few missing EntityManager and EntityCommandBuffer APIs. EntityQuery Entity filters need to work with deferred arrays. Lambdas aren't extendible yet. I could go on and on.

    And I still think there's a lot of boilerplate issues too. Some of your proposals I don't think would ever work without major compilation slowdowns for little benefit. Some of them I have actually implemented myself in my framework. And there are some issues you didn't even point out that I have implemented or plan to implement in the future.
     
    NotaNaN, SenseEater, apkdev and 5 others like this.
  5. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    825
    I am ok with the current api, but one thing that really annoys me is the current workflow or way you read HasComponent/ComponentDataFromEntity

    I know an entity is just an id and doesnt actually contain that data, but right now is weird.
    Code (CSharp):
    1.  
    2. // how it is now
    3. HasComponent<Component>(entity);
    4. // vs in my mind it should be this way
    5. entity.HasComponent<Component>();
    6.  
    7. // it could even be simplified?
    8. entity.Has<Translation>();
    9.  
    This extends to the
    ComponentDataFromEntity
    too where I think being forced to "name" the data makes it feel worse(as in
    if(TranslationDataFromEntity.HasComponent(entity));

    Grammatically I'm always saying to myself: if my translation component data; has component: entity, but I want to be reading it like: if my entity has a translation with this data.

    edit
    It felt like it made a little more sense back when CDFE used "Exists" instead of "HasComponent".

    I'm also still missing IJobForEach, after using chunk and batch and new lambdas for quite some time now, IJFE was a very nice intro to slightly more complex jobs, without the boilerplate overhead of chunk iteration. I honestly think the old IJFE really helped me understand the job system way more than the lamda does(though the variations of IJFE_WithXYZ were far from ideal), and I see a lot of people on discord making some incorrect assumptions from lamda usage where they could've benefited from starting off with IJFE instead.

    /end rant
     
    Last edited: May 13, 2021
  6. TWolfram

    TWolfram

    Joined:
    Aug 6, 2017
    Posts:
    75
    I feel like this might just be part of the problem: I've been using Entities for months and I never even saw that this was an option until now, every single tutorial I've seen uses ComponentDataFromEntity, including Unity's own manuals as far as I'm aware. There might be a whole lot more improvements that I'm not aware of, who knows.
    I agree that there needs to be a lot of work on user-friendliness/boilerplate code etc. I know we're not in the Physics forum but I find collisions to be especially cumbersome.

    I also think the way things are named can be pretty confusing, such as ComponentDataFromEntity. I'd expect that to be a single piece of data from an entity, rather than a list of componentdatas. Why the "from entity"? Why not call it something like "ComponentDataCollection", "ComponentDataFromQuery" or maybe even "ComponentDataFromEntities" instead of using the singular form. Also, renaming ComponentDataFromEntity.Exists to .HasComponent was a weird move to me as well. Now it reads "ComponentDataFromEntity.HasComponent(Entity)". The thought I have is either "Does this entity exist in this group of components?" or "Does this entity have this component?". To me, this syntax basically looks like you're asking "Does this component data have this component?" Instead, why didn't they call it something like "ComponentDatas.HasEntity(Entity)"? I'd find that a lot more readable. Anyways, I'm glad that we can just simply use "GetComponent" now, that's much closer to the existing syntax. Hope we get more of those kinds of improvements, and that Unity actually updates the examples in their manuals and post some new video tutorials or sample projects if they do.
     
    apkdev likes this.
  7. TWolfram

    TWolfram

    Joined:
    Aug 6, 2017
    Posts:
    75
    I agree with this as well, we're reading right to left now. It's kind of weird.

    Also, maybe "under the hood" entities are only an index but it's really not as different from components being attached to GOs as they make it seem every time they mention it. There's still only one component per entity and one entity per component, right? So other than the way it's stored, I really don't see that much of a difference functionally. And I think putting so much of an emphasis on that might only result in more confusion for people who are new to DOTS.
     
    apkdev likes this.
  8. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    All I can say is that I did my best.

    I think you're right that I could have probably named the thread and the pool better - as you can tell, my primary concerns were readability, maintainability, ease of learning and suitability for prototyping (or rather, quick iteration? I mean experimenting while writing production code, not writing throwaway code). I'm also mainly concerned with the "95% of the time" workflow. I'll try to adjust the first post to make this more evident, but I'm unfortunately unable to modify the poll or the thread title.

    I only included one code sample, so obviously it's going to be biased. The first post is already pretty lengthy and it still took me about 2 hours to put together. I do intend to post more examples when I have more time and get more ideas - in the meanwhile, you're welcome to help.

    I do realize there's plenty of stuff still missing or being worked on, but I'm more concerned that some of the "every day" code workflows become solidified before they're as good as they could be. But otherwise, please DO go on! More code examples would be especially helpful.

    I'm not sure that's actually accurate if the source generators thing pans out. But I'm not knowledgeable enough in the assembly weaving voodoo area OR the future source generator voodoo to judge this at a glance.

     
  9. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    791
    I agree with this a lot. It was bad enough that I ended up using GameObjectConversionUtility.ConvertGameObjectHierarchy and adding extra components and entities just to get my ECS prefabs.
     
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,653
    For that, we've just written a couple of abstract systems one time and just using them instead of SystemBase

    Get\SetComponent here for a long time.

    Well, it's not, it's expected implementation for ECS and DOD in general,
    entity.Get\SetComponent
    here wrong expectation, as it implies that entity is container\object from which you getting data, but it absolutely not, it's not a container, it's an index. Even if abstract from Unity implementation is always like this. You should think about components as about arrays from which you getting data by index (Entity), it's just the philosophy of ECS paradigm (without any real implementation details), and as you get data from an array, like this
    myDataArray[someIndex]
    you getting it by Unity ECS API -
    GetComponent(Entity)
    (you see, they even looks close :) ) and if think about it like you described - getting data from array should look like:
    someIndex.myDataArray[]


    Just read the documentation it's here veeeeeery long time already (since 0.7 version from far March 2020) :) https://docs.unity3d.com/Packages/com.unity.entities@0.17/manual/ecs_lookup_data.html it also has cross-links to other things with detailed explanation https://docs.unity3d.com/Packages/c...emBase_GetComponent__1_Unity_Entities_Entity_
     
    davenirline, Shinyclef and Occuros like this.
  11. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    677
    Using EntityManager doesn't rules out Burst FYI.

    But yeah, when talking about threading the boilerplate is needed, but still much less than doing the multi-threading from zero (I mean, if you want some kind of safety, if safety is not one of your concerns you could just ignore all that and use Tasks or other stuff lol)
     
    apkdev likes this.
  12. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    Something I was planning on adding to my framework in the next release was a concept of EntityWith<T>. Instead of storing raw entity fields in your ICDs, you could store these new types instead and provide a little extra context as to how the entity should be used. In addition, the syntax would read like this:
    Code (CSharp):
    1. public struct Icd : IComponentData
    2. {
    3.     public EntityWith<LocalToWorld> slot;
    4. }
    5.  
    6. // Usage
    7.  
    8. if (icd.slot.IsValid(localToWorldCdfe))
    9.     var slot = icd.slot[LocalToWorldCdfe];
    The main benefit of this is that you can much more clearly sequence these from left to right in a single line.
    Thanks. That helped a lot! Before I was really struggling to understand what you were asking since it was so broad. This gave the necessary focus so that hopefully you can get better answers.
     
    apkdev likes this.
  13. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    I know what you mean, and you're technically correct, but I still think it's the wrong choice. It's like forcefully reminding everyone of the architecture/implementation instead of focusing on making the code more readable. Of course, readability is subjective, but in terms of making the OO -> ECS transition as easy to possible, I think the "classic" syntax causes less confusion.

    I believe beginners won't have any clue about where and how their data is kept regardless of the syntax, and the people who read the docs also won't be confused either way.

    Also to be perfectly honest, I'm kinda tired of typing "EntityManager" in all of the contexts where it could be implicit. Small inconvenience, I know, but these add up.

    No problem, I also reported my own post and asked a mod to update the thread title.

    //edit: Many thanks to the kind moderator who adjusted the thread title.
     
    Last edited: May 14, 2021
  14. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Just want to say that this is also true of the GameObject/MonoBehaviour APIs. It’s just that’s people who work with MonoBehaviors are probably more used that implementation.

    Someone who learned to code in a DOD/ECS style might have the same complaint about the MonoBehaviour APIs: It forces you to think about how the data is stored, rather than making the code easier to read.

    Readabolity is often a matter of perspective, and what you’re familiar with.

    I think it’s also important to say that keeping track of how your data is used is never a bad thing to be aware of. Rather than use APIs which hide what’s going on with one’s data, I think we should be encouraged to understand what we’re doing to our computer hardware at every step.

    Rather than something to avoid, it’s probably a better idea to adapt, and get more comfortable thinking this way. Especially in a high performance DOTS world, the details about how and when you’re accessing your data can have a big impact on performance.
     
  15. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    825
    I made a few extensions for my own idea but honestly not sure if this is the way to go, and tbh I forget to use it most of the time anyway.

    Code (CSharp):
    1.        
    2. /// <summary> Make the code flow format make a bit more sense </summary>
    3.         public static bool HasComponent<T>(this Entity entity, SystemBase system) where T : struct, IComponentData
    4.         {
    5.             return system.EntityManager.HasComponent<T>(entity);
    6.         }
    7.      
    8.         public static bool HasComponent<T>(this Entity entity, EntityManager entityManager) where T : struct, IComponentData
    9.         {
    10.             return entityManager.HasComponent<T>(entity);
    11.         }
    12.      
    13.         public static bool HasComponent<T>(this Entity entity, ComponentDataFromEntity<T> componentDataFromEntity) where T : struct, IComponentData
    14.         {
    15.             return componentDataFromEntity.HasComponent(entity);
    16.         }
    17. etc with similar Set/Get
    18.  
    Code (CSharp):
    1.    
    2. // ComponentDataFromEntity
    3. if (!entity.HasComponent(ParentComponentData))  
    4. return;
    5. // SystemBase
    6. entity.HasComponent<Translation>(this) //where SystemBase is this
    7.  
     
    apkdev likes this.
  16. TWolfram

    TWolfram

    Joined:
    Aug 6, 2017
    Posts:
    75
  17. varnon

    varnon

    Joined:
    Jan 14, 2017
    Posts:
    52
    It's way better, but it still could use a fair bit of work. I think the core API is doable though. If you want to add anything else to that, like physics, animation, audio, etc, then it's going to need even more work. I don't know how I feel about Conversions and SubScenes yet.
     
  18. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    No matter what they do, people who spent years programming OOP will always need a whole lot of acclimation time to both get used to thinking in DOD and learning the tricks and quirks. So long people learn to code in OOP, ECS is doomed to be non intuitive.
    Nothing that you can't just learn and get used to, it will just not feel intuitive/natural for a while.

    Edit: Talking about Authoring API being bad, is this the only way to force a conversion during runtime? (I want both the Entity and the GameObject to exist at the same time after 'conversion')
    Code (CSharp):
    1.     public static World ConvertAuthoring(GameObject gameObject, ref Entity Entity, EntityManager em) {
    2.         World World = World.DefaultGameObjectInjectionWorld;
    3.         GameObjectConversionSystem convertor = World.GetExistingSystem<GameObjectConversionSystem>();
    4.  
    5.         object[] parameters = new object[] { Entity, em, convertor };
    6.         foreach (var component in gameObject.GetComponents<Component>()) {
    7.             if (component == null) {
    8.                 continue;
    9.             }
    10.             Type t = component.GetType();
    11.             try {
    12.                 if (t.FullName.EndsWith("Authoring")) {
    13.                     System.Reflection.MethodInfo methodInfo = t.GetMethod("Convert");
    14.                     if (methodInfo != null) {
    15.                         methodInfo.Invoke(component, parameters);
    16.                     }
    17.                 }
    18.             } catch (Exception) { }
    19.         }
    20.  
    21.         return World;
    22.     }
     
    Last edited: May 14, 2021
    apkdev likes this.
  19. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    478
    Yeah it's easy to miss things, I think we just need to be spending lots of time in the API to get to know it well.

    One thing I'm unsure about regarding the GetComponent<T>(Entity e) method is if it 'generates' the correct readonly access or not, I don't see a property bool readOnly on it... so not sure if it's a wise move for me to convert my read only ComponentFromDatas over to this convenience method. Anyone know?
     
  20. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,627
    It generates readonly unless you have a SetComponent<T> in the job
     
    apkdev and Shinyclef like this.
  21. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    Assuming you mean stuff like
    entity.GetComponent<T>()
    vs
    entityManager.GetComponentData<T>(entity)
    , I don't think this is applies to the DOD vs OOP distinction at all. Both of these are a random entity lookup, i. e. probably a cache miss, and they both involve a lot of internal ECS machinery to look up the component in memory.

    I don't think having the syntactic sugar encourages OOP thinking. How your code looks is completely orthogonal to understanding how your data is laid out in memory, and that is what DOD is essentially all about.

    In fact, I think the distinction between
    Entities.ForEach(...)
    and
    entity.GetComponent<T>()
    in terms of the memory access pattern is superbly clear. ForEach iterates over entities in an ordered manner. GetComponent does what it's always done - it grabs the data from wherever it is. It's like we could have had one thing less to teach, but we instead decided to throw away the clear, intuitive symmetry with the GameObject API.

    // edit: On second thought, I just tested out the SystemBase
    GetComponent<T>(entity)
    API and it is pretty nice since you don't have to type EntityManager all the time, and it is symmetrical to the GameObject API as opposed to the EntityManager.GetComponentData<T>() naming. I wonder if these method names will eventually get consolidated.

    (I'm ranting about a topic that's probably 100% settled, and I'm entirely fine with that.)

    (While I think having that syntax would be nice, I think we can live without it, and all things considered it's not a hill I want to die on.)
     
    Last edited: May 14, 2021
  22. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    I meant that Components feel more like database tables than variables in objects, the way you write your methods needs to accommodate to this change. Basically all data needs to be serialized and you can't have lists of lists of lists like in OOP
    (Technically you can, but if you do you might as well use OOP instead of ECS)
     
    apkdev likes this.
  23. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    This is true, and getting used to the difference is unavoidable. I think making some of the new syntaxes nicer could make this process easier to newcomers.
     
    Last edited: May 14, 2021
  24. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    863
    Current API is great. It is teaching you how to think in terms of data.
    Entity is just an index. It should not have Get/Set component methods. Otherwise it would be like with a .camera, .light and .rigidbody methods in monobehaviour. Which are maybe easy to understand for newcomers, but terrible in nature.
     
    Last edited: May 16, 2021
    apkdev and Shinyclef like this.
  25. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    I feel like this should be alluded to in the documentation, for some people with basic exposure to databases it might click easier. That's basically the archetecture of ECS, parallel table processing where:

    • Components == columns
    • Entity == row/id
    • Archetype == table of unique combinations of columns
    • Chunk == subset of rows
    • World == database(?)
    • ForEach == SELECT ... FROM (all tables)
     
    Nyanpas and apkdev like this.
  26. TWolfram

    TWolfram

    Joined:
    Aug 6, 2017
    Posts:
    75
    This is something that's still a bit unclear to me, this was mentioned in the Data Relationships talk as well. If ComponentDataFromEntity is random access, what would be the correct way to implement read relationships while still preventing cache misses? Are we supposed to make those relationships write relationships instead, or do entity relationships just inherently create random lookups? In the presentation, she does mention how choosing read vs write depends mostly on frequency, but at the same time doesn't mention exactly which option is the best in which scenario. Are we supposed to use write relationships for high-frequency transformations? Or would EntityCommandBuffers fix this?
     
    Last edited: May 17, 2021
  27. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    478
    Not every problem is one that can be solved without random access. Random access isn't something to avoid at all costs, it's just something to keep in mind when looking at your bottlenecks. I don't have any general advice to apply to all entity relationship scenarios or anything though, usually if you can think of a way to get linear access, you're making a compromise somewhere else anyway.
     
    apkdev likes this.
  28. TWolfram

    TWolfram

    Joined:
    Aug 6, 2017
    Posts:
    75
    I see, thanks!
     
    apkdev likes this.