From 52ca1fe68692c0f6f9f6b4f75a12db6605f84e9c Mon Sep 17 00:00:00 2001 From: Stefano Merotta <97297186+stefanomerotta@users.noreply.github.com> Date: Sat, 19 Aug 2023 07:05:28 +0200 Subject: [PATCH] fix: Replaces FindItem(s)ByType(s) implementation with BFS strategy (#1454) --- ModernUO.sln | 26 +- Projects/Server/Items/Container.cs | 237 ++++++++---------- .../UOContent/Engines/Craft/Core/CraftGump.cs | 6 +- .../UOContent/Engines/Craft/Core/CraftItem.cs | 8 +- .../Treasures of Tokuno/TreasuresOfTokuno.cs | 2 +- .../UOContent/Items/Containers/SalvageBag.cs | 2 +- Projects/UOContent/Items/Food/Beverage.cs | 4 +- Projects/UOContent/Mobiles/Townfolk/Banker.cs | 10 +- 8 files changed, 135 insertions(+), 160 deletions(-) diff --git a/ModernUO.sln b/ModernUO.sln index 1fc1bcb87..d46c956ba 100644 --- a/ModernUO.sln +++ b/ModernUO.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29102.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33927.249 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Projects\Server\Server.csproj", "{5E93BB35-3661-4822-9A8A-859726BAD87F}" EndProject @@ -12,19 +12,35 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UOContent.Tests", "Projects EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Analyze|Any CPU = Analyze|Any CPU Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU - Analyze|Any CPU = Analyze|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E93BB35-3661-4822-9A8A-859726BAD87F}.Analyze|Any CPU.ActiveCfg = Analyze|Any CPU + {5E93BB35-3661-4822-9A8A-859726BAD87F}.Analyze|Any CPU.Build.0 = Analyze|Any CPU {5E93BB35-3661-4822-9A8A-859726BAD87F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5E93BB35-3661-4822-9A8A-859726BAD87F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E93BB35-3661-4822-9A8A-859726BAD87F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E93BB35-3661-4822-9A8A-859726BAD87F}.Release|Any CPU.Build.0 = Release|Any CPU + {83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Analyze|Any CPU.ActiveCfg = Analyze|Any CPU + {83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Analyze|Any CPU.Build.0 = Analyze|Any CPU {83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83CF2484-BCCB-4B7C-9C5F-7AB43AEA5E8F}.Release|Any CPU.Build.0 = Release|Any CPU + {D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Analyze|Any CPU.ActiveCfg = Analyze|Any CPU + {D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Analyze|Any CPU.Build.0 = Analyze|Any CPU + {D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7A5D3AF-D607-46EF-BAAD-0D424190311F}.Release|Any CPU.Build.0 = Release|Any CPU + {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Analyze|Any CPU.ActiveCfg = Analyze|Any CPU + {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Analyze|Any CPU.Build.0 = Analyze|Any CPU {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Projects/Server/Items/Container.cs b/Projects/Server/Items/Container.cs index 73215494f..a86ab89e0 100644 --- a/Projects/Server/Items/Container.cs +++ b/Projects/Server/Items/Container.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using Server.Collections; using Server.Logging; using Server.Network; @@ -816,7 +817,7 @@ public class Container : Item throw new ArgumentNullException(nameof(grouper)); } - var typedItems = FindItemsByType(type, recurse); + var typedItems = CollectionsMarshal.AsSpan(FindItemsByType(type, recurse)); var groups = new List>(); var idx = 0; @@ -928,7 +929,7 @@ public class Container : Item for (var i = 0; i < types.Length; ++i) { - var typedItems = FindItemsByType(types[i], recurse); + var typedItems = CollectionsMarshal.AsSpan(FindItemsByType(types[i], recurse)); var groups = new List>(); var idx = 0; @@ -1044,7 +1045,7 @@ public class Container : Item for (var i = 0; i < types.Length; ++i) { - var typedItems = FindItemsByType(types[i], recurse); + var typedItems = CollectionsMarshal.AsSpan(FindItemsByType(types[i], recurse)); var groups = new List>(); var idx = 0; @@ -1147,14 +1148,14 @@ public class Container : Item throw new ArgumentException("length of types and amounts must match"); } - var items = new Item[types.Length][]; + var items = new List[types.Length]; var totals = new int[types.Length]; for (var i = 0; i < types.Length; ++i) { items[i] = FindItemsByType(types[i], recurse); - for (var j = 0; j < items[i].Length; ++j) + for (var j = 0; j < items[i].Count; ++j) { totals[i] += items[i][j].Amount; } @@ -1169,7 +1170,7 @@ public class Container : Item { var need = amounts[i]; - for (var j = 0; j < items[i].Length; ++j) + for (var j = 0; j < items[i].Count; ++j) { var item = items[i][j]; @@ -1202,14 +1203,14 @@ public class Container : Item throw new ArgumentException("length of types and amounts must match"); } - var items = new Item[types.Length][]; + var items = new List[types.Length]; var totals = new int[types.Length]; for (var i = 0; i < types.Length; ++i) { items[i] = FindItemsByType(types[i], recurse); - for (var j = 0; j < items[i].Length; ++j) + for (var j = 0; j < items[i].Count; ++j) { totals[i] += items[i][j].Amount; } @@ -1224,7 +1225,7 @@ public class Container : Item { var need = amounts[i]; - for (var j = 0; j < items[i].Length; ++j) + for (var j = 0; j < items[i].Count; ++j) { var item = items[i][j]; @@ -1252,7 +1253,7 @@ public class Container : Item public bool ConsumeTotal(Type type, int amount = 1, bool recurse = true, OnItemConsumed callback = null) { - var items = FindItemsByType(type, recurse); + var items = CollectionsMarshal.AsSpan(FindItemsByType(type, recurse)); // First pass, compute total var total = 0; @@ -1360,7 +1361,7 @@ public class Container : Item var best = 0; - var typedItems = FindItemsByType(type, recurse); + var typedItems = CollectionsMarshal.AsSpan(FindItemsByType(type, recurse)); var groups = new List>(); var idx = 0; @@ -1368,9 +1369,10 @@ public class Container : Item while (idx < typedItems.Length) { var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); + var group = new List + { + a + }; while (idx < typedItems.Length) { @@ -1421,7 +1423,7 @@ public class Container : Item var best = 0; - var typedItems = FindItemsByType(types, recurse); + var typedItems = CollectionsMarshal.AsSpan(FindItemsByType(types, recurse)); var groups = new List>(); var idx = 0; @@ -1429,9 +1431,10 @@ public class Container : Item while (idx < typedItems.Length) { var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); + var group = new List + { + a + }; while (idx < typedItems.Length) { @@ -1482,7 +1485,7 @@ public class Container : Item for (var i = 0; i < types.Length; ++i) { - var typedItems = FindItemsByType(types[i], recurse); + var typedItems = CollectionsMarshal.AsSpan(FindItemsByType(types[i], recurse)); var groups = new List>(); var idx = 0; @@ -1556,107 +1559,73 @@ public class Container : Item return total; } - public Item[] FindItemsByType(Type type, bool recurse = true) + public List FindItemsByType(Type type, bool recurse = true) { - if (m_FindItemsList.Count > 0) + using var queue = PooledRefQueue.Create(128); + queue.Enqueue(this); + var items = new List(); + while (queue.Count > 0) { - m_FindItemsList.Clear(); - } - - RecurseFindItemsByType(this, type, recurse, m_FindItemsList); - - return m_FindItemsList.ToArray(); - } - - private static void RecurseFindItemsByType(Item current, Type type, bool recurse, List list) - { - if (current == null || current.Items.Count == 0) - { - return; - } - - var items = current.Items; - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - if (type.IsInstanceOfType(item)) + var container = queue.Dequeue(); + foreach (var item in container.Items) { - list.Add(item); - } - - if (recurse && item is Container) - { - RecurseFindItemsByType(item, type, true, list); - } - } - } - - public Item[] FindItemsByType(Type[] types, bool recurse = true) - { - if (m_FindItemsList.Count > 0) - { - m_FindItemsList.Clear(); - } - - RecurseFindItemsByType(this, types, recurse, m_FindItemsList); - - return m_FindItemsList.ToArray(); - } - - private static void RecurseFindItemsByType(Item current, Type[] types, bool recurse, List list) - { - if (current == null || current.Items.Count == 0) - { - return; - } - - var items = current.Items; - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - if (InTypeList(item, types)) - { - list.Add(item); - } - - if (recurse && item is Container) - { - RecurseFindItemsByType(item, types, true, list); - } - } - } - - public Item FindItemByType(Type type, bool recurse = true) => RecurseFindItemByType(this, type, recurse); - - private static Item RecurseFindItemByType(Item current, Type type, bool recurse) - { - if (current == null || current.Items.Count == 0) - { - return null; - } - - var list = current.Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (type.IsInstanceOfType(item)) - { - return item; - } - - if (recurse && item is Container) - { - var check = RecurseFindItemByType(item, type, true); - - if (check != null) + if (type.IsInstanceOfType(item)) { - return check; + items.Add(item); + } + + if (recurse && item is Container itemContainer) + { + queue.Enqueue(itemContainer); + } + } + } + + return items; + } + + public List FindItemsByType(Type[] types, bool recurse = true) + { + using var queue = PooledRefQueue.Create(128); + queue.Enqueue(this); + var items = new List(); + while (queue.Count > 0) + { + var container = queue.Dequeue(); + foreach (var item in container.Items) + { + if (InTypeList(item, types)) + { + items.Add(item); + } + + if (recurse && item is Container itemContainer) + { + queue.Enqueue(itemContainer); + } + } + } + + return items; + } + + public Item FindItemByType(Type type, bool recurse = true) + { + using var queue = PooledRefQueue.Create(128); + queue.Enqueue(this); + while (queue.Count > 0) + { + var container = queue.Dequeue(); + foreach (var item in container.Items) + { + if (type.IsInstanceOfType(item)) + { + return item; + } + + if (recurse && item is Container itemContainer) + { + queue.Enqueue(itemContainer); } } } @@ -1664,33 +1633,23 @@ public class Container : Item return null; } - public Item FindItemByType(Type[] types, bool recurse = true) => RecurseFindItemByType(this, types, recurse); - - private static Item RecurseFindItemByType(Item current, Type[] types, bool recurse) + public Item FindItemByType(Type[] types, bool recurse = true) { - if (current == null || current.Items.Count == 0) + using var queue = PooledRefQueue.Create(128); + queue.Enqueue(this); + while (queue.Count > 0) { - return null; - } - - var list = current.Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (InTypeList(item, types)) + var container = queue.Dequeue(); + foreach (var item in container.Items) { - return item; - } - - if (recurse && item is Container) - { - var check = RecurseFindItemByType(item, types, true); - - if (check != null) + if (InTypeList(item, types)) { - return check; + return item; + } + + if (recurse && item is Container itemContainer) + { + queue.Enqueue(itemContainer); } } } diff --git a/Projects/UOContent/Engines/Craft/Core/CraftGump.cs b/Projects/UOContent/Engines/Craft/Core/CraftGump.cs index 29b1d469d..bceff2b1c 100644 --- a/Projects/UOContent/Engines/Craft/Core/CraftGump.cs +++ b/Projects/UOContent/Engines/Craft/Core/CraftGump.cs @@ -143,7 +143,7 @@ namespace Server.Engines.Craft { var items = from.Backpack.FindItemsByType(resourceType); - for (var i = 0; i < items.Length; ++i) + for (var i = 0; i < items.Count; ++i) { resourceCount += items[i].Amount; } @@ -187,7 +187,7 @@ namespace Server.Engines.Craft { var items = from.Backpack.FindItemsByType(resourceType); - for (var i = 0; i < items.Length; ++i) + for (var i = 0; i < items.Count; ++i) { resourceCount += items[i].Amount; } @@ -265,7 +265,7 @@ namespace Server.Engines.Craft { var items = from.Backpack.FindItemsByType(subResource.ItemType); - for (var j = 0; j < items.Length; ++j) + for (var j = 0; j < items.Count; ++j) { resourceCount += items[j].Amount; } diff --git a/Projects/UOContent/Engines/Craft/Core/CraftItem.cs b/Projects/UOContent/Engines/Craft/Core/CraftItem.cs index c86529ed4..06512f997 100644 --- a/Projects/UOContent/Engines/Craft/Core/CraftItem.cs +++ b/Projects/UOContent/Engines/Craft/Core/CraftItem.cs @@ -454,14 +454,14 @@ namespace Server.Engines.Craft } // TODO: Optimize allocation - var items = new Item[types.Length][]; + var items = new List[types.Length]; var totals = new int[types.Length]; for (var i = 0; i < types.Length; ++i) { items[i] = cont.FindItemsByType(types[i]); - for (var j = 0; j < items[i].Length; ++j) + for (var j = 0; j < items[i].Count; ++j) { if (items[i][j] is not IHasQuantity hq) { @@ -483,7 +483,7 @@ namespace Server.Engines.Craft { var need = amounts[i]; - for (var j = 0; j < items[i].Length; ++j) + for (var j = 0; j < items[i].Count; ++j) { var item = items[i][j]; @@ -534,7 +534,7 @@ namespace Server.Engines.Craft var amount = 0; - for (var i = 0; i < items.Length; ++i) + for (var i = 0; i < items.Count; ++i) { if (items[i] is not IHasQuantity hq) { diff --git a/Projects/UOContent/Engines/Treasures of Tokuno/TreasuresOfTokuno.cs b/Projects/UOContent/Engines/Treasures of Tokuno/TreasuresOfTokuno.cs index 24fe1e78a..42c656e3b 100644 --- a/Projects/UOContent/Engines/Treasures of Tokuno/TreasuresOfTokuno.cs +++ b/Projects/UOContent/Engines/Treasures of Tokuno/TreasuresOfTokuno.cs @@ -351,7 +351,7 @@ namespace Server.Gumps var items = pack.FindItemsByType(TreasuresOfTokuno.LesserArtifactsTotal); - for (var i = 0; i < items.Length; i++) + for (var i = 0; i < items.Count; i++) { var item = items[i]; if (item is ChestOfHeirlooms heirlooms && (!heirlooms.Locked || heirlooms.TrapLevel != 10)) diff --git a/Projects/UOContent/Items/Containers/SalvageBag.cs b/Projects/UOContent/Items/Containers/SalvageBag.cs index 1a75f8449..c6227402e 100644 --- a/Projects/UOContent/Items/Containers/SalvageBag.cs +++ b/Projects/UOContent/Items/Containers/SalvageBag.cs @@ -273,7 +273,7 @@ public partial class SalvageBag : Bag var items = FindItemsByType(_clothTypes); - for (var i = 0; i < items.Length; i++) + for (var i = 0; i < items.Count; i++) { from.AddToBackpack(items[i]); } diff --git a/Projects/UOContent/Items/Food/Beverage.cs b/Projects/UOContent/Items/Food/Beverage.cs index 6ec9af655..750067f99 100644 --- a/Projects/UOContent/Items/Food/Beverage.cs +++ b/Projects/UOContent/Items/Food/Beverage.cs @@ -667,7 +667,7 @@ public abstract partial class BaseBeverage : Item, IHasQuantity // First pass, compute total var total = 0; - for (var i = 0; i < items.Length; ++i) + for (var i = 0; i < items.Count; ++i) { if (items[i] is BaseBeverage bev && bev.Content == content && !bev.IsEmpty) { @@ -681,7 +681,7 @@ public abstract partial class BaseBeverage : Item, IHasQuantity var need = quantity; - for (var i = 0; i < items.Length; ++i) + for (var i = 0; i < items.Count; ++i) { if (items[i] is not BaseBeverage bev || bev.Content != content || bev.IsEmpty) { diff --git a/Projects/UOContent/Mobiles/Townfolk/Banker.cs b/Projects/UOContent/Mobiles/Townfolk/Banker.cs index 636867bdd..08f6eec55 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Banker.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Banker.cs @@ -62,7 +62,7 @@ namespace Server.Mobiles return Math.Max(0, (int)Math.Min(int.MaxValue, balance)); } - public static int GetBalance(Mobile m, out Item[] gold, out Item[] checks) + public static int GetBalance(Mobile m, out List gold, out List checks) { long balance = 0; @@ -72,7 +72,7 @@ namespace Server.Mobiles if (balance > int.MaxValue) { - gold = checks = Array.Empty(); + gold = checks = new List(); return int.MaxValue; } } @@ -94,7 +94,7 @@ namespace Server.Mobiles } else { - gold = checks = Array.Empty(); + gold = checks = new List(); } return Math.Max(0, (int)Math.Min(int.MaxValue, balance)); @@ -115,7 +115,7 @@ namespace Server.Mobiles return false; } - for (var i = 0; amount > 0 && i < gold.Length; ++i) + for (var i = 0; amount > 0 && i < gold.Count; ++i) { if (gold[i].Amount <= amount) { @@ -129,7 +129,7 @@ namespace Server.Mobiles } } - for (var i = 0; amount > 0 && i < checks.Length; ++i) + for (var i = 0; amount > 0 && i < checks.Count; ++i) { var check = (BankCheck)checks[i];