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.
This commit is contained in:
RoosterDragon
2015-07-22 23:06:12 +01:00
parent 6b9dd9670a
commit 942ce6b5a6
6 changed files with 39 additions and 25 deletions

View File

@@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.AI
return false; return false;
HackyAI.BotDebug("AI: {0} is starting production of {1}".F(player, item.Name)); 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) else if (currentBuilding.Done)
{ {
@@ -87,11 +87,11 @@ namespace OpenRA.Mods.Common.AI
if (location == null) if (location == null)
{ {
HackyAI.BotDebug("AI: {0} has nowhere to place {1}".F(player, currentBuilding.Item)); 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 else
{ {
world.IssueOrder(new Order("PlaceBuilding", player.PlayerActor, false) ai.QueueOrder(new Order("PlaceBuilding", player.PlayerActor, false)
{ {
TargetLocation = location.Value, TargetLocation = location.Value,
TargetString = currentBuilding.Item, TargetString = currentBuilding.Item,

View File

@@ -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.")] [Desc("How long to wait (in ticks) between structure production checks ticks when actively building things.")]
public readonly int StructureProductionActiveDelay = 10; 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.")] [Desc("Minimum range at which to build defensive structures near a combat hotspot.")]
public readonly int MinimumDefenseRadius = 5; 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. // Units that the ai already knows about. Any unit not on this list needs to be given a role.
List<Actor> activeUnits = new List<Actor>(); List<Actor> activeUnits = new List<Actor>();
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 readonly World World;
public Map Map { get { return World.Map; } } public Map Map { get { return World.Map; } }
@@ -200,6 +203,8 @@ namespace OpenRA.Mods.Common.AI
int attackForceTicks; int attackForceTicks;
int minAttackForceDelayTicks; int minAttackForceDelayTicks;
readonly Queue<Order> orders = new Queue<Order>();
public HackyAI(HackyAIInfo info, ActorInitializer init) public HackyAI(HackyAIInfo info, ActorInitializer init)
{ {
Info = info; Info = info;
@@ -245,6 +250,11 @@ namespace OpenRA.Mods.Common.AI
resourceTypeIndices.Set(World.TileSet.GetTerrainIndex(t.TerrainType), true); resourceTypeIndices.Set(World.TileSet.GetTerrainIndex(t.TerrainType), true);
} }
public void QueueOrder(Order order)
{
orders.Enqueue(order);
}
ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue) ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue)
{ {
var buildableThings = queue.BuildableItems(); var buildableThings = queue.BuildableItems();
@@ -449,6 +459,10 @@ namespace OpenRA.Mods.Common.AI
foreach (var b in builders) foreach (var b in builders)
b.Tick(); 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() internal Actor ChooseEnemyTarget()
@@ -592,7 +606,7 @@ namespace OpenRA.Mods.Common.AI
continue; continue;
// Tell the idle harvester to quit slacking: // 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) foreach (var a in newUnits)
{ {
if (a.HasTrait<Harvester>()) if (a.HasTrait<Harvester>())
World.IssueOrder(new Order("Harvest", a, false)); QueueOrder(new Order("Harvest", a, false));
else else
unitsHangingAroundTheBase.Add(a); unitsHangingAroundTheBase.Add(a);
@@ -702,7 +716,7 @@ namespace OpenRA.Mods.Common.AI
!IsRallyPointValid(rp.Trait.Location, rp.Actor.Info.Traits.GetOrDefault<BuildingInfo>())).ToArray(); !IsRallyPointValid(rp.Trait.Location, rp.Actor.Info.Traits.GetOrDefault<BuildingInfo>())).ToArray();
foreach (var a in buildings) 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... // Won't work for shipyards...
@@ -734,7 +748,7 @@ namespace OpenRA.Mods.Common.AI
// Don't transform the mcv if it is a fact // Don't transform the mcv if it is a fact
// HACK: This needs to query against MCVs directly // HACK: This needs to query against MCVs directly
if (mcv.HasTrait<Mobile>()) if (mcv.HasTrait<Mobile>())
World.IssueOrder(new Order("DeployTransform", mcv, false)); QueueOrder(new Order("DeployTransform", mcv, false));
} }
else else
BotDebug("AI: Can't find BaseBuildUnit."); BotDebug("AI: Can't find BaseBuildUnit.");
@@ -759,8 +773,8 @@ namespace OpenRA.Mods.Common.AI
if (desiredLocation == null) if (desiredLocation == null)
continue; continue;
World.IssueOrder(new Order("Move", mcv, true) { TargetLocation = desiredLocation.Value }); QueueOrder(new Order("Move", mcv, true) { TargetLocation = desiredLocation.Value });
World.IssueOrder(new Order("DeployTransform", mcv, true)); 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 // 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); BotDebug("AI: {2} found new target location {0} for support power {1}.", attackLocation, sp.Info.OrderName, Player.PlayerName);
waitingPowers[sp] += 10; 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); ChooseUnitToBuild(queue);
if (unit != null && Info.UnitsToBuild.Any(u => u.Key == unit.Name)) 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) void BuildUnit(string category, string name)
@@ -933,7 +947,7 @@ namespace OpenRA.Mods.Common.AI
return; return;
if (Map.Rules.Actors[name] != null) 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) public void Damaged(Actor self, AttackInfo e)
@@ -952,7 +966,7 @@ namespace OpenRA.Mods.Common.AI
{ {
BotDebug("Bot noticed damage {0} {1}->{2}, repairing.", BotDebug("Bot noticed damage {0} {1}->{2}, repairing.",
self, e.PreviousDamageState, e.DamageState); 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 });
} }
} }

View File

@@ -215,7 +215,7 @@ namespace OpenRA.Mods.Common.AI
{ {
if (IsRearm(a)) if (IsRearm(a))
continue; continue;
owner.World.IssueOrder(new Order("ReturnToBase", a, false)); owner.Bot.QueueOrder(new Order("ReturnToBase", a, false));
continue; continue;
} }
@@ -224,7 +224,7 @@ namespace OpenRA.Mods.Common.AI
} }
if (owner.TargetActor.HasTrait<ITargetable>() && CanAttackTarget(a, owner.TargetActor)) if (owner.TargetActor.HasTrait<ITargetable>() && 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)) if (IsRearm(a))
continue; continue;
owner.World.IssueOrder(new Order("ReturnToBase", a, false)); owner.Bot.QueueOrder(new Order("ReturnToBase", a, false));
continue; 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); owner.FuzzyStateMachine.ChangeState(owner, new AirIdleState(), true);

View File

@@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.AI
if (owner.AttackOrFleeFuzzy.CanAttack(owner.Units, enemyUnits)) if (owner.AttackOrFleeFuzzy.CanAttack(owner.Units, enemyUnits))
{ {
foreach (var u in owner.Units) 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. // We have gathered sufficient units. Attack the nearest enemy unit.
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackMoveState(), true); 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(); .Where(a => a.Owner == owner.Units.FirstOrDefault().Owner && owner.Units.Contains(a)).ToHashSet();
if (ownUnits.Count < owner.Units.Count) 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))) 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 else
{ {
@@ -105,7 +105,7 @@ namespace OpenRA.Mods.Common.AI
} }
else else
foreach (var a in owner.Units) 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)) if (ShouldFlee(owner))
@@ -141,7 +141,7 @@ namespace OpenRA.Mods.Common.AI
foreach (var a in owner.Units) foreach (var a in owner.Units)
if (!BusyAttack(a)) 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)) if (ShouldFlee(owner))
{ {

View File

@@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.AI
else else
{ {
foreach (var a in owner.Units) 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 });
} }
} }

View File

@@ -25,7 +25,7 @@ namespace OpenRA.Mods.Common.AI
{ {
var loc = RandomBuildingLocation(squad); var loc = RandomBuildingLocation(squad);
foreach (var a in squad.Units) 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) protected static CPos RandomBuildingLocation(Squad squad)