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;
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,

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.")]
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<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 Map Map { get { return World.Map; } }
@@ -200,6 +203,8 @@ namespace OpenRA.Mods.Common.AI
int attackForceTicks;
int minAttackForceDelayTicks;
readonly Queue<Order> orders = new Queue<Order>();
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<Harvester>())
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<BuildingInfo>())).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<Mobile>())
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 });
}
}

View File

@@ -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<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))
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);

View File

@@ -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))
{

View File

@@ -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 });
}
}

View File

@@ -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)