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

Resolved How can I get ECS to treat ScriptableObjects as Prefabs?

Discussion in 'Entity Component System' started by Sovogal, Mar 21, 2023.

  1. Sovogal

    Sovogal

    Joined:
    Oct 15, 2016
    Posts:
    98
    I'm hoping someone has some good ideas for how to make this work. Everything I have attempted ends up extremely convoluted.

    When I bake something with a prefab reference, ECS will bake that reference as though it's a standard subscene GameObject, attach a Prefab component to the resulting entities, update any references it finds on other entities to reference the resulting top-level entity. When I subsequently instantiate this prefab reference, all entities and components that resulted from the initial bake get created and remapped automagically.

    I'd like to be able to do something similar with ScriptableObjects, where I can bake it on the first reference, reference the scriptable "prefab," and instantiate all of the resulting entities and components and have them remap appropriately.

    Looking at the code for the Baker class, it appears to store everything it does in an ECB. So, if I baked my scriptable objects with an ECB, I should have access to all of the commands I used for baking. I just need to find a way to store this off so that I can replay it when I need to instantiate. Am I on the right track? Does anyone have a simpler or more achievable approach?

    Thanks!
     
    apkdev likes this.
  2. UniqueCode

    UniqueCode

    Joined:
    Oct 20, 2015
    Posts:
    40
    Well, take a look at all the options the IBaker interfaces gives you. You can create additional Entities, attach components etc. Depending on what you are trying to do, baking the ScriptableObject into a blob asset or buffers might make more sense.
     
    Deleted User likes this.
  3. Sovogal

    Sovogal

    Joined:
    Oct 15, 2016
    Posts:
    98
    Okay, this turns out to be WAY easier than I had thought.

    At first, I thought I would need to bake my hierarchy with an EntityCommandBuffer passed through the chain by ref. I created a singleton that stored the resulting EntityCommandBuffers in a NativeHashMap keyed by ID. I also exposed an instantiation method to queue up the instantiation request. In a system, I just call ProcessQueue to empty out the instantiation request queue. Code looked like this:


    Code (CSharp):
    1.         [BurstCompile]
    2.         public struct Singleton : IComponentData, IQueryTypeParameter
    3.         {
    4.             private NativeHashMap<int, IntPtr> _bakers;
    5.             private NativeHashMap<int, Entity> _roots;
    6.             private NativeQueue<IndexedScriptableEntity> _spawnQueue;
    7.          
    8.             private Singleton(EntityManager manager)
    9.             {
    10.                 var queryDesc = new EntityQueryDesc
    11.                 {
    12.                     All = new Unity.Entities.ComponentType[]
    13.                     {
    14.                         Unity.Entities.ComponentType
    15.                             .ReadOnly<Unity.Entities.EndSimulationEntityCommandBufferSystem.Singleton>()
    16.                     },
    17.                     Any = new Unity.Entities.ComponentType[] { }, None = new Unity.Entities.ComponentType[] { },
    18.                     Options = Unity.Entities.EntityQueryOptions.Default |
    19.                               Unity.Entities.EntityQueryOptions.IncludeSystems
    20.                 };
    21.                 _bakers = new NativeHashMap<int, IntPtr>(0, Allocator.Persistent);
    22.                 _roots = new NativeHashMap<int, Entity>(0, Allocator.Persistent);
    23.                 _spawnQueue = new NativeQueue<IndexedScriptableEntity>(Allocator.Persistent);
    24.             }
    25.  
    26.             public static Singleton Create(EntityManager manager)
    27.             {
    28.                 var singleton = new Singleton(manager);
    29.                 var entity = manager.CreateEntity();
    30.                 manager.SetName(entity, "IndexedScriptableObjectSingleton");
    31.                 manager.AddComponentData(entity, singleton);
    32.  
    33.                 return singleton;
    34.             }
    35.          
    36.             public bool Contains(int id)
    37.             {
    38.                 return _bakers.ContainsKey(id);
    39.             }
    40.  
    41.             [BurstCompile]
    42.             public void Register(int id, EntityCommandBuffer ecb, Entity root)
    43.             {
    44.                 if (_bakers.ContainsKey(id))
    45.                 {
    46.                     throw new InvalidOperationException($"Prefab with ID {id} already registered");
    47.                 }
    48.  
    49.                 BlobBuilder builder = new BlobBuilder(Allocator.Temp);
    50.              
    51.                 IntPtr ecbPtr = Marshal.AllocHGlobal(Marshal.SizeOf<EntityCommandBuffer>());
    52.                 Marshal.StructureToPtr<EntityCommandBuffer>(ecb, ecbPtr, false);
    53.              
    54.                 _bakers.Capacity++;
    55.                 _bakers.Add(id, ecbPtr);
    56.                 _roots.Capacity++;
    57.                 _roots.Add(id, root);
    58.             }
    59.  
    60.             [BurstCompile]
    61.             public void ProcessQueue(EntityManager manager)
    62.             {
    63.                 while (_spawnQueue.Count > 0)
    64.                 {
    65.                     var indexedScriptableEntity = _spawnQueue.Dequeue();
    66.                     var spawnECB = Marshal.PtrToStructure<EntityCommandBuffer>(_bakers[indexedScriptableEntity.ID]);
    67.                     spawnECB.SetComponent(_roots[indexedScriptableEntity.ID], new RootData
    68.                     {
    69.                         Value = indexedScriptableEntity.NewRoot
    70.                     });
    71.                     Debug.Log("Spawning");
    72.                     spawnECB.Playback(manager);
    73.                 }
    74.             }
    75.  
    76.             public Entity Instantiate(int id, Entity newRoot)
    77.             {
    78.                 if (_bakers.ContainsKey(id))
    79.                 {
    80.                     _spawnQueue.Enqueue(new IndexedScriptableEntity
    81.                     {
    82.                         ID = id,
    83.                         NewRoot = newRoot
    84.                     });
    85.  
    86.                     return _roots[id];
    87.                 }
    88.                 else
    89.                 {
    90.                     throw new InvalidOperationException($"Prefab with ID {id} not found");
    91.                 }
    92.             }
    93.         }
    94.  
    95.         internal struct IndexedScriptableEntity
    96.         {
    97.             public int ID;
    98.             public Entity NewRoot;
    99.         }
    At runtime, I received the following error indicating that you can't reply an EntityCommandBuffer that adds components that have entity references. Kind of a bummer.

    upload_2023-3-22_13-26-44.png

    However, it's much simpler than this. All you need to do is have prefab components on every new entity you spawn, and on the root entity, you must have a LinkedEntityGroupBuffer that includes the root and all children. Now, you can Instantiate the root just as you would a prefab.
     
    apkdev likes this.
  4. Sovogal

    Sovogal

    Joined:
    Oct 15, 2016
    Posts:
    98
    The main issue with baking it all into a blob asset is that I'm baking components that are used by systems for chunks of functionality that are composed at design time and are stateful. For example, a "skill" that works through a stateful timeline amd executes configurable events. The components used in this are being used elsewhere and throughout the framework in various ways to improve reuse, so refactoring into a BlobAssetReference would prove a difficult task.
     
    apkdev likes this.