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

Question Baked Prefab Registry (Editor ConversionSystem)

Discussion in 'Entity Component System' started by nicolasgramlich, Dec 23, 2020.

  1. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    Hello fellow Dotsers,

    Can I get a sanity check on this idea... :rolleyes:

    TL;DR: I'm trying to bake prefabs and a lookup map into a subscene instead of converting it at runtime.



    Currently I have a
    PrefabRegistryAuthoring
    which basically holds a list of prefabs to be converted to Entities.

    Code (CSharp):
    1. public class PrefabRegistryAuthoring : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs {
    2.    public List<PrefabRegistryEntryAuthoring> PrefabRegistryEntries = new List<PrefabRegistryEntryAuthoring>();
    3.  
    4.    public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs) {
    5.       referencedPrefabs.AddRange(PrefabRegistryEntries.ConvertAll(prefab => prefab.gameObject));
    6.    }
    7.  
    8.    public void Convert(Entity entity, EntityManager entityManager, GameObjectConversionSystem gameObjectConversionSystem) {
    9.       var prefabRegistryEntries = PrefabRegistryEntries;
    10.  
    11.       var prefabRegistrySystem = entityManager.World.GetOrCreateSystem<PrefabRegistrySystem>();
    12.       for (var i = 0; i < prefabRegistryEntries.Count; i++) {
    13.          var prefab = prefabRegistryEntries[i];
    14.          var prefabEntity = gameObjectConversionSystem.GetPrimaryEntity(prefab);
    15.  
    16.          prefabRegistrySystem.Add(prefabRegistryIdentifier, prefabEntity);
    17.       }
    18.    }
    19. }
    this class effectively shoves the Entity references into a
    NativeHashMap
    of a small
    PrefabRegistrySystem
    :

    Code (CSharp):
    1. public class PrefabRegistrySystem : SystemBase {
    2.    public NativeHashMap<int, Entity> Registry;
    3.  
    4.    protected override void OnCreate() {
    5.       Registry = new NativeHashMap<int, Entity>(300, Allocator.Persistent);
    6.    }
    7.  
    8.    protected override void OnDestroy() {
    9.       Registry.Dispose();
    10.    }
    11.  
    12.    public void Add(PrefabRegistryIdentifier identifier, Entity prefabEntity) {
    13.       Registry.Add((int) identifier, prefabEntity);
    14.    }
    15.  
    16.    public Entity Get(PrefabRegistryIdentifier identifier) {
    17.       return Registry.TryGetValue((int) identifier, out var entity) ? entity : Entity.Null;
    18.    }
    19. }
    so that in a
    SpawnSystem
    I can look up prefab entities by identifier and instantiate them like so:

    Code (CSharp):
    1. public class SpawnSystem : SystemBase {
    2.    private PrefabRegistrySystem prefabRegistrySystem;
    3.    private BeginSimulationEntityCommandBufferSystem beginSimulationEntityCommandBufferSystem;
    4.  
    5.    protected override void OnCreate() {
    6.       prefabRegistrySystem = World.GetOrCreateSystem<PrefabRegistrySystem>();
    7.       beginSimulationEntityCommandBufferSystem = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
    8.    }
    9.  
    10.    protected override void OnUpdate() {
    11.       var beginSimulationEntityCommandBuffer = beginSimulationEntityCommandBufferSystem.CreateCommandBuffer().AsParallelWriter();
    12.  
    13.       var prefabRegistrySystemRegistry = prefabRegistrySystem.Registry;
    14.  
    15.       Entities.WithReadOnly(prefabRegistrySystemRegistry).ForEach((Entity entity, int entityInQueryIndex, in UnitSpawnCommand unitSpawnCommand) => {
    16.          if (!prefabRegistrySystemRegistry.TryGetValue((int) unitSpawnCommand.PrefabRegistryIdentifier, out var prefabEntity)) {
    17.             var prefabEntityInstance = beginSimulationEntityCommandBuffer.Instantiate(entityInQueryIndex, prefabEntity);
    18.  
    19.             // Hooray
    20.          }
    21.       }).ScheduleParallel();
    22.  
    23.       beginSimulationEntityCommandBufferSystem.AddJobHandleForProducer(Dependency);
    24.    }
    25. }
    All this works without problems :)

    The "issue" is that this conversion happens at runtime which is why I can just get the
    PrefabRegistrySystem
    and shove entries into the
    NativeHashMap
    .

    So now, I'm thinking of putting the conversion into a SubScene, because that should be more efficient to load, as opposed to doing the runtime conversion every time I'm opening my game scene, right?

    So I'm thinking of somehow storing a NativeHashMap in a BlobAsset where I'd store the same PrefabIdentifier -> Entity mapping. The BlobAssets gets serialized into the subscene and should load very quickly.

    So this is where my plan gets a little blurry... :p

    Is a BlobAsset the only/correct way of approaching this? Because there doesn't seem to be a BlobHashMap (?) in the collections/entities code :(

    I was thinking something like this might make sense?

    Code (CSharp):
    1. public struct PrefabAssetRegistryComponent : IComponentData {
    2.    public BlobAssetReference<BlobHashMap<int, Entity>> HashMap;
    3. }
    and in my
    PrefabRegistryAuthoring.Convert
    method I'd do something like this:

    Code (CSharp):
    1. entityManager.AddComponentData(entity, new PrefabAssetRegistryV2 {
    2.    BlobHashMapAssetReference = BlobHashMapAssetReferenceThatISomehowConstructed
    3. });
    Is it even a valid approach to persist Entities for lookup like that? (It feels like the Entity, which to my knowledge is basically just an index, wouldn't be valid once the blob asset was deserialized from the subscene?)

    And if/once I got this to work, what would be a good way to pluck out that BlobAsset?
    Something like this:

    Code (CSharp):
    1. var entityQuery =  EntityManager.CreateEntityQuery(typeof(PrefabAssetRegistryComponent));
    2. var entity = entityQuery.GetSingletonEntity();
    3. if (entity != Entity.Null) {
    4.    var prefabAssetRegistry = entityQuery.GetSingleton<PrefabAssetRegistryComponent>();
    5.  
    6.    if (prefabAssetRegistry.BlobHashMapAssetReference.Value.TryGetValue((int) PrefabRegistryIdentifier.Foo, out var fooPrefabEntity)) {
    7.  
    8.        // Hooray
    9.    }
    10. }
    It would be immensely appreciated if someone could give me feedback if this is going into the right direction.
    I'm not 100% sure if this "bake into subscene" is a desired approach in the first place.

    Happy holidays :)
     
  2. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
  3. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    Hmmm... but I could just leave the identifier component on each of my prefab entities themselves and at runtime pluck them out and feed them into a hashmap. Aka the conversion is baked, just the constructing of the hashmap is a quick one time setup.

    Would that work?
     
  4. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    Here's my solution(attached source)
    And example:
    To create a prefab store:
    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. [assembly: RegisterGenericComponentType(typeof(SRTK.FXPrefabsStub.NameMap))]
    4. [assembly: RegisterGenericComponentType(typeof(SRTK.FXPrefabsStub.PrefabItem))]
    5. namespace SRTK
    6. {
    7.     public class FXPrefabsStub : PrefabStoreStub<FXPrefabsStub> { }
    8.     public class FXPrefabsStoreAuthoring : FXPrefabsStub.Authoring { }
    9. }
    10.  
    To get a prefab in managed function
    Code (CSharp):
    1. var VFPrefab = FXPrefabsStub.GetSingleton(this)["VFPoint"];
    To get a prefab in a job
    Code (CSharp):
    1.  
    2.             var fxPrefabJobField = FXPrefabsStub.GetCopy(this, Allocator.TempJob);
    3.             var ECBP = ECBS.CreateCommandBuffer().AsParallelWriter();
    4.             FixedString64 key = "VFPoint";
    5.             Entities.WithAll<CoreTag>()
    6.             .ForEach((int entityInQueryIndex, Entity e) =>
    7.             {
    8.                 ECBP.Instantiate(entityInQueryIndex, fxPrefabJobField[key]);
    9.             }).ScheduleParallel();
    10.             fxPrefabJobField.Dispose(Dependency);
    11.             ECBS.AddJobHandleForProducer(Dependency);
     

    Attached Files:

    Last edited: Dec 23, 2020
    tmonestudio and florianhanke like this.
  5. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    Thanks, I'll check it out tomorrow
     
  6. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    Yes, and you can store Entity in a DynamicBuffer. that will be remapped.
     
    nicolasgramlich likes this.
  7. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    I was just thinking that too. Conversion system creates a small entity that holds a buffer of all prefab entities. And a system that looks for this entity, shoves all the entities by identifier in the map and then destroys that entity (or stops looking for it).

    That sounds pretty fast, easy and efficient :)
     
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    In the past I just stored all prefabs in a subscene, load that subscene first, add Prefab component (these don't serialize for some reason?) and setup the associated maps etc. Avoids a lot of magic.

    Currently rethinking this a little too be more flexible with a new architecture I'm working on
     
    Last edited: Dec 23, 2020
    adammpolak and nicolasgramlich like this.
  9. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    I created a conversion flow that is prefab instead of scene based. So there is a base MB class that has a List<GameObject> and you create a derived class per feature. The flow is like sub scenes conversion is design time. The MB has buttons for convert and load/unload so you can test the results at design time. And a convert all button for convenience to re convert everything globally.

    The implementation was done like a year ago now so it's likely some parts are due for an update and I'm sure it could be improved on.. It uses Odin inspector for the buttons so you would need to replace that with a custom editor script.

    https://gist.github.com/gamemachine/6e1b8f58ccdf4072a42d0baba4b57174
     
  10. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    FYI at runtime I have a system that runs in Initialization that calls PrefabConvertToEntity.LoadAllPrefabs() and then disables itself. Systems that consume the entity prefabs just run ECS queries with EntityQueryOptions.IncludePrefab option. And those feature systems create whatever lookup tables they need with the prefab entities for later instantiation.
     
  11. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    Having systems handle their own lookup tables instead of a centralized is a great idea.

    I have it centralized right now, which works great, but requires to add dependencies between the System that writes to the hashmap and the ones that read from it.