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. Dismiss Notice

Question Why not pass UnsafeHashSet by reference and Count fails

Discussion in 'Entity Component System' started by liyibao, Aug 23, 2023.

  1. liyibao

    liyibao

    Joined:
    Sep 6, 2014
    Posts:
    25
    Code (CSharp):
    1. struct TestUnsafe
    2. {
    3.    public UnsafeHashSet<int> testUnsafe;
    4.    public NativeHashSet<int> testNative;
    5. }
    6. void Test()
    7. {
    8.       TestUnsafe test = new TestUnsafe();
    9.       test.testUnsafe = new UnsafeHashSet<int>(0, Allocator.Temp);
    10.       test.testNative = new NativeHashSet<int>(0, Allocator.Temp);
    11.       var testUnsafe = test.testUnsafe;
    12.       var testNative = test.testNative;
    13.       testUnsafe.Add(1);
    14.       testNative.Add(2);
    15.       Debug.Log("testUnsafe.Count:" + testUnsafe.Count);
    16.       Debug.Log("testNative.Count:" + testNative.Count);
    17.       Debug.Log("test.testUnsafe.Count:" + test.testUnsafe.Count);
    18.       Debug.Log("test.testNative.Count:" + test.testNative.Count);
    19.       foreach (var item in test.testUnsafe)
    20.       {
    21.           UnityEngine.Debug.Log("foreach test.testUnsafe:" + item);
    22.       }
    23.       foreach (var item in test.testNative)
    24.       {
    25.           UnityEngine.Debug.Log("foreach test.testNative:" + item);
    26.       }
    27.   }
     

    Attached Files:

    Last edited: Aug 25, 2023
  2. liyibao

    liyibao

    Joined:
    Sep 6, 2014
    Posts:
    25
    • Sorry, the picture is wrong, this is the correct output print

    upload_2023-8-24_9-42-33.png

    upload_2023-8-24_9-41-42.png
     
  3. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    282
    Do not post unformatted code. Do not post screenshots of code. Use code tags.
    https://forum.unity.com/threads/using-code-tags-properly.143875/

    Making a copy of an unsafe collection via assignment is not a safe operation if either value instance is modified after creating the second one. Unsafe collections store important data in the struct itself. Modifying an unsafe collection via one value instance will not necessary make the same changes on another value instance. Native collections do not have this problem because value instances use a pointer to an underlying unsafe collection, and changes to one instance of native collection will always modify the same unsafe collection data that the other instance uses.

    Unsafe collections should be passed by ref or pointer and should only exist in one location in memory. Performing an assignment of an unsafe collection local variable or field is not safe if either copy is then modified.
     
    xVergilx likes this.
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    That's not always true though. For example NativeList stored Count inside it once before it was fixed.
    Similar to NativeArray has now. But the general issue is the same.

    Some fields of the collections aren't stored via separate memory block. When struct is copied - its fields are copied, but pointer to the data memory block points to the same storage. Meaning fields like count will not match against other copy if modified elsewhere.

    Basically, there's a pointer to the data memory block
    unsafe void* SomeDataPtr
    and then there's fields like
    int m_Length
    . Both are value types (pointer and length), so they're both copied.

    When data at pointer changes => fields do not change for other copies of the struct.
    Since they're updated via respective method calls on specific copy only.

    Solution to this is to store data in the same memory block.
    But it also introduces extra performance costs and parallel restrictions which I think Unity attempts to avoid.

    TL;DR:
    Its important thing to remember that structs are still C# structs.
    They're copied even if they act like they are "references".
     
    Last edited: Aug 24, 2023
  5. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    282
    When was this? I haven't been able to find any references to such a thing. Note that the member in question is Length, not Count.
     
  6. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    282
    Really, the biggest danger with this is the possibility of an unsafe collection needing to do a reallocation in order to meet demand (e.g. exceeding a list's current capacity). You'll have one list updated to a new buffer and everything's hunky-dory. The other copy will still be pointing to the old memory, and you would end up doing bad things if you tried to access data from that copy, since it's no longer a valid memory reference.
     
    xVergilx likes this.
  7. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    In equal version of Collections for Entities 0.51-. Its still valid case for the NativeArray if you disassemble it.
     
  8. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    282
    https://github.com/needle-mirror/com.unity.entities/blob/0.51.0-preview.32/package.json
    https://github.com/needle-mirror/com.unity.entities/blob/0.51.1-preview.21/package.json
    https://github.com/needle-mirror/com.unity.collections/blob/1.3.1/Unity.Collections/NativeList.cs
    https://github.com/needle-mirror/com.unity.collections/blob/1.4.0/Unity.Collections/NativeList.cs
    I'm not really seeing anything like you described here. Do you know of a bug report or thread mentioning this?
    No need to disassemble anything for looking at some engine stuff.
    https://github.com/Unity-Technologi...ter/Runtime/Export/NativeArray/NativeArray.cs
    For this case, NativeArray are meant to have immutable length, so I don't see what the problem is.
     
    xVergilx likes this.
  9. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Perhaps my memory fails me then. Or I'm mixing it with the Length not being updated when NativeList was passed around between jobs involving physics commands case.