Baked Prefab Registry (Editor ConversionSystem)

Hello fellow Dotsers,

Can I get a sanity check on this idea… :roll_eyes:

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.

public class PrefabRegistryAuthoring : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs {
   public List<PrefabRegistryEntryAuthoring> PrefabRegistryEntries = new List<PrefabRegistryEntryAuthoring>();

   public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs) {
      referencedPrefabs.AddRange(PrefabRegistryEntries.ConvertAll(prefab => prefab.gameObject));
   }

   public void Convert(Entity entity, EntityManager entityManager, GameObjectConversionSystem gameObjectConversionSystem) {
      var prefabRegistryEntries = PrefabRegistryEntries;

      var prefabRegistrySystem = entityManager.World.GetOrCreateSystem<PrefabRegistrySystem>();
      for (var i = 0; i < prefabRegistryEntries.Count; i++) {
         var prefab = prefabRegistryEntries[i];
         var prefabEntity = gameObjectConversionSystem.GetPrimaryEntity(prefab);

         prefabRegistrySystem.Add(prefabRegistryIdentifier, prefabEntity);
      }
   }
}

this class effectively shoves the Entity references into a NativeHashMap of a small PrefabRegistrySystem:

public class PrefabRegistrySystem : SystemBase {
   public NativeHashMap<int, Entity> Registry;

   protected override void OnCreate() {
      Registry = new NativeHashMap<int, Entity>(300, Allocator.Persistent);
   }

   protected override void OnDestroy() {
      Registry.Dispose();
   }

   public void Add(PrefabRegistryIdentifier identifier, Entity prefabEntity) {
      Registry.Add((int) identifier, prefabEntity);
   }

   public Entity Get(PrefabRegistryIdentifier identifier) {
      return Registry.TryGetValue((int) identifier, out var entity) ? entity : Entity.Null;
   }
}

so that in a SpawnSystem I can look up prefab entities by identifier and instantiate them like so:

public class SpawnSystem : SystemBase {
   private PrefabRegistrySystem prefabRegistrySystem;
   private BeginSimulationEntityCommandBufferSystem beginSimulationEntityCommandBufferSystem;

   protected override void OnCreate() {
      prefabRegistrySystem = World.GetOrCreateSystem<PrefabRegistrySystem>();
      beginSimulationEntityCommandBufferSystem = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
   }

   protected override void OnUpdate() {
      var beginSimulationEntityCommandBuffer = beginSimulationEntityCommandBufferSystem.CreateCommandBuffer().AsParallelWriter();

      var prefabRegistrySystemRegistry = prefabRegistrySystem.Registry;

      Entities.WithReadOnly(prefabRegistrySystemRegistry).ForEach((Entity entity, int entityInQueryIndex, in UnitSpawnCommand unitSpawnCommand) => {
         if (!prefabRegistrySystemRegistry.TryGetValue((int) unitSpawnCommand.PrefabRegistryIdentifier, out var prefabEntity)) {
            var prefabEntityInstance = beginSimulationEntityCommandBuffer.Instantiate(entityInQueryIndex, prefabEntity);

            // Hooray
         }
      }).ScheduleParallel();

      beginSimulationEntityCommandBufferSystem.AddJobHandleForProducer(Dependency);
   }
}

All this works without problems :slight_smile:

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… :stuck_out_tongue:

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

I was thinking something like this might make sense?

public struct PrefabAssetRegistryComponent : IComponentData {
   public BlobAssetReference<BlobHashMap<int, Entity>> HashMap;
}

and in my PrefabRegistryAuthoring.Convert method I’d do something like this:

entityManager.AddComponentData(entity, new PrefabAssetRegistryV2 {
   BlobHashMapAssetReference = BlobHashMapAssetReferenceThatISomehowConstructed
});

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:

var entityQuery =  EntityManager.CreateEntityQuery(typeof(PrefabAssetRegistryComponent));
var entity = entityQuery.GetSingletonEntity();
if (entity != Entity.Null) {
   var prefabAssetRegistry = entityQuery.GetSingleton<PrefabAssetRegistryComponent>();

   if (prefabAssetRegistry.BlobHashMapAssetReference.Value.TryGetValue((int) PrefabRegistryIdentifier.Foo, out var fooPrefabEntity)) {

       // Hooray
   }
}

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 :slight_smile:

It’s me again…
It’s not going to work.

You don’t have TypeIndex problem.
But Blob+Entity will break you.

1 Like

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?

Here’s my solution(attached source)
And example:
To create a prefab store:

using Unity.Entities;

[assembly: RegisterGenericComponentType(typeof(SRTK.FXPrefabsStub.NameMap))]
[assembly: RegisterGenericComponentType(typeof(SRTK.FXPrefabsStub.PrefabItem))]
namespace SRTK
{
    public class FXPrefabsStub : PrefabStoreStub<FXPrefabsStub> { }
    public class FXPrefabsStoreAuthoring : FXPrefabsStub.Authoring { }
}

To get a prefab in managed function

var VFPrefab = FXPrefabsStub.GetSingleton(this)["VFPoint"];

To get a prefab in a job

            var fxPrefabJobField = FXPrefabsStub.GetCopy(this, Allocator.TempJob);
            var ECBP = ECBS.CreateCommandBuffer().AsParallelWriter();
            FixedString64 key = "VFPoint";
            Entities.WithAll<CoreTag>()
            .ForEach((int entityInQueryIndex, Entity e) =>
            {
                ECBP.Instantiate(entityInQueryIndex, fxPrefabJobField[key]);
            }).ScheduleParallel();
            fxPrefabJobField.Dispose(Dependency);
            ECBS.AddJobHandleForProducer(Dependency);

6652573–760006–HashMapBlob.cs (19.5 KB)
6652573–760012–PrefabStore.cs (10.8 KB)

2 Likes

Thanks, I’ll check it out tomorrow

Yes, and you can store Entity in a DynamicBuffer. that will be remapped.

1 Like

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 :slight_smile:

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

2 Likes

I created a conversion flow that is prefab instead of scene based. So there is a base MB class that has a List 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.

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.

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.