Search Unity

Initializing Component that uses GenerateAuthoringComponent

Discussion in 'Entity Component System' started by InnerScript, Aug 9, 2020.

  1. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    Currently, as far as I know, there's not a clean way to initialize an component that is added to an entity through GenerateAuthoringComponent.

    The best solution at the moment is to create a MonoBehaviour that implements IConvertGameObjectToEntity. The downside of this is obvious: It's another class to maintain, doesn't follow the ease of use utility GenerateAuthoringComponent was designed for, and creates a point of failure via maintenance.

    Another solution is to create a Component Tag that is something like "NeedsToInitialize", but the downsides of this is that it needs to be searched every frame for every specific type, needs to have a specific tag for specific types, needs to be managed to be called before the actual Component, needs to be removed which causes a chunk move, and is another point of failure.

    A third solution is to create a flag value in the Component struct that something like Initialized = false, that is processed on the first few lines of the System. The downside of this is that even if the ForEach didn't need the component that has the value, it needs it now - forever. Many init flags require one for each type, which gets passed around pointlessly later. Also, it introduces a branch when many of us are working hard to avoid "if" and "switch" statements in our ECS Systems. Again, also needs to be checked for every Component of that system, every frame.


    Why?
    An example of this need, is a Component that needs to cache the previous position of the Entity for something like motion vectors. The first run of the System, if the cached previous position is (0,0,0), but the object was at a different position it invalidates the first pass of the system and leads to unexpected behavior.


    My recommendation is to either add more markup for attributes (such as [InitializeFrom], or an interface that replaces GenerateAuthoringComponent that serves the same purpose on the Component struct.


    Interface:
    Through an interface, we could have standard function calls such as Convert (with a reference to the GameObject) but also editor specific ones such as DrawEditorPanel, etc. If these interface methods were given permission to access referenced memory, then developers could add whatever init logic they needed. They would also be called only once: When the Entity was created as part of the Runtime > World conversion.

    Ex:
    Convert(GameObject gameObj, Entity entity, EntityManager dstManager, GameObjectConversionSystem cs)


    Markup:
    The markup option, may be more limited due to it's nature, it would be easier for developers to implement common cases such as reading from Transform or in more advanced cases, from a static class is delegated to handle the logic.

    Ex:
    [GenerateAuthoringComponent]
    struct MyComponent {
    [InitializeFrom( typeof(Transform), "position" )]
    float3 InitialPosition;

    [InitializeFrom( typeof(MySingleton), "GetPlayerPosition" )]
    float3 PlayerOriginalPosition;
    }
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    May I ask what this use case has to do with the authoring component? It sounds like you really just want a reactive system to initialize the values at runtime. Authoring components won't help you with that.
     
  3. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    In this *particular* hybrid ECS workflow I am imaging GameObject prefabs, marked up with a number of Components that are GenerateAuthoringComponent, and then instantiated at runtime

    The editor side of things is just because of that structure, for that specific workflow.

    If I was making the entities by script, without a GameObject in the hybrid worklow, then initialization is not a problem, correct?

    Hence...
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I would really reconsider that workflow. There's no need to use authoring components directly at runtime like that. You can author a prefab, and convert it once, and then instantiate the entity multiple times instead. Besides, generating the authoring component does not actually initialize the values. It just exposed the values to be initialized by someone working in the editor. That doesn't help you with runtime initialization.
     
  5. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    Yes. That's the plan. Author prefabs and variants easily in the editor, that have components for ECS.

    The point is... If that's the plan, then initializing values in Components with runtime specific data, then have issues and trade offs (see OP).

    Considering how often initializing a runtime instance is needed, and that's there's a number of ways to hack around this that have clear trade offs in maintenance, code reliability, and performance ... That is a warning flag that something is missing in the API.


    Example:

    I have a BallisticMotion Component that I use this for slow arcing lobbing things, and fast moving arrows or bullets.

    So, I make a bunch of prefabs with their graphics and in the editor add BallisticMotion authoring component, and use that to tune in the properties of the prefab for speed, drag, etc. Each prefab is then a self-contained type of arrow that I can then instantiate at runtime.

    But, as part of the BallisticMotion System, I track motion vectors and need to know the previous position of the arrow. If the previous position is always 0,0,0, then the first run of the system is incorrect,. That runtime "first" position value needs to be initialized in some way.

    Loop back up to the the OP and look at the tradeoffs for different techniques of initializing values at runtime for prefab generated instances.
     
    Last edited: Aug 10, 2020
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    And what I am saying is that this initialization is not the responsibility of the authoring component. What you really want here is a reactive system to initialize the runtime-initialized data.
     
  7. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    Exactly, but and more streamlined and less error prone for the Prefab-based workflow.

    To me, it makes sense to make it a part of the authoring component mechanic, solely because if the Components were added to entities only via code, then initialization is "free". It's a non-issue when Entities and their whole Component structure is formed via code.

    Initialization only an issue when you're using the workflow of authoring components.
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Think about it this way: You'd be initializing in code in MonoBehaviours using an Awake or Start method for this particular piece of data. The authoring component cannot author runtime data. That makes no sense. You have authoring-initialized data, and you have runtime initialized data. They are not the same, and need to be initialized differently. Split them into separate components if you have to.

    And [GenerateAuthoringComponent] is really just a prototyping tool for programmers. If you are working with a team of designers, you really want to give them custom-made authoring components that are intuitive to them.
     
    charleshendry and NotaNaN like this.
  9. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    Where is it officially documented/stated that GenerateAuthoringComponent is a tool for programmers to prototype?

    Considering that in the latest releases, Unity is investing specifically into tools to assist the Hybrid workflow, I feel that Unity is steering ECS to be more tightly integrated into the editor rather than a purely coding exercise.

    I feel GenerateAuthoringComponent is just a baby step into making a work flow (the implicit topic of this thread) that more tightly integrates with the way people make things in Unity.

    If that is the case, and Unity is working on this work flow, then runtime initialization will need to be addressed.

    Edit, evidence....
    SubScene
    GenerateAuthoringComponent
    GameObjectConversionUtility
    IDeclareReferencedPrefabs
    DeclareReferencedAsset
    GameObjectExportGroup
    ConvertToEntity
    AddHybridComponent
    ... and more
     
    Last edited: Aug 10, 2020
  10. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    All that [GenerateAuthoringComponent] does is create something like (be aware of many typos):
    Code (CSharp):
    1. public class SomeComponentAuthoring : MonoBehaviour, IConvertToEntity
    2. {
    3.     [SerializeField] private SomeComponentData someComponent;
    4.  
    5.     public void Convert(Entity e, EntityManager em, ConversionSystem system)
    6.     {
    7.         em.AddComponentData(e, someComponent);
    8.     }
    9. }
    If you want anything more specialized than that, you should create your own MonoBehaviour with IConvertToEntity.

    But, as @DreamingImLatios pointed out, the conversion system is not meant for things that rely on **runtime data**, it runs at conversion time, which usually means "when building the application or when clicking play inside the editor". You can, however, read from ScriptableObjects from there (which is a good practice btw).

    About GenerateAuthoringComponent being a tool for programmers and/or prototyping, this is not officially stated anywhere, but I mostly agree with that. A designer doesn't want to put 10 components to get some functionality working (nor you want to remember those dependencies when creating a new prefab/gameobject) so you will want to create your own authoring components that are 1-to-many instead of relying purely on GenerateAuthoringComponent for every single component.
     
    charleshendry and NotaNaN like this.
  11. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    How do you imagine that working without much error?

    Lets consider the existence of RequireComponent. Why does it exist?

    We could all just write in our Start that if GetComponent<SomeType>() is null, then OK, we'll go out and make it. But, the issue there is that it's error prone. It's also not something you can just search for, and then you're back to making lots of boilerplate.

    That's my point: Making boilerplate is error prone, and distances the initialization into something else.


    Lets imagine a hypothetical bullet hell game, where your unique feature is that there's dozens of different types of enemies that have different bullets and attacks. Some enemy bullets move in straight lines, some zigzag around, others seek towards the player, or maybe explode within a radius of the player, etc etc etc, or even better with ECS in mind, a combination of these things.

    Now, someone needs to make all of these dozens/hundreds of different bullet types/combinations, and test them.

    Instead of just making prefabs for each bullet, with the added GenerateAuthoringComponent to define the behavior of the bullet.... with your recommendation now you have to write a custom way to define sets of Components, set values that dictate their behavior, set which Component struct properties are runtime set (and hide those in the editor), store them, initialize them, and all the UI workflow to do that. (things that Prefabs and GenerateAuthoringComponent handle for you!)

    All of that new code introduces a points of failure. Updates can't exist in one place, they now exist in a number of places and instead of making your Bullet Hell game you're now working with Boilerplate Hell software
     
  12. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    Using GenerateAuthoringComponent is exactly the same as creating one MonoBehaviour with IConvertToEntity for each of your components already. As both are the same, both works with Prefabs in the same way.

    If you created Authoring components that setups multiple components at once (like Unity.Physics components do) it is much less error-prone as you are gonna be centralizing the initialization of multiple dependent components in one place.

    But, from what you are saying, seems like you do want to rely on runtime data (as you want to have optional components that should affect other components initialization), for that a reactive system fits better for your use case, but the complexity is the same as if you were gonna do that at conversion time (but instead of a using MonoBehaviour + IConvertToEntity, you will need to create a System + some way to diff unitialized data from already intialized ones).
     
  13. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    No, I don't want optional components.

    I want clear, strongly typed markup that eliminates error prone boilerplate, that doesn't pollute the project with extra files and structs/classes to maintain, and makes it clear that some (and which) Component types require runtime data to initialize.
     
  14. NotaNaN

    NotaNaN

    Joined:
    Dec 14, 2018
    Posts:
    325
    Unfortunately, what you're asking for is idealistic, not realistic.
    You simply cannot have everything you're asking for without sacrificing something. (Unless perhaps you did a ton of custom coding to create exactly the authoring system you needed... But then you'd have to maintain it, and BANG, sacrifices!).

    Unity's Authoring Components are pretty much the perfect middle ground when it comes to authoring Entities, and they offer a ton of flexibility when it comes to how you're allowed to do it, too.

    That being said — there could be some improvements. But making them would require a pretty serious rewrite of how authoring works (breaking all of the current authoring workflow for users) and a BIG paradigm shift in order for the changes to be made... So I doubt they would be made.

    But hey, once a fully Pure DOTS Editor exists and is fully featured, there's a chance the authoring workflow might be overhauled completely. I just wouldn't bet on it happening anytime soon.
     
  15. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    If you have any pratical example, it would be easier to help. To the example shown in the OP it is clearly a situation where creating your own authoring component (MonoBehaviour, IConverToEntity) is the right solution.
    Code (CSharp):
    1. struct MyComponent
    2. {
    3.      float3 InitialPosition;
    4.      float3 PlayerOriginalPosition;
    5.  
    6. }
    7.  
    8. public class MyComponentAuthoring : MonoBehaviour, IConvertToEntity
    9. {
    10.     public void Convert(Entity e, EntityManager em, ConversionSystem system)
    11.     {
    12.         em.AddComponentData(e, new MyComponent
    13.         {
    14.             InitialPosition = transform.position,
    15.             PlayerOriginalPosition = MySingleton.GetPlayerPosition,
    16.         });
    17.     }
    18. }
    19.  
     
  16. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    Yes, that's what I have at the moment.

    Tell me, how do I find out which Components need runtime instantiation data using this method?

    How many extra classes will I need to maintain? (remember, MonoBehaviours and Components need to be one per file, with the same name)

    This is the boilerplate I'm looking to get rid of, when really IConvertToEntity is just setting a few values. If it's doing something more complex, or interacting with other MonoBehaviours, sure
     
  17. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    MonoBehaviours need to be one per file, Components don't (unless they have the [GenerateAuthoringComponent], which makes them have the same limitation as the MonoBehaviours).

    The code I sent above is completely valid in a single file.
    I keep my conversion pipeline and runtime logic separated so that I don't need to worry about having 1-to-1 stuff everywhere (which would surely increase the complexity of things). Not sure why you need to find out which Components need "runtime instantiation data" (neither what you really mean with that), but if you have a working example I could think of something.
     
  18. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    I'm working in the Hybrid workflow, and taking advantage of GenerateAuthoringComponent

    This thread is about initializing data in that particular workflow, and reducing the boilerplate or code maintenance
     
  19. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    I am not taking the thread off-topic... I work with the Hybrid workflow too, but GenerateAuthoringComponent has only one purpose, that is to reduce boilerplate in a specific use case (1-to-1 components to be set entirely through the inspector).

    What I am trying to do is to understand your use case and try to help you achieve what you want/need. You could create some custom attributes and custom processing for those attributes to make it work like your suggestion in the OP, but this is not the way that the Hybrid workflow works currently, so you would need to maintain that separated system anyway.

    Now, if your thread is about a feature request then I was not aware about it and I apologize for the interruption.
     
  20. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    The best example is this:

    You have an Origin and DistanceFromOrigin components. Both are IComponentData and both are assigned to a Prefab using GenerateAuthoringComponent.

    Origin has just one thing, Position, which is set where the object is placed in the world and never set again. It's simply the data for the creation position in world space.

    DistanceFromOrigin has the value Distance, and uses DistanceFromOriginSystem to calculate the distance each frame.


    Now, imagine if you looked at your project plan, and out of the 100+ IComponentData classes you've identified are needed, 48 of them need some data set at runtime.

    Without a MonoBehaviour, or using Tags, or flag properties... Show me how to cleanly set the Origin.Position.

    Because otherwise I'm going to have a project littered with boilerplate
     
  21. BackgroundMover

    BackgroundMover

    Joined:
    May 9, 2015
    Posts:
    224
    As someone mentioned in the discord, another translation of this problem is that there is no middle ground between [GenerateAuthoringComponent], which gives you zero logic execution, and IConvertGameObject, which maybe expects a little much rote typing, (to manually copy fields from your monobehaviour into your IComponentData).

    In my opinion it'd be nice if there was a middle ground between the two, a generic author class we could make concrete with our ICD type, that also provided the instance that was about to be passed to AddComponentData. Its still an additional .cs file, but it lessens the danger of adding a field to your ICD and forgetting to add it in your IConvertGameObject

    I know it seems like I'm off topic with this but this is where the convo lead in a parallel discussion
     

    Attached Files:

  22. NotaNaN

    NotaNaN

    Joined:
    Dec 14, 2018
    Posts:
    325
    Just going to quickly reply to this...

    The "middle ground" between Authoring Components and auto-generated ones is using the IComponentData struct directly within your Authoring Component. (This requires you to use System.Serializable for this to work).
    Doing this allows you to author multiple IData's in one Authoring Component, and when you do AddComponentData like this:
    Code (CSharp):
    1. destinationManager.AddComponentData(entity, *struct*);
    You don't need to manually map each Authoring variable to the variable in the IData (so updating an IData also updates the Authoring Component*).
    *Not all data types can be displayed in this manner in the Inspector. You will still need to map GameObject references to the corresponding variables in the struct, but most often this is trivial.

    P.S. Apologies for possibly going off-topic.
     
    BackgroundMover likes this.
  23. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    It doesn't. You have to use a little logic to come to that conclusion.

    One of the main goals of the GameObjectConversion workflow was to separate authoring and runtime data. Designers require working with a different data format than the format the runs really fast on hardware in a shipped game. Unity realized this shortly after the first couple releases of Tiny with C# support (Joachim even apologized on these forums for it taking so long to come to that realization). Unity then had the option of creating a new authoring representation and tooling, or re-purposing GameObjects for the role. They chose the latter, as it was less work and allowed for a more familiar experience for designers and programmers working with tooling.

    Now when you look at what [GenerateAuthoringComponent] does, with the exception of Entity references, it is pretty much a 1-1 mirror of the runtime data. So it isn't all that useful for separating authoring and runtime data representations. That's why it is great for prototyping, because you can write [GenerateAuthoringComponent] in your code now and write a proper authoring component later once you get things working.

    As to everything else, don't get me wrong. I think there's still a lot of room for improvement in terms of writing authoring components. But the original question is asking to be able to chop firewood with a fishing pole. Just because they are both related to surviving in the wilderness doesn't make the idea rational.
     
  24. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    I came to the exact opposite conclusion based on the prefab based hybrid workflow., the available types, interfaces, and their introduction timeline..

    • That we will author ECS marked up GameObjects in the editor, that will be instantiated at runtime however we see fit
    • That in the transition period we'll be able to add non ECS converted Unity systems to the GameObjects easily through things like AddHybridComponent
    • That we'll be able to manage their scene loading and serialization of ECS prefab lifecycles through SubScene
    • That we'll continue to have tools like IConvertGameObjectToEntity and GameObjectConversionSystem until all systems are ECS
    • That GenerateAuthoringComponent is the first of many more quality of life attributes to come

    That in the mean time until Unity transitions the whole engine to ECS, we'll be working in either a Pure ECS (either the whole game, or whole sub-systems), or a using a Hybrid workflow to migrate GameObject by GameObject, slowly as ECS gets more solidified.

    We'll see who's correct in a few years, I guess.
     
  25. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    This isn't the opposite conclusion at all. In fact, I agree with much of this specific post. I do believe there's a transition period and in half a decade, we might start seeing a dedicated ECS authoring world. Either that, or GameObjects become modified to be even better at the authoring role. I also agree that we will likely see some middle-ground authoring tools between [GenerateAuthoringComponent] and IConvertGameObjectToEntity. I guess to clarify what I said earlier, I don't believe [GenerateAuthoringComponent] is exclusively a prototyping tool. There really are times when the authoring data and runtime data are practically identical. But most of the time it is not, and hence most of the time [GenerateAuthoringComponent] works better as a prototyping tool (because when you are composing code, you really want the authoring data to be the runtime data in the short term). I don't think the middle ground will be more attributes. I suspect we may be seeing some codegenned base classes built on top of IConvertGameObjectToEntity. But who knows?

    Regardless, none of this applies to runtime initialization, and that's all I and others having been trying to tell you.
     
  26. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    Yes, it does. Because once you need to runtime initialize values with the hybrid workflow, you are left with a number of options that have needless boilerplate..... Just to set a single float value.

    The impulse of this thread was to start a discussion for how runtime initialization of hybrid entities should occur without so much boilerplate or project file pollution in the hybrid workflow, and seemed to quickly devolve into having to prove that it's necessary. Maybe I just have a particular distaste for high boilerplate or high maintenance code separation.
     
  27. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    FWIW, I've already implemented a prototype of the Interface method in the OP. Once it's done, I'll release it to the community and save everyone from having to make pointless 30 line MonoBehavior + IConvertGameObjectToEntity implementations that pretty much exist to set a value or two.

    Lets look at it by example...

    Here is what you needed before, for the bare minimum, which needs to exist as a separate file because it's a MonoBehaviour. Along with the file that implements IComponentData.

    Code (CSharp):
    1. using Unity.Entities;
    2. using UnityEngine;
    3.  
    4. //This monobehaviour exists soley to initialize the Component
    5. public class PlacementBallisticMotionInit : MonoBehaviour, IConvertGameObjectToEntity
    6. {
    7.     public Vector3 InitialImpulse;
    8.  
    9.     public bool UsePhysicsGravity = true;
    10.     public float Gravity = -9.8f;
    11.  
    12.     void IConvertGameObjectToEntity.Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    13.     {
    14.         dstManager.AddComponentData<PlacementBallisticMotion>(entity,
    15.             new PlacementBallisticMotion
    16.             {
    17.                 LastPosition = transform.position,
    18.                 Impulse = InitialImpulse,
    19.                 Gravity = Gravity
    20.             }
    21.         );
    22.     }
    23. }

    Now, compare that with how to initialize using the interface. No extra files, just implement the interface:

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Mathematics;
    3. using Unity.Transforms;
    4.  
    5. [GenerateAuthoringComponent]
    6. public struct PlacementBallisticMotion : IComponentData, IComponentDataInitialize
    7. {
    8.     public float3 LastPosition;
    9.     public float3 Impulse;
    10.     public float Gravity;
    11.  
    12.     public void Initialize(Entity entity, EntityManager manager)
    13.     {
    14.         LastPosition = manager.GetComponentData<Translation>(entity).Value;
    15.     }
    16. }
    23 lines of MonoBehaviour boilerplate removed. One unnecessary file to maintain removed (imagine if I added a property to the ICD, I need to remember this one has a MonoBehaviour to use it).

    Replaced by three lines in the IComponentData definition, which make it clear that this is initialized at runtime
     
    Last edited: Aug 11, 2020
  28. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    This code doesn't work when authored as a prefab saved to a subscene. How exactly are you initializing the transform data at runtime? You aren't instantiating a GameObject at runtime and running GameObjectConversion on it, are you?
     
  29. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    You're absolutely correct.

    Considering this is the "easy way" to initialize ICD's with data, but still doesn't address runtime data further validates we need some generalized initialization system
     
  30. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    As I have said before, reactive systems work pretty well for runtime initialization. Here's a real-world example: https://github.com/Dreaming381/lsss...earchAndDestroyInitializePersonalitySystem.cs

    Is it very different-looking from authoring initialization? Yes. Should it? I would generally think it should look different. After all, authoring and runtime are very different beasts. You don't always care about your authoring mechanisms to be performant, but you really care that runtime initialization is fast. So naturally you are going to write runtime initialization in the DOTS style where such initialization can be fast.
     
    Timboc likes this.
  31. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I don't know what your workflow is, but this whole thread feels like you're making a problem rather than solving one :confused:

    I really dislike the idea of putting runtime logic in authoring.
     
  32. InnerScript

    InnerScript

    Joined:
    Jan 29, 2017
    Posts:
    38
    I understand that, that and agree initialization should be fast. Using a Tag and System like you have is also something I've created as part of this learning/exploration process, and I've even consolidated it a bit further to reduce boilerplate and new classes to a minimum. It's one generic Tag and one System for many different ICD types in a custom System Group, which is closing in on something acceptable.


    But, I'm going to stand by my argument that authoring and initialization in in the Hybrid ECS workflow is still more cumbersome than it needs to be. That there's a lot of room for improvement, and we can build those improvements ourselves.

    The example you provided is 48 lines of boilerplate, one new class, one new struct, likely spread across 2-3 folders.... to ultimately execute just three lines of code. That the collective community thinks this is OK blows my mind.


    Yes, I understand that's a well tread pattern for runtime initialization in ECS. It works, it's fast, everyone is doing it. But no, I don't accept that level of boilerplate is how things should be - for any of us using the Hybrid workflow.

    At this moment, my mindset is on making tools to streamline the authoring and runtime initialization processes for Hybrid. Partly as a learning process, partly to streamline my planned Hybrid ECS system, and partly to donate to the community.


    I had hoped to solicit ideas from those more experienced than I of how we can do things better.... But I have to admit, it is a bit disheartening to just hear "go make your boilerplate like everyone else" time and time again.


    PS @DreamingImLatios: I was honestly just browsing through Latios on GitHub before checking the forums. There's lots of excellent ideas for DOTS captured in there, and I appreciate you making it public.

    I see that in the Latios GitHub you've also created a conversion system to automatically add ParticleSystem and ParticleSystemRenderer using AddHybridComponent. Maybe it's because you see the ridiculousness of needing a throw away boilerplate MonoBehaviour and drag and drop process, for each and every Hybrid GameObject that has particles. You also likely know that the "use a MonoBehaviour to add companion hybrid components" is also a well tread and recommended workflow that everyone says to use, but you disagreed with the collective and made the tooling to increase your quality of life, reduce boilerplate, and made that public. Cheers to that.
     
  33. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I think you are looking at the wrong repo. Neither LF version 0.1.0 nor version 0.2.0 have that. I personally use VFX graph for particles, which Hybrid Renderer adds automatically. But to your point, it adds it using a GameObjectConversionSystem and not a MonoBehaviour. So even Unity knows that using a MonoBehaviour just to AddHybridComponent of built-in types is nonsensical.

    Like I said, that was a real-world example. Not a minimal example. When you really look at what is actually there, there is hardly any boilerplate. Let's break it down.

    First, there are two components that I care about: AiSearchAndDestroyPersonality and AiSearchAndDestroyPersonalityInitializerValues. The latter contains a range of possible values I want to uniquely and randomly choose from every time a new AiShip is instantiated, and assign that to AiSearchAndDestroyPersonality. This really is runtime initialization. Consequently, both components contain exclusively necessary runtime data. Now while I do have a custom authoring component for setting these up (because I am nice to designers), I could have just as easily used [GenerateAuthoringComponent] instead for each of these.

    So now I have 2 components defined and a 48 line system that operates on them. However, my system is doing a bunch of complex deterministic RNG stuff inside a job which accounts for many of the lines of code. So if we strip that, the namespace, whitespace, and the usings all out, we end up with this:
    Code (CSharp):
    1. public class AiSearchAndDestroyInitializePersonalitySystem : SubSystem
    2. {
    3.     BeginInitializationEntityCommandBufferSystem m_ecbSystem;
    4.     EntityQuery m_query;
    5.  
    6.     protected override void OnCreate()
    7.     {
    8.         m_ecbSystem = World.GetExistingSystem<BeginInitializationEntityCommandBufferSystem>();
    9.     }
    10.  
    11.     protected override void OnUpdate()
    12.     {
    13.         Entities.WithAll<AiTag>().WithStoreEntityQueryInField(ref m_query).ForEach((ref AiSearchAndDestroyPersonality personality,
    14.                                                                                     in AiSearchAndDestroyPersonalityInitializerValues initalizer) =>
    15.         {
    16.             personality.targetLeadDistance = initalizer.targetLeadDistanceMinMax.x;
    17.         }).Schedule();
    18.  
    19.         m_ecbSystem.CreateCommandBuffer().RemoveComponent<AiSearchAndDestroyPersonalityInitializerValues>(m_query);
    20.         m_ecbSystem.AddJobHandleForProducer(Dependency);
    21.     }
    22. }
    That's less than half the lines!

    The remaining code consists of the initialization in the form of an Entities.ForEach, and the reactive system code. The latter dominates the line count. It is making three decisions:
    1) What sync point to use
    2) What to do with that sync point
    3) What JobHandle to use as the dependency for the ECB itself

    In my projects, I usually have a project-wide sync point. So if I can access that as a property, I can greatly reduce the size of (1). Similarly, (3) could probably be defaulted to Dependency at the end of OnUpdate. I am already planning on automatic sync point management in LF version 0.3.0. The resulting code would most likely look like this:
    Code (CSharp):
    1. public class AiSearchAndDestroyInitializePersonalitySystem : SubSystem
    2. {
    3.     EntityQuery m_query;
    4.  
    5.     protected override void OnUpdate()
    6.     {
    7.         Entities.WithAll<AiTag>().WithStoreEntityQueryInField(ref m_query).ForEach((ref AiSearchAndDestroyPersonality personality,
    8.                                                                                     in AiSearchAndDestroyPersonalityInitializerValues initalizer) =>
    9.         {
    10.             personality.targetLeadDistance = initalizer.targetLeadDistanceMinMax.x;
    11.         }).Schedule();
    12.  
    13.         latiosWorld.syncPoint.getEntityCommandBuffer().RemoveComponent<AiSearchAndDestroyPersonalityInitializerValues>(m_query);
    14.     }
    15. }
    In my opinion, anything less than this starts to creep into black-magic-that-easily-breaks territory.
     
    charleshendry and florianhanke like this.