How to Bake a Dictionary of Entities ?

I’m so excited that I finally started to understand ECS and how Bakers work, only to realize that I can’t even do something as simple as saving baked entities in a Dictionary to access them via a key.

Here’s what I’m trying to do:

I have a list of EntityDefinition, which are scriptable objects that reference models of my entities. They have fields like id (which I’ll use as a key) and a GameObject as a prefab (the part for the Baker).

I create a MonoBehaviour in a Sub Scene to store the list of all my entities. I process it in two ways:

  • The classic way, where I transform the list in Awake into a Dictionary<int, EntityDefinition> to retrieve a definition by its id.
  • A Baker, which creates an Entity containing an IComponentData with a NativeHashMap<int, Entity>, similar to the classic way.

Here is my code:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using Unity.Entities;
using Unity.Collections;

public class EntityDictionaryAuthoring : MonoBehaviour
{

  public EntityDefinition[] entityList = new EntityDefinition[0];

  // -------------------------------
  // Processed indexed Definitions (not Baked, keep as Dictionary)
  // -------------------------------
  // Be able to get access to the Dictionary with definition.id without looping the array
  public Dictionary<int, EntityDefinition> entities = new Dictionary<int, EntityDefinition>();

  // Tools
  // ----------------
  public int getMaxId()
  {
    int id = -1;
    foreach (EntityDefinition definition in entityList)
      if (definition.id > id)
        id = definition.id;
    return id;
  }


  // Process
  // ----------------
  // Process array of Definition as indexed Dictionary

  private void ProcessDictionary()
  {
    foreach (EntityDefinition definition in this.entityList)
      entities.Add(definition.id, definition);
  }
  void Awake()
  {
    ProcessDictionary();
  }


  // Baker
  // ----------------
  // Transform all entity Prefabs into ECS Entity

  private class Baker : Baker<EntityDictionaryAuthoring>
  {
    public override void Bake(EntityDictionaryAuthoring authoring)
    {
      Entity entity = GetEntity(TransformUsageFlags.None);

      // Create an Native HashMap for Baked Entity
      NativeHashMap<int, Entity> entities = new NativeHashMap<int, Entity>(authoring.getMaxId() + 1, Allocator.Persistent);
      foreach (EntityDefinition definition in authoring.entityList)
        entities.Add(definition.id, GetEntity(definition.prefabAuthoring, TransformUsageFlags.Dynamic));

      // Create a EntityDictionary IComponentData
      AddComponent(entity, new EntityDictionary
      {
        entities = entities,
      });
    }
  }
}


  // IComponentData
  // ----------------

public struct EntityDictionary : IComponentData
{
  public NativeHashMap<int, Entity> entities;
}


  // Scriptable EntityDefinition
  // ----------------

[System.Serializable]
[CreateAssetMenu(fileName = "Entity", menuName = "Infinitory/Entity/Entity", order = 50)]
public class EntityDefinition : ScriptableObject
{

  public int id;
  public GameObject prefabAuthoring;
}

But now, I have a lot of errors:

ArgumentException: Blittable component type ‘EntityDictionary’ on GameObject ‘DefinitionDictionary’ contains a (potentially nested) pointer field. Serializing bare pointers will likely lead to runtime errors. Remove this field and consider serializing the data it points to another way such as by using a BlobAssetReference or a [Serializable] ISharedComponent. If for whatever reason the pointer field should in fact be serialized, add the [ChunkSerializable] attribute to your type to bypass this error.
Unity.Entities.EntityDiffer.ThrowNonSerializationExceptions (Unity.Collections.NativeList`1[T] debugInfo) (at ./Library/PackageCache/com.unity.entities/Unity.Entities/Diff/EntityDifferComponentChanges.cs:1714)

InvalidOperationException: The previously scheduled job EntityDiffer:BuildEntityGuidToEntity reads from the Unity.Collections.NativeArray1[Unity.Entities.EntityDiffer+EntityInChunkWithGuid] BuildEntityGuidToEntity.SortedEntitiesWithGuid. You must call JobHandle.Complete() on the job EntityDiffer:BuildEntityGuidToEntity, before you can deallocate the Unity.Collections.NativeArray1[Unity.Entities.EntityDiffer+EntityInChunkWithGuid] safely.

Question 1: From what I understand, NativeHashMap is not “blittable” and won’t keep the data once baked. Is that correct?

Solutions?
A. Bake it at runtime when the game starts using Job.OnStartRunning?
B. Another better solution I haven’t thought of?

Question 2 (bonus): I haven’t tested this yet, but I read that MonoBehaviour on Sub Scenes is not available at runtime. So, my method of storing a Dictionary<int, EntityDefinition> won’t work either.

Solutions:
A. Store my entityList on another MonoBehaviour on the scene directly and access it from the Baker (using a Singleton, for example, or a reference stored on the Sub-scene authoring MonoBehaviour)?
B. Have two lists, one on the scene and another (the same) on the Sub-scene?

Can anyone help me out with these issues?

Normally I suggest that you put your pairs in a dynamic buffer, and then in a system at runtime convert it back into a NativeHashMap. However, typically people who ask this question are looking for a static dictionary replacement, whereas yours seems to be an instance.

In that case, this might be of interest to you. It is a DynamicHashMap implementation that can properly handle Entity references and blob asset references. Latios-Framework/Core/GameplayToolkit/DynamicHashMap.cs at v0.10.1 · Dreaming381/Latios-Framework · GitHub

Actually, I’m not sure what I need. I want to follow the ECS way of doing things, but I don’t fully understand it.

I’m creating a Factory City-builder game, so I need to be able to say, “the player wants to create BuildingA” (for me, this is actually a definition, and BuildingA is an ID for this definition).

For example, I have a Recipe that can transform Entity X into Entity Y, and I still need to reference them by their definition models, not a baked entity.

Or maybe I’m thinking too much in the “legacy” way?

One question about your solution:

Why do I have to go throug a Dynamic Buffer and on start of runtime transform to NativeHashMap instead of just creating my MonoBehaviour on my Scene, and create a Job to “Bake” everything direclty ?

If I understand it (still hard for me all those new things) I can use this DynamicHashMap the same way I’m using my NativeHashMap and it’ll handle the “Serializable issue” because in background it use something blittable ?

Baking is editor only. It doesn’t happen at runtime. And the Entity IDs are not stable. They have to be remapped, which requires they be stored in certain types that Unity knows how to remap. The DynamicHashMap implementation I shared is designed such that Unity knows how to find it and perform remapping.

As a general rule, if you try to modify a MonoBehaviour in any way inside a Baker, you are very likely doing something very wrong.

First, I want to avoid any misunderstandings. The IDs I’m talking about are integers that I set myself for each definition. These are model IDs (definition IDs), not entity IDs. So, my IDs are not for the entities (which are instances of these definitions).

I’m not trying to modify a MonoBehaviour, but rather to create a “dictionary (old way)” of definition.id => baked prefab so that I can later, in my jobs or other ECS processes, instantiate entities of this type (for example, saying “Instantiate a new entity with definitionId=4”).

I a hashmap, the key is an integer, and the value is an prefab entity reference.

You have to store this “dictionary” somewhere. And if you want its data to come from baking, you have to store it in a form where Unity knows how to remap the prefab entity references.

Do you use your DynamicHashMap for this?
I’m trying to use it for a dictionary of prefabs, but without success, and I didn’t found any example…
Could you please provide one. Just how to initialize it, thanks.

No I never used it.
In my case I used a classic Dictionary

I don’t structure my data this way. I know it is a common thing people like to do, but I have never made sense of what problem it actually solves.

It should just be this.

struct ExamplePair : IBufferElementData
{
	public DynamicHashMap<int, Entity>.Pair pair;
}

// In baker
var hashmapBuffer = AddBuffer<ExamplePair>(entity);
var hashmap = new DynamicHashMap<int, Entity>(buffer.Reinterpret<DynamicHashMap<int, Entity>.Pair>());

Thank you very much!
I need to dive into reinterpret buffer and low level memory management.

Just because I’m curious : the problem it solve is the need to instanciate various prefab from various spawners (that can be entities like a rabbit can spawn a baby rabbit) that may be managed in multiple systems (not all spawners have the same logic).
The number of creatures / items might be quite big.
Last constraint : because everything is either generated procedurally or loaded from a save, the subscene is empty (just an object with a list of prefabs to have them baked but not instanciated)

How would you approach this kind of issues?

Sorry I wanted to answer @DreamingImLatios

If you have a struct with only one field, you can reinterpret between the struct type and the field type and back without any issues. In this case, it is basically just “peeling back” the wrapper struct so that the DynamicHashMap can work with the pairs directly.

I think I do something kinda like this in LSSS. In a subscene, I have some config objects which describe the root data of procedural generation. I usually split the config into multiple objects because it ends up being less code for me. I have a config for each faction, as well as a config for a “spawner spawner” (it procedurally generates dynamic orbiting spawners). Each of these configs reference a few prefabs. Then those prefabs reference other prefabs, and so-on. The entities themselves contain the prefab references the systems that operate on them need. And therefore, there’s no need for some global directory.

LSSS is pretty simple, but I have a couple of prototypes that do more complex proc gen with randomization or dynamic gameplay spawning. The pattern still works. Sometimes I’ll make prefabs that solely exist to reference other prefabs (like a sound bank to randomize from). But I never need to key into these hashmap style.