From d0a24db828045f7c964d4afa8a85601674ce5482 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 14 Sep 2025 15:44:04 +0100 Subject: [PATCH] Removed recursion from `GeneratePool` Reduced use of locks --- .../Services/BotEquipmentModPoolService.cs | 103 +++++++----------- 1 file changed, 38 insertions(+), 65 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Services/BotEquipmentModPoolService.cs b/Libraries/SPTarkov.Server.Core/Services/BotEquipmentModPoolService.cs index 5b493e90..548fb46f 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BotEquipmentModPoolService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BotEquipmentModPoolService.cs @@ -47,70 +47,67 @@ public class BotEquipmentModPoolService( /// Create a dictionary of mods for each item passed in /// /// Items to find related mods and store in modPool - /// Mod pool to choose from e.g. "weapon" for weaponModPool + /// Mod pool to choose from e.g. "weapon" for weaponModPool protected ConcurrentDictionary>> GeneratePool( IEnumerable? inputItems, - string poolType + string poolKey ) { - // Null guard bad input if (inputItems is null || !inputItems.Any()) { - logger.Error(localisationService.GetText("bot-unable_to_generate_item_pool_no_items", poolType)); - + logger.Error(localisationService.GetText("bot-unable_to_generate_item_pool_no_items", poolKey)); return []; } + // Create pool we want to return var pool = new ConcurrentDictionary>>(); - foreach (var dbItem in inputItems) + + // Create queue to hold items we need to process/check for mods to add into the pool + // Add items passed in to method initially, add sub-mods later + var itemsToProcess = new Queue(inputItems); + + // Keep track of processed items to reduce unnecessary work + var processedItems = new HashSet(); + + while (itemsToProcess.TryDequeue(out var currentItem)) { - if (dbItem.Properties is null) - { - logger.Error( - localisationService.GetText("bot-item_missing_props_property", new { itemTpl = dbItem.Id, name = dbItem.Name }) - ); - - continue; - } - - // No slots for mods - if (dbItem.Properties.Slots is null || !dbItem.Properties.Slots.Any()) + // Null guard / we've already processed this item + if (currentItem is null || !processedItems.Add(currentItem.Id)) { continue; } - // Add base item (weapon/armor) to pool if it doesn't exist - var itemPool = pool.GetOrAdd(dbItem.Id, new ConcurrentDictionary>()); - - // Look for slots on item that hold mods - foreach (var slot in dbItem.Properties.Slots) + // No slots = skip + if (currentItem.Properties?.Slots is null || !currentItem.Properties.Slots.Any()) { - // Get whitelist of mods that fit into mod slot - var itemsThatFit = slot?.Properties?.Filters?.FirstOrDefault()?.Filter ?? []; - if (!itemsThatFit.Any()) + continue; + } + + // Get top-level pool, create if it doesn't exist + var itemPool = pool.GetOrAdd(currentItem.Id, new ConcurrentDictionary>()); + + foreach (var slot in currentItem.Properties.Slots) + { + var compatibleMods = slot?.Properties?.Filters?.FirstOrDefault()?.Filter; + if (compatibleMods is null || !compatibleMods.Any()) { + // No mod items in whitelist, skip continue; } - // Ensure Mod slot key + blank dict exist in pool - var modItemPool = GetSetModItemPool(itemPool, slot.Name); - foreach (var itemToAddTpl in itemsThatFit) + // Get or add set for this specific mod slot (e.g., "mod_scope"). + var modItemPool = itemPool.GetOrAdd(slot.Name, []); + + foreach (var modTpl in compatibleMods) { - // Does tpl exist inside mods' slots' hashset - if (!SetContainsTpl(modItemPool, itemToAddTpl)) - // Keyed by mod slot - { - AddTplToSet(modItemPool, itemToAddTpl); - } + modItemPool.Add(modTpl); - var subItemDetails = itemHelper.GetItem(itemToAddTpl).Value; - var hasSubItemsToAdd = subItemDetails.Properties?.Slots is not null && subItemDetails.Properties.Slots.Any(); - - // Item has Slots + pool doesn't have value - if (hasSubItemsToAdd && !pool.ContainsKey(subItemDetails.Id)) + // Also heck if mod ALSO has its own sub slots to process + var modItemDetails = itemHelper.GetItem(modTpl).Value; + if (modItemDetails?.Properties?.Slots?.Any() == true) { - // Recursive call - GeneratePool([subItemDetails], poolType); + // Has slots we need to check, add to processing queue + itemsToProcess.Enqueue(modItemDetails); } } } @@ -119,30 +116,6 @@ public class BotEquipmentModPoolService( return pool; } - private HashSet GetSetModItemPool(ConcurrentDictionary> dictionary, string slotName) - { - lock (_lockObject) - { - return dictionary.GetOrAdd(slotName, []); - } - } - - private bool SetContainsTpl(HashSet itemSet, MongoId tpl) - { - lock (_lockObject) - { - return itemSet.Contains(tpl); - } - } - - private bool AddTplToSet(HashSet itemSet, MongoId itemToAddTpl) - { - lock (_lockObject) - { - return itemSet.Add(itemToAddTpl); - } - } - /// /// Empty the mod pool ///