Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved Custom Native Container and ISystem

Discussion in 'Entity Component System' started by suity447, Dec 17, 2022.

  1. suity447

    suity447

    Joined:
    Oct 18, 2022
    Posts:
    33
    I tried implementing a native parallel counter as described here: https://docs.unity3d.com/Packages/c...obs-custom-types.html#custom-nativecontainers
    to use in an IJobEntity scheduled by an ISystem. However, when trying to create it in OnCreate like I would with a standard NativeContainer
    Code (CSharp):
    1. NativeCounter bulletCounter;
    2.  
    3.     [BurstCompile]
    4.     public void OnCreate(ref SystemState state)
    5.     {            
    6.         bulletCounter = new NativeCounter(Allocator.Persistent);
    7.     }
    I get an error message about using managed fields:
    ArgumentException: The system ShootSystem cannot contain managed fields. If you need have to store managed fields in your system, please use SystemBase instead. Reason: ShootSystem.bulletCounter.countIntegers is not blittable because it is not of value type (System.Int32*)
    ShootSystem.bulletCounter.m_DisposeSentinel is not blittable because it is not of value type (Unity.Collections.LowLevel.Unsafe.DisposeSentinel)

    I could of course use an ISystemBase but as e.g. NativeArrays which also use pointers can be used in an ISystem I would like to know if there is way to make this work?
     
  2. Harry-Wells

    Harry-Wells

    Joined:
    Oct 13, 2013
    Posts:
    73
    The main issue is using DisposeSentinel. The safety mechanisms don't need it anymore (and usages of it were removed in Collections 2.1.0-exp.4).
    For reference, here's a version that doesn't use DisposeSentinel and works in Burst compiled ISystem, IJobEntity etc.:
    Code (CSharp):
    1.  
    2.     /// <summary>
    3.     /// Provides a native thread-safe counter.
    4.     /// </summary>
    5.     [StructLayout(LayoutKind.Sequential)]
    6.     [NativeContainer]
    7.     public unsafe struct NativeCounterInt
    8.     {
    9.         // The actual pointer to the allocated count needs to have restrictions relaxed so jobs can be scheduled with this container
    10.         [NativeDisableUnsafePtrRestriction] private int* m_Counter;
    11.  
    12. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    13.         private AtomicSafetyHandle m_Safety;
    14. #endif
    15.  
    16.         // Keep track of where the memory for this was allocated
    17.         private AllocatorManager.AllocatorHandle m_Allocator;
    18.  
    19.         /// <summary>
    20.         /// Initializes an instance of <see cref="NativeCounterInt"/>.
    21.         /// </summary>
    22.         /// <param name="label">Allocator label.</param>
    23.         public NativeCounterInt(Allocator label)
    24.         {
    25.             this = default;
    26.             AllocatorManager.AllocatorHandle temp = label;
    27.             Initialize(ref temp);
    28.             // Initialize the count to 0 to avoid uninitialized data
    29.             Count = 0;
    30.         }
    31.  
    32.         internal void Initialize<U>(ref U allocator) where U : AllocatorManager.IAllocator
    33.         {
    34. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    35.             // Create the AtomicSafetyHandle
    36.             m_Safety = CollectionHelper.CreateSafetyHandle(allocator.Handle);
    37. #endif
    38.  
    39.             m_Allocator = allocator.Handle;
    40.             // Allocate native memory for a single integer
    41.             m_Counter = AllocatorManager.Allocate<int>(m_Allocator);
    42.         }
    43.  
    44.         /// <summary>
    45.         /// Increments this counter and returns the incremented value.
    46.         /// </summary>
    47.         /// <returns>Incremented value.</returns>
    48.         public int Increment()
    49.         {
    50.             // Verify that the caller has write permission on this data.
    51.             // This is the race condition protection, without these checks the AtomicSafetyHandle is useless
    52. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    53.             AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
    54. #endif
    55.             return Interlocked.Increment(ref *m_Counter);
    56.         }
    57.  
    58.         /// <summary>
    59.         /// Counter value.
    60.         /// </summary>
    61.         public int Count
    62.         {
    63.             get
    64.             {
    65.                 // Verify that the caller has read permission on this data.
    66.                 // This is the race condition protection, without these checks the AtomicSafetyHandle is useless
    67. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    68.                 AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
    69. #endif
    70.                 return *m_Counter;
    71.             }
    72.             set
    73.             {
    74.                 // Verify that the caller has write permission on this data. This is the race condition protection, without these checks the AtomicSafetyHandle is useless
    75. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    76.                 AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
    77. #endif
    78.                 *m_Counter = value;
    79.             }
    80.         }
    81.  
    82.         /// <summary>
    83.         /// True if counter was created.
    84.         /// </summary>
    85.         public bool IsCreated
    86.         {
    87.             get { return m_Counter != null; }
    88.         }
    89.  
    90.         /// <summary>
    91.         /// Disposes this instance.
    92.         /// </summary>
    93.         public void Dispose()
    94.         {
    95. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    96.             CollectionHelper.DisposeSafetyHandle(ref m_Safety);
    97. #endif
    98.             CheckNull(m_Counter);
    99.             AllocatorManager.Free(m_Allocator, m_Counter);
    100.             m_Counter = null;
    101.         }
    102.  
    103.         private static void CheckNull(void* data)
    104.         {
    105.             if (data == null)
    106.             {
    107.                 throw new Exception($"{nameof(NativeCounterInt)} has yet to be created or has been destroyed!");
    108.             }
    109.         }
    110.  
    111.         /// <summary>
    112.         /// Creates a parallel writer for this instance.
    113.         /// </summary>
    114.         /// <returns>Parallel writer.</returns>
    115.         public ParallelWriter AsParallelWriter() => this;
    116.  
    117.         /// <summary>
    118.         /// Parallel writer for a <see cref="NativeCounterInt"/>.
    119.         /// </summary>
    120.         [NativeContainer]
    121.         // This attribute is what makes it possible to use NativeCounter.Concurrent in a ParallelFor job
    122.         [NativeContainerIsAtomicWriteOnly]
    123.         public struct ParallelWriter
    124.         {
    125.             // Copy of the pointer from the full NativeCounter
    126.             [NativeDisableUnsafePtrRestriction] int* m_Counter;
    127.  
    128.             // Copy of the AtomicSafetyHandle from the full NativeCounter. The dispose sentinel is not copied since this inner struct does not own the memory and is not responsible for freeing it.
    129. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    130.             AtomicSafetyHandle m_Safety;
    131. #endif
    132.  
    133.             // This is what makes it possible to assign to NativeCounter.Concurrent from NativeCounter
    134.             /// <summary>
    135.             /// Provides implicit conversion from a <see cref="NativeCounterInt"/> to a <see cref="ParallelWriter"/>.
    136.             /// </summary>
    137.             /// <param name="cnt">Source counter.</param>
    138.             /// <returns>Parallel writer.</returns>
    139.             public static implicit operator ParallelWriter(NativeCounterInt cnt)
    140.             {
    141.                 ParallelWriter parallelWriter;
    142. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    143.                 AtomicSafetyHandle.CheckWriteAndThrow(cnt.m_Safety);
    144.                 parallelWriter.m_Safety = cnt.m_Safety;
    145.                 AtomicSafetyHandle.UseSecondaryVersion(ref parallelWriter.m_Safety);
    146. #endif
    147.  
    148.                 parallelWriter.m_Counter = cnt.m_Counter;
    149.                 return parallelWriter;
    150.             }
    151.  
    152.             /// <summary>
    153.             /// Increments the counter and returns the incremented value.
    154.             /// </summary>
    155.             /// <returns>Incremented value.</returns>
    156.             public int Increment()
    157.             {
    158.                 // Increment still needs to check for write permissions
    159. #if ENABLE_UNITY_COLLECTIONS_CHECKS
    160.                 AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
    161. #endif
    162.                 // The actual increment is implemented with an atomic, since it can be incremented by multiple threads at the same time
    163.                 return Interlocked.Increment(ref *m_Counter);
    164.             }
    165.         }
    166.     }
     
    koirat, Sirius64 and bb8_1 like this.
  3. suity447

    suity447

    Joined:
    Oct 18, 2022
    Posts:
    33
    Thank you!