0
0
mirror of https://github.com/sp-tarkov/loot-dump-processor.git synced 2025-02-13 07:30:44 -05:00

Refactored key generation and improved type safety across storage components

This commit is contained in:
bluextx 2025-01-11 11:50:02 +03:00
parent 02802ddc10
commit 9e9933bd32
20 changed files with 151 additions and 124 deletions

View File

@ -1,6 +1,3 @@
using System.Text.Json.Serialization;
namespace LootDumpProcessor.Model.Config; namespace LootDumpProcessor.Model.Config;
public class ReaderConfig public class ReaderConfig

View File

@ -1,22 +1,24 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using LootDumpProcessor.Storage; using LootDumpProcessor.Storage;
using LootDumpProcessor.Utils;
namespace LootDumpProcessor.Model.Processing; namespace LootDumpProcessor.Model.Processing;
public class LooseLootCounts : IKeyable public class LooseLootCounts : IKeyable
{ {
[JsonPropertyName("__id__")] public string __ID { get; set; } = KeyGenerator.GetNextKey(); [JsonPropertyName("__id__")] private string Id { get; set; }
public IKey Counts { get; set; } public IKey Counts { get; set; }
// public IKey Items { get; set; }
public IKey ItemProperties { get; set; } public IKey ItemProperties { get; set; }
public List<int> MapSpawnpointCount { get; set; } = new(); public List<int> MapSpawnpointCount { get; set; } = new();
public IKey GetKey() public LooseLootCounts(string id, IKey counts, IKey itemProperties)
{ {
return new FlatUniqueKey(new[] { __ID }); ArgumentException.ThrowIfNullOrWhiteSpace(id);
Id = id;
Counts = counts ?? throw new ArgumentNullException(nameof(counts));
ItemProperties = itemProperties ?? throw new ArgumentNullException(nameof(itemProperties));
} }
public IKey GetKey() => new FlatUniqueKey([Id]);
} }

View File

@ -7,70 +7,63 @@ namespace LootDumpProcessor.Model;
public class Template : IKeyable, ICloneable public class Template : IKeyable, ICloneable
{ {
[JsonIgnore] public string __ID { get; } = KeyGenerator.GetNextKey(); [JsonIgnore] public string internalId { get; }
public string Id { get; set; }
public bool IsContainer { get; set; }
public bool UseGravity { get; set; }
public bool RandomRotation { get; set; }
public Vector3 Position { get; set; }
public Vector3 Rotation { get; set; }
public bool IsGroupPosition { get; set; }
public List<GroupPosition> GroupPositions { get; set; }
public bool IsAlwaysSpawn { get; set; }
public string Root { get; set; }
public List<Item> Items { get; set; }
public Template(string internalId, string id, bool isContainer, bool useGravity, bool randomRotation,
Vector3 position, Vector3 rotation, bool isGroupPosition, List<GroupPosition> groupPositions,
bool isAlwaysSpawn, string root, List<Item> items)
{
this.internalId = internalId;
Id = id;
IsContainer = isContainer;
UseGravity = useGravity;
RandomRotation = randomRotation;
Position = position;
Rotation = rotation;
IsGroupPosition = isGroupPosition;
GroupPositions = groupPositions;
IsAlwaysSpawn = isAlwaysSpawn;
Root = root;
Items = items;
}
public string? Id { get; set; } private bool Equals(Template other) => Id == other.Id;
public bool? IsContainer { get; set; }
public bool? UseGravity { get; set; }
public bool? RandomRotation { get; set; }
public Vector3? Position { get; set; }
public Vector3? Rotation { get; set; }
public bool? IsGroupPosition { get; set; }
public List<GroupPosition>? GroupPositions { get; set; }
public bool? IsAlwaysSpawn { get; set; }
public string? Root { get; set; }
public required List<Item> Items { get; set; }
protected bool Equals(Template other) => Id == other.Id;
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true; if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false; return obj.GetType() == GetType() && Equals((Template)obj);
return Equals((Template)obj);
} }
public override int GetHashCode() => Id != null ? Id.GetHashCode() : 0; public override int GetHashCode() => Id != null ? Id.GetHashCode() : 0;
public IKey GetKey() public IKey GetKey() => new FlatUniqueKey([internalId]);
{
return new FlatUniqueKey(new[] { __ID });
}
public object Clone() => new Template public object Clone() => new Template
{ (
Id = Id, internalId,
IsContainer = IsContainer, Id,
UseGravity = UseGravity, IsContainer,
RandomRotation = RandomRotation, UseGravity,
Position = ProcessorUtil.Copy(Position), RandomRotation,
Rotation = ProcessorUtil.Copy(Rotation), ProcessorUtil.Copy(Position),
IsGroupPosition = IsGroupPosition, ProcessorUtil.Copy(Rotation),
GroupPositions = ProcessorUtil.Copy(GroupPositions), IsGroupPosition,
IsAlwaysSpawn = IsAlwaysSpawn, ProcessorUtil.Copy(GroupPositions),
Root = Root, IsAlwaysSpawn,
Items = ProcessorUtil.Copy(Items) Root,
}; ProcessorUtil.Copy(Items)
);
} }

View File

@ -1,15 +1,19 @@
using LootDumpProcessor.Model; using System.Globalization;
using LootDumpProcessor.Model;
using LootDumpProcessor.Model.Processing; using LootDumpProcessor.Model.Processing;
using LootDumpProcessor.Utils;
namespace LootDumpProcessor.Process; namespace LootDumpProcessor.Process;
public class ComposedKeyGenerator(ITarkovItemsProvider tarkovItemsProvider) : IComposedKeyGenerator public class ComposedKeyGenerator(ITarkovItemsProvider tarkovItemsProvider, IKeyGenerator keyGenerator)
: IComposedKeyGenerator
{ {
private readonly ITarkovItemsProvider _tarkovItemsProvider = private readonly ITarkovItemsProvider _tarkovItemsProvider =
tarkovItemsProvider ?? throw new ArgumentNullException(nameof(tarkovItemsProvider)); tarkovItemsProvider ?? throw new ArgumentNullException(nameof(tarkovItemsProvider));
public ComposedKey Generate(IEnumerable<Item> items) private readonly IKeyGenerator
_keyGenerator = keyGenerator ?? throw new ArgumentNullException(nameof(keyGenerator));
public ComposedKey Generate(IReadOnlyList<Item>? items)
{ {
var key = items?.Select(i => i.Tpl) var key = items?.Select(i => i.Tpl)
.Where(i => !string.IsNullOrEmpty(i) && .Where(i => !string.IsNullOrEmpty(i) &&
@ -17,8 +21,9 @@ public class ComposedKeyGenerator(ITarkovItemsProvider tarkovItemsProvider) : IC
.Cast<string>() .Cast<string>()
.Select(i => (double)i.GetHashCode()) .Select(i => (double)i.GetHashCode())
.Sum() .Sum()
.ToString() ?? KeyGenerator.GetNextKey(); .ToString(CultureInfo.InvariantCulture) ?? _keyGenerator.Generate();
var firstItem = items?.FirstOrDefault(); var firstItem = items?[0];
return new ComposedKey(key, firstItem); return new ComposedKey(key, firstItem);
} }
} }

View File

@ -4,5 +4,5 @@ namespace LootDumpProcessor.Process;
public interface IComposedKeyGenerator public interface IComposedKeyGenerator
{ {
ComposedKey Generate(IEnumerable<Item> items); ComposedKey Generate(IReadOnlyList<Item>? items);
} }

View File

@ -0,0 +1,6 @@
namespace LootDumpProcessor.Process;
public interface IKeyGenerator
{
string Generate();
}

View File

@ -0,0 +1,13 @@
namespace LootDumpProcessor.Process;
public class NumericKeyGenerator : IKeyGenerator
{
private ulong _currentKey;
public string Generate()
{
var key = _currentKey;
Interlocked.Increment(ref _currentKey);
return key.ToString();
}
}

View File

@ -24,7 +24,7 @@ public class MultithreadSteppedDumpProcessor(
IStaticContainersProcessor staticContainersProcessor, IStaticContainersProcessor staticContainersProcessor,
IAmmoProcessor ammoProcessor, IAmmoProcessor ammoProcessor,
ILooseLootProcessor looseLootProcessor, ILooseLootProcessor looseLootProcessor,
ILogger<MultithreadSteppedDumpProcessor> logger ILogger<MultithreadSteppedDumpProcessor> logger, IKeyGenerator keyGenerator
) )
: IDumpProcessor : IDumpProcessor
{ {
@ -43,6 +43,9 @@ public class MultithreadSteppedDumpProcessor(
private readonly ILogger<MultithreadSteppedDumpProcessor> _logger = private readonly ILogger<MultithreadSteppedDumpProcessor> _logger =
logger ?? throw new ArgumentNullException(nameof(logger)); logger ?? throw new ArgumentNullException(nameof(logger));
private readonly IKeyGenerator
_keyGenerator = keyGenerator ?? throw new ArgumentNullException(nameof(keyGenerator));
private static readonly IDataStorage _dataStorage = DataStorageFactory.GetInstance(); private static readonly IDataStorage _dataStorage = DataStorageFactory.GetInstance();
public Dictionary<OutputFileType, object> ProcessDumps(List<PartialData> dumps) public Dictionary<OutputFileType, object> ProcessDumps(List<PartialData> dumps)
@ -225,17 +228,17 @@ public class MultithreadSteppedDumpProcessor(
var lockObjectContainerCounts = new object(); var lockObjectContainerCounts = new object();
var lockObjectCounts = new object(); var lockObjectCounts = new object();
var looseLootCounts = new LooseLootCounts();
var lockObjectDictionaryCounts = new object(); var lockObjectDictionaryCounts = new object();
var dictionaryCounts = new FlatKeyableDictionary<string, int>(); var dictionaryCounts = new FlatKeyableDictionary<string, int>(_keyGenerator.Generate());
looseLootCounts.Counts = dictionaryCounts.GetKey();
var lockObjectDictionaryItemProperties = new object(); var lockObjectDictionaryItemProperties = new object();
var dictionaryItemProperties = new FlatKeyableDictionary<string, FlatKeyableList<Template>>(); var dictionaryItemProperties =
new FlatKeyableDictionary<string, FlatKeyableList<Template>>(_keyGenerator.Generate());
var actualDictionaryItemProperties = new FlatKeyableDictionary<string, IKey>(); var actualDictionaryItemProperties = new FlatKeyableDictionary<string, IKey>(_keyGenerator.Generate());
looseLootCounts.ItemProperties = actualDictionaryItemProperties.GetKey(); var looseLootCounts = new LooseLootCounts(_keyGenerator.Generate(), dictionaryCounts.GetKey(),
actualDictionaryItemProperties.GetKey());
dumpProcessData.LooseLootCounts.Add(mapName, looseLootCounts.GetKey()); dumpProcessData.LooseLootCounts.Add(mapName, looseLootCounts.GetKey());
@ -316,7 +319,7 @@ public class MultithreadSteppedDumpProcessor(
{ {
if (!dictionaryItemProperties.TryGetValue(uniqueKey, out var values)) if (!dictionaryItemProperties.TryGetValue(uniqueKey, out var values))
{ {
values = new FlatKeyableList<Template>(); values = new FlatKeyableList<Template>(_keyGenerator.Generate());
dictionaryItemProperties.Add(uniqueKey, values); dictionaryItemProperties.Add(uniqueKey, values);
actualDictionaryItemProperties.Add(uniqueKey, values.GetKey()); actualDictionaryItemProperties.Add(uniqueKey, values.GetKey());
} }

View File

@ -33,7 +33,7 @@ public class FileProcessor : IFileProcessor
var staticLoot = new List<Template>(); var staticLoot = new List<Template>();
foreach (var item in parsedData.Data.Data.LocationLoot.Loot) foreach (var item in parsedData.Data.Data.LocationLoot.Loot)
if (item.IsContainer ?? false) staticLoot.Add(item); if (item.IsContainer) staticLoot.Add(item);
else looseLoot.Add(item); else looseLoot.Add(item);
parsedData.Data = null; parsedData.Data = null;

View File

@ -11,7 +11,7 @@ namespace LootDumpProcessor.Process.Processor.v2.LooseLootProcessor;
public class LooseLootProcessor( public class LooseLootProcessor(
ILogger<LooseLootProcessor> logger, IDataStorage dataStorage, ITarkovItemsProvider tarkovItemsProvider, ILogger<LooseLootProcessor> logger, IDataStorage dataStorage, ITarkovItemsProvider tarkovItemsProvider,
IComposedKeyGenerator composedKeyGenerator IComposedKeyGenerator composedKeyGenerator, IKeyGenerator keyGenerator
) )
: ILooseLootProcessor : ILooseLootProcessor
{ {
@ -27,6 +27,9 @@ public class LooseLootProcessor(
private readonly IComposedKeyGenerator _composedKeyGenerator = private readonly IComposedKeyGenerator _composedKeyGenerator =
composedKeyGenerator ?? throw new ArgumentNullException(nameof(composedKeyGenerator)); composedKeyGenerator ?? throw new ArgumentNullException(nameof(composedKeyGenerator));
private readonly IKeyGenerator
_keyGenerator = keyGenerator ?? throw new ArgumentNullException(nameof(keyGenerator));
public PreProcessedLooseLoot PreProcessLooseLoot(List<Template> looseLoot) public PreProcessedLooseLoot PreProcessLooseLoot(List<Template> looseLoot)
{ {
var preProcessedLoot = new PreProcessedLooseLoot var preProcessedLoot = new PreProcessedLooseLoot
@ -34,7 +37,8 @@ public class LooseLootProcessor(
Counts = new Dictionary<string, int>() Counts = new Dictionary<string, int>()
}; };
var itemPropertiesDictionary = new SubdivisionedKeyableDictionary<string, List<Template>>(); var itemPropertiesDictionary =
new SubdivisionedKeyableDictionary<string, List<Template>>(_keyGenerator.Generate());
preProcessedLoot.ItemProperties = (AbstractKey)itemPropertiesDictionary.GetKey(); preProcessedLoot.ItemProperties = (AbstractKey)itemPropertiesDictionary.GetKey();
preProcessedLoot.MapSpawnpointCount = looseLoot.Count; preProcessedLoot.MapSpawnpointCount = looseLoot.Count;
@ -55,7 +59,7 @@ public class LooseLootProcessor(
if (!itemPropertiesDictionary.TryGetValue(sanitizedId, out var templates)) if (!itemPropertiesDictionary.TryGetValue(sanitizedId, out var templates))
{ {
templates = new FlatKeyableList<Template>(); templates = new FlatKeyableList<Template>(_keyGenerator.Generate());
itemPropertiesDictionary.Add(sanitizedId, templates); itemPropertiesDictionary.Add(sanitizedId, templates);
} }

View File

@ -20,7 +20,7 @@ public class StaticContainersProcessor : IStaticContainersProcessor
var locationLoot = rawMapDump.Data.LocationLoot; var locationLoot = rawMapDump.Data.LocationLoot;
var mapId = locationLoot.Id.ToLower(); var mapId = locationLoot.Id.ToLower();
var staticLootPositions = locationLoot.Loot var staticLootPositions = locationLoot.Loot
.Where(loot => loot.IsContainer.GetValueOrDefault()) .Where(loot => loot.IsContainer)
.OrderBy(loot => loot.Id) .OrderBy(loot => loot.Id)
.ToList(); .ToList();
@ -44,7 +44,8 @@ public class StaticContainersProcessor : IStaticContainersProcessor
_logger.LogDebug("Added static weapon with ID {WeaponId} to Map {MapId}.", copiedLoot.Id, mapId); _logger.LogDebug("Added static weapon with ID {WeaponId} to Map {MapId}.", copiedLoot.Id, mapId);
} }
var forcedStaticItems = LootDumpProcessorContext.GetForcedItems().TryGetValue(mapId, out var forcedItems) var forcedStaticItems = LootDumpProcessorContext.GetForcedItems()
.TryGetValue(mapId, out List<StaticForced>? forcedItems)
? forcedItems ? forcedItems
: new List<StaticForced>(); : new List<StaticForced>();
@ -61,13 +62,13 @@ public class StaticContainersProcessor : IStaticContainersProcessor
public IReadOnlyList<Template> CreateDynamicStaticContainers(RootData rawMapDump) public IReadOnlyList<Template> CreateDynamicStaticContainers(RootData rawMapDump)
{ {
var dynamicContainers = rawMapDump.Data.LocationLoot.Loot var dynamicContainers = rawMapDump.Data.LocationLoot.Loot
.Where(loot => loot.IsContainer.GetValueOrDefault() && .Where(loot => loot.IsContainer &&
!LootDumpProcessorContext.GetStaticWeaponIds().Contains(loot.Items.FirstOrDefault()?.Tpl)) !LootDumpProcessorContext.GetStaticWeaponIds().Contains(loot.Items.FirstOrDefault()?.Tpl))
.ToList(); .ToList();
foreach (var container in dynamicContainers) foreach (var container in dynamicContainers)
{ {
if (container.Items == null || !container.Items.Any()) if (container.Items == null || container.Items.Count == 0)
{ {
_logger.LogWarning("Dynamic container with ID {ContainerId} has no items.", container.Id); _logger.LogWarning("Dynamic container with ID {ContainerId} has no items.", container.Id);
continue; continue;

View File

@ -38,6 +38,7 @@ public static class Program
services.AddSingleton<ITarkovItemsProvider, TarkovItemsProvider>(); services.AddSingleton<ITarkovItemsProvider, TarkovItemsProvider>();
services.AddSingleton<IDataStorage>(_ => DataStorageFactory.GetInstance()); services.AddSingleton<IDataStorage>(_ => DataStorageFactory.GetInstance());
services.AddSingleton<IKeyGenerator, NumericKeyGenerator>();
services.AddTransient<IComposedKeyGenerator, ComposedKeyGenerator>(); services.AddTransient<IComposedKeyGenerator, ComposedKeyGenerator>();
services.AddTransient<IIntakeReader, JsonFileIntakeReader>(); services.AddTransient<IIntakeReader, JsonFileIntakeReader>();

View File

@ -1,11 +1,16 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using LootDumpProcessor.Utils;
namespace LootDumpProcessor.Storage.Collections; namespace LootDumpProcessor.Storage.Collections;
public class FlatKeyableDictionary<K, V> : Dictionary<K, V>, IKeyable public class FlatKeyableDictionary<K, V> : Dictionary<K, V>, IKeyable
{ {
[JsonPropertyName("__id__")] public string __ID { get; set; } = KeyGenerator.GetNextKey(); [JsonPropertyName("__id__")] private string Id { get; set; }
public IKey GetKey() => new FlatUniqueKey([__ID]); public FlatKeyableDictionary(string id)
{
ArgumentException.ThrowIfNullOrWhiteSpace(id);
Id = id;
}
public IKey GetKey() => new FlatUniqueKey([Id]);
} }

View File

@ -1,10 +1,14 @@
using LootDumpProcessor.Utils;
namespace LootDumpProcessor.Storage.Collections; namespace LootDumpProcessor.Storage.Collections;
public class FlatKeyableList<T> : List<T>, IKeyable public class FlatKeyableList<T> : List<T>, IKeyable
{ {
public string __ID { get; } = KeyGenerator.GetNextKey(); public string Id { get; }
public IKey GetKey() => new FlatUniqueKey([__ID]); public FlatKeyableList(string id)
{
ArgumentException.ThrowIfNullOrWhiteSpace(id);
Id = id;
}
public IKey GetKey() => new FlatUniqueKey([Id]);
} }

View File

@ -1,11 +1,16 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using LootDumpProcessor.Utils;
namespace LootDumpProcessor.Storage.Collections; namespace LootDumpProcessor.Storage.Collections;
public class SubdivisionedKeyableDictionary<K, V> : Dictionary<K, V>, IKeyable public class SubdivisionedKeyableDictionary<K, V> : Dictionary<K, V>, IKeyable
{ {
[JsonPropertyName("__id__")] public string __ID { get; set; } = KeyGenerator.GetNextKey(); public SubdivisionedKeyableDictionary(string id)
{
ArgumentException.ThrowIfNullOrWhiteSpace(id);
Id = id;
}
[JsonPropertyName("__id__")] private string Id { get; set; }
public string? Extras public string? Extras
{ {
@ -24,10 +29,10 @@ public class SubdivisionedKeyableDictionary<K, V> : Dictionary<K, V>, IKeyable
"dictionaries" "dictionaries"
}; };
subdivisions.AddRange(ExtraSubdivisions); subdivisions.AddRange(ExtraSubdivisions);
subdivisions.Add(__ID); subdivisions.Add(Id);
return new SubdivisionedUniqueKey(subdivisions.ToArray()); return new SubdivisionedUniqueKey(subdivisions.ToArray());
} }
return new SubdivisionedUniqueKey(["dictionaries", __ID]); return new SubdivisionedUniqueKey(["dictionaries", Id]);
} }
} }

View File

@ -9,7 +9,8 @@ public class FileDataStorage : IDataStorage
public bool Exists(IKey key) => StoreHandlerFactory.GetInstance(key.GetKeyType()).Exists(key); public bool Exists(IKey key) => StoreHandlerFactory.GetInstance(key.GetKeyType()).Exists(key);
public T GetItem<T>(IKey key) where T : IKeyable => StoreHandlerFactory.GetInstance(key.GetKeyType()).Retrieve<T>(key); public T GetItem<T>(IKey key) where T : IKeyable =>
StoreHandlerFactory.GetInstance(key.GetKeyType()).Retrieve<T>(key);
public void Clear() public void Clear()
{ {

View File

@ -2,31 +2,33 @@ using System.Text.RegularExpressions;
namespace LootDumpProcessor.Utils; namespace LootDumpProcessor.Utils;
public static class FileDateParser public static partial class FileDateParser
{ {
private static readonly Regex _fileDateRegex = private static readonly Regex FileDateRegex = GetRegex();
new(".*([0-9]{4})[-]([0-9]{2})[-]([0-9]{2})[_]([0-9]{2})[-]([0-9]{2})[-]([0-9]{2}).*");
public static bool TryParseFileDate(string fileName, out DateTime? date) public static bool TryParseFileDate(string fileName, out DateTime? date)
{ {
date = null; date = null;
if (!_fileDateRegex.IsMatch(fileName)) if (!FileDateRegex.IsMatch(fileName)) return false;
return false;
var match = _fileDateRegex.Match(fileName); var match = FileDateRegex.Match(fileName);
var year = match.Groups[1].Value; var year = match.Groups[1].Value;
var month = match.Groups[2].Value; var month = match.Groups[2].Value;
var day = match.Groups[3].Value; var day = match.Groups[3].Value;
var hour = match.Groups[4].Value; var hour = match.Groups[4].Value;
var mins = match.Groups[5].Value; var minutes = match.Groups[5].Value;
var secs = match.Groups[6].Value; var seconds = match.Groups[6].Value;
date = new DateTime( date = new DateTime(
int.Parse(year), int.Parse(year),
int.Parse(month), int.Parse(month),
int.Parse(day), int.Parse(day),
int.Parse(hour), int.Parse(hour),
int.Parse(mins), int.Parse(minutes),
int.Parse(secs) int.Parse(seconds)
); );
return true; return true;
} }
[GeneratedRegex(".*([0-9]{4})[-]([0-9]{2})[-]([0-9]{2})[_]([0-9]{2})[-]([0-9]{2})[-]([0-9]{2}).*")]
private static partial Regex GetRegex();
} }

View File

@ -1,15 +0,0 @@
namespace LootDumpProcessor.Utils;
public class KeyGenerator
{
private static long currentKey = 0L;
private static object lockObject = new();
public static string GetNextKey()
{
lock (lockObject)
{
return $"{++currentKey}";
}
}
}