using System.Collections.Concurrent; using LootDumpProcessor.Logger; using LootDumpProcessor.Model; using LootDumpProcessor.Model.Input; using LootDumpProcessor.Model.Output.StaticContainer; using LootDumpProcessor.Model.Processing; using LootDumpProcessor.Serializers.Json; using LootDumpProcessor.Storage; using LootDumpProcessor.Storage.Collections; namespace LootDumpProcessor.Process.Processor.DumpProcessor; public class MultithreadSteppedDumpProcessor : IDumpProcessor { private static IJsonSerializer _jsonSerializer = JsonSerializerFactory.GetInstance(); private static readonly List Runners = new(); private static readonly BlockingCollection _partialDataToProcess = new(); // if we need to, this variable can be moved to use the factory, but since the factory // needs a locking mechanism to prevent dictionary access exceptions, its better to keep // a reference to use here private static readonly IDataStorage _dataStorage = DataStorageFactory.GetInstance(); public Dictionary ProcessDumps(List dumps) { LoggerFactory.GetInstance().Log("Starting final dump processing", LogLevel.Info); var output = new Dictionary(); var dumpProcessData = GetDumpProcessData(dumps); LoggerFactory.GetInstance().Log("Heavy processing done!", LogLevel.Info); var staticContainers = new Dictionary(); var staticContainersLock = new object(); // We need to count how many dumps we have for each map var mapDumpCounter = new Dictionary(); var mapDumpCounterLock = new object(); // dictionary of maps, that has a dictionary of template and hit count var mapStaticContainersAggregated = new Dictionary>(); var mapStaticContainersAggregatedLock = new object(); Runners.Clear(); // BSG changed the map data so static containers are now dynamic, so we need to scan all dumps for the static containers. foreach (var dumped in dumps) { Runners.Add( Task.Factory.StartNew(() => { var data = _jsonSerializer.Deserialize(File.ReadAllText(dumped.BasicInfo.FileName)); // the if statement below will keep track of how many dumps we have for each map lock (mapDumpCounterLock) { if (mapDumpCounter.ContainsKey(data.Data.Name)) mapDumpCounter[data.Data.Name] += 1; else mapDumpCounter.Add(data.Data.Name, 1); } // the if statement below takes care of processing "forced" or real static data for each map, we only need // to do this once per map, so we dont care about doing it again lock (staticContainersLock) { if (!staticContainers.ContainsKey(data.Data.Name)) { var mapStaticLoot = StaticLootProcessor.CreateRealStaticContainers(data); staticContainers[mapStaticLoot.Item1] = mapStaticLoot.Item2; } } // the section below takes care of finding how many "dynamic static containers" we have on the map Dictionary mapAggregatedData; lock (mapStaticContainersAggregatedLock) { if (!mapStaticContainersAggregated.TryGetValue(data.Data.Name, out mapAggregatedData)) { mapAggregatedData = new Dictionary(); mapStaticContainersAggregated.Add(data.Data.Name, mapAggregatedData); } } foreach (var dynamicStaticContainer in StaticLootProcessor.CreateDynamicStaticContainers(data)) { lock (mapStaticContainersAggregatedLock) { if (mapAggregatedData.ContainsKey(dynamicStaticContainer)) mapAggregatedData[dynamicStaticContainer] += 1; else mapAggregatedData.Add(dynamicStaticContainer, 1); } } GCHandler.Collect(); }) ); } Task.WaitAll(Runners.ToArray()); // Aggregate and calculate the probability of a static container mapStaticContainersAggregated.ToDictionary( kv => kv.Key, kv => kv.Value.Select( td => new StaticDataPoint { Template = td.Key, Probability = GetStaticProbability(kv.Key, td, mapDumpCounter) } ).ToList() ).ToList().ForEach(kv => staticContainers[kv.Key].StaticContainers = kv.Value); // Static containers output.Add(OutputFileType.StaticContainer, staticContainers); // Ammo distribution output.Add( OutputFileType.StaticAmmo, StaticLootProcessor.CreateAmmoDistribution(dumpProcessData.ContainerCounts) ); // Static loot distribution output.Add( OutputFileType.StaticLoot, StaticLootProcessor.CreateStaticLootDistribution(dumpProcessData.ContainerCounts) ); // Loose loot distribution var looseLootDistribution = LooseLootProcessor.CreateLooseLootDistribution( dumpProcessData.MapCounts, dumpProcessData.LooseLootCounts ); var loot = dumpProcessData.MapCounts .Select(mapCount => mapCount.Key) .ToDictionary(mi => mi, mi => looseLootDistribution[mi]); output.Add(OutputFileType.LooseLoot, loot); return output; } private static double GetStaticProbability(string mapName, KeyValuePair td, Dictionary mapDumpCounter) { if (mapName == "Streets of Tarkov") { var result = LootDumpProcessorContext.GetTarkovItems().GetProbabilityByContainerId(td.Key.Id); if (result != null) { return Math.Round((double)(result.probability), 2); } } return Math.Round((double)((decimal)td.Value / (decimal)mapDumpCounter[mapName]), 2); } private DumpProcessData GetDumpProcessData(List dumps) { var dumpProcessData = new DumpProcessData(); dumps.GroupBy(x => x.BasicInfo.Map) .ToList() .ForEach(tuple => { var mapi = tuple.Key; var g = tuple.ToList(); LoggerFactory.GetInstance().Log( $"Processing map {mapi}, total dump data to process: {g.Count}", LogLevel.Info ); dumpProcessData.MapCounts[mapi] = g.Count; var lockObjectContainerCounts = new object(); var lockObjectCounts = new object(); var counts = new LooseLootCounts(); var lockObjectDictionaryCounts = new object(); var dictionaryCounts = new FlatKeyableDictionary(); counts.Counts = dictionaryCounts.GetKey(); /* var dictionaryItemCounts = new FlatKeyableDictionary>(); counts.Items = dictionaryItemCounts.GetKey(); */ var lockObjectDictionaryItemProperties = new object(); var dictionaryItemProperties = new FlatKeyableDictionary>(); var actualDictionaryItemProperties = new FlatKeyableDictionary(); counts.ItemProperties = actualDictionaryItemProperties.GetKey(); dumpProcessData.LooseLootCounts.Add(mapi, counts.GetKey()); // add the items to the queue foreach (var gi in g) { _partialDataToProcess.Add(gi); } // Call GC before running threads g = null; tuple = null; GCHandler.Collect(); // The data storage factory has a lock, we dont want the locks to occur when multithreading for (int i = 0; i < LootDumpProcessorContext.GetConfig().Threads; i++) { Runners.Add( Task.Factory.StartNew( () => { while (_partialDataToProcess.TryTake(out var partialData, TimeSpan.FromMilliseconds(5000))) { try { var dumpData = _dataStorage.GetItem(partialData.ParsedDumpKey); lock (lockObjectContainerCounts) { dumpProcessData.ContainerCounts.AddRange(dumpData.Containers); } // loose loot into ids on files var loadedDictionary = _dataStorage .GetItem>>( dumpData.LooseLoot.ItemProperties ); foreach (var (k, v) in loadedDictionary) { var count = dumpData.LooseLoot.Counts[k]; lock (lockObjectDictionaryCounts) { if (dictionaryCounts.ContainsKey(k)) dictionaryCounts[k] += count; else dictionaryCounts[k] = count; } /* var itemList = dumpData.LooseLoot.Items[k]; if (!dictionaryItemCounts.TryGetValue(k, out var itemCounts)) { itemCounts = new List(); dictionaryItemCounts.Add(k, itemCounts); } itemCounts.AddRange(itemList); */ lock (lockObjectDictionaryItemProperties) { if (!dictionaryItemProperties.TryGetValue(k, out var values)) { values = new FlatKeyableList