Cozur said:I'm trying to improve the performance of ACOK on the world map, as it has a tendency to stutter a bit much, while at the same time also having some weird pathfinding for the player character.
What are the most common reasons for the two above?
1) As Karlahan mentioned, be afraid of try_for_parties loops in triggers, especially as the party count climbs.
A fairly common old OSP code snippet in module_triggers.py creates kingdom parties such as patrols, scouts, foragers and caravans. This seems simple enough, but watch how it was written for native and see what happens when the factions, party count, etc, grows:
(try_for_range, ":cur_kingdom", kingdoms_begin, kingdoms_end),
.
(call_script, "script_create_kingdom_party_if_below_limit", ":cur_kingdom", spt_forager),
and that looks pretty safe... but if you dig a little deeper, script create_kingdom_party_if_below_limit has within it a "try_for_parties" loop in it.
For native, where you might have 6 factions and perhaps 600 parties, the math is bad enough but manageable:
[6 factions * (600 parties per time script _create_kingdom_party_if_below_limit gets called)], and it might get called once per party template spt_forager, spt_patrol, spt_forager, and spt_caravan. I might get away with a small stutter on the world map every time this trigger occurs, but I might shrug and say "slow PCs, nothing can be done". I might... but I might notice I just caused 1 single trigger to run a code snippet (6*600*4) times == 14400 times!! as 1 trigger!!!
Now for Perisno or Phantasy 2018 (or worse, Warsword Conquest), the number climbs due to both more factions and more total party count. Having some experience with each, taking Phantasy 2018 as a middle case I have 13 factions and 1100 parties total, so if this was called in a single trigger, I now have some code snippet running
(13 * 1100 * 4) times == 57,200 times! If that were just left as-is, I should expect a stutter on the world map lasting about 4 times longer than the same code executing at the mod it was first written for. Instead, I had to break it into 4 different triggers. But... that leads to issue #2
2) Consider the following triggers: one hourly, one at 2 hours, one at 4 hours, one at 8 hours, one at 24 hours, one weekly.
the next week, at the same time I'm processing the weekly trigger, also firing at that exact instant is the 24 hour, the 8 hour, the 4 hour, the two hour, and every single 1 hour trigger -- ALL AT ONCE.
The artful solution is to move as many triggers to prime numbers to slightly different timing so they don't fire at the same time, spreading the hit on performance out across time. If I had a trigger that fired every 24 hours, but could have gotten away with 23 or 25 hours, I now moved that little bit of performance spike to a new location, if I was graphing CPU time consumed versus time of day within game. If the above triggers for kingdom creation used to be say every 3 hours but now are at 13, 17, 19, and 23 hours, they hardly ever overlap, and the perceived performance hit is smoothed. If I really do need them to happen close together, I could still cheat a bit by setting the time to say 17.3 hours till next repeat for one, and 17.7 hours for the next one and 18.3 for the next one and 18.7 for the last one, although at hour 18 would also be firing all your 1 hour triggers, all your 2 hour triggers, all your 3 hour triggers, etc -- and all at once, so I hate burdening one of my mods with triggers firing at even numbers of hours. I prefer prime numbers or fractions of an hour, although there is a limit to how small an hour you can choose according to this line at module.ini (I think, and perhaps I'm wrong):
time_multiplier = 0.25 # I will guess this is the granularity of hourly timers, so if I have a timer set to 11.1 and another at 11.2 and a third at 11.25 and a fourth at 11.26 and another at 11.49 the first 3 fire together and the last two fire at the same time as 11.49 -- but I'm not entirely sure.
How to guesstimate the worst performance in your campaign map?
in python editor, find in files try_for_parties and just see how many triggers, simple_triggers, and scripts called from within triggers and simple triggers there are. Split those up first, so that say in the case of my example #1, HALF the factions were processed, then in a different trigger called on a different period (else there was no value in splitting it to a new trigger, unless there were the ability to run multiple threads, and if so be afraid of using reg values for any reason so that one thread didn't walk on regs used by another thread, ever).
I can't say you will instantly solve lag, but the difference in Perisno 0.771 (no optimization) and 0.773 (both optimizations above) was about 35%. I'll guess that even if my 0.7 series was partly discarded, the later Perisno kept at least those optimizations. I can say something similar occurred between Warsword Conquest 2016 (not optimized bandit spawns, same issue) and my purely experimental Warsword Rigale (optimized bandit spawns) build the FOLLOWING MONTH. Just changes at a few triggers! Removing icon checks for all the parties over water trimmed another 10% or so. There were certain triggers that were performance hogs compared to all other triggers.
By Phantasy 2018 I had cut the number of total parties and aggressively policed EVERY try_for_parties and try_for_agents use to groom the code. I think you'll need to review you code likewise.
Good luck with that. We both may be unpopular with other users, but I've seen you over the years and have great respect for your intelligence. I know how frustrating it is to mod with no one willing to rescue you, and users telling you how disappointed they are etc. When you break it down and start looking for places where the code really is calling something too many times you might find ways to optimize that didn't seem important before. I did it by hand instead of a fancy tool like Karlahan mentioned exists, but I am certain you can handle your code with just a few tips. Sometimes it just needed a little heads up to get the break through you were praying for (or however you handle tech support requests). It sure beats beating your head against the keyboard.
Good luck and 'God Speed'.
- GS