2024-04-16 18:29:40 +00:00
using System.Collections.Concurrent ;
2023-08-12 19:08:38 +01:00
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 ;
2024-04-16 18:29:40 +00:00
using LootDumpProcessor.Utils ;
2023-08-12 19:08:38 +01:00
namespace LootDumpProcessor.Process.Processor.DumpProcessor ;
public class MultithreadSteppedDumpProcessor : IDumpProcessor
{
private static IJsonSerializer _jsonSerializer = JsonSerializerFactory . GetInstance ( ) ;
private static readonly List < Task > Runners = new ( ) ;
private static readonly BlockingCollection < PartialData > _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 < OutputFileType , object > ProcessDumps ( List < PartialData > dumps )
{
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "Starting final dump processing" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
var output = new Dictionary < OutputFileType , object > ( ) ;
var dumpProcessData = GetDumpProcessData ( dumps ) ;
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "Heavy processing done!" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
var staticContainers = new Dictionary < string , MapStaticLoot > ( ) ;
var staticContainersLock = new object ( ) ;
// We need to count how many dumps we have for each map
var mapDumpCounter = new Dictionary < string , int > ( ) ;
var mapDumpCounterLock = new object ( ) ;
// dictionary of maps, that has a dictionary of template and hit count
var mapStaticContainersAggregated = new Dictionary < string , Dictionary < Template , int > > ( ) ;
var mapStaticContainersAggregatedLock = new object ( ) ;
2023-08-13 16:18:29 +01:00
2023-08-12 19:08:38 +01:00
Runners . Clear ( ) ;
// BSG changed the map data so static containers are now dynamic, so we need to scan all dumps for the static containers.
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "Queuing dumps for static data processing" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
foreach ( var dumped in dumps )
{
Runners . Add (
Task . Factory . StartNew ( ( ) = >
{
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Debug ) )
LoggerFactory . GetInstance ( ) . Log ( $"Processing static data for file {dumped.BasicInfo.FileName}" , LogLevel . Debug ) ;
2024-04-22 19:09:20 +01:00
var dataDump = _jsonSerializer . Deserialize < RootData > ( File . ReadAllText ( dumped . BasicInfo . FileName ) ) ;
var mapName = dataDump . Data . Name ;
2024-04-19 16:08:54 +01:00
// the if statement below takes care of processing "forced" or real static data for each map, only need
// to do this once per map, we dont care about doing it again
2023-08-12 19:08:38 +01:00
lock ( staticContainersLock )
{
2024-04-19 16:08:54 +01:00
if ( ! staticContainers . ContainsKey ( mapName ) )
2023-08-12 19:08:38 +01:00
{
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
2024-04-19 16:08:54 +01:00
LoggerFactory . GetInstance ( ) . Log ( $"Doing first time process for map {mapName} of real static data" , LogLevel . Info ) ;
2024-04-22 19:09:20 +01:00
var mapStaticContainers = StaticLootProcessor . CreateStaticWeaponsAndStaticForcedContainers ( dataDump ) ;
2024-04-19 16:08:54 +01:00
// .Item1 = map name
staticContainers [ mapStaticContainers . Item1 ] = mapStaticContainers . Item2 ;
2023-08-12 19:08:38 +01:00
}
}
2024-04-19 16:08:54 +01:00
// Takes care of finding how many "dynamic static containers" we have on the map
Dictionary < Template , int > mapAggregatedDataDict ;
2023-08-12 19:08:38 +01:00
lock ( mapStaticContainersAggregatedLock )
{
2024-04-19 16:08:54 +01:00
// Init dict if map key doesnt exist
if ( ! mapStaticContainersAggregated . TryGetValue ( mapName , out mapAggregatedDataDict ) )
2023-08-12 19:08:38 +01:00
{
2024-04-19 16:08:54 +01:00
mapAggregatedDataDict = new Dictionary < Template , int > ( ) ;
mapStaticContainersAggregated . Add ( mapName , mapAggregatedDataDict ) ;
2023-08-12 19:08:38 +01:00
}
}
2023-08-13 17:26:49 +01:00
// Only process the dump file if the date is higher (after) the configuration date
2024-04-19 16:08:54 +01:00
if ( DumpWasMadeAfterConfigThresholdDate ( dumped ) )
2023-08-12 19:08:38 +01:00
{
2024-04-19 16:08:54 +01:00
// Keep track of how many dumps we have for each map
2023-08-13 20:44:40 +01:00
lock ( mapDumpCounterLock )
{
2024-04-19 16:08:54 +01:00
IncrementMapCounterDictionaryValue ( mapDumpCounter , mapName ) ;
2023-08-13 20:44:40 +01:00
}
2023-09-08 15:35:19 +01:00
2024-04-22 19:09:20 +01:00
var containerIgnoreListExists = LootDumpProcessorContext . GetConfig ( ) . ContainerIgnoreList . TryGetValue ( dataDump . Data . Id . ToLower ( ) , out string [ ] ? ignoreListForMap ) ;
foreach ( var dynamicStaticContainer in StaticLootProcessor . CreateDynamicStaticContainers ( dataDump ) )
2023-08-12 19:08:38 +01:00
{
2023-08-13 17:26:49 +01:00
lock ( mapStaticContainersAggregatedLock )
{
2023-09-08 15:38:10 +01:00
if ( containerIgnoreListExists & & ignoreListForMap . Contains ( dynamicStaticContainer . Id ) )
2023-09-08 15:35:19 +01:00
{
2024-04-22 19:09:20 +01:00
// Skip adding containers to aggregated data if container id is in ignore list
2023-09-08 15:35:19 +01:00
continue ;
}
2024-04-22 19:09:20 +01:00
// Increment times container seen in dump by 1
2024-04-19 16:08:54 +01:00
if ( ! mapAggregatedDataDict . TryAdd ( dynamicStaticContainer , 1 ) )
mapAggregatedDataDict [ dynamicStaticContainer ] + = 1 ;
2023-08-13 17:26:49 +01:00
}
2023-08-12 19:08:38 +01:00
}
}
GCHandler . Collect ( ) ;
} )
) ;
}
Task . WaitAll ( Runners . ToArray ( ) ) ;
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "All static data processing threads finished" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
// Aggregate and calculate the probability of a static container
mapStaticContainersAggregated . ToDictionary (
kv = > kv . Key ,
kv = > kv . Value . Select (
td = > new StaticDataPoint
{
Template = td . Key ,
2024-04-22 19:09:20 +01:00
Probability = GetStaticContainerProbability ( kv . Key , td , mapDumpCounter ) // kv.Key = map name
2023-08-12 19:08:38 +01:00
}
) . ToList ( )
) . ToList ( ) . ForEach ( kv = > staticContainers [ kv . Key ] . StaticContainers = kv . Value ) ;
// Static containers
output . Add ( OutputFileType . StaticContainer , staticContainers ) ;
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "Processing ammo distribution" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
// Ammo distribution
output . Add (
OutputFileType . StaticAmmo ,
StaticLootProcessor . CreateAmmoDistribution ( dumpProcessData . ContainerCounts )
) ;
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "Processing static loot distribution" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
// Static loot distribution
output . Add (
OutputFileType . StaticLoot ,
2024-04-22 19:08:40 +01:00
StaticLootProcessor . CreateStaticLootDistribution ( dumpProcessData . ContainerCounts , staticContainers )
2023-08-12 19:08:38 +01:00
) ;
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "Processing loose loot distribution" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
// Loose loot distribution
var looseLootDistribution = LooseLootProcessor . CreateLooseLootDistribution (
dumpProcessData . MapCounts ,
dumpProcessData . LooseLootCounts
) ;
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "Collecting loose loot distribution information" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
var loot = dumpProcessData . MapCounts
. Select ( mapCount = > mapCount . Key )
. ToDictionary ( mi = > mi , mi = > looseLootDistribution [ mi ] ) ;
output . Add ( OutputFileType . LooseLoot , loot ) ;
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log ( "Dump processing fully completed!" , LogLevel . Info ) ;
2023-08-12 19:08:38 +01:00
return output ;
}
2024-04-19 16:08:54 +01:00
private static bool DumpWasMadeAfterConfigThresholdDate ( PartialData dataDump )
{
return FileDateParser . TryParseFileDate ( dataDump . BasicInfo . FileName , out var fileDate ) & &
fileDate . HasValue & &
fileDate . Value > LootDumpProcessorContext . GetConfig ( ) . DumpProcessorConfig
. SpawnContainerChanceIncludeAfterDate ;
}
private static void IncrementMapCounterDictionaryValue ( Dictionary < string , int > mapDumpCounter , string mapName )
{
if ( ! mapDumpCounter . TryAdd ( mapName , 1 ) )
{
// Dict has map, increment count by 1
mapDumpCounter [ mapName ] + = 1 ;
}
}
private static double GetStaticContainerProbability ( string mapName , KeyValuePair < Template , int > td , Dictionary < string , int > mapDumpCounter )
2023-09-08 15:35:19 +01:00
{
2023-08-13 16:18:29 +01:00
return Math . Round ( ( double ) ( ( decimal ) td . Value / ( decimal ) mapDumpCounter [ mapName ] ) , 2 ) ;
}
2024-04-19 16:08:54 +01:00
private static DumpProcessData GetDumpProcessData ( List < PartialData > dumps )
2023-08-12 19:08:38 +01:00
{
var dumpProcessData = new DumpProcessData ( ) ;
2024-04-19 16:08:54 +01:00
dumps . GroupBy ( dump = > dump . BasicInfo . Map )
2023-08-12 19:08:38 +01:00
. ToList ( )
. ForEach ( tuple = >
{
var mapi = tuple . Key ;
var g = tuple . ToList ( ) ;
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log (
$"Processing map {mapi}, total dump data to process: {g.Count}" ,
LogLevel . Info
) ;
2023-08-12 19:08:38 +01:00
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 < string , int > ( ) ;
counts . Counts = dictionaryCounts . GetKey ( ) ;
/ *
var dictionaryItemCounts = new FlatKeyableDictionary < string , List < string > > ( ) ;
counts . Items = dictionaryItemCounts . GetKey ( ) ;
* /
var lockObjectDictionaryItemProperties = new object ( ) ;
var dictionaryItemProperties = new FlatKeyableDictionary < string , FlatKeyableList < Template > > ( ) ;
var actualDictionaryItemProperties = new FlatKeyableDictionary < string , IKey > ( ) ;
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 < ParsedDump > ( partialData . ParsedDumpKey ) ;
lock ( lockObjectContainerCounts )
{
dumpProcessData . ContainerCounts . AddRange ( dumpData . Containers ) ;
}
// loose loot into ids on files
var loadedDictionary =
_dataStorage
. GetItem < SubdivisionedKeyableDictionary < string , List < Template > > > (
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 < string > ( ) ;
dictionaryItemCounts . Add ( k , itemCounts ) ;
}
itemCounts . AddRange ( itemList ) ;
* /
lock ( lockObjectDictionaryItemProperties )
{
if ( ! dictionaryItemProperties . TryGetValue ( k , out var values ) )
{
values = new FlatKeyableList < Template > ( ) ;
dictionaryItemProperties . Add ( k , values ) ;
actualDictionaryItemProperties . Add ( k , values . GetKey ( ) ) ;
}
values . AddRange ( v ) ;
}
}
lock ( lockObjectCounts )
{
2024-04-16 18:29:40 +00:00
counts . MapSpawnpointCount . Add ( dumpData . LooseLoot . MapSpawnpointCount ) ;
2023-08-12 19:08:38 +01:00
}
}
catch ( Exception e )
{
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Error ) )
LoggerFactory . GetInstance ( ) . Log (
$"ERROR OCCURRED:{e.Message}\n{e.StackTrace}" ,
LogLevel . Error
) ;
2023-08-12 19:08:38 +01:00
}
}
} ,
TaskCreationOptions . LongRunning )
) ;
}
// Wait until all runners are done processing
while ( ! Runners . All ( r = > r . IsCompleted ) )
{
2024-04-16 18:29:40 +00:00
if ( LoggerFactory . GetInstance ( ) . CanBeLogged ( LogLevel . Info ) )
LoggerFactory . GetInstance ( ) . Log (
$"One or more file processors are still processing files. Waiting {LootDumpProcessorContext.GetConfig().ThreadPoolingTimeoutMs}ms before checking again" ,
LogLevel . Info
) ;
2023-08-12 19:08:38 +01:00
Thread . Sleep (
TimeSpan . FromMilliseconds ( LootDumpProcessorContext . GetConfig ( ) . ThreadPoolingTimeoutMs ) ) ;
}
foreach ( var ( _ , value ) in dictionaryItemProperties )
{
_dataStorage . Store ( value ) ;
}
_dataStorage . Store ( dictionaryCounts ) ;
dictionaryCounts = null ;
GCHandler . Collect ( ) ;
/ *
DataStorageFactory . GetInstance ( ) . Store ( dictionaryItemCounts ) ;
dictionaryItemCounts = null ;
GC . Collect ( ) ;
* /
_dataStorage . Store ( actualDictionaryItemProperties ) ;
actualDictionaryItemProperties = null ;
GCHandler . Collect ( ) ;
_dataStorage . Store ( counts ) ;
counts = null ;
GCHandler . Collect ( ) ;
} ) ;
return dumpProcessData ;
}
}