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

Burst Fumbles over NativeArray indexing but not pointers

Discussion in 'Burst' started by DreamingImLatios, Aug 17, 2022.

  1. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Context: I'm experimenting with manually unrolling a single-axis sweep-and-prune algorithm. This particular attempt writes pairs packed as a ulong into a cache buffer and then conditionally increments the write index into the cache buffer. There's a few variants of this technique I'm still playing with, but I wanted to highlight something I discovered.

    When using NativeArray indexing, I got several instructions generated in the inner loop between tests. However, when I grabbed pointers from the NativeArrays and indexed them with ulong directly, things cleaned up quite a bit and I got more consistent usage of registers. The version using pointers performed better too, and was more or less the codegen I was trying to achieve.

    Am I being naive? Is this an issue with NativeArray? Is this an issue with Burst?

    I'm using Burst 1.8 prev 2 for this.

    Code:
    Code (CSharp):
    1. public struct EntityPair : IEquatable<EntityPair>
    2. {
    3.     public Entity a;
    4.     public Entity b;
    5.  
    6.     public EntityPair(Entity aa, Entity bb)
    7.     {
    8.         a = aa;
    9.         b = bb;
    10.     }
    11.  
    12.     public bool Equals(EntityPair other)
    13.     {
    14.         return a == other.a && b == other.b;
    15.     }
    16.  
    17.     public static bool operator ==(EntityPair a, EntityPair b)
    18.     {
    19.         return a.Equals(b);
    20.     }
    21.  
    22.     public static bool operator !=(EntityPair a, EntityPair b)
    23.     {
    24.         return !a.Equals(b);
    25.     }
    26. }
    27.  
    28. [BurstCompile(OptimizeFor = OptimizeFor.Performance)]
    29. public struct UnrolledSweepPoor : IJob
    30. {
    31.     [ReadOnly] public NativeArray<float>  xmins;
    32.     [ReadOnly] public NativeArray<float>  xmaxs;
    33.     [ReadOnly] public NativeArray<float4> minYZmaxYZsFlipped;
    34.     [ReadOnly] public NativeArray<Entity> entities;
    35.     public NativeList<EntityPair>         overlaps;
    36.  
    37.     public void Execute()
    38.     {
    39.         Hint.Assume(xmins.Length == xmaxs.Length);
    40.         Hint.Assume(xmins.Length == minYZmaxYZsFlipped.Length);
    41.  
    42.         var hitCache     = new NativeArray<ulong>(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
    43.         int nextHitIndex = 0;
    44.  
    45.         for (int i = 0; i < xmins.Length - 1; i++)
    46.         {
    47.             float4 current = -minYZmaxYZsFlipped[i].zwxy;
    48.  
    49.             float4 currentX = xmaxs[i];
    50.  
    51.             int j = i + 1;
    52.  
    53.             ulong pair  = (((ulong)i) << 32) | (uint)j;
    54.             ulong final = (((ulong)i) << 32) | ((uint)xmins.Length);
    55.  
    56.             while (pair + 3 < final)
    57.             {
    58.                 var nextMins = xmins.ReinterpretLoad<float4>((int)j);
    59.                 if (Hint.Unlikely(math.any(nextMins >= currentX)))
    60.                     break;
    61.  
    62.                 hitCache[nextHitIndex] = pair;
    63.                 if (math.bitmask(current < minYZmaxYZsFlipped[j]) == 0)
    64.                     nextHitIndex++;
    65.                 pair++;
    66.  
    67.                 hitCache[nextHitIndex] = pair;
    68.                 if (math.bitmask(current < minYZmaxYZsFlipped[j + 1]) == 0)
    69.                     nextHitIndex++;
    70.                 pair++;
    71.  
    72.                 hitCache[nextHitIndex] = pair;
    73.                 if (math.bitmask(current < minYZmaxYZsFlipped[j + 2]) == 0)
    74.                     nextHitIndex++;
    75.                 pair++;
    76.  
    77.                 hitCache[nextHitIndex] = pair;
    78.                 if (math.bitmask(current < minYZmaxYZsFlipped[j + 3]) == 0)
    79.                     nextHitIndex++;
    80.                 pair++;
    81.                 j += 4;
    82.  
    83.                 if (Hint.Unlikely(nextHitIndex >= 1020))
    84.                 {
    85.                     Drain(hitCache, nextHitIndex);
    86.                     nextHitIndex = 0;
    87.                 }
    88.             }
    89.  
    90.             while (pair < final && xmins[(int)j] < currentX.x)
    91.             {
    92.                 hitCache[nextHitIndex] = pair;
    93.                 if (math.bitmask(current < minYZmaxYZsFlipped[j]) == 0)
    94.                     nextHitIndex++;
    95.                 pair++;
    96.                 j++;
    97.             }
    98.  
    99.             if (nextHitIndex >= 1020)
    100.             {
    101.                 Drain(hitCache, nextHitIndex);
    102.                 nextHitIndex = 0;
    103.             }
    104.         }
    105.  
    106.         Drain(hitCache, nextHitIndex);
    107.     }
    108.  
    109.     [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    110.     void Drain(NativeArray<ulong> cache, int cacheCount)
    111.     {
    112.         for (int i = 0; i < cacheCount; i++)
    113.         {
    114.             ulong pair   = cache[i];
    115.             int   first  = (int)(pair >> 32);
    116.             int   second = (int)(pair & 0xffffffff);
    117.  
    118.             overlaps.Add(new EntityPair(entities[first], entities[second]));
    119.         }
    120.     }
    121. }
    122.  
    123. [BurstCompile(OptimizeFor = OptimizeFor.Performance)]
    124. public struct UnrolledSweep : IJob
    125. {
    126.     [ReadOnly] public NativeArray<float>  xmins;
    127.     [ReadOnly] public NativeArray<float>  xmaxs;
    128.     [ReadOnly] public NativeArray<float4> minYZmaxYZsFlipped;
    129.     [ReadOnly] public NativeArray<Entity> entities;
    130.     public NativeList<EntityPair>         overlaps;
    131.  
    132.     public unsafe void Execute()
    133.     {
    134.         Hint.Assume(xmins.Length == xmaxs.Length);
    135.         Hint.Assume(xmins.Length == minYZmaxYZsFlipped.Length);
    136.  
    137.         var   hitCache     = new NativeArray<ulong>(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
    138.         ulong nextHitIndex = 0;
    139.         var   hitCachePtr  = (ulong*)hitCache.GetUnsafePtr();
    140.         var   minMaxPtr    = (float4*)minYZmaxYZsFlipped.GetUnsafeReadOnlyPtr();
    141.  
    142.         for (int i = 0; i < xmins.Length - 1; i++)
    143.         {
    144.             float4 current = -minYZmaxYZsFlipped[i].zwxy;
    145.  
    146.             float4 currentX = xmaxs[i];
    147.  
    148.             ulong j = (ulong)i + 1;
    149.  
    150.             ulong pair  = (((ulong)i) << 32) | j;
    151.             ulong final = (((ulong)i) << 32) | ((uint)xmins.Length);
    152.  
    153.             while (pair + 3 < final)
    154.             {
    155.                 var nextMins = xmins.ReinterpretLoad<float4>((int)j);
    156.                 if (Hint.Unlikely(math.any(nextMins >= currentX)))
    157.                     break;
    158.  
    159.                 hitCachePtr[nextHitIndex] = pair;
    160.                 if (math.bitmask(current < minMaxPtr[j]) == 0)
    161.                     nextHitIndex++;
    162.                 pair++;
    163.  
    164.                 hitCachePtr[nextHitIndex] = pair;
    165.                 if (math.bitmask(current < minMaxPtr[j + 1]) == 0)
    166.                     nextHitIndex++;
    167.                 pair++;
    168.  
    169.                 hitCachePtr[nextHitIndex] = pair;
    170.                 if (math.bitmask(current < minMaxPtr[j + 2]) == 0)
    171.                     nextHitIndex++;
    172.                 pair++;
    173.  
    174.                 hitCachePtr[nextHitIndex] = pair;
    175.                 if (math.bitmask(current < minMaxPtr[j + 3]) == 0)
    176.                     nextHitIndex++;
    177.                 pair++;
    178.                 j += 4;
    179.  
    180.                 if (Hint.Unlikely(nextHitIndex >= 1020))
    181.                 {
    182.                     Drain(hitCache, nextHitIndex);
    183.                     nextHitIndex = 0;
    184.                 }
    185.             }
    186.  
    187.             while (pair < final && xmins[(int)j] < currentX.x)
    188.             {
    189.                 hitCachePtr[nextHitIndex] = pair;
    190.                 if (math.bitmask(current < minMaxPtr[j]) == 0)
    191.                     nextHitIndex++;
    192.                 pair++;
    193.                 j++;
    194.             }
    195.  
    196.             if (nextHitIndex >= 1020)
    197.             {
    198.                 Drain(hitCache, nextHitIndex);
    199.                 nextHitIndex = 0;
    200.             }
    201.         }
    202.  
    203.         Drain(hitCache, nextHitIndex);
    204.     }
    205.  
    206.     [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    207.     void Drain(NativeArray<ulong> cache, ulong cacheCount)
    208.     {
    209.         for (int i = 0; i < (int)cacheCount; i++)
    210.         {
    211.             ulong pair   = cache[i];
    212.             int   first  = (int)(pair >> 32);
    213.             int   second = (int)(pair & 0xffffffff);
    214.  
    215.             overlaps.Add(new EntityPair(entities[first], entities[second]));
    216.         }
    217.     }
    218. }
    NativeArray gen (poor)
    Code (CSharp):
    1. .LBB7_16:
    2. === FindPairsSimplified.cs(1424, 1)                    if (Hint.Unlikely(math.any(nextMins >= currentX)))
    3.         lea               rax, [r13 + r14]
    4.         mov               rdx, qword ptr [rcx]
    5. .Ltmp96:
    6.         add               rdx, r9
    7. .Ltmp97:
    8.         vcmpleps          xmm0, xmm6, xmmword ptr [rdx + 4*r14]
    9.         vmovmskps         edx, xmm0
    10.         test              edx, edx
    11.         jne               .LBB7_5
    12. === FindPairsSimplified.cs(1428, 1)                    if (math.bitmask(current < minYZmaxYZsFlipped[j]) == 0)
    13.         lea               rdi, [r13 + r14 + 3]
    14. .Ltmp98:
    15.         movsxd            rdx, r8d
    16.         mov               qword ptr [r12 + 8*rdx], rax
    17. .Ltmp99:
    18.         mov               rax, qword ptr [rcx + 112]
    19. .Ltmp100:
    20.         vcmpltps          xmm0, xmm7, xmmword ptr [rax + rbx]
    21.         vmovmskps         esi, xmm0
    22.         cmp               esi, 1
    23.         adc               edx, 0
    24. .Ltmp101:
    25.         lea               rsi, [r13 + r14 + 1]
    26.         movsxd            rdx, edx
    27.         mov               qword ptr [r12 + 8*rdx], rsi
    28. .Ltmp102:
    29.         lea               esi, [r15 + r14 + 2]
    30.         movsxd            rsi, esi
    31.         shl               rsi, 4
    32. .Ltmp103:
    33. === FindPairsSimplified.cs(1433, 1)                    if (math.bitmask(current < minYZmaxYZsFlipped[j + 1]) == 0)
    34.         vcmpltps          xmm0, xmm7, xmmword ptr [rax + rsi]
    35.         vmovmskps         esi, xmm0
    36.         cmp               esi, 1
    37.         adc               edx, 0
    38. .Ltmp104:
    39.         lea               rsi, [r13 + r14 + 2]
    40.         movsxd            rdx, edx
    41.         mov               qword ptr [r12 + 8*rdx], rsi
    42. .Ltmp105:
    43.         lea               esi, [r15 + r14 + 3]
    44.         movsxd            rsi, esi
    45.         shl               rsi, 4
    46. .Ltmp106:
    47. === FindPairsSimplified.cs(1438, 1)                    if (math.bitmask(current < minYZmaxYZsFlipped[j + 2]) == 0)
    48.         vcmpltps          xmm0, xmm7, xmmword ptr [rax + rsi]
    49.         vmovmskps         esi, xmm0
    50.         cmp               esi, 1
    51.         adc               edx, 0
    52. .Ltmp107:
    53.         movsxd            r8, edx
    54.         mov               qword ptr [r12 + 8*r8], rdi
    55. .Ltmp108:
    56.         lea               edx, [r15 + r14 + 4]
    57.         movsxd            rdx, edx
    58.         shl               rdx, 4
    59. .Ltmp109:
    60. === FindPairsSimplified.cs(1443, 1)                    if (math.bitmask(current < minYZmaxYZsFlipped[j + 3]) == 0)
    61.         vcmpltps          xmm0, xmm7, xmmword ptr [rax + rdx]
    62.         vmovmskps         eax, xmm0
    63.         cmp               eax, 1
    64.         adc               r8d, 0
    65. === FindPairsSimplified.cs(1448, 1)                    if (Hint.Unlikely(nextHitIndex >= 1020))
    66.         cmp               r8d, 1019
    67.         jg                .LBB7_18
    68. .LBB7_3:
    69. === FindPairsSimplified.cs(1421, 1)                while (pair + 3 < final)
    70.         lea               rax, [r13 + r14 + 7]
    71.         add               r14, 4
    72.         add               rbx, 64
    73.         cmp               rax, qword ptr [rbp - 8]
    74.         jb                .LBB7_16
    75.         jmp               .LBB7_4
    76. .LBB7_18:
    77. === FindPairsSimplified.cs(1450, 1)                        Drain(hitCache, nextHitIndex);
    78.         mov               qword ptr [rbp - 64], r12
    79.         vmovups           xmmword ptr [rbp - 56], xmm8
    80.         mov               rax, qword ptr [rbp]
    81.         mov               qword ptr [rbp - 40], rax
    82.         mov               eax, dword ptr [rbp + 8]
    83.         mov               dword ptr [rbp - 32], eax
    84.         mov               eax, dword ptr [rbp + 12]
    85.         mov               dword ptr [rbp - 28], eax
    86.         mov               qword ptr [rbp - 24], 0
    87.         mov               qword ptr [rbp - 16], 2
    88.         lea               rdx, [rbp - 64]
    89.         mov               rsi, r9
    90.         mov               qword ptr [rbp - 96], r10
    91.         mov               rdi, r11
    92.         call              "OptimizationAdventures.UnrolledSweepPoor.Drain(OptimizationAdventures.UnrolledSweepPoor* this, Unity.Collections.NativeArray`1<ulong> cache, int cacheCount) -> void_9136ab577d8b85bf6b250ef6053d3a6d from OptimizationAdventures, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null@@24"
    93.         mov               r11, rdi
    94.         mov               r10, qword ptr [rbp - 96]
    95.         mov               r9, rsi
    96.         mov               rcx, qword ptr [rbp - 72]
    97.         xor               r8d, r8d
    98.         jmp               .LBB7_3
    Pointer-based gen (faster)
    Code (CSharp):
    1. .LBB7_23:
    2. === FindPairsSimplified.cs(1329, 1)                    if (Hint.Unlikely(math.any(nextMins >= currentX)))
    3.         lea               rax, [r13 + r14]
    4. .Ltmp95:
    5.         mov               rbx, qword ptr [rcx]
    6.         mov               rdx, r12
    7.         and               rdx, rsi
    8. .Ltmp96:
    9.         vcmpleps          xmm0, xmm6, xmmword ptr [rbx + rdx]
    10.         vmovmskps         edx, xmm0
    11.         test              edx, edx
    12.         jne               .LBB7_6
    13. === FindPairsSimplified.cs(1332, 1)                    hitCachePtr[nextHitIndex] = pair;
    14.         lea               rdx, [r13 + r14 + 3]
    15.         mov               qword ptr [rdi + 8*r8], rax
    16. === FindPairsSimplified.cs(1333, 1)                    if (math.bitmask(current < minMaxPtr[j]) == 0)
    17.         vcmpltps          xmm0, xmm7, xmmword ptr [r15 + 4*r12]
    18.         vmovmskps         eax, xmm0
    19.         cmp               eax, 1
    20.         adc               r8, 0
    21. === FindPairsSimplified.cs(1337, 1)                    hitCachePtr[nextHitIndex] = pair;
    22.         lea               rax, [r13 + r14 + 1]
    23.         mov               qword ptr [rdi + 8*r8], rax
    24. === FindPairsSimplified.cs(1338, 1)                    if (math.bitmask(current < minMaxPtr[j + 1]) == 0)
    25.         vcmpltps          xmm0, xmm7, xmmword ptr [r15 + 4*r12 + 16]
    26.         vmovmskps         eax, xmm0
    27.         cmp               eax, 1
    28.         adc               r8, 0
    29. === FindPairsSimplified.cs(1342, 1)                    hitCachePtr[nextHitIndex] = pair;
    30.         lea               rax, [r13 + r14 + 2]
    31.         mov               qword ptr [rdi + 8*r8], rax
    32. === FindPairsSimplified.cs(1343, 1)                    if (math.bitmask(current < minMaxPtr[j + 2]) == 0)
    33.         vcmpltps          xmm0, xmm7, xmmword ptr [r15 + 4*r12 + 32]
    34.         vmovmskps         eax, xmm0
    35.         cmp               eax, 1
    36.         adc               r8, 0
    37. === FindPairsSimplified.cs(1347, 1)                    hitCachePtr[nextHitIndex] = pair;
    38.         mov               qword ptr [rdi + 8*r8], rdx
    39. === FindPairsSimplified.cs(1348, 1)                    if (math.bitmask(current < minMaxPtr[j + 3]) == 0)
    40.         vcmpltps          xmm0, xmm7, xmmword ptr [r15 + 4*r12 + 48]
    41.         vmovmskps         eax, xmm0
    42.         cmp               eax, 1
    43.         adc               r8, 0
    44. === FindPairsSimplified.cs(1353, 1)                    if (Hint.Unlikely(nextHitIndex >= 1020))
    45.         cmp               r8, 1019
    46.         ja                .LBB7_17
    47. .LBB7_4:
    48. === FindPairsSimplified.cs(1326, 1)                while (pair + 3 < final)
    49.         lea               rax, [r13 + r14 + 7]
    50.         add               r14, 4
    51.         add               r12, 16
    52.         cmp               rax, qword ptr [rbp - 8]
    53.         jb                .LBB7_23
    54.         jmp               .LBB7_5
    55. .LBB7_17:
    56. === FindPairsSimplified.cs(1355, 1)                        Drain(hitCache, nextHitIndex);
    57.         mov               qword ptr [rbp - 64], rdi
    58.         vmovups           xmmword ptr [rbp - 56], xmm8
    59.         mov               rax, qword ptr [rbp]
    60.         mov               qword ptr [rbp - 40], rax
    61.         mov               eax, dword ptr [rbp + 8]
    62.         mov               dword ptr [rbp - 32], eax
    63.         mov               eax, dword ptr [rbp + 12]
    64.         mov               dword ptr [rbp - 28], eax
    65.         mov               qword ptr [rbp - 24], 0
    66.         mov               qword ptr [rbp - 16], 2
    67.         lea               rdx, [rbp - 64]
    68.         mov               rbx, r10
    69.         mov               qword ptr [rbp - 96], r11
    70.         mov               rsi, r9
    71.         call              "OptimizationAdventures.UnrolledSweep.Drain(OptimizationAdventures.UnrolledSweep* this, Unity.Collections.NativeArray`1<ulong> cache, ulong cacheCount) -> void_9136ab577d8b85bf6b250ef6053d3a6d from OptimizationAdventures, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null@@24"
    72.         mov               r9, rsi
    73.         movabs            rsi, 8589934588
    74.         mov               r11, qword ptr [rbp - 96]
    75.         mov               r10, rbx
    76.         mov               rcx, qword ptr [rbp - 72]
    77.         xor               r8d, r8d
    78.         jmp               .LBB7_4
     
  2. Zuntatos

    Zuntatos

    Joined:
    Nov 18, 2012
    Posts:
    612
    Check what you get if you make `i` an uint and `j` an ulong in the UnrolledSweepPoor. And make sure that with that change the loop comparison is a uint comparison without promoting the comparison to long. You'll still have to cast to (int) on every indexer though, because sadly there isn't indexer overloading in C#. Maybe with Hint.Assumes() you can promise the compiler that the ulong is in int range.

    I don't have a benchmark environment with your code, so I can't check that. But it does give interesting code gen differences compared to int i, j.
     
  3. sheredom

    sheredom

    Unity Technologies

    Joined:
    Jul 15, 2019
    Posts:
    300
    Have you got some perf data running each one to show please? Just curious before I dive in what the delta is.
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    1000: Poor - 212.7 microseconds, Pointers - 156.0 microseconds
    2000: Poor - 1.534 milliseconds, Pointers - 1.082 milliseconds
    5000: Poor - 3.443 milliseconds, Pointers - 2.502 milliseconds
    10000: Poor - 14.36 milliseconds, Pointers - 10.01 milliseconds

    For additional context, I have a few other variants in my test lineup. I have an "OptimalOrder" which is what I've been using for my projects and rather than sign flip the max values of the y and z components, it uses shuffles to group the mins and maxes into separate registers for both comparison boxes. That used to be the fastest until the newest Burst versions. "Poor" performs worse than that. With the latest Burst versions, two other variants all of a sudden started performing better. There's one that uses the max component sign flipping trick these unrolled versions also use, and there's one which also uses a sentinel box to terminate the loop without counter checks. Those also dispatch immediately on found pairs. The pointer-based unrolled version outperforms both of these by a very slim (a couple percent) margin.

    I guess what surprises me the most is that following the conventional API tripped up Burst so much, even though I've also seen Burst completely change the way two nested loops work and the variables involved such that it would be difficult to associate with the original code (but the results were correct and fast :D). But again, I could just be naive here. I'm no expert in the rules of C# or IL. I just have a general idea of what I want to see in the Burst inspector and how that might correlate to what I am measuring in VTune.

    Speaking of VTune, here's the pointer-based version running on 5000 elements 5000 times:
    upload_2022-8-17_11-19-36.png
     
  5. sheredom

    sheredom

    Unity Technologies

    Joined:
    Jul 15, 2019
    Posts:
    300
    One difference between both is the int index in the NativeArray case, and the ulong in the pointer case. With the int, you see that before it actually stores into the hitCache it is doing movsxd - it is sign extending the integer up to a 64-bit index (which isn't free!).

    It might be enough to make that a uint and cast it to int before the indexing on the NativeArray - not ideal but worth checking.
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Yeah. Seeing that sign extension was what led me to explore using pointers in the first place. With pointers, the code is perfectly clear about what it is doing. With NativeArrays that require casting on every index operation, that's just fighting the API and obfuscating things.

    I'm getting the performance I was looking for, so I don't have any real incentive to try any hacks and workarounds because I already have one that gets exactly what I want. The reason I posted this is because it was a surprising performance pitfall that a lot of people are going to fall into, because that's what the API and all examples suggest doing.
     
  7. Spy-Master

    Spy-Master

    Joined:
    Aug 4, 2022
    Posts:
    282
    Code (CSharp):
    1. public unsafe struct UnsafeList<T> where T : unmanaged
    2. {
    3.     public T* Ptr;
    4.     public int Length;
    5.  
    6.     public T this[int index]
    7.     {
    8.         readonly get => Ptr[index];
    9.         set => Ptr[index] = value;
    10.     }
    11.  
    12.     public T this[uint index]
    13.     {
    14.         readonly get => Ptr[index];
    15.         set => Ptr[index] = value;
    16.     }
    17.  
    18.     public T this[long index]
    19.     {
    20.         readonly get => Ptr[index];
    21.         set => Ptr[index] = value;
    22.     }
    23.  
    24.     public T this[ulong index]
    25.     {
    26.         readonly get => Ptr[index];
    27.         set => Ptr[index] = value;
    28.     }
    29.  
    30.     public T this[string index]
    31.     {
    32.         readonly get => Ptr[index.Length];
    33.         set => Ptr[index.Length] = value;
    34.     }
    35.  
    36.     public T this[HttpClient index]
    37.     {
    38.         readonly get => Ptr[(int)index.PostAsync("https://google.com", new StringContent("")).Result.StatusCode];
    39.         set => Ptr[index.GetStreamAsync("https://forum.unity.com").Result.Length] = value;
    40.     }
    41. }
    42.  
    43. public unsafe struct NativeList<T> where T : unmanaged
    44. {
    45.     internal UnsafeList<T>* m_ListData;
    46.    
    47.     public T this[int index]
    48.     {
    49.         readonly get => (*m_ListData)[index];
    50.         set => (*m_ListData)[index] = value;
    51.     }
    52.    
    53.     public T this[uint index]
    54.     {
    55.         readonly get => (*m_ListData)[index];
    56.         set => (*m_ListData)[index] = value;
    57.     }
    58.    
    59.     public T this[long index]
    60.     {
    61.         readonly get => (*m_ListData)[index];
    62.         set => (*m_ListData)[index] = value;
    63.     }
    64.    
    65.     public T this[ulong index]
    66.     {
    67.         readonly get => (*m_ListData)[index];
    68.         set => (*m_ListData)[index] = value;
    69.     }
    70. }
     
  8. Zuntatos

    Zuntatos

    Joined:
    Nov 18, 2012
    Posts:
    612
    Hah, point taken. I've even overloaded the indexer on my own UnsafeNativeArray