Perf impact of libcampaign and group management

For porting of the campaign to javascript discussions
Post Reply
User avatar
Berserk Cyborg
Code contributor
Code contributor
Posts: 938
Joined: 26 Sep 2016, 19:56

Perf impact of libcampaign and group management

Post by Berserk Cyborg »

The group management of enemy units is kind of... intense. Even for one group. It's probably the worst part of the campaign library in terms of performance and can cause stuttering in Warzone. A good test case to see this with is on the Alpha 7 mission (defend the plateau). Now, this is not something most people would take notice of with normal gameplay (unless there are lots of groups), however setting game speeds at 10x+ will reveal it clearly by watching the mission timer at those speeds.

In __camTacticsTickForGroup() there are three areas of suspect for this:
1. Regrouping logic.
2. Target choosing (__camPickTarget()).
3. the large for-loop and the end of the function.

1 and 3 seem to be the problem areas. 2 does not seem to have much of an impact as the others and can easily be cached and throttled over a few seconds without affecting the groups much.

I have tried throttling each of those areas for a few seconds and it does help to an extent, but I observe moments of smooth gameplay followed by a repeating hiccup that becomes more and more noticeable as more groups are made. :hmm:

Once I did a test where I replaced the cluster analysis with a super simple group average and a distance check from that average. It emulated the cluster analysis well enough for me to conclude that the cluster analysis is a big part of the problem, but still I saw that hiccup albeit not as long lasting anymore.

So, the performance issue lies mostly with the cluster analysis and that for-loop. Or it's somewhere else and I am missing it, but I think I have it pegged down to those two areas.

Regrouping
Cool in theory. In practice, it does not seem viable... at least for now, so I seldom choose to enable regrouping. I have observed some weirdness with it, though. Commonly, I see an issue where "close but far to path" coordinates are chosen for the regrouping point (think: group is very close to the bottom of a hill and now some units must move to the top of it which takes minutes to get to).

Other times I have seen it get defeated due to the coordinate where the group is supposed to regroup at is unreachable (pits and mountains that nothing but a VTOL could get to, water tile where the group is land only propulsion). Thus causing the whole group to remain motionless forever until something forces some of the units to move elsewhere.

Alpha 7.zip
(54.58 KiB) Downloaded 356 times
User avatar
NoQ
Special
Special
Posts: 6226
Joined: 24 Dec 2009, 11:35
Location: /var/zone

Re: Perf impact of libcampaign and group management

Post by NoQ »

Clustering code is copied almost without change from NullBot. I spent a huge amount of iterations before coming up with the current regrouping algorithm and i doubt it can be simplified significantly for the purposes of a general-purpose AI, but it can probably be simplified significantly for specialized groups in the campaign.

The main problem with the group's average coordinates method is that it makes the whole group retreat back to base for a short time whenever a newly produced unit is added to the group. It is just one unit, but it is veeery far away, so this is enough to cause the group to be completely unable to move forward as long as new droids keep being added. So, for instance, if no new units are being produced, group average coordinates is a good enough clustering method. It is also fine when a factory produces death squads that are replaced rather than reinforced. But when a group is being continuously reinforced from a factory, a good regrouping method is essential.

Moving the cluster analysis (or some other formation code) to C++ is definitely an option.

The large for-loop can be separated into smaller loops for small chunks of the group queued into different game frames in order to avoid stuttering. There are probably a lot of actual optimizations that can be implemented there.
User avatar
Berserk Cyborg
Code contributor
Code contributor
Posts: 938
Joined: 26 Sep 2016, 19:56

Re: Perf impact of libcampaign and group management

Post by Berserk Cyborg »

So I was messing around with this again. Turns out it's caused by ordering units to move/scout with orderDroidLoc(). I guess, by extension, any order that makes a unit move is going to cause problems. This can cause unexplained "lag" or "screen choppiness/tearing" while the script perf logs for relevant functions remain good. Even more so if timers or queues are very fast.

Maybe we could mitigate this by ignoring repeated orders against the same object or location.
MIH-XTC
Trained
Trained
Posts: 368
Joined: 31 Jan 2014, 07:06

Re: Perf impact of libcampaign and group management

Post by MIH-XTC »

I don't understand what you guys are talking about but group management in player vs player is accomplished with "select all similar units". Usually players will select a truck (or two if there are different types) and then use select all similar to group. Same thing is done with cyborgs and tanks.

In terms of AI, I always used eventDroidBuilt() and then grouped based on type. I also had a backup trigger to enumDroids() to regroup based on location and health.

If you're trying to group by proximity (cluster analysis???), I don't know that there would ever be a need to do that because droids should already be in groups before they leave the base anyways.

I wrote a proximity check trigger in WZscript to defend bases and it didn't have any performance issues. It checked for enemy droids within 25 tiles of HQ and beaconed the location of enemy droid if so. The AI would then go to the location of the beacon to defend.

BTW, I just started working on campaign as of yesterday. I'm going to give it a whirl.

EDIT: I guess campaign is different in the sense there are multiple bases whereas mp there is only one. Might present challenges.
User avatar
Berserk Cyborg
Code contributor
Code contributor
Posts: 938
Joined: 26 Sep 2016, 19:56

Re: Perf impact of libcampaign and group management

Post by Berserk Cyborg »

By "group management" we are talking about controlling units in a group. Or, more simply, what AI bot makers would call "tactics". In campaign this can range from a simple attack order, to compromising a location (LZ), to defending a location, to patrolling certain locations, or following a commander's orders.

Campaign doesn't make one giant group of units (can be possible). Instead, there are multiple groups of units that are created from factories, transporters, "spawn points", and from preset "grouped up" units placed on the map itself. Factories are capable of reinforcing preset groups on several missions. A recent addition is the ability to automatically create a group should a non-grouped unit be attacked, which pulls in all surrounding non-grouped units from about 6 tiles away. This reactive behavior is much better than letting the unit stare off into space as it gets pounded by a mortar from afar.

NoQ's cluster analysis is a way to keep a group of units close to eachother, as done with NullBot. This is especially helpful if units have vastly different speeds.

Again, it's the repeated calls of orderDroidLoc() that is causing the extreme lag. I was thinking the cluster analysis and the looped enumRange()s were causing issues at first, but the surrounding code actually does just fine performance-wise.
MIH-XTC
Trained
Trained
Posts: 368
Joined: 31 Jan 2014, 07:06

Re: Perf impact of libcampaign and group management

Post by MIH-XTC »

After playing around with cam1a I think there is a fundamental misunderstanding of how groups were intended to work. In FlaMe it shows that cam1a has 2 scavs - players 6 and 7. I think group management was intended to be accomplished through using multiple scav players. I don't see anywhere in campaign where multiple AI player numbers are referenced in the same scenario.

This came to my attention while trying to produce a simple droid. The code snippet below should produce a basic droid if it has a timer set from eventStartLevel() but it doesn't produce any droids.
Spoiler:

I looked at how camSetFactories() works and the excessive number of parameters makes me think this is way too complicated. Most of the parameters in camSetFactories() should be specific to each scav player, not each factory. Anything additional can be set with eventDroidBuilt(). That would simplify things tremendously and the above loop can be used to produce and manage droids just as easy with a timer.

All of the campaign maps need remastering in FlaMe but the object format for FlaMe is still .csv. Part of the remastering is to assign the proper scav players to the appropriate sections of the map.

I still can't figure out why the above snippet doesn't produce a droid. By default scav factories should be enabled if they're explicitly told to make droids. Seems they are disabled or something.

I will make it such that the scavs reproduce and build their own factories - perhaps up to 50 of them. I'm basically importing ultimate scavs into the campaign. This won't work the way things are currently structured.
User avatar
Berserk Cyborg
Code contributor
Code contributor
Posts: 938
Joined: 26 Sep 2016, 19:56

Re: Perf impact of libcampaign and group management

Post by Berserk Cyborg »

MIH-XTC wrote: 20 Jul 2019, 13:06 After playing around with cam1a I think there is a fundamental misunderstanding of how groups were intended to work. In FlaMe it shows that cam1a has 2 scavs - players 6 and 7. I think group management was intended to be accomplished through using multiple scav players. I don't see anywhere in campaign where multiple AI player numbers are referenced in the same scenario.
I think your reading too much into a simple oddity. The only time there are multiple scavenger players, 6 & 7, is on Alpha 1/2. After those missions there will only be one scavenger player, 7. I like to think this could have been to have different factions of scavengers, some friendly with each other, or not.

Not sure how this relates to groups though. A group is just a few units doing something, like attacking or defending a position.
MIH-XTC wrote: 20 Jul 2019, 13:06 This came to my attention while trying to produce a simple droid. The code snippet below should produce a basic droid if it has a timer set from eventStartLevel() but it doesn't produce any droids.
Scavengers aren't granted their components from rules.js like in multiplayer. All campaign AI components are enabled on the fly from within __camBuildDroid(). So chances are your factory can't produce anything. You might want to make use of structureIdle() since I think not using it will keep overriding the previous attempt to produce a unit (never tried this myself).
MIH-XTC wrote: 20 Jul 2019, 13:06 I looked at how camSetFactories() works and the excessive number of parameters makes me think this is way too complicated. Most of the parameters in camSetFactories() should be specific to each scav player, not each factory. Anything additional can be set with eventDroidBuilt(). That would simplify things tremendously and the above loop can be used to produce and manage droids just as easy with a timer.
The many parameters for factories is very much intended and keeps things highly modular. Each factory is basically its own AI "brain", capable of producing units (or not if conditions permit), putting them into an existing group or a new one, and ordering them to do stuff.

You don't need to build multiple factories if your attempting to put Ultimate Scavs into campaign. I made it so campaign AI have instant unit production (see __camGrantSpecialResearch()). It is possible to produce 10 units a second, no matter how powerful (at least with current build times), from every factory should a sufficiently low throttle be used and the campaign library's production timer is lowered from 800 ms to 100 ms.
MIH-XTC
Trained
Trained
Posts: 368
Joined: 31 Jan 2014, 07:06

Re: Perf impact of libcampaign and group management

Post by MIH-XTC »

Berserk Cyborg wrote: 20 Jul 2019, 16:32 Scavengers aren't granted their components from rules.js like in multiplayer. All campaign AI components are enabled on the fly from within __camBuildDroid(). So chances are your factory can't produce anything. You might want to make use of structureIdle() since I think not using it will keep overriding the previous attempt to produce a unit (never tried this myself).
Aight, sweet. On the fly enabling is very helpful instead of having to maintain static arrays.


Berserk Cyborg wrote: 20 Jul 2019, 16:32 You don't need to build multiple factories if your attempting to put Ultimate Scavs into campaign. I made it so campaign AI have instant unit production (see __camGrantSpecialResearch()). It is possible to produce 10 units a second, no matter how powerful (at least with current build times), from every factory should a sufficiently low throttle be used and the campaign library's production timer is lowered from 800 ms to 100 ms.
One of my goals of re-doing the campaign is to bridge the gap with mp. I will use the same behavior/stats as mp so I want the scav production times to be the same.
Berserk Cyborg wrote: 20 Jul 2019, 16:32 The many parameters for factories is very much intended and keeps things highly modular. Each factory is basically its own AI "brain", capable of producing units (or not if conditions permit), putting them into an existing group or a new one, and ordering them to do stuff.
I understand the modularity approach but adding new AI factories on the fly is not straightforward. This is an example of unintended “broken” behavior from the original game. As far as I know, the AI in campaign does not expand or build new factories? I don’t think it’s supposed to be like that.

I take a much different approach to factory production and group management by relying on certain checks at eventDroidBuilt(). If droids are always assigned an initial group, they would never need to be regrouped based on proximity. The only time they become ungrouped or need regrouping is when they go to repair. I think the performance costs are minimal on eventDroidBuilt() if the checks are done in the right order.

My thinking is to always queue all AI factory production on eventStartLevel(). If production is too much or too little then I’ll adjust the amount of oil the scavs have.

Just to clarify my intentions, I’m going to make the current campaign much harder and prepare players for mp. In mp there are scenarios where your opponent might storm your base with 100 flamers or VTOLs. No such scenarios exist in campaign but they will soon. I’m pro at editing in FlaMe so I can hook this up. I want to add more scavs, AI droids, structures, etc… to make the game significantly harder. I won’t change any of the objectives, missions, events or anything like that. If you’re not familiar with ultimate scavs, here’s a screenshot of them playing rush map. I circled the extra factories they built.

Image

Cam1a is too early for this but Cam1b might not be off limits for a light introduction 😊
Post Reply