Skip to content

Reduce lag spikes from HarvesterBotModule.#21471

Merged
PunkPun merged 3 commits into
OpenRA:bleedfrom
RoosterDragon:ai-harv-perf
Aug 7, 2024
Merged

Reduce lag spikes from HarvesterBotModule.#21471
PunkPun merged 3 commits into
OpenRA:bleedfrom
RoosterDragon:ai-harv-perf

Conversation

@RoosterDragon
Copy link
Copy Markdown
Member

@RoosterDragon RoosterDragon commented Jul 3, 2024

Reduce lag spikes from HarvesterBotModule.

The AI uses HarvesterBotModule to check for idle harvesters, and give them harvest orders. By default it scans every 50 ticks (2 seconds at normal speed), and for any idle harvesters locates an ore patch and issues a harvest order. This FindNextResource to scan for a suitable ore path is quite expensive. If the AI has to scan for ore patches for several harvesters, then this can produce a noticeable lag spike. Additionally, when there are no available ore patches, the scan will just keep repeating since the harvesters will always be idle - thus the lag spikes repeat every 50 ticks.

To reduce the impact, there already exists a randomization on the first scan interval so that multiple different AIs scan on different ticks. By ensuring the AI players scan at different times, we avoid a huge lag spike where they all operate on the same tick.

To reduce the impact even more, we make four additional changes:

  • Scans continue to be done every 50 ticks to detect harvesters. But we spread out the searches for ore patches over multiple later ticks. We'll only perform one ore patch search per tick. This means instead of ordering e.g. 30 harvesters on a single tick and creating a spike, we order one on each tick over the next 30 ticks instead. This spreads out the performance impact.
  • When a harvester fails to locate any suitable ore patch, we put it on a longer cooldown, by default 5x the regular cooldown. We don't need to scan as often for these harvesters, since it'll take time for new resources to appear.
  • We change the path search in FindNextResource from FindPathToTargetCellByPredicate to FindPathToTargetCells. The format in an undirected path search that must flood fill from the start location. If ore is on the other side of the map, this entails searching the whole map which is very expensive. By maintaining a lookup of resource types per cell, we can instead give the target locations directly to the path search. This lookup requires a small overhead to maintain, but allows for a far more efficient path search to be carried out. The search can be directed towards the target locations, and the hierarchical path finder can be employed resulting in a path search that explores far fewer cells. A few tweaks are made to ResourceClaimLayer to avoid it creating empty list entries when this can be avoided.
  • We adjust how the enemy avoidance cost is done. Previously, this search used world.FindActorsInCircle to check for nearby enemies, but this check was done for every cell that was searched, and is itself quite expensive. Now, we create a series of "bins" and cache the additional cost for that bin. This is a less fine grained approach but is sufficient for our intended goal of "avoid resource patches with too many enemies nearby". The customCost function is now less expensive so we can reuse the avoidance cost stored for each bin, rather than calculating fresh for every cell.

Fixes #18910. Fixes #16163 (no longer required now the perf is fixed)


Test map:

ai-harv-perf-test.zip

Spawn an AI in spawn A.

Bleed: A huge lag spike occurs when the map is started, and the AI orders the harvesters all at once.

image

PR (before improvement): A small impact persists for several ticks on the performance graph when the map starts, but no lag spike.

image

PR (current): The impact of bot_tick is now almost entirly removed.

image

The test map also shows harvesters avoiding the ore patch with many enemies, but some are willing to go to an area with light enemy presence rather than going too far.

@BryanQuigley
Copy link
Copy Markdown
Contributor

Quick review, LGTM although a careful observer would be able to tell if an ore patch has enemies on it.

@RoosterDragon RoosterDragon force-pushed the ai-harv-perf branch 2 times, most recently from 6fa0cff to 1145bd3 Compare July 5, 2024 18:01
@dnqbob
Copy link
Copy Markdown
Contributor

dnqbob commented Jul 6, 2024

I think this workaround cannot fix the key problem of HarvesterBotModule:
scan resource tile and check reachability -> scan enemy nearby on each resource tile.

On the worst case, it will go through the entire map to find a place to harvest.

@RoosterDragon
Copy link
Copy Markdown
Member Author

I think this workaround cannot fix the key problem of HarvesterBotModule: scan resource tile and check reachability -> scan enemy nearby on each resource tile.

On the worst case, it will go through the entire map to find a place to harvest.

You're right. I've pushed a commit which uses a proper path search instead. This improves the situation even further - and revealed a bonus pathfinding bug which I've fixed in order to make it work.

Using the same test map, the perf graph is now even less affected by the bot_tick timing.

image

Comment thread OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs
@dnqbob

This comment was marked as resolved.

@anvilvapre
Copy link
Copy Markdown
Contributor

With shroud disabled - or when explored - would a harvester drive all the way across the map to possibly harvest ore next to an enemy harvester? Just curious.

@anvilvapre
Copy link
Copy Markdown
Contributor

Could not lead to starvation of harvester A because B each time find the ore earlier? What if A and B belong to different teams.

@RoosterDragon
Copy link
Copy Markdown
Member Author

Yes, currently the logic puts no limit on the distance. So if there's no nearby ore patches, it is allowed to drive across the map to find some. It prefers ore patches without enemies, but that's only a preference. So if there's only one ore patch available and it has enemies nearby, it will still attempt to go there.

But primarily it wants a nearby ore patch, so it won't go driving across the map until it's used up the nearby ones.

Copy link
Copy Markdown
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI harvester's never respected the rule that they should choose ore that's closer to the refinery. As this is changing the heuteristic could that be addressed as well?

On bleed this logic seems to be broken even for non-ai players though. Dunno when it regressed

@RoosterDragon
Copy link
Copy Markdown
Member Author

This AI logic scans from the havester location, rather than the refinery. But you mention this being broken for players too? That sounds like it's something different. This system is for AI players only, to notice idle harvesters and get them doing something. It's not related to the automated things a harvester for both a human and AI player would do as it carries out orders.

Could you link any relevant tickets? I can't tell whether this is something we should tweak in this PR or come back to separately.

@anvilvapre
Copy link
Copy Markdown
Contributor

PunkPun may also refer to what i described in #20058, for players yes.

@RoosterDragon
Copy link
Copy Markdown
Member Author

I've pushed an additional commit that changes the behaviour, for AI players only, to locate ore closest to a refinery, rather than the harvester's location.

@anvilvapre
Copy link
Copy Markdown
Contributor

Isn't it odd that harvesters of AI players would use other logic than of normal players to find ore? Couldn't it be better fixed in a central location/one-level-up? A normal player also doesn't often direct a harvester? Not sure.

@RoosterDragon
Copy link
Copy Markdown
Member Author

The behaviour of this bot module is only for idle harvesters of the AI player, it doesn't affect the automated decisions made by the harvester itself.

So if a human players sees an idle harvester, they will need to order it after some ore themselves. This bot module is doing the equivalent for an AI player.

Once the harvester is harvesting, this bot module is hands off after that, until they go idle again.

Copy link
Copy Markdown
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran this test AI game and it seems like anecdotally harvesters seem to never repath. There's no attempts to harvest next to the refinery or away from danger

harvs.orarep.zip

@RoosterDragon
Copy link
Copy Markdown
Member Author

Again, this logic only applies to idle harvesters - once they're already harvesting this bot module does not touch them. That's the existing behaviour.

My scope for the PR was to fix the performance of the existing logic. If you want different logic, please do raise a separate issue for that. I wish to keep any fundamental behaviour changes out of scope for this PR.

@PunkPun
Copy link
Copy Markdown
Member

PunkPun commented Jul 29, 2024

hmm, that makes it very hard to test. Harvesters almost never idle

@RoosterDragon
Copy link
Copy Markdown
Member Author

The PR description has a test map.

Comment thread OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs Outdated
Comment thread OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs Outdated
When this hits the case that "As both ends are accessible, we can freely swap them." - we must note that we are reversing the path search and pass the information into the HierarchicalPathFinder. When a normal path search occurs, the actor trying to pathfind will never check its own location - and thus never gets blocked by itself. When a search is reversed, the search will check the actors location. If we inform the search it is doing done in reverse, it will special case this scenario and avoid the actor blocking itself. But if it is not told about this scenario, then this special case is not applied and no path will be found when in fact a path is possible.
The AI uses HarvesterBotModule to check for idle harvesters, and give them harvest orders. By default it scans every 50 ticks (2 seconds at normal speed), and for any idle harvesters locates an ore patch and issues a harvest order. This FindNextResource to scan for a suitable ore path is quite expensive. If the AI has to scan for ore patches for several harvesters, then this can produce a noticeable lag spike. Additionally, when there are no available ore patches, the scan will just keep repeating since the harvesters will always be idle - thus the lag spikes repeat every 50 ticks.

To reduce the impact, there already exists a randomization on the first scan interval so that multiple different AIs scan on different ticks. By ensuring the AI players scan at different times, we avoid a huge lag spike where they all operate on the same tick.

To reduce the impact even more, we make four additional changes:
- Scans continue to be done every 50 ticks to detect harvesters. But we spread out the searches for ore patches over multiple later ticks. We'll only perform one ore patch search per tick. This means instead of ordering e.g. 30 harvesters on a single tick and creating a spike, we order one on each tick over the next 30 ticks instead. This spreads out the performance impact.
- When a harvester fails to locate any suitable ore patch, we put it on a longer cooldown, by default 5x the regular cooldown. We don't need to scan as often for these harvesters, since it'll take time for new resources to appear.
- We change the path search in FindNextResource from FindPathToTargetCellByPredicate to FindPathToTargetCells. The format in an undirected path search that must flood fill from the start location. If ore is on the other side of the map, this entails searching the whole map which is very expensive. By maintaining a lookup of resource types per cell, we can instead give the target locations directly to the path search. This lookup requires a small overhead to maintain, but allows for a far more efficient path search to be carried out. The search can be directed towards the target locations, and the hierarchical path finder can be employed resulting in a path search that explores far fewer cells. A few tweaks are made to ResourceClaimLayer to avoid it creating empty list entries when this can be avoided.
- We adjust how the enemy avoidance cost is done. Previously, this search used world.FindActorsInCircle to check for nearby enemies, but this check was done for every cell that was searched, and is itself quite expensive. Now, we create a series of "bins" and cache the additional cost for that bin. This is a less fine grained approach but is sufficient for our intended goal of "avoid resource patches with too many enemies nearby". The customCost function is now less expensive so we can reuse the avoidance cost stored for each bin, rather than calculating fresh for every cell.
When HarvesterBotModule is ordering idle harvesters to nearby resources, it previously scanned from the harvester's current location. Instead, it now scans from the location of the nearest refinery. As the harvester will likely make many runs between the resource and the refinery, it is better to choose a location near the refinery. This will minimise overall distance travelled to harvest the resource patch.
Copy link
Copy Markdown
Member

@Mailaender Mailaender left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't spot regressions in-game. Path finding seems effective. Thread avoidance seems to work, but it is not flawless.

@PunkPun PunkPun merged commit da8eb68 into OpenRA:bleed Aug 7, 2024
@PunkPun
Copy link
Copy Markdown
Member

PunkPun commented Aug 7, 2024

changelog

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Poor performance after harvesters use up available ore/gems Add AI-only overrides for Harvester search radii

6 participants