From c45e78cf1d5fd1d164c8d1bb750c14a1d82e31e1 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 5 Jul 2024 19:42:22 +0100 Subject: [PATCH] Improve performance of AIUtils.FindQueues The AI would often invoke this method inside of loops, searching for a different category of queue each time. This would result in multiple searches against the trait dictionary to locate matching queues. Now we alter the method to create a lookup of all the queues keyed by category. This allows a single trait search to be performed. UnitBuilderBotModule and BaseBuilderBotModule are updated to fetch this lookup once when required, and pass the results along to avoid calling the method more times than necessary. This improves their performance. --- OpenRA.Mods.Common/AIUtils.cs | 7 ++++--- .../Traits/BotModules/BaseBuilderBotModule.cs | 5 +++-- .../BotModuleLogic/BaseBuilderQueueManager.cs | 4 ++-- .../Traits/BotModules/UnitBuilderBotModule.cs | 20 ++++++++++++------- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/OpenRA.Mods.Common/AIUtils.cs b/OpenRA.Mods.Common/AIUtils.cs index ea9f0efaa6..e9635ba526 100644 --- a/OpenRA.Mods.Common/AIUtils.cs +++ b/OpenRA.Mods.Common/AIUtils.cs @@ -34,11 +34,12 @@ namespace OpenRA.Mods.Common .Any(availableCells => availableCells > 0); } - public static IEnumerable FindQueues(Player player, string category) + public static ILookup FindQueuesByCategory(Player player) { return player.World.ActorsWithTrait() - .Where(a => a.Actor.Owner == player && a.Trait.Info.Type == category && a.Trait.Enabled) - .Select(a => a.Trait); + .Where(a => a.Actor.Owner == player && a.Trait.Enabled) + .Select(a => a.Trait) + .ToLookup(pq => pq.Info.Type); } public static int CountActorsWithNameAndTrait(string actorName, Player owner) diff --git a/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs index e4a9b35774..e785faabd4 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs @@ -220,6 +220,7 @@ namespace OpenRA.Mods.Common.Traits // PERF: We tick only one type of valid queue at a time // if AI gets enough cash, it can fill all of its queues with enough ticks var findQueue = false; + var queuesByCategory = AIUtils.FindQueuesByCategory(player); for (int i = 0, builderIndex = currentBuilderIndex; i < builders.Length; i++) { if (++builderIndex >= builders.Length) @@ -227,7 +228,7 @@ namespace OpenRA.Mods.Common.Traits --builders[builderIndex].WaitTicks; - var queues = AIUtils.FindQueues(player, builders[builderIndex].Category).ToArray(); + var queues = queuesByCategory[builders[builderIndex].Category].ToArray(); if (queues.Length != 0) { if (!findQueue) @@ -254,7 +255,7 @@ namespace OpenRA.Mods.Common.Traits } } - builders[currentBuilderIndex].Tick(bot); + builders[currentBuilderIndex].Tick(bot, queuesByCategory); } void IBotRespondToAttack.RespondToAttack(IBot bot, Actor self, AttackInfo e) diff --git a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs index 5f71b25c3e..9f207fcc5e 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs @@ -56,7 +56,7 @@ namespace OpenRA.Mods.Common.Traits waterState = WaterCheck.DontCheck; } - public void Tick(IBot bot) + public void Tick(IBot bot, ILookup queuesByCategory) { // If failed to place something N consecutive times, wait M ticks until resuming building production if (failCount >= baseBuilder.Info.MaximumFailedPlacementAttempts && --failRetryTicks <= 0) @@ -106,7 +106,7 @@ namespace OpenRA.Mods.Common.Traits // PERF: Queue only one actor at a time per category itemQueuedThisTick = false; var active = false; - foreach (var queue in AIUtils.FindQueues(player, Category)) + foreach (var queue in queuesByCategory[Category]) { if (TickQueue(bot, queue)) active = true; diff --git a/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs index 37e2318489..0b8ea8f034 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs @@ -91,25 +91,31 @@ namespace OpenRA.Mods.Common.Traits if (ticks % FeedbackTime == 0) { + ILookup queuesByCategory = null; + var buildRequest = queuedBuildRequests.FirstOrDefault(); if (buildRequest != null) { - BuildUnit(bot, buildRequest); + queuesByCategory ??= AIUtils.FindQueuesByCategory(player); + BuildUnit(bot, buildRequest, queuesByCategory); queuedBuildRequests.Remove(buildRequest); } if (Info.IdleBaseUnitsMaximum <= 0 || Info.IdleBaseUnitsMaximum > idleUnitCount) { + queuesByCategory ??= AIUtils.FindQueuesByCategory(player); for (var i = 0; i < Info.UnitQueues.Length; i++) { if (++currentQueueIndex >= Info.UnitQueues.Length) currentQueueIndex = 0; - if (AIUtils.FindQueues(player, Info.UnitQueues[currentQueueIndex]).Any()) + var category = Info.UnitQueues[currentQueueIndex]; + var queues = queuesByCategory[category].ToArray(); + if (queues.Length != 0) { // PERF: We tick only one type of valid queue at a time // if AI gets enough cash, it can fill all of its queues with enough ticks - BuildRandomUnit(bot, Info.UnitQueues[currentQueueIndex]); + BuildRandomUnit(bot, queues); break; } } @@ -127,13 +133,13 @@ namespace OpenRA.Mods.Common.Traits return queuedBuildRequests.Count(r => r == requestedActor); } - void BuildRandomUnit(IBot bot, string category) + void BuildRandomUnit(IBot bot, ProductionQueue[] queues) { if (Info.UnitsToBuild.Count == 0) return; // Pick a free queue - var queue = AIUtils.FindQueues(player, category).FirstOrDefault(q => !q.AllQueued().Any()); + var queue = queues.FirstOrDefault(q => !q.AllQueued().Any()); if (queue == null) return; @@ -146,7 +152,7 @@ namespace OpenRA.Mods.Common.Traits } // In cases where we want to build a specific unit but don't know the queue name (because there's more than one possibility) - void BuildUnit(IBot bot, string name) + void BuildUnit(IBot bot, string name, ILookup queuesByCategory) { var actorInfo = world.Map.Rules.Actors[name]; if (actorInfo == null) @@ -159,7 +165,7 @@ namespace OpenRA.Mods.Common.Traits ProductionQueue queue = null; foreach (var pq in buildableInfo.Queue) { - queue = AIUtils.FindQueues(player, pq).FirstOrDefault(q => !q.AllQueued().Any()); + queue = queuesByCategory[pq].FirstOrDefault(q => !q.AllQueued().Any()); if (queue != null) break; }