UnitBuilderBotModule and BaseBuilderBotModule fix on muti-queue performance:
1. Only allow new item being queued when cash above a certain number 2. Only tick one kind of queues at one tick, reduce the pressure on the actived tick 3. 'BaseBuilderBotModule' will check all buildings in producing, avoid queue mutiple same buildings.
This commit is contained in:
@@ -132,6 +132,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
[Desc("When should the AI start building specific buildings.")]
|
[Desc("When should the AI start building specific buildings.")]
|
||||||
public readonly Dictionary<string, int> BuildingDelays = null;
|
public readonly Dictionary<string, int> BuildingDelays = null;
|
||||||
|
|
||||||
|
[Desc("Only queue construction of a new structure when above this requirement.")]
|
||||||
|
public readonly int ProductionMinCashRequirement = 500;
|
||||||
|
|
||||||
public override object Create(ActorInitializer init) { return new BaseBuilderBotModule(init.Self, this); }
|
public override object Create(ActorInitializer init) { return new BaseBuilderBotModule(init.Self, this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +152,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
public CPos DefenseCenter { get; private set; }
|
public CPos DefenseCenter { get; private set; }
|
||||||
|
|
||||||
|
// Actor, ActorCount.
|
||||||
|
public Dictionary<string, int> BuildingsBeingProduced = new();
|
||||||
|
|
||||||
readonly World world;
|
readonly World world;
|
||||||
readonly Player player;
|
readonly Player player;
|
||||||
PowerManager playerPower;
|
PowerManager playerPower;
|
||||||
@@ -156,13 +162,16 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
IResourceLayer resourceLayer;
|
IResourceLayer resourceLayer;
|
||||||
IBotPositionsUpdated[] positionsUpdatedModules;
|
IBotPositionsUpdated[] positionsUpdatedModules;
|
||||||
CPos initialBaseCenter;
|
CPos initialBaseCenter;
|
||||||
readonly List<BaseBuilderQueueManager> builders = new();
|
|
||||||
|
readonly BaseBuilderQueueManager[] builders;
|
||||||
|
int currentBuilderIndex = 0;
|
||||||
|
|
||||||
public BaseBuilderBotModule(Actor self, BaseBuilderBotModuleInfo info)
|
public BaseBuilderBotModule(Actor self, BaseBuilderBotModuleInfo info)
|
||||||
: base(info)
|
: base(info)
|
||||||
{
|
{
|
||||||
world = self.World;
|
world = self.World;
|
||||||
player = self.Owner;
|
player = self.Owner;
|
||||||
|
builders = new BaseBuilderQueueManager[info.BuildingQueues.Count + info.DefenseQueues.Count];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Created(Actor self)
|
protected override void Created(Actor self)
|
||||||
@@ -171,14 +180,14 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
playerResources = self.Owner.PlayerActor.Trait<PlayerResources>();
|
playerResources = self.Owner.PlayerActor.Trait<PlayerResources>();
|
||||||
resourceLayer = self.World.WorldActor.TraitOrDefault<IResourceLayer>();
|
resourceLayer = self.World.WorldActor.TraitOrDefault<IResourceLayer>();
|
||||||
positionsUpdatedModules = self.Owner.PlayerActor.TraitsImplementing<IBotPositionsUpdated>().ToArray();
|
positionsUpdatedModules = self.Owner.PlayerActor.TraitsImplementing<IBotPositionsUpdated>().ToArray();
|
||||||
}
|
|
||||||
|
|
||||||
protected override void TraitEnabled(Actor self)
|
var i = 0;
|
||||||
{
|
|
||||||
foreach (var building in Info.BuildingQueues)
|
foreach (var building in Info.BuildingQueues)
|
||||||
builders.Add(new BaseBuilderQueueManager(this, building, player, playerPower, playerResources, resourceLayer));
|
builders[i++] = new BaseBuilderQueueManager(this, building, player, playerPower, playerResources, resourceLayer);
|
||||||
|
|
||||||
foreach (var defense in Info.DefenseQueues)
|
foreach (var defense in Info.DefenseQueues)
|
||||||
builders.Add(new BaseBuilderQueueManager(this, defense, player, playerPower, playerResources, resourceLayer));
|
builders[i++] = new BaseBuilderQueueManager(this, defense, player, playerPower, playerResources, resourceLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IBotPositionsUpdated.UpdatedBaseCenter(CPos newLocation)
|
void IBotPositionsUpdated.UpdatedBaseCenter(CPos newLocation)
|
||||||
@@ -195,10 +204,49 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
void IBotTick.BotTick(IBot bot)
|
void IBotTick.BotTick(IBot bot)
|
||||||
{
|
{
|
||||||
|
// TODO: this causes pathfinding lag when AI's gets blocked in
|
||||||
SetRallyPointsForNewProductionBuildings(bot);
|
SetRallyPointsForNewProductionBuildings(bot);
|
||||||
|
|
||||||
foreach (var b in builders)
|
BuildingsBeingProduced.Clear();
|
||||||
b.Tick(bot);
|
|
||||||
|
// 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;
|
||||||
|
for (int i = 0, builderIndex = currentBuilderIndex; i < builders.Length; i++)
|
||||||
|
{
|
||||||
|
if (++builderIndex >= builders.Length)
|
||||||
|
builderIndex = 0;
|
||||||
|
|
||||||
|
--builders[builderIndex].WaitTicks;
|
||||||
|
|
||||||
|
var queues = AIUtils.FindQueues(player, builders[builderIndex].Category).ToArray();
|
||||||
|
if (queues.Length != 0)
|
||||||
|
{
|
||||||
|
if (!findQueue)
|
||||||
|
{
|
||||||
|
currentBuilderIndex = builderIndex;
|
||||||
|
findQueue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh "BuildingsBeingProduced" only when AI can produce
|
||||||
|
if (playerResources.GetCashAndResources() >= Info.ProductionMinCashRequirement)
|
||||||
|
{
|
||||||
|
foreach (var queue in queues)
|
||||||
|
{
|
||||||
|
var producing = queue.AllQueued().FirstOrDefault();
|
||||||
|
if (producing == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (BuildingsBeingProduced.TryGetValue(producing.Item, out var number))
|
||||||
|
BuildingsBeingProduced[producing.Item] = number + 1;
|
||||||
|
else
|
||||||
|
BuildingsBeingProduced.Add(producing.Item, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builders[currentBuilderIndex].Tick(bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IBotRespondToAttack.RespondToAttack(IBot bot, Actor self, AttackInfo e)
|
void IBotRespondToAttack.RespondToAttack(IBot bot, Actor self, AttackInfo e)
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
{
|
{
|
||||||
sealed class BaseBuilderQueueManager
|
sealed class BaseBuilderQueueManager
|
||||||
{
|
{
|
||||||
readonly string category;
|
public readonly string Category;
|
||||||
|
public int WaitTicks;
|
||||||
|
|
||||||
readonly BaseBuilderBotModule baseBuilder;
|
readonly BaseBuilderBotModule baseBuilder;
|
||||||
readonly World world;
|
readonly World world;
|
||||||
@@ -27,7 +28,6 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
readonly PlayerResources playerResources;
|
readonly PlayerResources playerResources;
|
||||||
readonly IResourceLayer resourceLayer;
|
readonly IResourceLayer resourceLayer;
|
||||||
|
|
||||||
int waitTicks;
|
|
||||||
Actor[] playerBuildings;
|
Actor[] playerBuildings;
|
||||||
int failCount;
|
int failCount;
|
||||||
int failRetryTicks;
|
int failRetryTicks;
|
||||||
@@ -36,6 +36,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
int cachedBuildings;
|
int cachedBuildings;
|
||||||
int minimumExcessPower;
|
int minimumExcessPower;
|
||||||
|
|
||||||
|
bool itemQueuedThisTick = false;
|
||||||
|
|
||||||
WaterCheck waterState = WaterCheck.NotChecked;
|
WaterCheck waterState = WaterCheck.NotChecked;
|
||||||
|
|
||||||
public BaseBuilderQueueManager(BaseBuilderBotModule baseBuilder, string category, Player p, PowerManager pm,
|
public BaseBuilderQueueManager(BaseBuilderBotModule baseBuilder, string category, Player p, PowerManager pm,
|
||||||
@@ -47,7 +49,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
playerPower = pm;
|
playerPower = pm;
|
||||||
playerResources = pr;
|
playerResources = pr;
|
||||||
resourceLayer = rl;
|
resourceLayer = rl;
|
||||||
this.category = category;
|
Category = category;
|
||||||
failRetryTicks = baseBuilder.Info.StructureProductionResumeDelay;
|
failRetryTicks = baseBuilder.Info.StructureProductionResumeDelay;
|
||||||
minimumExcessPower = baseBuilder.Info.MinimumExcessPower;
|
minimumExcessPower = baseBuilder.Info.MinimumExcessPower;
|
||||||
if (baseBuilder.Info.NavalProductionTypes.Count == 0)
|
if (baseBuilder.Info.NavalProductionTypes.Count == 0)
|
||||||
@@ -94,23 +96,27 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only update once per second or so
|
// Only update once per second or so
|
||||||
if (--waitTicks > 0)
|
if (WaitTicks > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
playerBuildings = world.ActorsHavingTrait<Building>().Where(a => a.Owner == player).ToArray();
|
playerBuildings = world.ActorsHavingTrait<Building>().Where(a => a.Owner == player).ToArray();
|
||||||
var excessPowerBonus = baseBuilder.Info.ExcessPowerIncrement * (playerBuildings.Length / baseBuilder.Info.ExcessPowerIncreaseThreshold.Clamp(1, int.MaxValue));
|
var excessPowerBonus = baseBuilder.Info.ExcessPowerIncrement * (playerBuildings.Length / baseBuilder.Info.ExcessPowerIncreaseThreshold.Clamp(1, int.MaxValue));
|
||||||
minimumExcessPower = (baseBuilder.Info.MinimumExcessPower + excessPowerBonus).Clamp(baseBuilder.Info.MinimumExcessPower, baseBuilder.Info.MaximumExcessPower);
|
minimumExcessPower = (baseBuilder.Info.MinimumExcessPower + excessPowerBonus).Clamp(baseBuilder.Info.MinimumExcessPower, baseBuilder.Info.MaximumExcessPower);
|
||||||
|
|
||||||
|
// PERF: Queue only one actor at a time per category
|
||||||
|
itemQueuedThisTick = false;
|
||||||
var active = false;
|
var active = false;
|
||||||
foreach (var queue in AIUtils.FindQueues(player, category))
|
foreach (var queue in AIUtils.FindQueues(player, Category))
|
||||||
|
{
|
||||||
if (TickQueue(bot, queue))
|
if (TickQueue(bot, queue))
|
||||||
active = true;
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Add a random factor so not every AI produces at the same tick early in the game.
|
// Add a random factor so not every AI produces at the same tick early in the game.
|
||||||
// Minimum should not be negative as delays in HackyAI could be zero.
|
// Minimum should not be negative as delays in HackyAI could be zero.
|
||||||
var randomFactor = world.LocalRandom.Next(0, baseBuilder.Info.StructureProductionRandomBonusDelay);
|
var randomFactor = world.LocalRandom.Next(0, baseBuilder.Info.StructureProductionRandomBonusDelay);
|
||||||
|
|
||||||
waitTicks = active ? baseBuilder.Info.StructureProductionActiveDelay + randomFactor
|
WaitTicks = active ? baseBuilder.Info.StructureProductionActiveDelay + randomFactor
|
||||||
: baseBuilder.Info.StructureProductionInactiveDelay + randomFactor;
|
: baseBuilder.Info.StructureProductionInactiveDelay + randomFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,11 +127,16 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
// Waiting to build something
|
// Waiting to build something
|
||||||
if (currentBuilding == null && failCount < baseBuilder.Info.MaximumFailedPlacementAttempts)
|
if (currentBuilding == null && failCount < baseBuilder.Info.MaximumFailedPlacementAttempts)
|
||||||
{
|
{
|
||||||
|
// PERF: We shouldn't be queueing new units when we're low on cash
|
||||||
|
if (playerResources.GetCashAndResources() < baseBuilder.Info.ProductionMinCashRequirement || itemQueuedThisTick)
|
||||||
|
return false;
|
||||||
|
|
||||||
var item = ChooseBuildingToBuild(queue);
|
var item = ChooseBuildingToBuild(queue);
|
||||||
if (item == null)
|
if (item == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bot.QueueOrder(Order.StartProduction(queue.Actor, item.Name, 1));
|
bot.QueueOrder(Order.StartProduction(queue.Actor, item.Name, 1));
|
||||||
|
itemQueuedThisTick = true;
|
||||||
}
|
}
|
||||||
else if (currentBuilding != null && currentBuilding.Done)
|
else if (currentBuilding != null && currentBuilding.Done)
|
||||||
{
|
{
|
||||||
@@ -335,7 +346,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
var buildingVariantInfo = actorInfo.TraitInfoOrDefault<PlaceBuildingVariantsInfo>();
|
var buildingVariantInfo = actorInfo.TraitInfoOrDefault<PlaceBuildingVariantsInfo>();
|
||||||
var variants = buildingVariantInfo?.Actors ?? Array.Empty<string>();
|
var variants = buildingVariantInfo?.Actors ?? Array.Empty<string>();
|
||||||
|
|
||||||
var count = playerBuildings.Count(a => a.Info.Name == name || variants.Contains(a.Info.Name));
|
var count = playerBuildings.Count(a => a.Info.Name == name || variants.Contains(a.Info.Name)) + (baseBuilder.BuildingsBeingProduced.TryGetValue(name, out var num) ? num : 0);
|
||||||
|
|
||||||
// Do we want to build this structure?
|
// Do we want to build this structure?
|
||||||
if (count * 100 > frac.Value * playerBuildings.Length)
|
if (count * 100 > frac.Value * playerBuildings.Length)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
public readonly int IdleBaseUnitsMaximum = 12;
|
public readonly int IdleBaseUnitsMaximum = 12;
|
||||||
|
|
||||||
[Desc("Production queues AI uses for producing units.")]
|
[Desc("Production queues AI uses for producing units.")]
|
||||||
public readonly HashSet<string> UnitQueues = new() { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" };
|
public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" };
|
||||||
|
|
||||||
[Desc("What units to the AI should build.", "What relative share of the total army must be this type of unit.")]
|
[Desc("What units to the AI should build.", "What relative share of the total army must be this type of unit.")]
|
||||||
public readonly Dictionary<string, int> UnitsToBuild = null;
|
public readonly Dictionary<string, int> UnitsToBuild = null;
|
||||||
@@ -37,6 +37,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
[Desc("When should the AI start train specific units.")]
|
[Desc("When should the AI start train specific units.")]
|
||||||
public readonly Dictionary<string, int> UnitDelays = null;
|
public readonly Dictionary<string, int> UnitDelays = null;
|
||||||
|
|
||||||
|
[Desc("Only queue construction of a new unit when above this requirement.")]
|
||||||
|
public readonly int ProductionMinCashRequirement = 500;
|
||||||
|
|
||||||
public override object Create(ActorInitializer init) { return new UnitBuilderBotModule(init.Self, this); }
|
public override object Create(ActorInitializer init) { return new UnitBuilderBotModule(init.Self, this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +54,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
IBotRequestPauseUnitProduction[] requestPause;
|
IBotRequestPauseUnitProduction[] requestPause;
|
||||||
int idleUnitCount;
|
int idleUnitCount;
|
||||||
|
int currentQueueIndex = 0;
|
||||||
|
PlayerResources playerResources;
|
||||||
|
|
||||||
int ticks;
|
int ticks;
|
||||||
|
|
||||||
@@ -64,6 +69,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
protected override void Created(Actor self)
|
protected override void Created(Actor self)
|
||||||
{
|
{
|
||||||
requestPause = self.Owner.PlayerActor.TraitsImplementing<IBotRequestPauseUnitProduction>().ToArray();
|
requestPause = self.Owner.PlayerActor.TraitsImplementing<IBotRequestPauseUnitProduction>().ToArray();
|
||||||
|
playerResources = self.Owner.PlayerActor.Trait<PlayerResources>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IBotNotifyIdleBaseUnits.UpdatedIdleBaseUnits(List<Actor> idleUnits)
|
void IBotNotifyIdleBaseUnits.UpdatedIdleBaseUnits(List<Actor> idleUnits)
|
||||||
@@ -73,7 +79,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
void IBotTick.BotTick(IBot bot)
|
void IBotTick.BotTick(IBot bot)
|
||||||
{
|
{
|
||||||
if (requestPause.Any(rp => rp.PauseUnitProduction))
|
// PERF: We shouldn't be queueing new units when we're low on cash
|
||||||
|
if (playerResources.GetCashAndResources() < Info.ProductionMinCashRequirement || requestPause.Any(rp => rp.PauseUnitProduction))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ticks++;
|
ticks++;
|
||||||
@@ -87,8 +94,19 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
queuedBuildRequests.Remove(buildRequest);
|
queuedBuildRequests.Remove(buildRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var q in Info.UnitQueues)
|
for (var i = 0; i < Info.UnitQueues.Length; i++)
|
||||||
BuildUnit(bot, q, idleUnitCount < Info.IdleBaseUnitsMaximum);
|
{
|
||||||
|
if (++currentQueueIndex >= Info.UnitQueues.Length)
|
||||||
|
currentQueueIndex = 0;
|
||||||
|
|
||||||
|
if (AIUtils.FindQueues(player, Info.UnitQueues[currentQueueIndex]).Any())
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
BuildUnit(bot, Info.UnitQueues[currentQueueIndex], idleUnitCount < Info.IdleBaseUnitsMaximum);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user