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. Join us on Thursday, June 8, for a Q&A with Unity's Content Pipeline group here on the forum, and on the Unity Discord, and discuss topics around Content Build, Import Workflows, Asset Database, and Addressables!
    Dismiss Notice

Question Unity.Entities.EndFixedStepSimulationEntityCommandBufferSystem?

Discussion in 'Entity Component System' started by Naewulf, Mar 1, 2023.

  1. Naewulf

    Naewulf

    Joined:
    Jun 17, 2019
    Posts:
    19
    I'm trying to instantiate a bunch of 3d tiles that will build a grid/tile kind of map in the screen. It was previously being done successfully with objects, but now I'm refactoring that code to work with ECS.

    Here's what I've done:
    1. Created a subscene;
    2. Put a new game object inside that subscene, so it can be converted to Entity. That's the only object currently in my subscene;
    3. Added a monobehaviour (which has a baker) to that object;
    4. In the Bake function, I filled up a NativeHashMap<int2, MyStructure> field in the authoring with a for loop. It looks something like this: "authoring.Nodes.Add(pos, new MyStruct { stuff... })". One of the fields of this "MyStructure" thing is an Entity type that receives GetEntity() of the prefab that needs to be in that specific struct for instatiation later;
    5. Created a System to handle the instantiation itself using burstcompile. Removing burstcompile changes nothing though;
    6. Added [UpdateInGroup(typeof(InitializationSystemGroup))] to the system struct because I was getting an error while trying to get a singleton that had not yet been created while running the "OnUpdate logic" to get my "Nodes" array.
    7. Did a "for" loop (iterating through all the keys) to do the instantiation in the "OnUpdate" method of that system and that loop does this: "Entity x = ecb.Instantiate(map.Nodes[keys].Entity);". Then, after the for loop finishes I did: "ecb.Playback(state.EntityManager);"
    Relevant Info (maybe): I have an AssetsManager that loads the resources by using "Resources.LoadAll<>()" and stores them in a dictionary in which the key is the asset name. This is done in its Awake method. On the "Bake" function I check if "AssetsManager.Instance" is not null so I can do the for loop described by the item 2 (above). The "Instance" is set before the "Resources.LoadAll<>()" call, and when setting my Entity I do exactly this: "Entity = GetEntity(AssetsManager.Instance.GetAsset(authoring.MapStructure.Tiles.PrefabName).gameObject)". I don't think this is the cause of the problem, because the baker ends up adding the nodes when I get to the System part, so, I think the prefabs have been already loaded by then. After all, the System "for" loop runs through all my 168 nodes which are created by the baker.

    Expected Result: In my tests, only one kind of prefab is being associated with all nodes because I wanted to keep things simple at first. So, for each node I expected to have its associated prefab (which in the test scenario is always the same) instantiated and visible on the screen as an entity in order to build the tile/grid/thing map. If that worked, I would create the real map associating different prefabs to each node as necessary.

    Problem: For some reason when I run this thing I can see in the Entity Hierarchy (runtime) a blue instance of my prefab, complete with all components that it shoud have (I think) at the same level of the game object to which I added the monobehavior with baking. Together with that bunch of Unity internal Systems and Components that fill up the hierarchy at runtime by default, I also get one "Unity.Entities.EndFixedStepSimulationEntityCommandBufferSyste" (yes, without the "m", not a typo) for each Entity I tried to instantiate, with various Indexes and Versions. Their components are simply "Tags", with "Simulate" checked, "End Fixed Step Simulation Entity Command Buffer System | Singleton", which is empty if I try to expand, and "System Instance", which also is empty as I expand. No Aspects, and in Relationships I have a "End Fixed Step Simulation Entity Command Buffer System" >> "Query #1" >> "Unity.Entities.EndFixedStepSimulationEntityCommandBufferSystem.Singleton". The ">>" in this case represents that one is nested to the one before it, so only one "root" element in the Relationships. Nothing is rendered, no other components are added, and there's no clue that those things were created by my instatiations other than they go away if I comment away the instantiation code, and they seem to be as plenty as my instatiation for loop count (which is 168 in this test). The results are the same if using "state.EntityManager" instead of the "EntityCommandBuffer".

    I have a feeling this is a noob problem caused by my lack of knowledge about the ECS tools. Been messing around with ECS for just a couple of days on a project that's been shelved for quite some time because it's very CPU intensive and I didn't manage to make it run proberly back then. Since ECS is now at pre release stages I thought I should give it a go, and I would be very grateful if anyone could offer me some help.

    Thanks in advance.
     

    Attached Files:

    • 1.jpg
      1.jpg
      File size:
      35.2 KB
      Views:
      19
    • 2.jpg
      2.jpg
      File size:
      169.5 KB
      Views:
      19
    • 3.jpg
      3.jpg
      File size:
      165.8 KB
      Views:
      19
    • 4.jpg
      4.jpg
      File size:
      160.3 KB
      Views:
      19
    Last edited: Mar 1, 2023
  2. suity447

    suity447

    Joined:
    Oct 18, 2022
    Posts:
    33
    This may not be the only problem but entities that are created via GetEntity are not the same entities that are used at runtime. If these entities are added to an IComponentData or an IBufferElement they are automatically remapped. That means you cannot store entities created this way in BlobAssets or NativeContainers. However this should net a different error than the one you are getting. Perhaps you could post some of your code as the description is not entirely clear for me.
     
    Naewulf likes this.
  3. Naewulf

    Naewulf

    Joined:
    Jun 17, 2019
    Posts:
    19
    This is the "AssetsManager.cs"
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using System.Linq;
    5. using Unity.Entities;
    6. using UnityEditor;
    7. using UnityEngine;
    8.  
    9. public class AssetsManager : MonoBehaviour
    10. {
    11.     public static AssetsManager Instance { get; protected set; }
    12.  
    13.     [ColorUsage(true, true)] public Color SelectedEmissionColor;
    14.     public Color[] ColorVariations;
    15.     public SelectableObject SelectedObject;
    16.  
    17.     private Dictionary<string, UnityEngine.Object> _loadedAssets = new();
    18.  
    19.     protected virtual void Awake()
    20.     {
    21.         foreach (var x in Resources.LoadAll<IndexedAsset>(""))
    22.         {
    23.             _loadedAssets.Add(x.IndexedName, x);
    24.         }
    25.  
    26.         DontDestroyOnLoad(gameObject);
    27.  
    28.         if (Instance != null && Instance != this)
    29.         {
    30.             Destroy(this);
    31.         }
    32.         else
    33.         {
    34.             Instance = this;
    35.         }
    36.     }
    37.  
    38.     public IndexedAsset GetAsset(string name)
    39.     {
    40.         if (_loadedAssets.ContainsKey(name)) return (IndexedAsset) _loadedAssets[name];
    41.         else return null;
    42.     }
    43.  
    44.     public T GetAsset<T>(string name)
    45.     {
    46.         if (_loadedAssets.ContainsKey(name) && ((GameObject)_loadedAssets[name]).GetComponent<T>() != null) return ((GameObject)_loadedAssets[name]).GetComponent<T>();
    47.         else return (T)Convert.ChangeType(null, typeof(T));
    48.     }
    49. }
    This is the "MapMono.cs", which is the script that I add to the single GameObject inside the subscene:
    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Mathematics;
    4. using UnityEngine;
    5. using Debug = System.Diagnostics.Debug;
    6. using Random = Unity.Mathematics.Random;
    7.  
    8. public class MapMono : MonoBehaviour
    9. {
    10.     public int2 MapDimensions;
    11.     public uint RandomSeed;
    12.     public int2 MapOffset;
    13.     public NativeHashMap<int2, PathNode> Nodes;
    14.     public MapStructure MapStructure;
    15.     private PlayerSave ps = new();
    16.     private bool loaded = false;
    17.  
    18.     private NativeArray<PathNode> pathNodes;
    19.     private NativeArray<int> goalPathNodes;
    20.  
    21.     public void Create()
    22.     {
    23.         if (loaded) return;
    24.  
    25.         if (PlayerInfoManager.Instance != null)
    26.         {
    27.             SaveManager.LoadSavedData(ref ps, PlayerInfoManager.Instance.Username, PlayerInfoManager.Instance.ID);
    28.             if (ps.Maps.Count > 0) SetMapStructure(ps.Maps[0].ID);
    29.             else Debug.WriteLine("No map to load.");
    30.         }
    31.     }
    32.  
    33.     public void SetMapStructure(string id, bool selectable = true)
    34.     {
    35.         foreach (MapStructure m in ps.Maps)
    36.         {
    37.             if (m.ID.Trim() == id.Trim())
    38.             {
    39.                 MapStructure = m;
    40.             }
    41.         }
    42.     }
    43. }
    44.  
    45. public class MapBaker : Baker<MapMono>
    46. {
    47.     public override void Bake(MapMono authoring)
    48.     {
    49.         authoring.Create();
    50.  
    51.         if (authoring.MapStructure != null && authoring.MapStructure.Tiles != null && AssetsManager.Instance != null)
    52.         {
    53.             if (authoring.Nodes.IsCreated) authoring.Nodes.Dispose();
    54.  
    55.             authoring.Nodes = new NativeHashMap<int2, PathNode>(1, Allocator.Persistent);
    56.  
    57.             for (int i = 0; i < authoring.MapStructure.Tiles.Count; i++)
    58.             {
    59.                 Debug.WriteLine((int)authoring.MapStructure.Tiles[i].PosX + " - " + (int)authoring.MapStructure.Tiles[i].PosY);
    60.  
    61.                 int2 pos = new int2((int)authoring.MapStructure.Tiles[i].PosX, (int)authoring.MapStructure.Tiles[i].PosY);
    62.  
    63.                 if (!authoring.Nodes.ContainsKey(pos))
    64.                 {
    65.                     authoring.Nodes.Add(pos, new PathNode
    66.                     {
    67.                         realX = (int)authoring.MapStructure.Tiles[i].PosX,
    68.                         realY = (int)authoring.MapStructure.Tiles[i].PosY,
    69.                         gridX = (int)authoring.MapStructure.Tiles[i].PosX + authoring.MapOffset.x,
    70.                         gridY = (int)authoring.MapStructure.Tiles[i].PosY + authoring.MapOffset.y,
    71.                         index = -1,
    72.                         parent = -1,
    73.                         tag = -1,
    74.                         Entity = GetEntity(AssetsManager.Instance.GetAsset(authoring.MapStructure.Tiles[i].PrefabName).gameObject),
    75.                         canBuildOver = false,
    76.                         isWalkable = false
    77.                     });
    78.                 }
    79.             }
    80.  
    81.             AddComponent(new MapProperties
    82.             {
    83.                 MapDimensions = authoring.MapDimensions,
    84.                 Nodes = authoring.Nodes
    85.             });
    86.         }
    87.  
    88.         AddComponent(new HelperRandom
    89.         {
    90.             Value = Random.CreateFromIndex(authoring.RandomSeed)
    91.         });
    92.     }
    93. }
    94.  
    95. public struct PathNode
    96. {
    97.     public Entity Entity;
    98.  
    99.     public int gridX;
    100.     public int gridY;
    101.  
    102.     public int realX;
    103.     public int realY;
    104.  
    105.     public int index;
    106.  
    107.     private int gCost;
    108.     private int hCost;
    109.     private int fCost;
    110.  
    111.     public bool isWalkable;
    112.     public bool canBuildOver;
    113.  
    114.     public int tag;
    115.  
    116.     public int parent;
    117.  
    118.     public void SetGCost(int p) { gCost = p; fCost = gCost + hCost; }
    119.     public int GetGCost() { return gCost; }
    120.  
    121.     public void SetHCost(int p) { hCost = p; fCost = gCost + hCost; }
    122.     public int GetHCost() { return hCost; }
    123.  
    124.     public int GetFCost() { return fCost; }
    125. }
    And finally, this is the System code:
    Code (CSharp):
    1. using System.Diagnostics;
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Mathematics;
    6. using UnityEngine.UIElements;
    7.  
    8. [BurstCompile]
    9. [UpdateInGroup(typeof(InitializationSystemGroup))]
    10. public partial struct SpawnTilesSystem : ISystem
    11. {
    12.     [BurstCompile]
    13.     public void OnCreate(ref SystemState state)
    14.     {
    15.         state.RequireForUpdate<MapProperties>();
    16.     }
    17.  
    18.     [BurstCompile]
    19.     public void OnDestroy(ref SystemState state)
    20.     {
    21.  
    22.     }
    23.  
    24.     [BurstCompile]
    25.     public void OnUpdate(ref SystemState state)
    26.     {
    27.         state.Enabled = false;
    28.         var mapProps = SystemAPI.GetSingletonEntity<MapProperties>();
    29.         var map = SystemAPI.GetAspectRW<MapAspect>(mapProps);
    30.  
    31.         var ecb = new EntityCommandBuffer(Allocator.Temp);
    32.         var keys = map.Nodes.GetKeyArray(Allocator.Temp);
    33.  
    34.         for (int i = 0; i < keys.Length; i++)
    35.         {
    36.             Entity x = ecb.Instantiate(map.Nodes[keys[i]].Entity);
    37.         }
    38.  
    39.         ecb.Playback(state.EntityManager);
    40.     }
    41. }
    I think that's the relevant code. If something else is needed please let me know.

    Edit: I'm aware that the GetEntity() return is not the same as an instantiated entity. I've been treating the GetEntity() return just like we used to do with prefabs. I'm storing them in the nodes so they can be instantiated later "for real", which I try to do in the System logic, where the problem happens.

    Edit2: That's probably the issue though, because I'm doing exactly what you said I can't do. I'm storing the "prefab" entity inside a NativeHashMap container as part of the PathNode structs. If that's the main problem, what can be done fix this while allowing me to instantiate different entities for each node in the System code?

    Thanks for your answer.
     
    Last edited: Mar 1, 2023
  4. suity447

    suity447

    Joined:
    Oct 18, 2022
    Posts:
    33
    You can use them as prefabs, however you cannot store them like this in a native container during baking. Entities created during baking this way are invalid during runtime. The system is smart enough to remap them to valid entities but only if they are stored in a component or a dynamic buffer. The baking system will not look into your node container so it cannot remap them correctly. I suspect what is happening is that the entity data for your prefab during baking (just two ints) is the same as the system entity for the ecb-system so that you do not get an error but instantiate this entity instead.
    I suggest you store your nodes during baking in a dynamic buffer {int2, Node} and rebuild the map in the ISystem.
     
    Naewulf likes this.
  5. Naewulf

    Naewulf

    Joined:
    Jun 17, 2019
    Posts:
    19
    Oh, that makes a lot of sense. Didn't know about this dynamic buffer thing. Gonna research about how to use them and give it a go. Thanks for your time!
     
  6. Naewulf

    Naewulf

    Joined:
    Jun 17, 2019
    Posts:
    19
    It worked!
    You're awesome. Thanks again!
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,728
    Just a quick thing to add in, if you are modifying an authoring component in a baker, that's usually a sign that you are doing something wrong. All authoring data should be treated as read-only while baking.
     
    elliotc-unity likes this.
  8. Naewulf

    Naewulf

    Joined:
    Jun 17, 2019
    Posts:
    19
    Yeah, I was thinking about that actually. To be honest I'm not feeling confortable with how I did it because it sure looks like a hack. I'm thinking about doing something like a "prefab entity" class that has a GameObject field and bakes that into an Entity field, and try to access that from the Systems when needed somehow. Are there "best practices" to be followed about that?
     
  9. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,728
  10. Naewulf

    Naewulf

    Joined:
    Jun 17, 2019
    Posts:
    19
    What I'm trying to do has only one extra complexity when compared to the examples I've seen online. I don't have the prefabs set anywhere.

    My understanding of the baking process is that it only works if you have a GameObject field in a MonoBehaviour, then you set up a Baker for that MonoBehavior, and in that baker you set the Entity field of the Component struct to GetEntity(authoring.prefab).

    In my case I'm not dragging the prefabs to some GameObject field anywhere, because my prefabs are loaded dynamically using Resources.LoadAll.

    In other words, I need a way to convert from GameObject to Entity at runtime, but the "GetEntity()" function is only present inside the Baker, which won't even fire in my case.

    After I get the list from Resources.LoadAll, I don't know how to convert those prefabs into entities.

    That's why I tried to use a baker function from a singleton that exists in the editor, so it actually runs it's baker, to convert the dynamically loaded assets into Entities.

    It's working, but I agree that it's hacky and ugly. I'm sure there is a proper way of doing this, but I don't know any at the moment.
     
    Last edited: Mar 1, 2023
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,728
    No such thing. It is Editor or bust, unless you use a custom runtime conversion workflow and skip subscene and built-in baking altogether.

    Why are you loading them this way? Unity has some mechanisms for entities related to this which may be useful: https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/content-management.html
     
  12. Naewulf

    Naewulf

    Joined:
    Jun 17, 2019
    Posts:
    19
    I'm loading them this way because they are the tiles of the map. The idea is to have many different tiles, hundreds of them. I have a json file that tells the game which ones to load, since not every player will use every tile... so there's no need to load lots of them when they won't even get used ingame. And, even if I ignore that, having to manually keep adding new tiles to like a "tile manager" that has a "tile list" dont seem to be a practical way of doing this, if I can get them on demand from the Resources. Gonna check out that documentation though and see if there's something that can be of help. Thanks for the ideas!
     
  13. suity447

    suity447

    Joined:
    Oct 18, 2022
    Posts:
    33
    Do they all have to be prefabs? If not you could create one (or multiple if you have groups) prefab entity via baking and only load/populate the data during runtime. For a very basic example instead of a green forest tile prefab and a blue sea tile prefab you could have a tile prefab and the color gets set at runtime.
     
  14. Naewulf

    Naewulf

    Joined:
    Jun 17, 2019
    Posts:
    19
    hmmm I actually have only 3 types of tiles. Tiles, Spawners and Goals. The difference between them (of the same type) is only the material actually... Maybe I can do something like you said. Gonna give it a go. Thanks!
     
    Last edited: Mar 2, 2023