Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feedback Need for a ScriptableObject replacement

Discussion in 'Entity Component System' started by burningmime, Jun 23, 2020.

  1. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Right now there seems to be no easy way to load an entity-containing asset (eg a set of prefabs + some metadata) then instantiate that. You need to use hacks involving MonoBehaviour + SubScene. For a concrete example, consider a set of "props" that go through walls (doors and windows) in a building editor. I don't know a priori where they'll be or which ones will be used, since the user places them. But once the user places them, I need to instantiate the prefabs.

    In the MB world, this was a ScriptableObject, with an array of GameObjects (prefabs) for the props plus some metadata about them. Here's a simplified version of the code I had:

    Code (CSharp):
    1.     public class PropSet : ScriptableObject
    2.     {
    3.         public GameObject[] propObjects;
    4.         public int[] propIdToHoleId;
    5.         public HoleSet holeSet;
    6. }
    Now here's the equivalent in ECS land:

    Code (CSharp):
    1.     [RequiresEntityConversion]
    2.     [AddComponentMenu("building/PropSet")]
    3.     public class PropSet_Authoring : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
    4.     {
    5.         public GameObject[] propObjects;
    6.         public int[] propIdToHoleId;
    7.         public HoleSet holeSet;
    8.  
    9.         public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs) =>
    10.             referencedPrefabs.AddRange(propObjects.Where(po => po != null).Distinct());
    11.  
    12.         public void Convert(Entity e, EntityManager dst, GameObjectConversionSystem gcs)
    13.         {
    14.             dst.AddComponentData(e, new PropSetComponent { holes = holeSet.convertToBlobAsset(gcs.BlobAssetStore) });
    15.             dst.SetName(e, "PropSet root");
    16.             DynamicBuffer<PropPrefab> props = dst.AddBuffer<PropPrefab>(e);
    17.             props.ResizeUninitialized(propObjects.Length);
    18.             for(int i = 0; i < propObjects.Length; ++i)
    19.             {
    20.                 Entity pe = Entity.Null;
    21.                 if(propObjects[i] != null)
    22.                 {
    23.                     pe = gcs.GetPrimaryEntity(propObjects[i]);
    24.                     dst.SetName(pe, "PropSet prefab root: " + propObjects[i].name);
    25.                 }
    26.                 props[i] = new PropPrefab
    27.                 {
    28.                     entity = pe,
    29.                     holeId = propIdToHoleId[i]
    30.                 };
    31.             }
    32.         }
    33.     }
    34.  
    35.     internal struct PropSetComponent : IComponentData
    36.     {
    37.         public BlobAssetReference<HoleSet.Blob> holes;
    38.     }
    39.  
    40.     internal struct PropSetData
    41.     {
    42.         public PropSetComponent set;
    43.         public DynamicBuffer<PropPrefab> prefabs;
    44.     }
    45.  
    46.     internal struct PropPrefab : IBufferElementData
    47.     {
    48.         public Entity entity;
    49.         public int holeId;
    50.     }
    51.  
    52.     // omitting HoleSet and the BlobAsset, which in itself is an adventure
    53.     // to build, but you get the picture
    Now I might need a measuring tape to be sure, but I feel like the one on the bottom is a little longer. And aside from code complexity...

    1. adding a SubScene with an empty GameObject feels kludgy
    2. these prefab/data-only entities live in the same world as real entities, potentially confusing queries
    3. you can't easily refrence a subscene from the editor
    4. loading a subscene (given a guid or addressable) is often painful

    This easily applies to other types of games/apps. For example, consider an RTS where there is a set of units that can be spawned.
     
    Last edited: Jun 23, 2020
  2. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    Isn't it what blob assets are made for ? you can create a scriptable object to edit the data in the editor and on conversion you make a blobasset representing that data at runtime to use it in your systems.
     
    burningmime likes this.
  3. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    You can't store prefabs in blobs, AFAIK. I'm using them (see above) for things that are easily data only.
     
  4. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    Then declare the prefab and store the entity.
     
    burningmime likes this.
  5. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    You can store an Entity in the Blobs without any issue.

    EDIT: @WAYN_Group was faster than me lol
     
    burningmime likes this.
  6. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    OMG REALLY!? 135791113!!!!

    Well, that partly answers my questions. Is there a way to make them Addressable or otherwise load them at runtime?
     
  7. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    Blob Assets can be attached to entity component data with blob asset reference so you can build them during convertion and add them to the entity.

    I have no experiance with adressable sorry :oops:
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,222
    While you can technically store entities (prefab or not) in blobs, you shouldn't. The reason is that entity remapping is not applied to blobs. The entity values (index and version) stored in a subscene are not the same as the entities they become when the subscene is loaded at runtime. They are remapped. But the blob has no way of knowing this and will point to the wrong entities. Catastrophe may ensue. Even if you only create blobs at runtime and avoid this, I'm 90% confident that your blob logic is leaky. It is very hard to create blobs at runtime without generating a memory leak.

    Converting SOs to entities is the correct approach. Yes it is more code than using SOs as is, but that is the price to pay for separating authoring and runtime data, which is required to obtain the performance DOTS promises. I expect Unity will improve this once they get conversion, subscenes, and live link fully stabilized. That has been quite the battle for them.
     
  9. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    OK, thanks, that's what I thought. So can I convert the SO to an entity somehow (at build time) without needing a SubScene, and load those entities up at runtime? Or am I stuck with using almost-empty SubScenes?
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,222
    Any conversion done at build time goes into a subscene. So if you want it to exist as an entity at build time, then it needs to be in a subscene. Whether that subscene is a separate subscene or the subscene containing the user of that data depends on your use case. I personally use a runtime entity merging mechanism when dealing with data I want to be truly global. Then I author that global state as a prefab I can drop into any scene.
     
    burningmime likes this.
  11. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    OK cool, but that still gets back to...

    1. How can I load a SubScene synchronously (or get a callback when it's loaded, at least)?
    2. How can I load or unload a SubScene given a path, guid, or SceneAsset? (I don't actually want this data to be global -- loading all the props would be a memory sink on low-end hardware, just the one in use.)
    3. How can I avoid prefabs living in the same World as real entities?
    4. Why does this all feel so hacky? (empty game objects in subscenes, splitting data across components vs buffer elements, etc).

    Also, that "runtime entity merging mechanism" sounds cool; can you share more about it? Is it part of your framework?
     
    Last edited: Jun 25, 2020
  12. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,222
    1) It has something to do with the RequestSceneLoaded component. I haven't dug into it. I use default subscene streaming for partitioning the world and use real scenes for real scenes. It has also been a little bit since I have played with subscenes. I'm still mostly in the code development phase of my game where runtime conversion of a small number of entities plus runtime proc gen has been a more stable workflow.
    2) Memory management for mobile in DOTS isn't really a thing yet. It is something they have planned, but need to get other pieces working first.
    3) Don't bother trying to prevent this. It is messy and really your issue is with the debugging tools rather than the implementation itself.
    4) For one, it is in preview and not finished. But also, splitting authoring and runtime data apart is a massive paradigm shift for Unity that makes a lot of people uncomfortable. It is very powerful though once you understand what it can do.

    As for the merging mechanism, yes. That is part of the framework, which you can find a pre-release version of embedded in my WIP space shooter. The relevant code definitions can be found here: https://github.com/Dreaming381/lsss...work/Core/Core/Components/GlobalEntityData.cs
    Docs: https://github.com/Dreaming381/Latios-Framework/tree/master/Documentation/Core#global-entities

    If your game is composed of just a single giant open world, it doesn't add much. But if you are making a game with multiple levels split into separate scenes (scenes, not subscenes), then this merging mechanism makes reasoning about more globalized data a lot easier when writing systems.
     
    burningmime likes this.
  13. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    988
    Looking up the no remmap of entity in subscene blob asset I found this thread :
    https://forum.unity.com/threads/entity-inside-blobs.767144/

    Not totally sure I understand why it does not work because all posts about it feel contradictory but Joachim Ante seems to propose a work around :
    Store the entity reference in a dynamic buffer and sotre the index to that entity in the blob asset.
     
  14. Stroustrup

    Stroustrup

    Joined:
    May 18, 2020
    Posts:
    142
    Code (CSharp):
    1. public struct BlobContainer : IComponentData {
    2.     public BlobAssetReference<ManagedContainer> value;
    3.     public struct ManagedContainer {
    4.         public GameObject gameObject;
    5.         public Action invokable;
    6.     }
    7. }
    you can store anything in a blob, and thereby overcome the non blittable type restriction on IComponentData, but you'll have to use withoutburst() when accessing a component containing a managed blob
     
  15. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    You can't do that. It's not how blobs work and it's absolutely not intended. Blobs have a similar restriction - blittable types. Unity just not implemented yet validators for that (this is why you can put that struct as a generic argument, but reference types in that struct will break any time unexpectedly).
    upload_2020-6-26_19-33-32.png

    upload_2020-6-26_19-35-25.png
    FYI @s_schoener

    For that
    class
    IComponentData exists.
     
  16. Stroustrup

    Stroustrup

    Joined:
    May 18, 2020
    Posts:
    142
    what about native hashmaps, native lists etc? is them working in a burstable context inside an icomponent data struct inside a blobasset intended?
     
    Last edited: Jun 26, 2020
  17. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    Only
    unsafe
    versions as they used just pure unmanaged memory and pointers (but you should understand what are you doing). For plain
    NativeList
    etc. compiler will complain to you about
    DisposeSentinel
    , which is neither primitive nor blittable. And at the same time you can use
    NativeList
    etc. in
    class IComponentData
    , but only on the main thread and without burst.
     
  18. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    I'm clearly no expert, but I think you can convert lists to BlobArray fairly easily. Hashes seem like they'd harder to store; you'd need to come up with a custom way to store them if you need O(1) access (or just store them as a BlobArray then load them up into a hash at runtime if you can eat the startup time and performance).

    One thing you can do with blobs that you can't do as easily with components is a primitive form of OO/subclassing (since their storage size doesn't need to be fixed). Physics does this for colliders.