Improved performance of GetItemWithChildren()

Reduced number of enumerations of `itemWithChildren` inside AddItemWithChildrenToEquipmentSlot()` by converting children to list at start of method
Applied additional filtering to child items collection inside `GetContainerItemsWithChildren()`
This commit is contained in:
Chomp
2025-08-06 23:14:32 +01:00
parent 2530418e5b
commit ccfac42814
3 changed files with 90 additions and 42 deletions
@@ -230,7 +230,6 @@ public static class ItemExtensions
}
/// <summary>
/// TODO: return IEnumerable and update all calling code
/// Get an item with its attachments (children)
/// </summary>
/// <param name="items">List of items (item + possible children)</param>
@@ -239,48 +238,63 @@ public static class ItemExtensions
/// <returns>list of Item objects</returns>
public static List<Item> GetItemWithChildren(this IEnumerable<Item> items, MongoId baseItemId, bool excludeStoredItems = false)
{
// Use dictionary to make key lookup faster, convert to list before being returned
// Convert to list if not already
var itemList = items.ToList();
OrderedDictionary<MongoId, Item> result = [];
// Find desired root item
var desiredRootItem = itemList.FirstOrDefault(item => item.Id == baseItemId);
if (desiredRootItem is null)
// Create dict of items by parentId
var childrenByParent = new Dictionary<string, List<Item>>(itemList.Count);
foreach (var child in itemList)
{
var key = child.ParentId;
if (key is null)
{
continue;
}
if (childrenByParent.TryGetValue(key, out var list))
{
list.Add(child);
}
else
{
childrenByParent[key] = [child];
}
}
// Find root item
var root = itemList.FirstOrDefault(i => i.Id == baseItemId);
if (root is null)
{
// Root not found, nothing to return, exit
return [];
}
result.Add(desiredRootItem.Id, desiredRootItem);
var rootItemIdString = desiredRootItem.Id.ToString();
foreach (var item in itemList)
var result = new List<Item>();
var stack = new Stack<Item>();
stack.Push(root);
while (stack.Count > 0)
{
if (result.ContainsKey(item.Id))
var current = stack.Pop();
result.Add(current);
if (!childrenByParent.TryGetValue(current.Id.ToString(), out var children))
{
// Already processed, skip
// No children, skip to next
continue;
}
// Skip items with different parentId
if (item.ParentId != rootItemIdString)
foreach (var child in children)
{
continue;
}
// Is stored in parent and disallowed
if (excludeStoredItems && item.Location is not null)
{
continue;
}
// Item may have children, check
foreach (var subItem in GetItemWithChildren(itemList, item.Id))
{
result.Add(subItem.Id, subItem);
// child item has a location property = is stored inside parent
if (excludeStoredItems && child.Location is not null)
{
continue;
}
stack.Push(child);
}
}
return result.Values.ToList();
return result;
}
/// <summary>
@@ -435,7 +435,7 @@ public class BotGeneratorHelper(
/// <param name="rootItemTplId">Root items tpl id</param>
/// <param name="itemWithChildren">Item to add</param>
/// <param name="inventory">Inventory to add item+children into</param>
/// <param name="containersIdFull"></param>
/// <param name="containersIdFull">Container Ids with no space for more items</param>
/// <returns>ItemAddedResult result object</returns>
public ItemAddedResult AddItemWithChildrenToEquipmentSlot(
HashSet<EquipmentSlots> equipmentSlots,
@@ -446,6 +446,8 @@ public class BotGeneratorHelper(
HashSet<string>? containersIdFull = null
)
{
var itemWithChildrenList = itemWithChildren.ToList();
// Track how many containers are unable to be found
var missingContainerCount = 0;
foreach (var equipmentSlotId in equipmentSlots)
@@ -455,8 +457,8 @@ public class BotGeneratorHelper(
continue;
}
// Get container to put item into
var container = inventory.Items.FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
// Get container from inventory to put item into
var container = inventory.Items?.FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
if (container is null)
{
missingContainerCount++;
@@ -466,7 +468,7 @@ public class BotGeneratorHelper(
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug(
$"Unable to add item: {itemWithChildren.FirstOrDefault()?.Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}"
$"Unable to add item: {itemWithChildrenList.FirstOrDefault()?.Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}"
);
}
@@ -494,7 +496,7 @@ public class BotGeneratorHelper(
}
// Get x/y grid size of item
var (itemWidth, itemHeight) = inventoryHelper.GetItemSize(rootItemTplId, rootItemId, itemWithChildren);
var (itemWidth, itemHeight) = inventoryHelper.GetItemSize(rootItemTplId, rootItemId, itemWithChildrenList);
// Iterate over each grid in the container and look for a big enough space for the item to be placed in
var currentGridCount = 1;
@@ -520,7 +522,7 @@ public class BotGeneratorHelper(
);
// Get root items in container we can iterate over to find out what space is free
var containerItemsToCheck = existingContainerItems.Where(x => x.SlotId == slotGrid.Name).ToList();
var containerItemsToCheck = existingContainerItems.Where(x => x.SlotId == slotGrid.Name);
var containerItemsWithChildren = GetContainerItemsWithChildren(containerItemsToCheck, inventory.Items);
if (slotGrid.Props is not null)
@@ -539,7 +541,7 @@ public class BotGeneratorHelper(
// Free slot found, add item
if (findSlotResult.Success ?? false)
{
var parentItem = itemWithChildren.FirstOrDefault(i => i.Id == rootItemId);
var parentItem = itemWithChildrenList.FirstOrDefault(i => i.Id == rootItemId);
// Set items parent to container id
if (parentItem is not null)
@@ -554,7 +556,7 @@ public class BotGeneratorHelper(
};
}
(inventory.Items ?? []).AddRange(itemWithChildren);
(inventory.Items ?? []).AddRange(itemWithChildrenList);
return ItemAddedResult.SUCCESS;
}
@@ -570,7 +572,7 @@ public class BotGeneratorHelper(
// No space in this grid, move to next container grid and try again
}
// if we got to this point, the item couldn't be placed on the container
// If we got to this point, the item couldn't be placed on the container
if (containersIdFull is null)
{
continue;
@@ -608,14 +610,14 @@ public class BotGeneratorHelper(
return result;
}
// Filter out all items without location prop, (child items)
var itemsWithoutLocation = inventoryItems.Where(item => item.Location is null);
// Get collection of items likely to be children of root items
var itemsWithoutLocation = inventoryItems.Where(item => item.Location is null && item.ParentId is not null).ToList();
foreach (var rootItem in containerRootItems)
{
// Check item in container for children, store for later insertion into `containerItemsToCheck`
// (used later when figuring out how much space weapon takes up)
List<Item> itemsToFilter = [.. itemsWithoutLocation, rootItem];
var itemWithChildItems = itemsToFilter.GetItemWithChildren(rootItem.Id);
itemsWithoutLocation.Insert(0, rootItem);
var itemWithChildItems = itemsWithoutLocation.GetItemWithChildren(rootItem.Id);
// Item had children, replace existing data with item + its children
result.AddRange(itemWithChildItems);
+34 -2
View File
@@ -63,7 +63,12 @@ public class ItemTests
public void GetItemWithChildren_mods_and_inventory_item()
{
var testData = new List<Item>();
var rootItem = new Item { Id = new MongoId(), Template = ItemTpl.AMMOBOX_127X33_COPPER_20RND };
var rootItem = new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMOBOX_127X33_COPPER_20RND,
ParentId = new MongoId(),
};
var childItem = new Item
{
Id = new MongoId(),
@@ -83,7 +88,8 @@ public class ItemTests
var result = testData.GetItemWithChildren(rootItem.Id, false);
Assert.AreEqual(result[1].Id, childItem.Id);
Assert.Contains(childItem, result);
Assert.Contains(childItem2, result);
Assert.AreEqual(result.Count, 3);
}
@@ -292,4 +298,30 @@ public class ItemTests
Assert.AreEqual(false, profile.Inventory.Items.FirstOrDefault(item => item.Id == item2Id).Upd.SpawnedInSession);
Assert.AreEqual(true, profile.Inventory.Items.FirstOrDefault(item => item.Id == item3Id).Upd.SpawnedInSession);
}
[Test]
public void GetItemWithChildren_rootIdNotFound()
{
var testData = new List<Item>();
var rootItem = new Item { Id = new MongoId(), Template = ItemTpl.AMMOBOX_127X33_COPPER_20RND };
var childItem = new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_127X33_COPPER,
ParentId = rootItem.Id,
};
var childOfChild = new Item
{
Id = new MongoId(),
Template = ItemTpl.AMMO_26X75_GREEN,
ParentId = childItem.Id,
};
testData.Add(rootItem);
testData.Add(childItem);
testData.Add(childOfChild);
var result = testData.GetItemWithChildren(new MongoId(), true);
Assert.AreEqual(result.Count, 0);
}
}