Search Unity

Question How To Change A Refrence With DOTS ?

Discussion in 'Entity Component System' started by tahsinXYZ, Nov 25, 2021.

  1. tahsinXYZ

    tahsinXYZ

    Joined:
    Aug 14, 2019
    Posts:
    70
    For example, I have a MonoBehaviour called "Soldier" and I want his/her refrence get changed directly. Like assigning refrence. Is it possible ?
     
  2. Baggers_

    Baggers_

    Joined:
    Sep 10, 2017
    Posts:
    98
    The term 'reference' is rather overloaded so you are probably going to have to expand your question. Do you mean a NativeReference<T>?, BlobAssetReference<T>?, a c# reference type (I'm assuming so given you're mentioning MonoBehaviour), etc.

    The best thing would be to show the code of the thing you are trying to do.
     
    tahsinXYZ and apkdev like this.
  3. tahsinXYZ

    tahsinXYZ

    Joined:
    Aug 14, 2019
    Posts:
    70
    For example, we have a for loop, list of objects and list of object datas. We want to recalculate every each single data and distribute to objects. Is it posibble ?
    BTW thank for reply!
     
  4. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Please post code, of what you try to do.
    Are you using jobs?

    With jobs, you are expected to operate on data, not references.
     
  5. tahsinXYZ

    tahsinXYZ

    Joined:
    Aug 14, 2019
    Posts:
    70
    Code (CSharp):
    1. [BurstCompile]
    2.     private void ExecuteFarmGrow()
    3.     {
    4.         FarmableResourceNode.FarmGrowJob farmGrowJob = new FarmableResourceNode.FarmGrowJob();
    5.         farmGrowJob.deltaTime = Time.deltaTime;
    6.         farmGrowJob.datas = new NativeArray<FarmableResourceNode.Data>(farmableResourceNodes.Count, Allocator.TempJob);
    7.         for (int i = 0; i < farmableResourceNodes.Count; i++)
    8.         {
    9.             farmGrowJob.datas[i] = farmableResourceNodes[i].data;
    10.         }
    11.         farmGrowJob.results = new NativeArray<FarmableResourceNode.Data>(farmableResourceNodes.Count, Allocator.TempJob);
    12.         JobHandle jobHandle = farmGrowJob.Schedule(farmableResourceNodes.Count, 32);
    13.         jobHandle.Complete();
    14.         farmGrowJob.datas.Dispose();
    15.         for (int i = 0; i < farmableResourceNodes.Count; i++)
    16.         {
    17.             farmableResourceNodes[i].data = farmGrowJob.results[i];
    18.             farmableResourceNodes[i].ChangeLODMeshByStage();
    19.         }
    20.         farmGrowJob.results.Dispose();
    21.     }
    22.     public struct DistributeJob : IJobParallelFor
    23.     {
    24.         public NativeList<FarmableResourceNode.Data> original;
    25.         public NativeArray<FarmableResourceNode.Data> newDatas;
    26.         public void Execute(int index)
    27.         {
    28.             original[index] = newDatas[index];
    29.         }
    30.     }
    Here is my code. There is a farmable resource node and inside node there is data. FarmGrowJob is producing data, it is ok but it does not distribute data and i have to cycle trough all farmable resource nodes but it makes performance lower. The question is how can I do this with jobs ?
     
  6. Baggers_

    Baggers_

    Joined:
    Sep 10, 2017
    Posts:
    98
    The question that comes to mind to me is: Why copy the data in and out?

    It feels from your code that the data wants to be kept together. My gut reaction is to have a manager that owns that NativeArray of data and give each farmableResourceNode an index into that array.

    Then the manager can own the jobs and the nodes can interact with them without excessive copying.

    You can add helpers to the farmableResourceNode class in order to make it easier/safer to work with, for example:

    Code (CSharp):
    1. using System;
    2. using System.Diagnostics;
    3. using Unity.Collections;
    4. using Unity.Collections.LowLevel.Unsafe;
    5. using UnityEditor;
    6.  
    7. struct ImportantData
    8. {
    9.     public float AhYesVeryImportant;
    10.  
    11.     public override string ToString() => $"ImportantData{{ AhYesVeryImportant={AhYesVeryImportant} }}";
    12. }
    13.  
    14. readonly struct ImportantDataKey
    15. {
    16.     public readonly int Index;
    17.     public readonly uint Version;
    18.  
    19.     public ImportantDataKey(int index, uint version)
    20.     {
    21.         Index = index;
    22.         Version = version;
    23.     }
    24.  
    25.     public override string ToString() => $"ImportantDataKey{{ Index={Index}, Version={Version} }}";
    26. }
    27.  
    28. class SomeManager : IDisposable
    29. {
    30.     const int INITIAL_DATA_CAPACITY = 1024;
    31.     const int INITIAL_FREED_CAPACITY = 64;
    32.  
    33.     NativeList<ImportantData> _data;
    34.     NativeList<int> _freeSlots;
    35.  
    36.     #if ENABLE_UNITY_COLLECTIONS_CHECKS
    37.     NativeList<uint> _versions;
    38.     #endif
    39.  
    40.     public SomeManager()
    41.     {
    42.         _data = new NativeList<ImportantData>(INITIAL_DATA_CAPACITY, Allocator.Persistent);
    43.         _freeSlots = new NativeList<int>(INITIAL_FREED_CAPACITY, Allocator.Persistent);
    44.  
    45.         #if ENABLE_UNITY_COLLECTIONS_CHECKS
    46.         _versions = new NativeList<uint>(INITIAL_DATA_CAPACITY, Allocator.Persistent);
    47.         #endif
    48.     }
    49.  
    50.     public void Dispose()
    51.     {
    52.         if (_data.IsCreated) // protect against double dispose
    53.         {
    54.             _data.Dispose();
    55.             _data = default; // I set this to default only because I like IsCreated to be false after dispose
    56.  
    57.             _freeSlots.Dispose();
    58.             _freeSlots = default;
    59.  
    60.             #if ENABLE_UNITY_COLLECTIONS_CHECKS
    61.             _versions.Dispose();
    62.             _versions = default;
    63.             #endif
    64.         }
    65.     }
    66.  
    67.     public ImportantDataKey RequestDataSlot(ImportantData initialValue = default)
    68.     {
    69.         if (_freeSlots.Length == 0)
    70.         {
    71.             #if ENABLE_UNITY_COLLECTIONS_CHECKS
    72.             _versions.Add(1);
    73.             #endif
    74.  
    75.             var key = new ImportantDataKey(_data.Length, version: 1);
    76.             _data.Add(initialValue);
    77.  
    78.             return key;
    79.         }
    80.         else
    81.         {
    82.             var i = _freeSlots.Length - 1;
    83.             var index = _freeSlots[i];
    84.             _freeSlots.RemoveAtSwapBack(i);
    85.  
    86.             _data[index] = initialValue;
    87.          
    88.             #if ENABLE_UNITY_COLLECTIONS_CHECKS
    89.             return new ImportantDataKey(index, _versions[index]);
    90.             #else
    91.             return new ImportantDataKey(index, 0);
    92.             #endif
    93.         }
    94.     }
    95.  
    96.     public void ReleaseDataSlot(ImportantDataKey key)
    97.     {
    98.         CheckKey(in key);
    99.         _freeSlots.Add(key.Index);
    100.  
    101.         #if ENABLE_UNITY_COLLECTIONS_CHECKS
    102.         _versions[key.Index]++;
    103.         #endif
    104.     }
    105.  
    106.     // This is unsafe as you must not request more slots while using the ref, however it is a pleasent
    107.     // way to work with data inside manager
    108.     public unsafe ref ImportantData GetUnsafeDataRef(ImportantDataKey key)
    109.     {
    110.         CheckKey(in key);
    111.         var elemPtr = ((ImportantData*) _data.GetUnsafePtr()) + key.Index;
    112.         return ref UnsafeUtility.AsRef<ImportantData>(elemPtr);
    113.     }
    114.  
    115.     public ImportantData GetData(ImportantDataKey key)
    116.     {
    117.         CheckKey(in key);
    118.         return _data[key.Index];
    119.     }
    120.  
    121.     public void SetData(ImportantDataKey key, in ImportantData data)
    122.     {
    123.         CheckKey(in key);
    124.         _data[key.Index] = data;
    125.     }
    126.  
    127.     [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
    128.     void CheckKey(in ImportantDataKey key)
    129.     {
    130.         if (_versions[key.Index] != key.Version)
    131.         {
    132.             throw new Exception(nameof(SomeManager) + ": invalid use of expired key");
    133.         }
    134.  
    135.         if (key.Index < 0 || key.Index >= _data.Length)
    136.         {
    137.             throw new IndexOutOfRangeException("ElementRef: Index is out of range in NativeArray.");
    138.         }
    139.     }
    140. }
    141.  
    142. static class PokeTheCode
    143. {
    144.     [MenuItem("Experiments/SomeManager")]
    145.     static void TestSomeManager()
    146.     {
    147.         using var mgr = new SomeManager();
    148.  
    149.         var key0 = mgr.RequestDataSlot();
    150.  
    151.         // show the initial value
    152.         UnityEngine.Debug.Log($"0: data is {mgr.GetData(key0)}");
    153.  
    154.         // let's set some data
    155.         mgr.SetData(key0, new ImportantData() {AhYesVeryImportant = 10});
    156.  
    157.         // and get the data
    158.         UnityEngine.Debug.Log($"1: data is now {mgr.GetData(key0)}");
    159.  
    160.         // now, let's set it using the unsafe ref
    161.         ref var val = ref mgr.GetUnsafeDataRef(key0);
    162.         UnityEngine.Debug.Log($"2: data is still {val}");
    163.         val.AhYesVeryImportant = 20;
    164.  
    165.         // show that we did, in fact, modify the data inside the manager
    166.         UnityEngine.Debug.Log($"3: data is now{mgr.GetData(key0)}");
    167.  
    168.         // and we release the data
    169.         mgr.ReleaseDataSlot(key0);
    170.      
    171.         // Lets grab a new key
    172.         var key1 = mgr.RequestDataSlot();
    173.         UnityEngine.Debug.Log($"4: new data is {mgr.GetData(key1)}");
    174.      
    175.         // We can see that we have reused the index, but the version is different. This minimizes the
    176.         // chance that we will accidentally damage our data with old keys
    177.         UnityEngine.Debug.Log($"Just for fun, the two keys {key0} -v- {key1}");
    178.  
    179.         // uncomment below for use after release error
    180.         // UnityEngine.Debug.Log($"data is {mgr.GetData(key0)}");
    181.  
    182.         // uncomment below for double release error
    183.         // mgr.ReleaseDataSlot(key0);
    184.     }
    185. }
    186.  
    The PokeTheCode class gives you a menu-item in the editor to try the manager. This uses a versioned index as a key in order to try and protect invalid uses of the data.

    Depending on the amount of churn of nodes you might want to handle the _data list differently, but I hope this gives some ideas of how to approach this.

    warning: I've knocked this up quickly and not tested it properly so I hope the code isn't too far off sanity
     
  7. Baggers_

    Baggers_

    Joined:
    Sep 10, 2017
    Posts:
    98
    To answer the original question directly. You *could* make a non-burst job that uses managed code. But I, as strongly as possible, advise against it. It would still be slow, but slow on a thread. It would also be far less safe that native collections.

    The fastest code is no code, so I refer back to my comment above which shows how to keep the data in a manager instead :)