/ src / Ryujinx.Graphics.Gpu / Shader / HashTable / PartitionedHashTable.cs
PartitionedHashTable.cs
  1  using System;
  2  using System.Collections.Generic;
  3  using System.Diagnostics;
  4  
  5  namespace Ryujinx.Graphics.Gpu.Shader.HashTable
  6  {
  7      /// <summary>
  8      /// Partitioned hash table.
  9      /// </summary>
 10      /// <typeparam name="T"></typeparam>
 11      public class PartitionedHashTable<T>
 12      {
 13          /// <summary>
 14          /// Entry for a given data size.
 15          /// </summary>
 16          private readonly struct SizeEntry
 17          {
 18              /// <summary>
 19              /// Size for the data that will be stored on the hash table on this entry.
 20              /// </summary>
 21              public int Size { get; }
 22  
 23              /// <summary>
 24              /// Number of entries on the hash table.
 25              /// </summary>
 26              public int TableCount => _table.Count;
 27  
 28              private readonly PartitionHashTable<T> _table;
 29  
 30              /// <summary>
 31              /// Creates an entry for a given size.
 32              /// </summary>
 33              /// <param name="size">Size of the data to be stored on this entry</param>
 34              public SizeEntry(int size)
 35              {
 36                  Size = size;
 37                  _table = new PartitionHashTable<T>();
 38              }
 39  
 40              /// <summary>
 41              /// Gets an item for existing data, or adds a new one.
 42              /// </summary>
 43              /// <param name="data">Data associated with the item</param>
 44              /// <param name="dataHash">Hash of <paramref name="data"/></param>
 45              /// <param name="item">Item to be added</param>
 46              /// <returns>Existing item, or <paramref name="item"/> if not present</returns>
 47              public T GetOrAdd(byte[] data, uint dataHash, T item)
 48              {
 49                  Debug.Assert(data.Length == Size);
 50                  return _table.GetOrAdd(data, dataHash, item);
 51              }
 52  
 53              /// <summary>
 54              /// Adds a new item.
 55              /// </summary>
 56              /// <param name="data">Data associated with the item</param>
 57              /// <param name="dataHash">Hash of <paramref name="data"/></param>
 58              /// <param name="item">Item to be added</param>
 59              /// <returns>True if added, false otherwise</returns>
 60              public bool Add(byte[] data, uint dataHash, T item)
 61              {
 62                  Debug.Assert(data.Length == Size);
 63                  return _table.Add(data, dataHash, item);
 64              }
 65  
 66              /// <summary>
 67              /// Adds a partial entry.
 68              /// </summary>
 69              /// <param name="ownerData">Full entry data</param>
 70              /// <param name="dataHash">Hash of the sub-region of the data that belongs to this entry</param>
 71              /// <returns>True if added, false otherwise</returns>
 72              public bool AddPartial(byte[] ownerData, uint dataHash)
 73              {
 74                  return _table.AddPartial(ownerData, dataHash, Size);
 75              }
 76  
 77              /// <summary>
 78              /// Fills a new hash table with "partials" of existing full entries of higher size.
 79              /// </summary>
 80              /// <param name="newEntry">Entry with the new hash table</param>
 81              public void FillPartials(SizeEntry newEntry)
 82              {
 83                  Debug.Assert(newEntry.Size < Size);
 84                  _table.FillPartials(newEntry._table, newEntry.Size);
 85              }
 86  
 87              /// <summary>
 88              /// Tries to find an item on the hash table.
 89              /// </summary>
 90              /// <param name="dataAccessor">Data accessor</param>
 91              /// <param name="item">The item on the table, if found, otherwise unmodified</param>
 92              /// <param name="data">The data on the table, if found, otherwise unmodified</param>
 93              /// <returns>Table lookup result</returns>
 94              public PartitionHashTable<T>.SearchResult TryFindItem(scoped ref SmartDataAccessor dataAccessor, scoped ref T item, scoped ref byte[] data)
 95              {
 96                  return _table.TryFindItem(ref dataAccessor, Size, ref item, ref data);
 97              }
 98          }
 99  
100          private readonly List<SizeEntry> _sizeTable;
101  
102          /// <summary>
103          /// Creates a new partitioned hash table.
104          /// </summary>
105          public PartitionedHashTable()
106          {
107              _sizeTable = new List<SizeEntry>();
108          }
109  
110          /// <summary>
111          /// Adds a new item to the table.
112          /// </summary>
113          /// <param name="data">Data</param>
114          /// <param name="item">Item associated with the data</param>
115          public void Add(byte[] data, T item)
116          {
117              GetOrAdd(data, item);
118          }
119  
120          /// <summary>
121          /// Gets an existing item from the table, or adds a new one if not present.
122          /// </summary>
123          /// <param name="data">Data</param>
124          /// <param name="item">Item associated with the data</param>
125          /// <returns>Existing item, or <paramref name="item"/> if not present</returns>
126          public T GetOrAdd(byte[] data, T item)
127          {
128              SizeEntry sizeEntry;
129  
130              int index = BinarySearch(_sizeTable, data.Length);
131              if (index < _sizeTable.Count && _sizeTable[index].Size == data.Length)
132              {
133                  sizeEntry = _sizeTable[index];
134              }
135              else
136              {
137                  if (index < _sizeTable.Count && _sizeTable[index].Size < data.Length)
138                  {
139                      index++;
140                  }
141  
142                  sizeEntry = new SizeEntry(data.Length);
143  
144                  _sizeTable.Insert(index, sizeEntry);
145  
146                  for (int i = index + 1; i < _sizeTable.Count; i++)
147                  {
148                      _sizeTable[i].FillPartials(sizeEntry);
149                  }
150              }
151  
152              HashState hashState = new();
153              hashState.Initialize();
154  
155              for (int i = 0; i < index; i++)
156              {
157                  ReadOnlySpan<byte> dataSlice = new ReadOnlySpan<byte>(data)[.._sizeTable[i].Size];
158                  hashState.Continue(dataSlice);
159                  _sizeTable[i].AddPartial(data, hashState.Finalize(dataSlice));
160              }
161  
162              hashState.Continue(data);
163              return sizeEntry.GetOrAdd(data, hashState.Finalize(data), item);
164          }
165  
166          /// <summary>
167          /// Performs binary search on a list of hash tables, each one with a fixed data size.
168          /// </summary>
169          /// <param name="entries">List of hash tables</param>
170          /// <param name="size">Size to search for</param>
171          /// <returns>Index of the hash table with the given size, or nearest one otherwise</returns>
172          private static int BinarySearch(List<SizeEntry> entries, int size)
173          {
174              int left = 0;
175              int middle = 0;
176              int right = entries.Count - 1;
177  
178              while (left <= right)
179              {
180                  middle = left + ((right - left) >> 1);
181  
182                  SizeEntry entry = entries[middle];
183  
184                  if (size == entry.Size)
185                  {
186                      break;
187                  }
188  
189                  if (size < entry.Size)
190                  {
191                      right = middle - 1;
192                  }
193                  else
194                  {
195                      left = middle + 1;
196                  }
197              }
198  
199              return middle;
200          }
201  
202          /// <summary>
203          /// Tries to find an item on the table.
204          /// </summary>
205          /// <param name="dataAccessor">Data accessor</param>
206          /// <param name="item">Item, if found</param>
207          /// <param name="data">Data, if found</param>
208          /// <returns>True if the item was found on the table, false otherwise</returns>
209          public bool TryFindItem(IDataAccessor dataAccessor, out T item, out byte[] data)
210          {
211              SmartDataAccessor sda = new(dataAccessor);
212  
213              item = default;
214              data = null;
215  
216              int left = 0;
217              int right = _sizeTable.Count;
218  
219              while (left != right)
220              {
221                  int index = left + ((right - left) >> 1);
222  
223                  PartitionHashTable<T>.SearchResult result = _sizeTable[index].TryFindItem(ref sda, ref item, ref data);
224  
225                  if (result == PartitionHashTable<T>.SearchResult.FoundFull)
226                  {
227                      return true;
228                  }
229  
230                  if (result == PartitionHashTable<T>.SearchResult.NotFound)
231                  {
232                      right = index;
233                  }
234                  else /* if (result == PartitionHashTable<T>.SearchResult.FoundPartial) */
235                  {
236                      left = index + 1;
237                  }
238              }
239  
240              data = null;
241              return false;
242          }
243      }
244  }