Make threaded access safer in ItemDB

This commit is contained in:
Matt Nadareski
2025-01-14 13:34:05 -05:00
parent 3954a959be
commit 7c0b200e16

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
#endif #endif
using System.Xml.Serialization; using System.Xml.Serialization;
@@ -240,13 +241,9 @@ namespace SabreTools.DatFiles
item = rom; item = rom;
} }
// Get the key and add the file // If only adding statistics, we add just item stats
string key = item.GetKey(ItemKey.Machine);
// If only adding statistics, we add an empty key for games and then just item stats
if (statsOnly) if (statsOnly)
{ {
EnsureBucketingKey(key);
DatStatistics.AddItemStatistics(item); DatStatistics.AddItemStatistics(item);
return -1; return -1;
} }
@@ -261,8 +258,15 @@ namespace SabreTools.DatFiles
/// </summary> /// </summary>
public long AddMachine(Machine machine) public long AddMachine(Machine machine)
{ {
_machines[_machineIndex++] = machine; #if NET40_OR_GREATER || NETCOREAPP
return _machineIndex - 1; long index = Interlocked.Increment(ref _machineIndex) - 1;
_machines.TryAdd(index, machine);
return index;
#else
long index = _machineIndex++ - 1;
_machines[index] = machine;
return index;
#endif
} }
/// <summary> /// <summary>
@@ -270,8 +274,15 @@ namespace SabreTools.DatFiles
/// </summary> /// </summary>
public long AddSource(Source source) public long AddSource(Source source)
{ {
_sources[_sourceIndex++] = source; #if NET40_OR_GREATER || NETCOREAPP
return _sourceIndex - 1; long index = Interlocked.Increment(ref _sourceIndex) - 1;
_sources.TryAdd(index, source);
return index;
#else
long index = _sourceIndex++ - 1;
_sources[index] = source;
return index;
#endif
} }
/// <summary> /// <summary>
@@ -282,12 +293,16 @@ namespace SabreTools.DatFiles
var keys = Array.FindAll(SortedKeys, k => k != null); var keys = Array.FindAll(SortedKeys, k => k != null);
foreach (string key in keys) foreach (string key in keys)
{ {
// If the key doesn't exist, skip // Get items for the bucket
if (!_buckets.ContainsKey(key)) var items = GetItemsForBucket(key);
if (items == null || items.Count == 0)
continue; continue;
// Convert to list of indices for ease of access
List<long> itemsList = [.. items.Keys];
// If there are no non-blank items, remove // If there are no non-blank items, remove
else if (!_buckets[key].Exists(i => GetItem(i) != null && GetItem(i) is not Blank)) if (!itemsList.Exists(i => GetItem(i) != null && GetItem(i) is not Blank))
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
_buckets.TryRemove(key, out _); _buckets.TryRemove(key, out _);
#else #else
@@ -304,7 +319,13 @@ namespace SabreTools.DatFiles
var itemIndices = _items.Keys; var itemIndices = _items.Keys;
foreach (long itemIndex in itemIndices) foreach (long itemIndex in itemIndices)
{ {
#if NET40_OR_GREATER || NETCOREAPP
if (!_items.TryGetValue(itemIndex, out var datItem))
continue;
#else
var datItem = _items[itemIndex]; var datItem = _items[itemIndex];
#endif
if (datItem == null || datItem.GetBoolFieldValue(DatItem.RemoveKey) != true) if (datItem == null || datItem.GetBoolFieldValue(DatItem.RemoveKey) != true)
continue; continue;
@@ -317,10 +338,17 @@ namespace SabreTools.DatFiles
/// </summary> /// </summary>
public DatItem? GetItem(long index) public DatItem? GetItem(long index)
{ {
#if NET40_OR_GREATER || NETCOREAPP
if (!_items.TryGetValue(index, out var datItem))
return null;
return datItem;
#else
if (!_items.ContainsKey(index)) if (!_items.ContainsKey(index))
return null; return null;
return _items[index]; return _items[index];
#endif
} }
/// <summary> /// <summary>
@@ -346,20 +374,34 @@ namespace SabreTools.DatFiles
if (bucketName == null) if (bucketName == null)
return []; return [];
#if NET40_OR_GREATER || NETCOREAPP
if (!_buckets.TryGetValue(bucketName, out var itemIds))
return [];
#else
if (!_buckets.ContainsKey(bucketName)) if (!_buckets.ContainsKey(bucketName))
return []; return [];
var itemIds = _buckets[bucketName]; var itemIds = _buckets[bucketName];
#endif
var datItems = new Dictionary<long, DatItem>(); var datItems = new Dictionary<long, DatItem>();
foreach (long itemId in itemIds) foreach (long itemId in itemIds)
{ {
// Ignore missing IDs // Ignore missing IDs
#if NET40_OR_GREATER || NETCOREAPP
if (!_items.TryGetValue(itemId, out var datItem) || datItem == null)
continue;
#else
if (!_items.ContainsKey(itemId)) if (!_items.ContainsKey(itemId))
continue; continue;
if (!filter || _items[itemId].GetBoolFieldValue(DatItem.RemoveKey) != true) var datItem = _items[itemId];
datItems[itemId] = _items[itemId]; if (datItem == null)
continue;
#endif
if (!filter || datItem.GetBoolFieldValue(DatItem.RemoveKey) != true)
datItems[itemId] = datItem;
} }
return datItems; return datItems;
@@ -378,11 +420,20 @@ namespace SabreTools.DatFiles
foreach (long itemId in itemIds) foreach (long itemId in itemIds)
{ {
// Ignore missing IDs // Ignore missing IDs
#if NET40_OR_GREATER || NETCOREAPP
if (!_items.TryGetValue(itemId, out var datItem) || datItem == null)
continue;
#else
if (!_items.ContainsKey(itemId)) if (!_items.ContainsKey(itemId))
continue; continue;
if (!filter || _items[itemId].GetBoolFieldValue(DatItem.RemoveKey) != true) var datItem = _items[itemId];
datItems[itemId] = _items[itemId]; if (datItem == null)
continue;
#endif
if (!filter || datItem.GetBoolFieldValue(DatItem.RemoveKey) != true)
datItems[itemId] = datItem;
} }
return datItems; return datItems;
@@ -401,11 +452,20 @@ namespace SabreTools.DatFiles
foreach (long itemId in itemIds) foreach (long itemId in itemIds)
{ {
// Ignore missing IDs // Ignore missing IDs
#if NET40_OR_GREATER || NETCOREAPP
if (!_items.TryGetValue(itemId, out var datItem) || datItem == null)
continue;
#else
if (!_items.ContainsKey(itemId)) if (!_items.ContainsKey(itemId))
continue; continue;
if (!filter || _items[itemId].GetBoolFieldValue(DatItem.RemoveKey) != true) var datItem = _items[itemId];
datItems[itemId] = _items[itemId]; if (datItem == null)
continue;
#endif
if (!filter || datItem.GetBoolFieldValue(DatItem.RemoveKey) != true)
datItems[itemId] = datItem;
} }
return datItems; return datItems;
@@ -416,10 +476,17 @@ namespace SabreTools.DatFiles
/// </summary> /// </summary>
public Machine? GetMachine(long index) public Machine? GetMachine(long index)
{ {
#if NET40_OR_GREATER || NETCOREAPP
if (!_machines.TryGetValue(index, out var machine))
return null;
return machine;
#else
if (!_machines.ContainsKey(index)) if (!_machines.ContainsKey(index))
return null; return null;
return _machines[index]; return _machines[index];
#endif
} }
/// <summary> /// <summary>
@@ -440,6 +507,15 @@ namespace SabreTools.DatFiles
/// </summary> /// </summary>
public KeyValuePair<long, Machine?> GetMachineForItem(long itemIndex) public KeyValuePair<long, Machine?> GetMachineForItem(long itemIndex)
{ {
#if NET40_OR_GREATER || NETCOREAPP
if (!_itemToMachineMapping.TryGetValue(itemIndex, out long machineIndex))
return new KeyValuePair<long, Machine?>(-1, null);
if (!_machines.TryGetValue(machineIndex, out var machine))
return new KeyValuePair<long, Machine?>(-1, null);
return new KeyValuePair<long, Machine?>(machineIndex, machine);
#else
if (!_itemToMachineMapping.ContainsKey(itemIndex)) if (!_itemToMachineMapping.ContainsKey(itemIndex))
return new KeyValuePair<long, Machine?>(-1, null); return new KeyValuePair<long, Machine?>(-1, null);
@@ -447,7 +523,10 @@ namespace SabreTools.DatFiles
if (!_machines.ContainsKey(machineIndex)) if (!_machines.ContainsKey(machineIndex))
return new KeyValuePair<long, Machine?>(-1, null); return new KeyValuePair<long, Machine?>(-1, null);
return new KeyValuePair<long, Machine?>(machineIndex, _machines[machineIndex]); var machine = _machines[machineIndex];
return new KeyValuePair<long, Machine?>(machineIndex, machine);
#endif
} }
/// <summary> /// <summary>
@@ -557,23 +636,36 @@ namespace SabreTools.DatFiles
/// </summary> /// </summary>
internal long AddItem(DatItem item, long machineIndex, long sourceIndex) internal long AddItem(DatItem item, long machineIndex, long sourceIndex)
{ {
#if NET40_OR_GREATER || NETCOREAPP
// Add the item with a new index // Add the item with a new index
_items[_itemIndex++] = item; long index = Interlocked.Increment(ref _itemIndex) - 1;
_items.TryAdd(index, item);
// Add the machine mapping // Add the machine mapping
_itemToMachineMapping[_itemIndex - 1] = machineIndex; _itemToMachineMapping.TryAdd(index, machineIndex);
// Add the source mapping // Add the source mapping
_itemToSourceMapping[_itemIndex - 1] = sourceIndex; _itemToSourceMapping.TryAdd(index, sourceIndex);
#else
// Add the item with a new index
long index = _itemIndex++ - 1;
_items[index] = item;
// Add the machine mapping
_itemToMachineMapping[index] = machineIndex;
// Add the source mapping
_itemToSourceMapping[index] = sourceIndex;
#endif
// Add the item statistics // Add the item statistics
DatStatistics.AddItemStatistics(item); DatStatistics.AddItemStatistics(item);
// Add the item to the default bucket // Add the item to the default bucket
PerformItemBucketing(_itemIndex - 1, _bucketedBy, lower: true, norename: true); PerformItemBucketing(index, _bucketedBy, lower: true, norename: true);
// Return the used index // Return the used index
return _itemIndex - 1; return index - 1;
} }
#endregion #endregion
@@ -647,7 +739,11 @@ namespace SabreTools.DatFiles
} }
// Add back all roms with the proper flags // Add back all roms with the proper flags
#if NET40_OR_GREATER || NETCOREAPP
_buckets.TryAdd(key, [.. output.Keys, .. left.Keys]);
#else
_buckets[key] = [.. output.Keys, .. left.Keys]; _buckets[key] = [.. output.Keys, .. left.Keys];
#endif
return output; return output;
} }
@@ -696,7 +792,11 @@ namespace SabreTools.DatFiles
} }
// Add back all roms with the proper flags // Add back all roms with the proper flags
#if NET40_OR_GREATER || NETCOREAPP
_buckets.TryAdd(key, [.. output.Keys, .. left.Keys]);
#else
_buckets[key] = [.. output.Keys, .. left.Keys]; _buckets[key] = [.. output.Keys, .. left.Keys];
#endif
return output; return output;
} }
@@ -879,12 +979,17 @@ namespace SabreTools.DatFiles
/// </summary> /// </summary>
private string GetBucketKey(long itemIndex, ItemKey bucketBy, bool lower, bool norename) private string GetBucketKey(long itemIndex, ItemKey bucketBy, bool lower, bool norename)
{ {
#if NET40_OR_GREATER || NETCOREAPP
if (!_items.TryGetValue(itemIndex, out var datItem) || datItem == null)
return string.Empty;
#else
if (!_items.ContainsKey(itemIndex)) if (!_items.ContainsKey(itemIndex))
return string.Empty; return string.Empty;
var datItem = _items[itemIndex]; var datItem = _items[itemIndex];
if (datItem == null) if (datItem == null)
return string.Empty; return string.Empty;
#endif
var machine = GetMachineForItem(itemIndex); var machine = GetMachineForItem(itemIndex);
if (machine.Value == null) if (machine.Value == null)
@@ -897,6 +1002,8 @@ namespace SabreTools.DatFiles
string bucketKey = bucketBy switch string bucketKey = bucketBy switch
{ {
// Treat NULL like machine
ItemKey.NULL => (norename ? string.Empty : sourceKeyPadded) + machineName,
ItemKey.Machine => (norename ? string.Empty : sourceKeyPadded) + machineName, ItemKey.Machine => (norename ? string.Empty : sourceKeyPadded) + machineName,
_ => GetBucketHashValue(datItem, bucketBy), _ => GetBucketHashValue(datItem, bucketBy),
}; };
@@ -975,10 +1082,10 @@ namespace SabreTools.DatFiles
private void EnsureBucketingKey(string key) private void EnsureBucketingKey(string key)
{ {
// If the key is missing from the dictionary, add it // If the key is missing from the dictionary, add it
if (!_buckets.ContainsKey(key))
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
_buckets.TryAdd(key, []); _buckets.GetOrAdd(key, []);
#else #else
if (!_buckets.ContainsKey(key))
_buckets[key] = []; _buckets[key] = [];
#endif #endif
} }
@@ -1024,8 +1131,19 @@ namespace SabreTools.DatFiles
private void PerformItemBucketing(long itemIndex, ItemKey bucketBy, bool lower, bool norename) private void PerformItemBucketing(long itemIndex, ItemKey bucketBy, bool lower, bool norename)
{ {
string? bucketKey = GetBucketKey(itemIndex, bucketBy, lower, norename); string? bucketKey = GetBucketKey(itemIndex, bucketBy, lower, norename);
lock (bucketKey)
{
EnsureBucketingKey(bucketKey); EnsureBucketingKey(bucketKey);
#if NET40_OR_GREATER || NETCOREAPP
if (!_buckets.TryGetValue(bucketKey, out var bucket) || bucket == null)
return;
bucket.Add(itemIndex);
#else
_buckets[bucketKey].Add(itemIndex); _buckets[bucketKey].Add(itemIndex);
#endif
}
} }
/// <summary> /// <summary>
@@ -1046,14 +1164,16 @@ namespace SabreTools.DatFiles
for (int i = 0; i < bucketKeys.Length; i++) for (int i = 0; i < bucketKeys.Length; i++)
#endif #endif
{ {
var itemIndices = _buckets[bucketKeys[i]];
if (itemIndices == null || itemIndices.Count == 0)
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
if (!_buckets.TryGetValue(bucketKeys[i], out var itemIndices))
return; return;
#else #else
continue; var itemIndices = _buckets[bucketKeys[i]];
#endif #endif
if (itemIndices == null || itemIndices.Count == 0)
return;
var datItems = itemIndices var datItems = itemIndices
.FindAll(i => _items.ContainsKey(i)) .FindAll(i => _items.ContainsKey(i))
.Select(i => new KeyValuePair<long, DatItem>(i, _items[i])) .Select(i => new KeyValuePair<long, DatItem>(i, _items[i]))
@@ -1065,10 +1185,11 @@ namespace SabreTools.DatFiles
if (dedupeType == DedupeType.Full || (dedupeType == DedupeType.Game && bucketBy == ItemKey.Machine)) if (dedupeType == DedupeType.Full || (dedupeType == DedupeType.Game && bucketBy == ItemKey.Machine))
datItems = Merge(datItems); datItems = Merge(datItems);
_buckets[bucketKeys[i]] = [.. datItems.Select(kvp => kvp.Key)];
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
_buckets.TryAdd(bucketKeys[i], [.. datItems.Select(kvp => kvp.Key)]);
}); });
#else #else
_buckets[bucketKeys[i]] = [.. datItems.Select(kvp => kvp.Key)];
} }
#endif #endif
} }
@@ -1089,7 +1210,11 @@ namespace SabreTools.DatFiles
for (int i = 0; i < bucketKeys.Length; i++) for (int i = 0; i < bucketKeys.Length; i++)
#endif #endif
{ {
#if NET452_OR_GREATER || NETCOREAPP
_buckets.TryGetValue(bucketKeys[i], out var itemIndices);
#else
var itemIndices = _buckets[bucketKeys[i]]; var itemIndices = _buckets[bucketKeys[i]];
#endif
if (itemIndices == null || itemIndices.Count == 0) if (itemIndices == null || itemIndices.Count == 0)
{ {
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
@@ -1108,10 +1233,11 @@ namespace SabreTools.DatFiles
Sort(ref datItems, norename); Sort(ref datItems, norename);
_buckets[bucketKeys[i]] = [.. datItems.Select(kvp => kvp.Key)];
#if NET40_OR_GREATER || NETCOREAPP #if NET40_OR_GREATER || NETCOREAPP
_buckets.TryAdd(bucketKeys[i], [.. datItems.Select(kvp => kvp.Key)]);
}); });
#else #else
_buckets[bucketKeys[i]] = [.. datItems.Select(kvp => kvp.Key)];
} }
#endif #endif
} }