From 094605be9dd8ece2f2062d78f2c83b9927c321e5 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:21:49 -0700 Subject: [PATCH 01/12] fix: Eliminates allocations in TryDropItems --- Projects/Server/Items/Container.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Projects/Server/Items/Container.cs b/Projects/Server/Items/Container.cs index bf4fab0b10..07de51fcfa 100644 --- a/Projects/Server/Items/Container.cs +++ b/Projects/Server/Items/Container.cs @@ -425,8 +425,8 @@ public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage, public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params ReadOnlySpan droppedItems) { - var dropItems = new List(); - var stackItems = new List(); + using var dropItems = PooledRefQueue.Create(); + using var stackItems = PooledRefQueue.Create(); var extraItems = 0; var extraWeight = 0; @@ -446,7 +446,7 @@ public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params ReadO if (item is not Container && CheckHold(from, dropped, false, false, 0, extraWeight) && item.CanStackWith(dropped)) { - stackItems.Add(new ItemStackEntry(item, dropped)); + stackItems.Enqueue(new ItemStackEntry(item, dropped)); extraWeight += (int)Math.Ceiling(item.Weight * (item.Amount + dropped.Amount)) - item.PileWeight; // extra weight delta, do not need TotalWeight as we do not have hybrid stackable container types stacked = true; @@ -456,7 +456,7 @@ public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params ReadO if (!stacked && CheckHold(from, dropped, false, true, extraItems, extraWeight)) { - dropItems.Add(dropped); + dropItems.Enqueue(dropped); extraItems++; extraWeight += dropped.TotalWeight + dropped.PileWeight; } @@ -464,14 +464,15 @@ public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params ReadO if (dropItems.Count + stackItems.Count == droppedItems.Length) // All good { - for (var i = 0; i < dropItems.Count; i++) + while (dropItems.Count > 0) { - DropItem(dropItems[i]); + DropItem(dropItems.Dequeue()); } - for (var i = 0; i < stackItems.Count; i++) + while (stackItems.Count > 0) { - stackItems[i].m_StackItem.StackWith(from, stackItems[i].m_DropItem, false); + var stackItem = stackItems.Dequeue(); + stackItem.m_StackItem.StackWith(from, stackItem.m_DropItem, false); } return true; From a1f6e59556cac65a57d6a22912a511233a74ab1b Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:42:44 -0700 Subject: [PATCH 02/12] Eliminates allocations in houses and regions for mobiles --- Projects/Server/Regions/Region.cs | 42 ++++++++++++++++- .../Implementors/RegionCommandImplementor.cs | 5 +- .../Engines/CannedEvil/ChampionSpawn.cs | 23 ++++++---- .../Doom/LeverPuzzle/LeverPuzzleController.cs | 46 ++++++++++--------- .../UOContent/Gumps/Houses/HouseGumpAOS.cs | 6 +-- Projects/UOContent/Mobiles/PlayerMobile.cs | 8 +++- Projects/UOContent/Multis/Houses/BaseHouse.cs | 16 +++---- .../Multis/Houses/HouseFoundation.cs | 21 +++++++-- 8 files changed, 115 insertions(+), 52 deletions(-) diff --git a/Projects/Server/Regions/Region.cs b/Projects/Server/Regions/Region.cs index a53e6466ff..58fe382e86 100644 --- a/Projects/Server/Regions/Region.cs +++ b/Projects/Server/Regions/Region.cs @@ -547,10 +547,29 @@ public Region GetRegion(string regionName, bool caseSensitive = true) public virtual bool AcceptsSpawnsFrom(Region region) => AllowSpawn() && (region == this || Parent?.AcceptsSpawnsFrom(region) == true); - public List GetPlayers() + public PooledRefList GetPlayersPooled() { - var list = new List(); + var list = PooledRefList.Create(); + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + foreach (var ns in sector.Clients) + { + var player = ns.Mobile; + if (player?.Deleted == false && player.Region.IsPartOf(this)) + { + list.Add(ns.Mobile); + } + } + } + + return list; + } + + public List GetPlayers() + { + List list = []; for (var i = 0; i < Sectors?.Length; i++) { var sector = Sectors[i]; @@ -609,6 +628,25 @@ public List GetMobiles() return list; } + public PooledRefList GetMobilesPooled() + { + var list = PooledRefList.Create(); + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var mobile in sector.Mobiles) + { + if (mobile.Region.IsPartOf(this)) + { + list.Add(mobile); + } + } + } + + return list; + } + public int GetMobileCount() { var count = 0; diff --git a/Projects/UOContent/Commands/Generic/Implementors/RegionCommandImplementor.cs b/Projects/UOContent/Commands/Generic/Implementors/RegionCommandImplementor.cs index c866e41dc8..05f6d3d3c6 100644 --- a/Projects/UOContent/Commands/Generic/Implementors/RegionCommandImplementor.cs +++ b/Projects/UOContent/Commands/Generic/Implementors/RegionCommandImplementor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Collections; namespace Server.Commands.Generic { @@ -33,8 +34,10 @@ public override void Compile(Mobile from, BaseCommand command, ref string[] args if (mobiles) { - foreach (var mob in reg.GetMobiles()) + using var mobileList = reg.GetMobilesPooled(); + for (var i = 0; i < mobileList.Count; i++) { + var mob = mobileList[i]; if (BaseCommand.IsAccessible(from, mob) && ext.IsValid(mob)) { list.Add(mob); diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs index 1aca46e316..02cc28ebe2 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using ModernUO.Serialization; +using Server.Collections; using Server.Engines.Virtues; using Server.Gumps; using Server.Items; @@ -1364,6 +1365,8 @@ public override void AlterLightLevel(Mobile m, ref int global, ref int personal) global = Math.Max(global, 1 + Spawn.Level); //This is a guesstimate. TODO: Verify & get exact values // OSI testing: at 2 red skulls, light = 0x3 ; 1 red = 0x3.; 3 = 8; 9 = 0xD 8 = 0xD 12 = 0x12 10 = 0xD } + private static readonly HashSet _addresses = []; + public override void OnEnter(Mobile m) { if (!m.Player || m.AccessLevel != AccessLevel.Player || Spawn.Active) @@ -1371,8 +1374,6 @@ public override void OnEnter(Mobile m) return; } - Region parent = Parent ?? this; - if (Spawn.ReadyToActivate) { Spawn.Start(); @@ -1384,27 +1385,29 @@ public override void OnEnter(Mobile m) return; } - List players = parent.GetPlayers(); - List addresses = new List(); + using var players = (Parent ?? this).GetPlayersPooled(); + for (var i = 0; i < players.Count; i++) { - if (players[i].AccessLevel == AccessLevel.Player && players[i].NetState != null && - !addresses.Contains(players[i].NetState.Address) && !((PlayerMobile)players[i]).Young) + var player = players[i]; + if (player.AccessLevel == AccessLevel.Player && player.NetState != null && !((PlayerMobile)player).Young) { - addresses.Add(players[i].NetState.Address); + _addresses.Add(player.NetState.Address); } } - if (addresses.Count >= 15) + if (_addresses.Count >= 15) { - foreach (Mobile player in players) + for (var i = 0; i < players.Count; i++) { - player.SendMessage(0x20, Spawn.BroadcastMessage); + players[i].SendMessage(0x20, Spawn.BroadcastMessage); } Spawn.ActivatedByProximity = true; Spawn.BeginRestart(TimeSpan.FromMinutes(5.0)); } + + _addresses.Clear(); } public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation) diff --git a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs index 8af67df584..bd48c38ee3 100644 --- a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs +++ b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs @@ -669,12 +669,13 @@ public LampRoomTimer(LeverPuzzleController controller) protected override void OnTick() { ticks++; - var mobiles = m_Controller._lampRoom.GetMobiles(); + using var mobiles = m_Controller._lampRoom.GetMobilesPooled(); if (ticks >= 71 || m_Controller._lampRoom.GetPlayerCount() == 0) { - foreach (var mobile in mobiles) + for (var i = 0; i < mobiles.Count; i++) { + var mobile = mobiles[i]; if (mobile?.Deleted == false && !mobile.IsDeadBondedPet) { mobile.Kill(); @@ -691,33 +692,36 @@ protected override void OnTick() level++; } - foreach (var mobile in mobiles) + for (var i = 0; i < mobiles.Count; i++) { - if (IsValidDamagable(mobile)) + var mobile = mobiles[i]; + if (!IsValidDamagable(mobile)) { - if (ticks % 2 == 0 && level == 5) + continue; + } + + if (ticks % 2 == 0 && level == 5) + { + if (mobile.Player) { - if (mobile.Player) + mobile.Say(1062092); + if (AniSafe(mobile)) { - mobile.Say(1062092); - if (AniSafe(mobile)) - { - mobile.Animate(32, 5, 1, true, false, 0); - } + mobile.Animate(32, 5, 1, true, false, 0); } - - DoDamage(mobile, 15, 20, true); } - if (Utility.Random((int)(level & ~0xfffffffc), 3) == 3) - { - mobile.ApplyPoison(mobile, PA2[level]); - } + DoDamage(mobile, 15, 20, true); + } - if (ticks % 12 == 0 && level > 0 && mobile.Player) - { - mobile.SendLocalizedMessage(PA[level][0], null, PA[level][1]); - } + if (Utility.Random((int)(level & ~0xfffffffc), 3) == 3) + { + mobile.ApplyPoison(mobile, PA2[level]); + } + + if (ticks % 12 == 0 && level > 0 && mobile.Player) + { + mobile.SendLocalizedMessage(PA[level][0], null, PA[level][1]); } } diff --git a/Projects/UOContent/Gumps/Houses/HouseGumpAOS.cs b/Projects/UOContent/Gumps/Houses/HouseGumpAOS.cs index 3a159286f9..bd4a6ce7b5 100644 --- a/Projects/UOContent/Gumps/Houses/HouseGumpAOS.cs +++ b/Projects/UOContent/Gumps/Houses/HouseGumpAOS.cs @@ -870,7 +870,7 @@ public static void ConvertHouse_Callback(Mobile from, bool okay, BaseHouse house } var items = house.GetItems(); - var mobiles = house.GetMobiles(); + using var mobiles = house.GetMobilesPooled(); newHouse.MoveToWorld( new Point3D( @@ -1216,7 +1216,7 @@ public override void OnResponse(NetState sender, in RelayInfo info) ); var r = m_House.Region; - var list = r.GetMobiles(); + using var list = r.GetMobilesPooled(); for (var i = 0; i < list.Count; ++i) { @@ -1258,7 +1258,7 @@ public override void OnResponse(NetState sender, in RelayInfo info) } var r = m_House.Region; - var list = r.GetMobiles(); + using var list = r.GetMobilesPooled(); for (var i = 0; i < list.Count; ++i) { diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 54c9fe4b0d..7bc3bf308f 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -1547,13 +1547,17 @@ private static void EventSink_Disconnected(Mobile from) // Eject all from house from.RevealingAction(); - foreach (var item in context.Foundation.GetItems()) + var list = context.Foundation.GetItems(); + for (var i = 0; i < list.Count; i++) { + var item = list[i]; item.Location = context.Foundation.BanLocation; } - foreach (var mobile in context.Foundation.GetMobiles()) + using var mobiles = context.Foundation.GetMobilesPooled(); + for (var i = 0; i < mobiles.Count; i++) { + var mobile = mobiles[i]; mobile.Location = context.Foundation.BanLocation; } diff --git a/Projects/UOContent/Multis/Houses/BaseHouse.cs b/Projects/UOContent/Multis/Houses/BaseHouse.cs index 095f955695..b5fb2787a2 100644 --- a/Projects/UOContent/Multis/Houses/BaseHouse.cs +++ b/Projects/UOContent/Multis/Houses/BaseHouse.cs @@ -1233,24 +1233,24 @@ public List GetItems() return list; } - public List GetMobiles() + public PooledRefList GetMobilesPooled() { if (Map == null || Map == Map.Internal) { - return new List(); + return PooledRefList.Create(0); } - var list = new List(); - - foreach (var mobile in Region.GetMobiles()) + var mobileList = Region.GetMobilesPooled(); + for (var i = mobileList.Count - 1; i >= 0; i--) { - if (IsInside(mobile)) + var mobile = mobileList[i]; + if (!IsInside(mobile)) { - list.Add(mobile); + mobileList.Remove(mobile); } } - return list; + return mobileList; } public virtual bool CheckAosLockdowns(int need) => GetAosCurLockdowns() + need <= GetAosMaxLockdowns(); diff --git a/Projects/UOContent/Multis/Houses/HouseFoundation.cs b/Projects/UOContent/Multis/Houses/HouseFoundation.cs index 46be9fdfaa..2c12373e0d 100644 --- a/Projects/UOContent/Multis/Houses/HouseFoundation.cs +++ b/Projects/UOContent/Multis/Houses/HouseFoundation.cs @@ -901,8 +901,10 @@ public void BeginCustomize(Mobile m) item.Location = BanLocation; } - foreach (var mobile in GetMobiles()) + using var mobiles = GetMobilesPooled(); + for (var i = 0; i < mobiles.Count; i++) { + var mobile = mobiles[i]; if (mobile != m) { mobile.Location = BanLocation; @@ -1294,13 +1296,18 @@ public void EndConfirmCommit(Mobile from) // Eject all from house from.RevealingAction(); - foreach (var item in GetItems()) + var items = GetItems(); + for (var i = 0; i < items.Count; i++) { + var item = items[i]; item.Location = BanLocation; } - foreach (var mobile in GetMobiles()) + using var mobiles = GetMobilesPooled(); + var list = GetMobilesPooled(); + for (var i = 0; i < list.Count; i++) { + var mobile = list[i]; mobile.Location = BanLocation; } @@ -1760,13 +1767,17 @@ public static void Designer_Close(NetState state, IEntity e, EncodedReader reade // Eject all from house from.RevealingAction(); - foreach (var item in context.Foundation.GetItems()) + var list = context.Foundation.GetItems(); + for (var i = 0; i < list.Count; i++) { + var item = list[i]; item.Location = context.Foundation.BanLocation; } - foreach (var mobile in context.Foundation.GetMobiles()) + using var mobiles = context.Foundation.GetMobilesPooled(); + for (var i = 0; i < mobiles.Count; i++) { + var mobile = mobiles[i]; mobile.Location = context.Foundation.BanLocation; } From 53cdacddf7bf346006c1a2a255e907b9c641dc6e Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:44:48 -0700 Subject: [PATCH 03/12] Eliminates allocations in region items --- Projects/Server/Regions/Region.cs | 20 +++++++++++++++++++ .../Implementors/RegionCommandImplementor.cs | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Projects/Server/Regions/Region.cs b/Projects/Server/Regions/Region.cs index 58fe382e86..1c71756a40 100644 --- a/Projects/Server/Regions/Region.cs +++ b/Projects/Server/Regions/Region.cs @@ -687,6 +687,26 @@ public List GetItems() return list; } + public PooledRefList GetItemsPooled() + { + var list = PooledRefList.Create(); + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var item in sector.Items) + { + if (Find(item.Location, item.Map).IsPartOf(this)) + { + list.Add(item); + } + } + } + + return list; + } + public int GetItemCount() { var count = 0; diff --git a/Projects/UOContent/Commands/Generic/Implementors/RegionCommandImplementor.cs b/Projects/UOContent/Commands/Generic/Implementors/RegionCommandImplementor.cs index 05f6d3d3c6..1010bc7c13 100644 --- a/Projects/UOContent/Commands/Generic/Implementors/RegionCommandImplementor.cs +++ b/Projects/UOContent/Commands/Generic/Implementors/RegionCommandImplementor.cs @@ -47,7 +47,8 @@ public override void Compile(Mobile from, BaseCommand command, ref string[] args if (items) { - foreach (var item in reg.GetItems()) + using var itemList = reg.GetItemsPooled(); + foreach (var item in itemList) { if (BaseCommand.IsAccessible(from, item) && ext.IsValid(item)) { From 797a3ab75093744c8ff79772d6f1003cb618677f Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:45:07 -0700 Subject: [PATCH 04/12] Removes dead code --- Projects/UOContent/Accounting/Account.cs | 34 ------------------------ 1 file changed, 34 deletions(-) diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs index 9e554fd726..9709011c86 100644 --- a/Projects/UOContent/Accounting/Account.cs +++ b/Projects/UOContent/Accounting/Account.cs @@ -849,40 +849,6 @@ private bool UpgradePassword(string password, PasswordProtectionAlgorithm algori return true; } - /// - /// Deserializes a list of string values from an xml element. Null values are not added to the list. - /// - /// The XmlElement from which to deserialize. - /// String list. Value will never be null. - private static string[] LoadAccessCheck(XmlElement node) - { - string[] stringList; - var accessCheck = node["accessCheck"]; - - if (accessCheck != null) - { - var list = new List(); - - foreach (XmlElement ip in accessCheck.GetElementsByTagName("ip")) - { - var text = Utility.GetText(ip, null); - - if (text != null) - { - list.Add(text); - } - } - - stringList = list.ToArray(); - } - else - { - stringList = []; - } - - return stringList; - } - /// /// Deserializes a list of IPAddress values from an xml element. /// From b75a25f19cb0b2eff877e7b7c5ce4a806c819d1e Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:47:33 -0700 Subject: [PATCH 05/12] Eliminates allocations in design insert --- .../UOContent/Commands/Generic/Commands/DesignInsert.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Projects/UOContent/Commands/Generic/Commands/DesignInsert.cs b/Projects/UOContent/Commands/Generic/Commands/DesignInsert.cs index 345ff37c97..c8d294a8ff 100644 --- a/Projects/UOContent/Commands/Generic/Commands/DesignInsert.cs +++ b/Projects/UOContent/Commands/Generic/Commands/DesignInsert.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Collections; using Server.Gumps; using Server.Items; using Server.Multis; @@ -112,7 +113,7 @@ private void OnConfirmCallback(Mobile from, bool okay, List list, bool s if (okay) { - var foundations = new List(); + using var foundations = PooledRefQueue.Create(); flushToLog = list.Count > 20; for (var i = 0; i < list.Count; ++i) @@ -127,7 +128,7 @@ private void OnConfirmCallback(Mobile from, bool okay, List list, bool s if (!foundations.Contains(house)) { - foundations.Add(house); + foundations.Enqueue(house); } break; @@ -146,9 +147,9 @@ private void OnConfirmCallback(Mobile from, bool okay, List list, bool s } } - foreach (var house in foundations) + while (foundations.Count > 0) { - house.Delta(ItemDelta.Update); + foundations.Dequeue().Delta(ItemDelta.Update); } } else From 96a3a92e02291769b7be5738b8c5784415dd37ed Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:49:51 -0700 Subject: [PATCH 06/12] Eliminates more allocations in champions --- .../Engines/CannedEvil/CannedEvilTimer.cs | 39 ++++++++++--------- .../UOContent/Engines/CannedEvil/GenChamps.cs | 9 +++-- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs index d7ca9e1b73..0f2c6ef3ab 100644 --- a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs +++ b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using Server.Collections; using Server.Misc; namespace Server.Engines.CannedEvil @@ -64,32 +65,34 @@ public CannedEvilTimer() : base(TimeSpan.Zero, TimeSpan.FromMinutes(1.0)) _sliceTime = Core.Now; } - public void OnSlice(ICollection list, bool rotate = true) where T : ChampionSpawn + public static void OnSlice(ICollection list, bool rotate = true) where T : ChampionSpawn { - if (list.Count > 0) + if (list.Count <= 0) { - List valid = new List(); + return; + } + + using var valid = PooledRefQueue.Create(); - foreach (T spawn in list) + foreach (T spawn in list) + { + if (spawn.AlwaysActive && !spawn.Active) { - if (spawn.AlwaysActive && !spawn.Active) - { - spawn.ReadyToActivate = true; - } - else if (rotate && (!spawn.Active || spawn.Kills == 0 && spawn.Level == 0)) - { - spawn.Active = false; - spawn.ReadyToActivate = false; - - valid.Add(spawn); - } + spawn.ReadyToActivate = true; } - - if (valid.Count > 0) + else if (rotate && (!spawn.Active || spawn.Kills == 0 && spawn.Level == 0)) { - valid[Utility.Random(valid.Count)].ReadyToActivate = true; + spawn.Active = false; + spawn.ReadyToActivate = false; + + valid.Enqueue(spawn); } } + + if (valid.Count > 0 && valid.PeekRandom() is T t) + { + t.ReadyToActivate = true; + } } protected override void OnTick() diff --git a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs index 0a3b526ea0..0e5bc83748 100644 --- a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs +++ b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using Server.Collections; using Server.Logging; namespace Server.Engines.CannedEvil @@ -65,18 +66,18 @@ private static void ChampGen_OnCommand(CommandEventArgs e) */ //We assume that all champion spawns are generated here. - List spawns = new List(); + using var spawns = PooledRefQueue.Create(); foreach (Item item in World.Items.Values) { if (item is ChampionSpawn spawn) { - spawns.Add(spawn); + spawns.Enqueue(spawn); } } - for (int i = spawns.Count - 1; i >= 0; i--) + while (spawns.Count > 0) { - spawns[i].Delete(); + spawns.Dequeue().Delete(); } Process(DungeonLocations); From f48631a29fefe197a84b657227ba5b892b9c651d Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:53:50 -0700 Subject: [PATCH 07/12] Eliminates allocations in GenGauntlet --- Projects/UOContent/Engines/Doom/GenGauntlet.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Projects/UOContent/Engines/Doom/GenGauntlet.cs b/Projects/UOContent/Engines/Doom/GenGauntlet.cs index c770afa856..6d36e9aea2 100644 --- a/Projects/UOContent/Engines/Doom/GenGauntlet.cs +++ b/Projects/UOContent/Engines/Doom/GenGauntlet.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Server.Collections; using Server.Items; using Server.Mobiles; @@ -272,18 +273,23 @@ public static void CreateVarietyDealer(int x, int y) FacialHairHue = 0x482 }; - var items = new List(dealer.Items); + using var toDelete = PooledRefQueue.Create(); - for (var i = 0; i < items.Count; ++i) + for (var i = 0; i < dealer.Items.Count; ++i) { - var item = items[i]; + var item = dealer.Items[i]; if (item.Layer is not Layer.ShopBuy and not Layer.ShopResale and not Layer.ShopSell) { - item.Delete(); + toDelete.Enqueue(item); } } + while (toDelete.Count > 0) + { + toDelete.Dequeue().Delete(); + } + dealer.AddItem(new FloppyHat(1)); dealer.AddItem(new Robe(1)); dealer.AddItem(new LanternOfSouls()); From d78ec79cc06421a26dbeb2da6b2fcc85fa38303d Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:02:33 -0700 Subject: [PATCH 08/12] Eliminates allocations in GuardAI --- .../Factions/Mobiles/Guards/GuardAI.cs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs b/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs index 804ddbef79..910c393bbd 100644 --- a/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs +++ b/Projects/UOContent/Engines/Factions/Mobiles/Guards/GuardAI.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Collections; using Server.Factions.AI; using Server.Items; using Server.Mobiles; @@ -695,41 +696,42 @@ public override bool Think() var dexMod = GetStatMod(m_Guard, StatType.Dex); var intMod = GetStatMod(m_Guard, StatType.Int); - var types = new List(); + using var spellTypes = PooledRefQueue.Create(); if (strMod <= 0) { - types.Add(typeof(StrengthSpell)); + spellTypes.Enqueue(typeof(StrengthSpell)); } if (dexMod <= 0 && IsAllowed(GuardAI.Melee)) { - types.Add(typeof(AgilitySpell)); + spellTypes.Enqueue(typeof(AgilitySpell)); } if (intMod <= 0 && IsAllowed(GuardAI.Magic)) { - types.Add(typeof(CunningSpell)); + spellTypes.Enqueue(typeof(CunningSpell)); } if (IsAllowed(GuardAI.Bless)) { - if (types.Count > 1) + if (spellTypes.Count > 1) { spell = new BlessSpell(m_Guard); } - else if (types.Count == 1) + else if (spellTypes.Count == 1) { - spell = types[0].CreateInstance(m_Guard, null); + spell = spellTypes.Dequeue().CreateInstance(m_Guard, null); } } - else if (types.Count > 0) + else if (spellTypes.Count > 0) { - if (types[0] == typeof(StrengthSpell)) + var spellType = spellTypes.Dequeue(); + if (spellType == typeof(StrengthSpell)) { UseItemByType(typeof(BaseStrengthPotion)); } - else if (types[0] == typeof(AgilitySpell)) + else if (spellType == typeof(AgilitySpell)) { UseItemByType(typeof(BaseAgilityPotion)); } @@ -749,30 +751,30 @@ public override bool Think() var dexMod = GetStatMod(combatant, StatType.Dex); var intMod = GetStatMod(combatant, StatType.Int); - var types = new List(); + using var spellTypes = PooledRefQueue.Create(); if (strMod >= 0) { - types.Add(typeof(WeakenSpell)); + spellTypes.Enqueue(typeof(WeakenSpell)); } if (dexMod >= 0 && IsAllowed(GuardAI.Melee)) { - types.Add(typeof(ClumsySpell)); + spellTypes.Enqueue(typeof(ClumsySpell)); } if (intMod >= 0 && IsAllowed(GuardAI.Magic)) { - types.Add(typeof(FeeblemindSpell)); + spellTypes.Enqueue(typeof(FeeblemindSpell)); } - if (types.Count > 1) + if (spellTypes.Count > 1) { spell = new CurseSpell(m_Guard); } - else if (types.Count == 1) + else if (spellTypes.Count == 1) { - spell = types[0].CreateInstance(m_Guard, null); + spell = spellTypes.Dequeue().CreateInstance(m_Guard, null); } } } From b31a3b3ac8599d936e29d80b18bf5a07f617f1c8 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:09:17 -0700 Subject: [PATCH 09/12] Eliminates more allocations --- .../Engines/ML Quests/Mobiles/BoonCollector.cs | 3 +-- .../Engines/ML Quests/Objectives/DeliverObjective.cs | 9 +++++---- Projects/UOContent/Items/Books/BaseBook.cs | 11 +++++++---- .../Items/Bulletin Boards/BulletinMessage.cs | 5 +++-- Projects/UOContent/Items/Containers/Strongbox.cs | 11 ++++++----- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Projects/UOContent/Engines/ML Quests/Mobiles/BoonCollector.cs b/Projects/UOContent/Engines/ML Quests/Mobiles/BoonCollector.cs index 6dd4e47825..86754c5e5a 100644 --- a/Projects/UOContent/Engines/ML Quests/Mobiles/BoonCollector.cs +++ b/Projects/UOContent/Engines/ML Quests/Mobiles/BoonCollector.cs @@ -118,8 +118,7 @@ public void TalkTo(PlayerMobile pm) } else { - var conversation = new List(); - conversation.AddRange(Incomplete); + List conversation = [..Incomplete]; var context = MLQuestSystem.GetContext(pm); diff --git a/Projects/UOContent/Engines/ML Quests/Objectives/DeliverObjective.cs b/Projects/UOContent/Engines/ML Quests/Objectives/DeliverObjective.cs index e8d6890282..2b6679ddca 100644 --- a/Projects/UOContent/Engines/ML Quests/Objectives/DeliverObjective.cs +++ b/Projects/UOContent/Engines/ML Quests/Objectives/DeliverObjective.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Collections; using Server.Gumps; using Server.Items; using Server.Logging; @@ -46,7 +47,7 @@ public virtual void SpawnDelivery(Container pack) return; } - var delivery = new List(); + using var delivery = PooledRefQueue.Create(); for (var i = 0; i < Amount; ++i) { @@ -54,7 +55,7 @@ public virtual void SpawnDelivery(Container pack) if (item != null) { - delivery.Add(item); + delivery.Enqueue(item); if (item.Stackable && Amount > 1) { @@ -64,9 +65,9 @@ public virtual void SpawnDelivery(Container pack) } } - foreach (var item in delivery) + while (delivery.Count > 0) { - pack.DropItem(item); // Confirmed: on OSI items are added even if your pack is full + pack.DropItem(delivery.Dequeue()); // Confirmed: on OSI items are added even if your pack is full } } diff --git a/Projects/UOContent/Items/Books/BaseBook.cs b/Projects/UOContent/Items/Books/BaseBook.cs index b70dcf854f..f932e15b44 100644 --- a/Projects/UOContent/Items/Books/BaseBook.cs +++ b/Projects/UOContent/Items/Books/BaseBook.cs @@ -141,11 +141,14 @@ public string[] ContentAsStringArray { get { - var lines = new List(); - - foreach (var bpi in Pages) + using var lines = PooledRefQueue.Create(256); + for (var i = 0; i < Pages.Length; i++) { - lines.AddRange(bpi.Lines); + var bpi = Pages[i]; + for (var j = 0; j < bpi.Lines.Length; j++) + { + lines.Enqueue(bpi.Lines[j]); + } } return lines.ToArray(); diff --git a/Projects/UOContent/Items/Bulletin Boards/BulletinMessage.cs b/Projects/UOContent/Items/Bulletin Boards/BulletinMessage.cs index 2e4500d10f..6d4e1101c6 100644 --- a/Projects/UOContent/Items/Bulletin Boards/BulletinMessage.cs +++ b/Projects/UOContent/Items/Bulletin Boards/BulletinMessage.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using ModernUO.Serialization; +using Server.Collections; using Server.Targeting; namespace Server.Items @@ -22,7 +23,7 @@ public BulletinMessage(Mobile poster, BulletinMessage thread, string subject, st PostedHue = Poster.Hue; Lines = lines; - var list = new List(); + using var list = PooledRefQueue.Create(poster.Items.Count); for (var i = 0; i < poster.Items.Count; ++i) { @@ -30,7 +31,7 @@ public BulletinMessage(Mobile poster, BulletinMessage thread, string subject, st if (item.Layer >= Layer.OneHanded && item.Layer <= Layer.Mount) { - list.Add(new BulletinEquip(item.ItemID, item.Hue)); + list.Enqueue(new BulletinEquip(item.ItemID, item.Hue)); } } diff --git a/Projects/UOContent/Items/Containers/Strongbox.cs b/Projects/UOContent/Items/Containers/Strongbox.cs index 08d6778a09..9be8411bae 100644 --- a/Projects/UOContent/Items/Containers/Strongbox.cs +++ b/Projects/UOContent/Items/Containers/Strongbox.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using ModernUO.Serialization; +using Server.Collections; using Server.Multis; namespace Server.Items; @@ -101,16 +102,16 @@ private void Chop(Mobile from) public Container ConvertToStandardContainer() { - Container metalBox = new MetalBox(); - var subItems = new List(Items); + var metalBox = new MetalBox(); + using var subItems = PooledRefList.Create(Items.Count); + subItems.AddRange(Items); - foreach (var subItem in subItems) + for (var i = 0; i < subItems.Count; i++) { - metalBox.AddItem(subItem); + metalBox.AddItem(subItems[i]); } Delete(); - return metalBox; } } From 74265d905c9056e5ba249e9ddfa3f525cb8ff572 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:11:41 -0700 Subject: [PATCH 10/12] Eliminates allocations when equipping your corpse items --- Projects/UOContent/Items/Misc/Corpses/Corpse.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Projects/UOContent/Items/Misc/Corpses/Corpse.cs b/Projects/UOContent/Items/Misc/Corpses/Corpse.cs index 8ea7d1319d..9ddea258f2 100644 --- a/Projects/UOContent/Items/Misc/Corpses/Corpse.cs +++ b/Projects/UOContent/Items/Misc/Corpses/Corpse.cs @@ -824,25 +824,28 @@ public virtual void Open(Mobile from, bool checkSelfLoot) } var pack = from.Backpack; + using var items = PooledRefList.Create(128); if (RestoreEquip != null && pack != null) { - var packItems = new List(pack.Items); // Only items in the top-level pack are re-equipped + items.AddRange(pack.Items); - for (var i = 0; i < packItems.Count; i++) + // Only items in the top-level pack are re-equipped + for (var i = 0; i < items.Count; i++) { - var packItem = packItems[i]; + var packItem = items[i]; if (RestoreEquip.Contains(packItem) && packItem.Movable) { from.EquipItem(packItem); } } - } - var items = new List(Items); + items.Clear(); + } var didntFit = false; + items.AddRange(Items); for (var i = 0; !didntFit && i < items.Count; ++i) { From 2aa397e3c3838776932d44e5b9d7e3661bb30a0c Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:14:48 -0700 Subject: [PATCH 11/12] Eliminates allocations in Equip/Unequip macro --- Projects/UOContent/Mobiles/PlayerMobile.cs | 82 ++++++++++--------- .../Network/Packets/IncomingItemPackets.cs | 10 ++- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 7bc3bf308f..c77d3d26e8 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -986,67 +986,71 @@ public static void TargetedSkillUse(Mobile from, IEntity target, int skillId) from.TargetLocked = false; } - public static void EquipMacro(Mobile m, List list) + public static void EquipMacro(Mobile m, ref PooledRefList list) { - if (m is PlayerMobile { Alive: true } pm && pm.Backpack != null) + if (m is not PlayerMobile { Alive: true } pm || pm.Backpack == null) { - var pack = pm.Backpack; + return; + } - foreach (var serial in list) + var pack = pm.Backpack; + + foreach (var serial in list) + { + Item item = null; + foreach (var i in pack.Items) { - Item item = null; - foreach (var i in pack.Items) + if (i.Serial == serial) { - if (i.Serial == serial) - { - item = i; - break; - } + item = i; + break; } + } - if (item == null) - { - continue; - } + if (item == null) + { + continue; + } - var toMove = pm.FindItemOnLayer(item.Layer); + var toMove = pm.FindItemOnLayer(item.Layer); - if (toMove != null) - { - // pack.DropItem(toMove); - toMove.Internalize(); + if (toMove != null) + { + // pack.DropItem(toMove); + toMove.Internalize(); - if (!pm.EquipItem(item)) - { - pm.EquipItem(toMove); - } - else - { - pack.DropItem(toMove); - } + if (!pm.EquipItem(item)) + { + pm.EquipItem(toMove); } else { - pm.EquipItem(item); + pack.DropItem(toMove); } } + else + { + pm.EquipItem(item); + } } } - public static void UnequipMacro(Mobile m, List layers) + public static void UnequipMacro(Mobile m, ref PooledRefList layers) { - if (m is PlayerMobile { Alive: true } pm && pm.Backpack != null) + if (m is not PlayerMobile { Alive: true } pm || pm.Backpack == null) { - var pack = pm.Backpack; - var eq = m.Items; + return; + } - for (var i = eq.Count - 1; i >= 0; i--) + var pack = pm.Backpack; + var eq = m.Items; + + for (var i = eq.Count - 1; i >= 0; i--) + { + var item = eq[i]; + if (layers.Contains(item.Layer)) { - var item = eq[i]; - if (layers.Contains(item.Layer)) - { - pack.TryDropItem(pm, item, false); - } + pack.TryDropItem(pm, item, false); } } } diff --git a/Projects/UOContent/Network/Packets/IncomingItemPackets.cs b/Projects/UOContent/Network/Packets/IncomingItemPackets.cs index f2ad7fd8ff..2ba38be1df 100644 --- a/Projects/UOContent/Network/Packets/IncomingItemPackets.cs +++ b/Projects/UOContent/Network/Packets/IncomingItemPackets.cs @@ -16,6 +16,7 @@ using System.Buffers; using System.Collections.Generic; using System.IO; +using Server.Collections; using Server.Items; using Server.Mobiles; @@ -112,24 +113,25 @@ public static void DropReq(NetState state, SpanReader reader) public static void EquipMacro(NetState state, SpanReader reader) { int count = reader.ReadByte(); - var serialList = new List(count); + var serialList = PooledRefList.Create(count); for (var i = 0; i < count; ++i) { serialList.Add((Serial)reader.ReadUInt32()); } - PlayerMobile.EquipMacro(state.Mobile, serialList); + PlayerMobile.EquipMacro(state.Mobile, ref serialList); + serialList.Dispose(); } public static void UnequipMacro(NetState state, SpanReader reader) { int count = reader.ReadByte(); - var layers = new List(count); + var layers = PooledRefList.Create(count); for (var i = 0; i < count; ++i) { layers.Add((Layer)reader.ReadUInt16()); } - PlayerMobile.UnequipMacro(state.Mobile, layers); + PlayerMobile.UnequipMacro(state.Mobile, ref layers); } } From b14434ca1b2439e553a2df392d07ca989c33d84e Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:18:49 -0700 Subject: [PATCH 12/12] Fixes compile error in CannedEvilTimer --- Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs index 0f2c6ef3ab..6599bd04a3 100644 --- a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs +++ b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs @@ -39,25 +39,25 @@ public static void Initialize() public static void AddSpawn(DungeonChampionSpawn spawn) { _dungeonSpawns.Add(spawn); - Instance?.OnSlice(_dungeonSpawns, false); + OnSlice(_dungeonSpawns, false); } public static void AddSpawn(LLChampionSpawn spawn) { _lostLandsSpawns.Add(spawn); - Instance?.OnSlice(_lostLandsSpawns, false); + OnSlice(_lostLandsSpawns, false); } public static void RemoveSpawn(DungeonChampionSpawn spawn) { _dungeonSpawns.Remove(spawn); - Instance?.OnSlice(_dungeonSpawns, false); + OnSlice(_dungeonSpawns, false); } public static void RemoveSpawn(LLChampionSpawn spawn) { _lostLandsSpawns.Remove(spawn); - Instance?.OnSlice(_lostLandsSpawns, false); + OnSlice(_lostLandsSpawns, false); } public CannedEvilTimer() : base(TimeSpan.Zero, TimeSpan.FromMinutes(1.0))