From 942ce6b5a67da06000999c0df05852edeefe0250 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Wed, 22 Jul 2015 23:06:12 +0100 Subject: [PATCH] Throttle the issuing of orders by the AI. To prevent the AI generating lag spikes by issuing too many orders in a single tick, it is now limited in how many orders can be issued per tick. Extra orders are queued. This allows the performance hit of issuing multiple orders to be spread out over several ticks. --- OpenRA.Mods.Common/AI/BaseBuilder.cs | 6 ++-- OpenRA.Mods.Common/AI/HackyAI.cs | 36 +++++++++++++------ OpenRA.Mods.Common/AI/States/AirStates.cs | 8 ++--- OpenRA.Mods.Common/AI/States/GroundStates.cs | 10 +++--- .../AI/States/ProtectionStates.cs | 2 +- OpenRA.Mods.Common/AI/States/StateBase.cs | 2 +- 6 files changed, 39 insertions(+), 25 deletions(-) diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index 8b5d29477f..e70fa4656e 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.AI return false; HackyAI.BotDebug("AI: {0} is starting production of {1}".F(player, item.Name)); - world.IssueOrder(Order.StartProduction(queue.Actor, item.Name, 1)); + ai.QueueOrder(Order.StartProduction(queue.Actor, item.Name, 1)); } else if (currentBuilding.Done) { @@ -87,11 +87,11 @@ namespace OpenRA.Mods.Common.AI if (location == null) { HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); - world.IssueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); + ai.QueueOrder(Order.CancelProduction(queue.Actor, currentBuilding.Item, 1)); } else { - world.IssueOrder(new Order("PlaceBuilding", player.PlayerActor, false) + ai.QueueOrder(new Order("PlaceBuilding", player.PlayerActor, false) { TargetLocation = location.Value, TargetString = currentBuilding.Item, diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index d58cb04d19..82485786bc 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -58,6 +58,9 @@ namespace OpenRA.Mods.Common.AI [Desc("How long to wait (in ticks) between structure production checks ticks when actively building things.")] public readonly int StructureProductionActiveDelay = 10; + [Desc("Minimum portion of pending orders to issue each tick (e.g. 5 issues at least 1/5th of all pending orders). Excess orders remain queued for subsequent ticks.")] + public readonly int MinOrderQuotientPerTick = 5; + [Desc("Minimum range at which to build defensive structures near a combat hotspot.")] public readonly int MinimumDefenseRadius = 5; @@ -189,7 +192,7 @@ namespace OpenRA.Mods.Common.AI // Units that the ai already knows about. Any unit not on this list needs to be given a role. List activeUnits = new List(); - public const int FeedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag. + public const int FeedbackTime = 30; // ticks; = a bit over 1s. must be >= netlag. public readonly World World; public Map Map { get { return World.Map; } } @@ -200,6 +203,8 @@ namespace OpenRA.Mods.Common.AI int attackForceTicks; int minAttackForceDelayTicks; + readonly Queue orders = new Queue(); + public HackyAI(HackyAIInfo info, ActorInitializer init) { Info = info; @@ -245,6 +250,11 @@ namespace OpenRA.Mods.Common.AI resourceTypeIndices.Set(World.TileSet.GetTerrainIndex(t.TerrainType), true); } + public void QueueOrder(Order order) + { + orders.Enqueue(order); + } + ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue) { var buildableThings = queue.BuildableItems(); @@ -449,6 +459,10 @@ namespace OpenRA.Mods.Common.AI foreach (var b in builders) b.Tick(); + + var ordersToIssueThisTick = Math.Min((orders.Count + Info.MinOrderQuotientPerTick - 1) / Info.MinOrderQuotientPerTick, orders.Count); + for (var i = 0; i < ordersToIssueThisTick; i++) + World.IssueOrder(orders.Dequeue()); } internal Actor ChooseEnemyTarget() @@ -592,7 +606,7 @@ namespace OpenRA.Mods.Common.AI continue; // Tell the idle harvester to quit slacking: - World.IssueOrder(new Order("Harvest", a, false)); + QueueOrder(new Order("Harvest", a, false)); } } @@ -606,7 +620,7 @@ namespace OpenRA.Mods.Common.AI foreach (var a in newUnits) { if (a.HasTrait()) - World.IssueOrder(new Order("Harvest", a, false)); + QueueOrder(new Order("Harvest", a, false)); else unitsHangingAroundTheBase.Add(a); @@ -702,7 +716,7 @@ namespace OpenRA.Mods.Common.AI !IsRallyPointValid(rp.Trait.Location, rp.Actor.Info.Traits.GetOrDefault())).ToArray(); foreach (var a in buildings) - World.IssueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor), SuppressVisualFeedback = true }); + QueueOrder(new Order("SetRallyPoint", a.Actor, false) { TargetLocation = ChooseRallyLocationNear(a.Actor), SuppressVisualFeedback = true }); } // Won't work for shipyards... @@ -734,7 +748,7 @@ namespace OpenRA.Mods.Common.AI // Don't transform the mcv if it is a fact // HACK: This needs to query against MCVs directly if (mcv.HasTrait()) - World.IssueOrder(new Order("DeployTransform", mcv, false)); + QueueOrder(new Order("DeployTransform", mcv, false)); } else BotDebug("AI: Can't find BaseBuildUnit."); @@ -759,8 +773,8 @@ namespace OpenRA.Mods.Common.AI if (desiredLocation == null) continue; - World.IssueOrder(new Order("Move", mcv, true) { TargetLocation = desiredLocation.Value }); - World.IssueOrder(new Order("DeployTransform", mcv, true)); + QueueOrder(new Order("Move", mcv, true) { TargetLocation = desiredLocation.Value }); + QueueOrder(new Order("DeployTransform", mcv, true)); } } @@ -814,7 +828,7 @@ namespace OpenRA.Mods.Common.AI // Valid target found, delay by a few ticks to avoid rescanning before power fires via order BotDebug("AI: {2} found new target location {0} for support power {1}.", attackLocation, sp.Info.OrderName, Player.PlayerName); waitingPowers[sp] += 10; - World.IssueOrder(new Order(sp.Info.OrderName, supportPowerMngr.Self, false) { TargetLocation = attackLocation.Value, SuppressVisualFeedback = true }); + QueueOrder(new Order(sp.Info.OrderName, supportPowerMngr.Self, false) { TargetLocation = attackLocation.Value, SuppressVisualFeedback = true }); } } } @@ -923,7 +937,7 @@ namespace OpenRA.Mods.Common.AI ChooseUnitToBuild(queue); if (unit != null && Info.UnitsToBuild.Any(u => u.Key == unit.Name)) - World.IssueOrder(Order.StartProduction(queue.Actor, unit.Name, 1)); + QueueOrder(Order.StartProduction(queue.Actor, unit.Name, 1)); } void BuildUnit(string category, string name) @@ -933,7 +947,7 @@ namespace OpenRA.Mods.Common.AI return; if (Map.Rules.Actors[name] != null) - World.IssueOrder(Order.StartProduction(queue.Actor, name, 1)); + QueueOrder(Order.StartProduction(queue.Actor, name, 1)); } public void Damaged(Actor self, AttackInfo e) @@ -952,7 +966,7 @@ namespace OpenRA.Mods.Common.AI { BotDebug("Bot noticed damage {0} {1}->{2}, repairing.", self, e.PreviousDamageState, e.DamageState); - World.IssueOrder(new Order("RepairBuilding", self.Owner.PlayerActor, false) { TargetActor = self }); + QueueOrder(new Order("RepairBuilding", self.Owner.PlayerActor, false) { TargetActor = self }); } } diff --git a/OpenRA.Mods.Common/AI/States/AirStates.cs b/OpenRA.Mods.Common/AI/States/AirStates.cs index 62432e90ed..9cb08991bc 100644 --- a/OpenRA.Mods.Common/AI/States/AirStates.cs +++ b/OpenRA.Mods.Common/AI/States/AirStates.cs @@ -215,7 +215,7 @@ namespace OpenRA.Mods.Common.AI { if (IsRearm(a)) continue; - owner.World.IssueOrder(new Order("ReturnToBase", a, false)); + owner.Bot.QueueOrder(new Order("ReturnToBase", a, false)); continue; } @@ -224,7 +224,7 @@ namespace OpenRA.Mods.Common.AI } if (owner.TargetActor.HasTrait() && CanAttackTarget(a, owner.TargetActor)) - owner.World.IssueOrder(new Order("Attack", a, false) { TargetActor = owner.TargetActor }); + owner.Bot.QueueOrder(new Order("Attack", a, false) { TargetActor = owner.TargetActor }); } } @@ -247,11 +247,11 @@ namespace OpenRA.Mods.Common.AI if (IsRearm(a)) continue; - owner.World.IssueOrder(new Order("ReturnToBase", a, false)); + owner.Bot.QueueOrder(new Order("ReturnToBase", a, false)); continue; } - owner.World.IssueOrder(new Order("Move", a, false) { TargetLocation = RandomBuildingLocation(owner) }); + owner.Bot.QueueOrder(new Order("Move", a, false) { TargetLocation = RandomBuildingLocation(owner) }); } owner.FuzzyStateMachine.ChangeState(owner, new AirIdleState(), true); diff --git a/OpenRA.Mods.Common/AI/States/GroundStates.cs b/OpenRA.Mods.Common/AI/States/GroundStates.cs index ea8fc18297..fdb022a56f 100644 --- a/OpenRA.Mods.Common/AI/States/GroundStates.cs +++ b/OpenRA.Mods.Common/AI/States/GroundStates.cs @@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.AI if (owner.AttackOrFleeFuzzy.CanAttack(owner.Units, enemyUnits)) { foreach (var u in owner.Units) - owner.World.IssueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.TargetActor.Location }); + owner.Bot.QueueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.TargetActor.Location }); // We have gathered sufficient units. Attack the nearest enemy unit. owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackMoveState(), true); @@ -87,9 +87,9 @@ namespace OpenRA.Mods.Common.AI .Where(a => a.Owner == owner.Units.FirstOrDefault().Owner && owner.Units.Contains(a)).ToHashSet(); if (ownUnits.Count < owner.Units.Count) { - owner.World.IssueOrder(new Order("Stop", leader, false)); + owner.Bot.QueueOrder(new Order("Stop", leader, false)); foreach (var unit in owner.Units.Where(a => !ownUnits.Contains(a))) - owner.World.IssueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.Location }); + owner.Bot.QueueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.Location }); } else { @@ -105,7 +105,7 @@ namespace OpenRA.Mods.Common.AI } else foreach (var a in owner.Units) - owner.World.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); + owner.Bot.QueueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); } if (ShouldFlee(owner)) @@ -141,7 +141,7 @@ namespace OpenRA.Mods.Common.AI foreach (var a in owner.Units) if (!BusyAttack(a)) - owner.World.IssueOrder(new Order("Attack", a, false) { TargetActor = owner.Bot.FindClosestEnemy(a.CenterPosition) }); + owner.Bot.QueueOrder(new Order("Attack", a, false) { TargetActor = owner.Bot.FindClosestEnemy(a.CenterPosition) }); if (ShouldFlee(owner)) { diff --git a/OpenRA.Mods.Common/AI/States/ProtectionStates.cs b/OpenRA.Mods.Common/AI/States/ProtectionStates.cs index cf1a99035c..22423e62d6 100644 --- a/OpenRA.Mods.Common/AI/States/ProtectionStates.cs +++ b/OpenRA.Mods.Common/AI/States/ProtectionStates.cs @@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.AI else { foreach (var a in owner.Units) - owner.World.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); + owner.Bot.QueueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); } } diff --git a/OpenRA.Mods.Common/AI/States/StateBase.cs b/OpenRA.Mods.Common/AI/States/StateBase.cs index 868f9a1727..7c898db728 100644 --- a/OpenRA.Mods.Common/AI/States/StateBase.cs +++ b/OpenRA.Mods.Common/AI/States/StateBase.cs @@ -25,7 +25,7 @@ namespace OpenRA.Mods.Common.AI { var loc = RandomBuildingLocation(squad); foreach (var a in squad.Units) - squad.World.IssueOrder(new Order("Move", a, false) { TargetLocation = loc }); + squad.Bot.QueueOrder(new Order("Move", a, false) { TargetLocation = loc }); } protected static CPos RandomBuildingLocation(Squad squad)