Search Unity

Official DOTS Development Status And Next Milestones - December 2021

Discussion in 'Entity Component System' started by LaurentGibert, Dec 9, 2021.

Thread Status:
Not open for further replies.
  1. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Nevermind. I forgot hybrid components can only serialize GameObject references within the same subscene. So the only way to do this is to use runtime conversion, but in that case, class ICDs are sufficient.

    I still want to know how Unity plans to make normal GameObjects play well with subscene entities now that hybrid components are being limited and runtime conversion is continuing to be discouraged.
     
  2. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    260
    Even thought hybrid components are being limited, you can still tie GOs and Entities together via managed component datas and AddComponentObject. It is fairly straightforward to create a managed component data that references a GO prefab that you can spawn and link to your entity in a system. It is also straightforward to store an addressables asset reference in a managed component data and instantiate a game object via addressables.
     
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    I'm not talking about being able to instantiate GameObjects at runtime. I am purely talking about an entity in a subscene storing a serialized reference to a GO in the scene containing the subscene.

    Right now that is not possible (or at least I am not aware of a good way to do it), and that needs to be fixed if Unity truly wants to embrace a hybrid workflow.
     
  4. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    866
    It is not exactly what you want but you can use GameObject.Find() or GameObject.FindWithTag to get a list of refences at runtime. I store all the refences on the same gameObject. Many different ways to do this. It is more complex then dragging and dropping gameObject references in the editor, but it is possible in some cases. Or at least it has been possible for the use cases I have had so far.
     
    Last edited: Dec 23, 2021
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    I had to reread your post a few times to think of a way to make this not awful for designers or performance. But you are right. This is a viable route with the right Find function and a well-written referencer type and EditorPropertyDrawer.
     
  6. razzraziel

    razzraziel

    Joined:
    Sep 13, 2018
    Posts:
    396
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I use a GUID for mine, but it does not require disabled domain reload at all. (At least I think it does not require it)
    Basically I create at Editor time my GUID and store it in the game object, and that GUID must be unique and never ever change during the whole game development. (Protagonist, game npc, buildings, etc)
    Runtime GameObject<->Entity uses another class that is similar but not quite, one big difference is that the runtime MUST ALWAYS destroy the game object on save/load, the entities and systems are responsible for creating a new one in it's place. The GameObject side never serializes any data, at most it sends to the Entitiy side so the Entity may serialize for it. (buttons that activate entities, collider for entities, etc)

    I'd guess technically I never serialize any GameObject at all, so maybe my solution do not apply.
    Code (CSharp):
    1. using KinematicCharacterController;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.IO;
    5. using System.Linq;
    6. using System.Text;
    7. using Unity.Collections;
    8. using Unity.Entities;
    9. using Unity.Entities.CodeGeneratedJobForEach;
    10. #if UNITY_EDITOR
    11. using UnityEditor;
    12. #endif
    13. using UnityEngine;
    14. using UnityEngine.Assertions;
    15.  
    16. public static class GUIDGenerator {
    17.     public struct RuntimeEntityIdentifier : IComponentData, AutoSaveIComponentData {
    18.         public int Value;
    19.     }
    20.     private static readonly Dictionary<string, GameObject> GUIDToObj = new Dictionary<string, GameObject>();
    21.     private static readonly Dictionary<string, int> GUIDToEntity = new Dictionary<string, int>();
    22.     private static readonly Dictionary<GameObject, string> ObjToGUID = new Dictionary<GameObject, string>();
    23.     private static int CurrentEntityCode = 0;
    24.     public static Entity FindOrCreateEntity(World World, EntityManager em, GameObject obj, string GUID) {
    25.         if (!GUIDToEntity.TryGetValue(GUID, out int EntityCode)) {
    26.             EntityCode = -1;
    27.         }
    28.         if (EntityCode == -1) {
    29.             Entity e = em.CreateEntity();
    30.             em.AddComponentData(e, new RuntimeEntityIdentifier() { Value = EntityCode = CurrentEntityCode++ });
    31.             em.AddComponentObject(e, new RuntimeEntityToGameObject(obj));
    32.             GameObjectToEntity.ConvertAuthoring(World, obj, ref e, em);
    33. #if UNITY_EDITOR
    34.             em.SetName(e, "RuntimeEntity: " + EntityCode);
    35. #endif
    36.             GUIDToEntity[GUID] = EntityCode;
    37.             return e;
    38.         } else {
    39.             Entity ret = default;
    40.             object box = ret;
    41.             World.DefaultGameObjectInjectionWorld.GetExistingSystem<DumbForEachAccess>().DumbEntities.ForEach((Entity e, ref RuntimeEntityIdentifier id) => {
    42.                 if (EntityCode == id.Value) {
    43.                     box = e;
    44.                     return;
    45.                 }
    46.             });
    47.             ret = (Entity)box;
    48.             if (ret != default) {
    49.                 //em.RemoveComponent<RuntimeEntityToGameObject>(ret);
    50.                 em.AddComponentObject(ret, new RuntimeEntityToGameObject(obj));
    51.                 em.SetEnabled(ret, true);
    52.                 return ret;
    53.             } else {
    54.                 return default;
    55.             }
    56.         }
    57.     }
    58.     public static void GenerateGUID(GameObjectToEntity target) {
    59.         EnsureInit();
    60.         if (target.GUID != null && target.GUID != "") {
    61.             if (GUIDToObj.ContainsKey(target.GUID)) {
    62.                 if (GUIDToObj[target.GUID] != target.gameObject || ObjToGUID[target.gameObject] != target.GUID) {
    63.                     Debug.LogError("GameObjectToEntity has GUID but is not mapped. Mapping regenerated", target);
    64.                 } else {
    65.                     return;
    66.                 }
    67.             }
    68.         }
    69.         if (target.GUID == null || target.GUID == "") {
    70.             string guid = Guid.NewGuid().ToString();
    71.             Debug.LogError("THIS IS NOT AN ERROR IF OLD GUID WAS EMPTY OR NULL. THIS IS AN ERROR OTHERWISE: Old GUID: " + target.GUID + "; GUID Generated: " + guid + " for game object: " + target.gameObject.name, target.gameObject);
    72.             while (GUIDToObj.ContainsKey(guid)) {
    73.                 guid = Guid.NewGuid().ToString();
    74.             }
    75.             GUIDToObj[ObjToGUID[target.gameObject] = guid] = target.gameObject;
    76.             target.GUID = guid;
    77.         }
    78.     }
    79.  
    80.     private static void EnsureInit() {
    81.         if (GUIDToObj.Count == 0) {
    82.             UnityEngine.Object[] objs = UnityEngine.Object.FindObjectsOfType(typeof(GameObjectToEntity));
    83.             foreach (UnityEngine.Object obj in objs) {
    84.                 GameObjectToEntity ote = ((GameObjectToEntity)obj);
    85.                 if (ote.GUID != null && ote.GUID != "") {
    86.                     ObjToGUID[GUIDToObj[ote.GUID] = ote.gameObject] = ote.GUID;
    87.                 }
    88.             }
    89.             foreach (UnityEngine.Object obj in objs) {
    90.                 GameObjectToEntity ote = ((GameObjectToEntity)obj);
    91.                 if (ote.GUID == null && ote.GUID == "") {
    92.                     GUIDGenerator.GenerateGUID(ote);
    93.                 }
    94.             }
    95.         }
    96.     }
    97.  
    98.     internal static void Delete(GameObject gameObject) {
    99.         EnsureInit();
    100.         if (ObjToGUID.ContainsKey(gameObject)) {
    101.             GUIDToObj.Remove(ObjToGUID[gameObject]);
    102.             ObjToGUID.Remove(gameObject);
    103.         }
    104.     }
    105.  
    106.     internal static GameObject GetGameObjectFromGUID(string guid, string name) {
    107.         EnsureInit();
    108.         if (GUIDToObj.ContainsKey(guid)) {
    109.             return GUIDToObj[guid];
    110.         }
    111.         Debug.LogError("GUID: " + guid + " not found" + (name != "" ? "Name was: " + name : ""));
    112.         return null;
    113.     }
    114. }
    115.  
    116. #if UNITY_EDITOR
    117. [CustomEditor(typeof(GameObjectToEntity))]
    118. public class GameObjectToEntityEditor : Editor {
    119.     public override void OnInspectorGUI() {
    120.         DrawDefaultInspector();
    121.         GUI.enabled = false;
    122.         EditorGUILayout.TextField("Entity", "" + ((GameObjectToEntity)target).Entity);
    123.         GUI.enabled = true;
    124.     }
    125. }
    126. #endif
    127. public class GameObjectToEntityMapSystem : SystemBase, OnReloadDatabase {
    128.     public Dictionary<string, Entity> EntityMap;
    129.     public Dictionary<Entity, string> MapEntity;
    130.     public Dictionary<Entity, GameObject> MapGameObject;
    131.     protected override void OnCreate() {
    132.         EntityMap = new Dictionary<string, Entity>();
    133.         MapEntity = new Dictionary<Entity, string>();
    134.         MapGameObject = new Dictionary<Entity, GameObject>();
    135.         Enabled = false;
    136.     }
    137.     public void Register(string ID, Entity Entity, GameObject obj) {
    138.         EntityMap[ID] = Entity;
    139.         MapEntity[Entity] = ID;
    140.         MapGameObject[Entity] = obj;
    141.     }
    142.     public Entity Get(string ID) {
    143.         return EntityMap[ID];
    144.     }
    145.     public string Get(Entity Entity) {
    146.         return MapEntity[Entity];
    147.     }
    148.     public GameObject GetGameObject(Entity Entity) {
    149.         return MapGameObject[Entity];
    150.     }
    151.     public bool TryGet(string ID, out Entity Entity) {
    152.         return EntityMap.TryGetValue(ID, out Entity);
    153.     }
    154.     public bool TryGet(Entity Entity, out string ID) {
    155.         return MapEntity.TryGetValue(Entity, out ID);
    156.     }
    157.     public bool TryGetGameObject(Entity Entity, out GameObject GameObject) {
    158.         return MapGameObject.TryGetValue(Entity, out GameObject);
    159.     }
    160.     protected override void OnUpdate() {
    161.     }
    162.  
    163.     public void OnReloadDatabase() {
    164.         EntityMap.Clear();
    165.         MapEntity.Clear();
    166.         MapGameObject.Clear();
    167.     }
    168. }
    169. [ExecuteInEditMode]
    170. public class GameObjectToEntity : MonoBehaviour {
    171.     [KinematicCharacterController.ReadOnlyAttribute]
    172.     public string GUID;
    173.     public string DatabaseEntityID;
    174.     public Entity Entity;
    175.     public bool FailSilently = false;
    176.     public Action<EntityManager, Entity> AfterConvert;
    177. #if UNITY_EDITOR
    178.     public static bool DISABLE_CHECK;
    179.     [HideInInspector]
    180.     public bool justCreated;
    181. #endif
    182.  
    183.     private void Awake() {
    184. #if UNITY_EDITOR
    185.         if (!UnityEngine.Application.isPlaying) {
    186.             justCreated = false;
    187.             GUIDGenerator.GenerateGUID(this);
    188.         }
    189. #endif
    190.     }
    191.     public void Initialize(World World) {
    192. #if UNITY_EDITOR
    193.         if (UnityEngine.Application.isPlaying) {
    194.             if (justCreated) {
    195.                 Debug.LogError("GameObjectToEntity created on runtime. Use RuntimeGameObjectToEntity during runtime instead", gameObject);
    196.             }
    197. #endif
    198.  
    199.             EntityManager em = World.EntityManager;
    200.             GameObjectToEntityMapSystem GameObjectToEntityMapSystem = World.GetOrCreateSystem<GameObjectToEntityMapSystem>();
    201.             if (DatabaseEntityID != null && DatabaseEntityID.Length > 0) {
    202.                 Entity = DatabaseLoader.CloneFromDatabaseWorld(DatabaseEntityID, FailSilently);
    203.             } else {
    204.                 Entity = em.CreateEntity();
    205.             }
    206.             if (Entity == Entity.Null) {
    207.                 Entity = em.CreateEntity();
    208.             }
    209.             GameObjectToEntityMapSystem.Register(DatabaseEntityID, Entity, gameObject);
    210.  
    211.             ConvertAuthoring(World, gameObject, ref Entity, em);
    212. #if UNITY_EDITOR
    213.             DISABLE_CHECK = true;
    214. #endif
    215.             em.AddComponentObject(Entity, new EntityToGameObject(gameObject));
    216.             AfterConvert?.Invoke(em, Entity);
    217. #if UNITY_EDITOR
    218.             EntityToGameObject val = em.GetComponentObject<EntityToGameObject>(Entity);
    219.             Assert.IsTrue(val.GameObject == gameObject);
    220.             DISABLE_CHECK = false;
    221.         }
    222.         justCreated = false;
    223. #endif
    224.     }
    225.  
    226.     public static World ConvertAuthoring(World World, GameObject gameObject, ref Entity Entity, EntityManager em) {
    227.         GameObjectConversionSystem convertor = World.GetExistingSystem<GameObjectConversionSystem>();
    228.  
    229.         object[] parameters = new object[] { Entity, em, convertor };
    230.         foreach (var component in gameObject.GetComponents<Component>()) {
    231.             if (component == null) {
    232.                 continue;
    233.             }
    234.             Type t = component.GetType();
    235.             try {
    236.                 if (t.FullName.EndsWith("Authoring")) {
    237.                     System.Reflection.MethodInfo methodInfo = t.GetMethod("Convert");
    238.                     if (methodInfo != null) {
    239.                         methodInfo.Invoke(component, parameters);
    240.                     }
    241.                 }
    242.             } catch (Exception) { }
    243.         }
    244.  
    245.         return World;
    246.     }
    247.  
    248.  
    249.     public bool ForceConvertNow<T>(out T value) where T : struct, IComponentData {
    250.         World World = World.DefaultGameObjectInjectionWorld;
    251.         bool TempWorld = false;
    252.         if (World == null) {
    253.             World = new World("Temp World");
    254.             TempWorld = true;
    255.         }
    256.         GameObjectConversionSystem convertor = World.GetExistingSystem<GameObjectConversionSystem>();
    257.         Entity Entity = World.EntityManager.CreateEntity();
    258.         object[] parameters = new object[] { Entity, World.EntityManager, convertor };
    259.         foreach (var component in gameObject.GetComponents<Component>()) {
    260.             if (component == null) {
    261.                 continue;
    262.             }
    263.             Type t = component.GetType();
    264.             try {
    265.                 if (/*t.FullName.Contains(typeof(T).FullName)&& */t.FullName.EndsWith("Authoring")) {
    266.                     System.Reflection.MethodInfo methodInfo = t.GetMethod("Convert");
    267.                     if (methodInfo != null) {
    268.                         methodInfo.Invoke(component, parameters);
    269.                     }
    270.                 }
    271.             } catch (Exception) { }
    272.         }
    273.         if (World.EntityManager.HasComponent<T>(Entity)) {
    274.             value = World.EntityManager.GetComponentData<T>(Entity);
    275.             World.EntityManager.DestroyEntity(Entity);
    276.             if (TempWorld) {
    277.                 World.Dispose();
    278.             }
    279.             return true;
    280.         }
    281.         value = default;
    282.         if (TempWorld) {
    283.             World.Dispose();
    284.         }
    285.         return false;
    286.     }
    287.  
    288.     private void OnDestroy() {
    289.         GUIDGenerator.Delete(gameObject);
    290. #if UNITY_EDITOR
    291.         if (UnityEngine.Application.isPlaying) {
    292. #endif
    293.             World.DefaultGameObjectInjectionWorld?.EntityManager.RemoveComponent<EntityToGameObject>(Entity);
    294. #if UNITY_EDITOR
    295.         }
    296. #endif
    297.     }
    298.     private void OnDrawGizmos() {
    299.         ECSRaycastVolume ECSRaycastVolume;
    300.         //#if UNITY_EDITOR
    301.         //        if (!Application.isPlaying && ForceConvertNow(out ECSRaycastVolume)) {
    302.         //            Vector3 pos = transform.position + ECSRaycastVolume.Offset;
    303.         //            float hei = (ECSRaycastVolume.Height / 2 + ECSRaycastVolume.Width / 2);
    304.         //            DebugExtension.DrawCapsule(pos - hei * Vector3.up, pos + hei * Vector3.up, ECSRaycastVolume.Width / 2);
    305.         //        }
    306.         //#endif
    307.         if (Application.isPlaying) {
    308.             EntityManager? EntityManager = World.DefaultGameObjectInjectionWorld?.EntityManager;
    309.             if (EntityManager.HasValue) {
    310.                 if (EntityManager.Value.Exists(Entity) && EntityManager.Value.HasComponent<ECSRaycastVolume>(Entity)) {
    311.                     ECSRaycastVolume = EntityManager.Value.GetComponentData<ECSRaycastVolume>(Entity);
    312.                     Vector3 pos = transform.position + ECSRaycastVolume.Offset;
    313.                     float hei = (ECSRaycastVolume.Height / 2 + ECSRaycastVolume.Width / 2);
    314.                     DebugExtension.DrawCapsule(pos - hei * Vector3.up, pos + hei * Vector3.up, ECSRaycastVolume.Width / 2);
    315.                 }
    316.             }
    317.         }
    318.     }
    319. }
    320.  
    321. public class EntityToGameObject : Component, AutoSaveComponent {
    322.     public byte[] GenerateData(EntityManager em, Entity e) {
    323.         if (GameObject != null) {
    324.             GameObjectToEntity GameObjectToEntity = GameObject.GetComponent<GameObjectToEntity>();
    325.             if (GameObjectToEntity != null) {
    326.                 using (MemoryStream stream = new MemoryStream()) {
    327.                     //using (DeflateStream compress = new DeflateStream(stream, CompressionMode.Compress))
    328.                     using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(stream)) {
    329.                         byte[] encode = Encoding.UTF8.GetBytes(GameObjectToEntity.GUID + "|" + GameObject.name);
    330.                         bw.Write(encode.Length);
    331.                         bw.Write(encode);
    332.  
    333.                         AutoSaveMonoBehaviour[] autoSaveMonoBehaviour = GameObject.GetComponents<AutoSaveMonoBehaviour>();
    334.                         bw.Write(autoSaveMonoBehaviour.Length);
    335.                         foreach (AutoSaveMonoBehaviour AutoSaveMonoBehaviour in autoSaveMonoBehaviour) {
    336.                             byte[] data = Encoding.UTF8.GetBytes(AutoSaveMonoBehaviour.GetType().AssemblyQualifiedName);
    337.                             bw.Write(data.Length);
    338.                             bw.Write(data);
    339.                             data = AutoSaveMonoBehaviour.GenerateData(em, e);
    340.                             bw.Write(data.Length);
    341.                             bw.Write(data);
    342.                         }
    343.                     }
    344.                     return stream.ToArray();
    345.                 }
    346.             } else {
    347.                 Debug.Log("Could not Save: " + GameObject.name + ", GameObjectToEntity is null");
    348.                 return new byte[0];
    349.             }
    350.         } else {
    351.             Debug.Log("Could not Save: " + GameObject.name + ", GameObject is null");
    352.             return new byte[0];
    353.         }
    354.     }
    355.     public void LoadFromData(byte[] data, EntityRecoverer recover, Entity e) {
    356.         using (MemoryStream mems = new MemoryStream(data)) using (BinaryReader sr = new BinaryReader(mems)) {
    357.             byte[] enc = sr.ReadBytes(sr.ReadInt32());
    358.             string guid = Encoding.UTF8.GetString(enc);
    359.             string name = "";
    360.             if (guid.Contains("|")) {
    361.                 string[] split = guid.Split('|');
    362.                 guid = split[0];
    363.                 name = split[1];
    364.             }
    365.             GameObject = GUIDGenerator.GetGameObjectFromGUID(guid, name);
    366.  
    367.             if (GameObject == null) {
    368.                 Debug.Log("Could not load: " + (guid + " name was " + name));
    369.                 GameObject = GameObject.Find(name);
    370.                 if (GameObject == null) {
    371.                     Debug.Log("Could not recover by name");
    372.                 } else {
    373.                     Debug.Log("Recovered by name, can be wrong", GameObject);
    374.                 }
    375.             }
    376.             if (GameObject != null) {
    377.                 GameObjectToEntity gameObjectToEntity = GameObject.GetComponent<GameObjectToEntity>();
    378.                 if (gameObjectToEntity == null) {
    379.                     Debug.Log("gameObjectToEntity is null on object: " + gameObject, gameObject);
    380.                 }
    381.                 gameObjectToEntity.Entity = e;
    382.                 gameObjectToEntity.AfterConvert?.Invoke(recover.manager, e);
    383.                 GameObjectToEntityMapSystem GameObjectToEntityMapSystem = recover.manager.World.GetOrCreateSystem<GameObjectToEntityMapSystem>();
    384.                 GameObjectToEntityMapSystem.Register(gameObjectToEntity.DatabaseEntityID, e, GameObject);
    385.                 foreach (IReloadCacheOnLoad reloader in GameObject.GetComponentsInChildren<IReloadCacheOnLoad>()) {
    386.                     StaticFlags.PostLoadCalls((EM) => reloader.ReloadCache(EM));
    387.                 }
    388.             } else {
    389.                 recover.manager.RemoveComponent<EntityToGameObject>(e);
    390.             }
    391.             int total = sr.ReadInt32();
    392.             if (GameObject != null) {
    393.                 AutoSaveMonoBehaviour[] autoSaveMonoBehaviour = GameObject.GetComponents<AutoSaveMonoBehaviour>();
    394.                 for (int i = 0; i < total; i++) {
    395.                     string AssemblyQualifiedName = Encoding.UTF8.GetString(sr.ReadBytes(sr.ReadInt32()));
    396.                     AutoSaveMonoBehaviour AutoSaveMonoBehaviour = autoSaveMonoBehaviour.First(T => T.GetType() == Type.GetType(AssemblyQualifiedName));
    397.                     byte[] data2 = sr.ReadBytes(sr.ReadInt32());
    398.                     if (AutoSaveMonoBehaviour != null) {
    399.                         AutoSaveMonoBehaviour.LoadFromData(data2, recover, e);
    400.                     }
    401.                 }
    402.             } else {
    403.                 for (int i = 0; i < total; i++) {
    404.                     string AssemblyQualifiedName = Encoding.UTF8.GetString(sr.ReadBytes(sr.ReadInt32()));
    405.                     byte[] data2 = sr.ReadBytes(sr.ReadInt32());
    406.                 }
    407.             }
    408.         }
    409.     }
    410.     public GameObject GameObject;
    411.  
    412.     public EntityToGameObject() {
    413. #if UNITY_EDITOR
    414.         if (!GameObjectToEntity.DISABLE_CHECK && UnityEngine.Application.isPlaying) {
    415.             Debug.LogError("GameObjectToEntity created on runtime. Use RuntimeEntityToGameObject during runtime instead");
    416.         }
    417. #endif
    418.     }
    419.     public EntityToGameObject(GameObject GameObject) : this() {
    420.         this.GameObject = GameObject;
    421.     }
    422.  
    423. }
    424.  
    425. public interface IReloadCacheOnLoad {
    426.     void ReloadCache(EntityManager EntityManager);
    427. }
     
  8. s_schoener

    s_schoener

    Unity Technologies

    Joined:
    Nov 4, 2019
    Posts:
    81
    Not systematically for 0.50, yet. We are definitely looking into moving as much code over to ISystem as possible, whereever it makes sense.
    We do have plans and an implementation for unmanaged shared components that work within Burst, but I am not sure whether this has made it into 0.50. We are working towards making as much code as possible compatible with Burst, and shared components are an obvious hole right now.

    There is no such mechanism right now and I cannot speak for the team responsible for this part. However, there are a lot of interesting problems on the way to doing this. Hybrid components were in a way one approach to this: instead of converting a game object, you would take the game object 'as is' and put it into an asset bundle that you can load along with the entities scene. The problem with referencing an object from another scene is (among other things) that the subscene is converted independent of the parent scene and could be loaded without that parent scene being loaded. This sounds like a bigger workflow problem to me right now that requires a broader solution, and I'm personally not sure how to best solve that (- luckily, that's also not what I'm in charge of). For my personal projects, I use a similar approach to that outlined above: Hook things up at Runtime by either injecting data from a MonoBehaviour into an Entity world, or by having a system look for the relevant data on startup.
     
  9. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    857
  10. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    306
    That's great news. I've always asked for something built in to Entities along those lines.

    My current wish list, not in any order of preference, all would be great to have.
    1. Job accessible unmanaged ISharedComponents
    2. In-job structural change API using ExclusiveEntityTransactions (primarily the altering of Shared Component values / indices)
    3. Fully feature complete ISystemBase. Primarily IJob, IJobFor, and IJobEntityBatch bursted scheduling support. A nice feature but not mandatory would be EntityQuery creation inside burst, one of the reasons why GetSingleton<>() can not be bursted. I've designed my current code implementation such that I could just add an "I" in front of my systems and a
      [BurstCompile]
      and everything will work.
    4. Dynamic chunk sizes, hopefully chunks can be defined by either a compile time or in-code entity count, not Byte size. Also different sized chunks depending on usage: singleton entities dont need to reserve a full 16384 byte sized chunk just for themselves. And enforced multiple of 8 entity length for easy manual SIMD vectorization.
    5. Improved or less boilerplate BlobAsset creation code. Current implementation is so painful.
    6. Improved Entity Inspector performance. Past say 100,000 entities, the search and scrolling features really starts to die. And I'm working with 2.4M so I guess I just hope.
    7. Blob Asset viewer. Just some way to see what's in a blob asset from the Unity Editor.
     
    Last edited: Dec 29, 2021
    Guedez, deus0, Lionious and 6 others like this.
  11. deus0

    deus0

    Joined:
    May 12, 2015
    Posts:
    256
    Regarding bursting everything; I would like AudioClips, Cameras, AudioListeners & Mesh Uploading to be burst / parallel jobs compatible. If Mesh class becomes a struct, or an equivalent version we can use, that would be nice to avoid shared components too (I mostly use unique meshes or I use compute buffers). I like the idea of dynamic chunk sizes, I didn't realize a single entity chunk would use 16384 bytes but I remember Unity talking about optimizing entity use for smaller sizes, so that's probably why. I use alot of entities for events atm so reducing that burden would be nice for scaling.
     
    Tanner555, DevViktoria and bb8_1 like this.
  12. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    Djayp likes this.
  13. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    Is there any better way than using an unsafe pointer to store a reference to a native collection in a component? is there any plan to add one?
     
  14. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    Some time ago I investigated how Unity saves the component data exactly.
    Basically, the memory is allocated in 1MB blocks with 64 chunks each (64 * 16kb = 1024kb). The 64 comes from the fact that a 64-bit integer is used as a bit mask to remember which chunk is used.
    If you now make the chunk size variable, that also means that a minimum of 64 chunks of this size are allocated, and then the size of the chunks must also be saved for each block. In the end, this creates further overhead, because when reserving entity component memory, the size of the memory block must also be taken into account, and even if there are memory blocks with free slots, new memory must be allocated if the chunk size does not match. and you get more memory fragmentation.
     
    PolarTron and Kmsxkuse like this.
  15. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    These 64 chunks all must have the same archetype?
     
  16. nykwil

    nykwil

    Joined:
    Feb 28, 2015
    Posts:
    49
  17. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    No, they can be different.
     
  18. Deleted User

    Deleted User

    Guest

    Thanks DOTS team for the update! And happy new year to y'all <3
     
    mikaelK, Tony_Max, bb8_1 and 3 others like this.
  19. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,684
  20. bb8_1

    bb8_1

    Joined:
    Jan 20, 2019
    Posts:
    100
    lol yes sorry - i was too excited and haste - lol - i thought we have some really good news early in 22
     
    BackgroundMover likes this.
  21. Dessix-Machina

    Dessix-Machina

    Joined:
    Aug 5, 2013
    Posts:
    6
    Version 2022.1b feels weak without the benefits of DOTS- I'm really hoping to see even experimental-level compatibility with it to allow use of Unity Netcode, but Collections has warnings and Entities is flat-out incompatible with later C# versions. Are we likely to see a development version 2022Q1, so we have something to build against?
     
    LGTDEV likes this.
  22. LaurentGibert

    LaurentGibert

    Administrator

    Joined:
    Jan 23, 2020
    Posts:
    173
    Hi everyone, first, let me wish you a wonderful new year 2022!

    Thanks so much for all the questions, suggestions, comments!! Too much for answering everything, especially as our next update would probably be available before we get to the end of it ;). So I will just do a couple clarifications, and for the rest, please stay tuned for the next communication that will clarify the scope of 0.50, and unveil details about 1.0.

    • 0.50 is a breaking change from 0.17, and 1.0 will be a breaking change from 0.50.
    • 0.50 is only planned to be compatible with 2020 LTS and 2021 LTS.
    • 1.0 is only planned to be compatible starting with 2022 LTS and following releases.
    Now the size of the impact for your game code will depend on the APIs you use. With both 0.50 and 1.0, we will provide as much migration guidance as possible to minimize the impact. Please be patient for the scope to be clarified so you can better assess how important the break would be to your specific projects.

    We want to get there as quickly as possible. We start first with 0.50 on 2020 LTS because there are productions based on that LTS that will benefit from obtaining patches and fixes. Then catching up quickly after with 2021 to be compatible with the upcoming LTS so we consolidate our baseline. Right after, we can focus on having the alpha versions of 1.0 to progressively get distributed on the 2022 tech stream.

    Thanks again for all your input, your feedback is very valuable to us and taken into account.

    Laurent
     
    Last edited: Jan 12, 2022
    JohngUK, shamsfk, LudiKha and 39 others like this.
  23. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    715
    Don't get our hopes up ;) Unless 0.50 is coming out in January?

    Do you have anything to share in regards to the Subscene workflow? Is there any plan to change that? Will Build Configurations get any update?

    A lot of people here and in the Tiny forums are also asking what's going to happen to Tiny? Any pointers to how this will shape up going forward? "It is going to be deprecated" is a valid answer.
     
    Last edited: Jan 12, 2022
    NotaNaN, Wattosan, Anthiese and 5 others like this.
  24. Also, if possible add some information on additional projects, those which rely on ECS for some reasons, when can we expect them to be revived? Or not at all? From Tiny to AI Planner.
     
    NotaNaN, Wattosan, Anthiese and 2 others like this.
  25. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    306
    On a side note, I actually don't have any issue with the build window for DOTS. Well, the inspector window itself is broken in 2022.1 Beta but if you just click around, all the modules are still there and the "Build and Run" button works.
     
    bb8_1 likes this.
  26. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    0.5 was already released january 2020, you mean 0.50 for sure:p
     
    alexandre-fiset and bb8_1 like this.
  27. Menion-Leah

    Menion-Leah

    Joined:
    Nov 5, 2014
    Posts:
    189
    +1 for cross-platform determinism.

    THAT would be the killer feature of Unity compared to other engines.
    Fingers crossed... waiting for it since you promised it to be a part of the new Unity Physics, in 2018.
     
    IAL likes this.
  28. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    838
    +2 for it. Recently made a thread here to discuss determinism in Unity, since seems like something a lot of people are interested in.
     
    ThatDan123, IAL, Anthiese and 2 others like this.
  29. Sebioff

    Sebioff

    Joined:
    Dec 22, 2013
    Posts:
    218
    +3 then, would really love to see deterministic math happening
     
    Menion-Leah likes this.
  30. Armitage1982

    Armitage1982

    Joined:
    Jul 26, 2012
    Posts:
    38
    About Unity Physics,
    Wouldn't it be easier to negotiate better terms for indie with Microsoft and deploy full support for Havok?

    Havok is complete, robust, battle-tested and comes with very good documentation. There is no need to reinvent the wheel. Unity Physics is to Unity what Chaos is to Unreal Engine, I hope its development will not be at the expense of Havok integration. On Tuesday it will be a year since the package has not been updated.

    Personally, I'd rather be able to use .Net 6 with Havok and keep my current workflow, than wait to learn DOTS and have to use Unity Physics.
     
    Last edited: Jan 15, 2022
    Saniell and JoNax97 like this.
  31. I don't understand this. Unity Physics and Havok are serving two distinct purpose, but both of them serving ECS/DOTS-only. Where you can use Unity Physics, you can use Havok (if the licensing is in order, obviously). There is no wheels reinvented here. Maybe you could elaborate what do you mean exactly?
     
  32. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    I agree, it would be perfect to just have havok as a baseline physics engine for DOTS fully fledged and who knows, it could happen. But Unity Physics would still be required for better potential determinism and because it is stateless.

    An eventual havok could cover those use-cases easily enough though, so I do not see a negative to your request, at least.
     
    Anthiese likes this.
  33. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,752
    What two different purposes do Unity physics and Havok physics serve? AFAIK the most important difference is that one is stateless and the other stateful. Other than that I think there’s huge overlap in the function they serve.
     
  34. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    Stateless physics engines are better suited for networked games, because they can be rolled back with much greater ease.
     
  35. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,752
    That doesn’t feel like enough separation to classify as “different purposes”, but I guess it’s good to know I shouldn’t bother with Unity Physics if I don’t want to make a networked game.
     
  36. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    But you have to pay for Havok if you use DOTS. It's Unity's DOTS Physics, or Havok.

    Of course, Physx is for gameobject projects.
     
  37. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,752
    I guess that means Unity does not have a free DOTS Physics solution for non networked games.
     
  38. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Well I disagree. I think it's perfectly good for most games. In fact I had to ask nicely a few times for stacking support in it, which makes it pretty useful for most games. Had I not, it'd been useful for just networked games, or scene query based stuff.

    Most games don't really need much more than that though. I mean I'm having trouble trying to think what recent AAA game needs more than Unity's provided DOTS physics.

    Perhaps a simulation of a lot of barrels would be infeasible due to it being stateless. Not playing many barrel games. But VR, ragdolls, a bookshelf with books on, all that stuff works.

    It won't beat havok for complex game sims with lots happening, but then to get that far, you need to be making something pretty big.

    Personally I'll stick with Physx and gameobject pattern for now. Perhaps they'll improve DOTS physics so much I can't say no?
     
    NotaNaN likes this.
  39. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,752
    Same.

    I’m just trying to figure out how Havok and Unity Physics serve two distinct purposes.
     
  40. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
    Most games doesn't need deterministic physics.
    Many games don't need even real physics. You can have own solution, which uses 2D simplified physics in many cases.
    There are few situations, where determinism matter for physics.
    But I haven't seen here any particular game yet, using DOTS, which requires real deterministic physics for multiplier, that can not be achieved in much simpler way.

    If anything, network bandwidth allows to utilize speed of networking. We are far beyond 90s and early 20s, where network was turtling. We can live stream whole videos.
    Also, with saying that, many games can be semi deterministic. Don't have to be fully deterministic. That means, you can keep things in synch.

    If I remember correctly, Havok focused on performance and accuracy of physics.
    Imagine for example, need for the destruction simulation. That where Havok can shine.
    Stacking and many collisions.
     
  41. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    UnityPhysics is better for efficient networked physics

    Havok is better for stable stacks of bodies and more simulation quality/performance

    If we had to choose only one, we'd have to determine which one of these scenarios would have to be impossible in Unity. For example, in monobehaviour physics, proper low-cost & low-jitter networked physics simulation is not a possibility, because PhysX is pretty much a Havok equivalent (stateful). I don't think anyone has figured out how to make a single physics engine that excels in all scenarios
     
    Last edited: Jan 17, 2022
    NotaNaN, DrBoum, Anthiese and 3 others like this.
  42. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Well one is free the other is not. If they don't serve different purpose they at least serve two different target.

    There are a lot or indie game dev studio and/or beginner which I'm sure are glad not to have to pay for a physics solutions. (I for one am ;) )
     
    DevViktoria and mariandev like this.
  43. Iron-Warrior

    Iron-Warrior

    Joined:
    Nov 3, 2009
    Posts:
    838
    Other users have noted the different applications Havok and Unity Physics serve, but
    as noted in the post I linked, Unity Physics is entirely decoupled from ECS, allowing a custom game object wrapper. (Unity Physics is still written in HPC# and uses the Job system for its internal step).
     
    Menion-Leah likes this.
  44. cultureulterior

    cultureulterior

    Joined:
    Mar 15, 2015
    Posts:
    68
    From what I'm hearing, there's still no dots support for a UI, and nobody seems to know the answer about tiny?
     
  45. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    306
    UIElements / UIToolkit (built into the editor from 2021+) is completely independent of DOTS. However, that does not mean you can not use it in DOTS, just that there is no Job / burstable accessible API into UIToolkit.

    So any UI displayed will need to be done on the main thread inside OnUpdate() or similar in the traditional OOS format of coding. And from the comments earlier by Unity, there are no plans to made UIToolkit Burst friendly sadly.
     
    BackgroundMover likes this.
  46. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    225
    Wow... that was a lot of off topic (sorry but I had to point it out...)

    I don't remember if it was already asked or not but:
    If the end goal for my project is v1.0, will it be worth updating to 0.50 first?
     
  47. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
    Depends.
    0.50 is most likely happens in early 2022. But 1.0 optimistically saying in 2023.
    So unless there is something you need from 0.50 and nothing braking changes, which could halt your project, then perhaps is worth move. But I will be a little reserved on it, on existing project.
     
  48. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    Very much like the opt in approach. Data oriented design is too slow and clunky to work with for 90% of game dev but excited to have it when we need to do the same thing 1 million times
     
  49. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
    Why people thinks, DOTS is only good for calculating 1mln things? It is completely fine for many thousands things. Or for optimising things. Or mobile hardware things. Plenty usages.

    But yes, DOTS is not for everyone.
     
  50. YuriyVotintsev

    YuriyVotintsev

    Joined:
    Jun 11, 2013
    Posts:
    93
    It is completely fine for ten things as well. I use it because i like this architecture, not because of performance or optimisation.
     
Thread Status:
Not open for further replies.