/ src / Ryujinx.Common / Memory / PartialUnmaps / ThreadLocalMap.cs
ThreadLocalMap.cs
 1  using System.Runtime.InteropServices;
 2  using System.Threading;
 3  using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers;
 4  
 5  namespace Ryujinx.Common.Memory.PartialUnmaps
 6  {
 7      /// <summary>
 8      /// A simple fixed size thread safe map that can be used from native code.
 9      /// Integer thread IDs map to corresponding structs.
10      /// </summary>
11      /// <typeparam name="T">The value type for the map</typeparam>
12      [StructLayout(LayoutKind.Sequential, Pack = 1)]
13      public struct ThreadLocalMap<T> where T : unmanaged
14      {
15          public const int MapSize = 20;
16  
17          public Array20<int> ThreadIds;
18          public Array20<T> Structs;
19  
20          public static readonly int ThreadIdsOffset;
21          public static readonly int StructsOffset;
22  
23          /// <summary>
24          /// Populates the field offsets for use when emitting native code.
25          /// </summary>
26          static ThreadLocalMap()
27          {
28              ThreadLocalMap<T> instance = new();
29  
30              ThreadIdsOffset = OffsetOf(ref instance, ref instance.ThreadIds);
31              StructsOffset = OffsetOf(ref instance, ref instance.Structs);
32          }
33  
34          /// <summary>
35          /// Gets the index of a given thread ID in the map, or reserves one.
36          /// When reserving a struct, its value is set to the given initial value.
37          /// Returns -1 when there is no space to reserve a new entry.
38          /// </summary>
39          /// <param name="threadId">Thread ID to use as a key</param>
40          /// <param name="initial">Initial value of the associated struct.</param>
41          /// <returns>The index of the entry, or -1 if none</returns>
42          public int GetOrReserve(int threadId, T initial)
43          {
44              // Try get a match first.
45  
46              for (int i = 0; i < MapSize; i++)
47              {
48                  int compare = Interlocked.CompareExchange(ref ThreadIds[i], threadId, threadId);
49  
50                  if (compare == threadId)
51                  {
52                      return i;
53                  }
54              }
55  
56              // Try get a free entry. Since the id is assumed to be unique to this thread, we know it doesn't exist yet.
57  
58              for (int i = 0; i < MapSize; i++)
59              {
60                  int compare = Interlocked.CompareExchange(ref ThreadIds[i], threadId, 0);
61  
62                  if (compare == 0)
63                  {
64                      Structs[i] = initial;
65                      return i;
66                  }
67              }
68  
69              return -1;
70          }
71  
72          /// <summary>
73          /// Gets the struct value for a given map entry.
74          /// </summary>
75          /// <param name="index">Index of the entry</param>
76          /// <returns>A reference to the struct value</returns>
77          public ref T GetValue(int index)
78          {
79              return ref Structs[index];
80          }
81  
82          /// <summary>
83          /// Releases an entry from the map.
84          /// </summary>
85          /// <param name="index">Index of the entry to release</param>
86          public void Release(int index)
87          {
88              Interlocked.Exchange(ref ThreadIds[index], 0);
89          }
90      }
91  }