I thought I'd share a few bits from Viking Conquest. If there is something particular you'd like to see, let me know.
For this inauguration, I present stochastic triggers, a probabilistic technique for reducing lag. It helps with larger mods, as many triggers require exponentially more processing power as material grows. For example, faction AI requires each faction to consider every other -- an unavoidable nested loop. In Native, with six factions, this meant under forty loops. This is run seventy times to randomize the start for new games, or about 2500 loops. In Brytenwalda, with thirty two factions, the same script looped about 72000 times. Hence the infamous "wait five minutes" message in early versions of that mod.
The idea in triggers is, instead of running all instances once every so many frames, spread the processing evenly over all frames. An observant coder would notice this doesn't reduce processing, but it keeps it from happening all at once and potentially causing a lag.
Stochastic triggers have the added benefit of injecting a bit of controlled chaos into the game. With them, one never knows the exact result, but can control the expected, overall result.
Take for example, this weekly trigger:
Instead of looping through all the villages every week, we'll split them over the entire week. If we want every village to take an average of a week to refresh its inventories, we simply divide that period by the number of villages. So say one has 110 villages, as in VC. We simply replace the loop for a random function and put in our new interval.
Now neither we nor the players will know exactly WHEN a village might have new items. It might be in 1.5 hours. It might be in four weeks. But on AVERAGE it will be in one week. Plus all the villages are not processed together at the very moment every OTHER weekly trigger in the game is firing. In fact, the same technique applied to the other triggers of the same period virtually guarantees they will no longer be doing their processing at the same time.
Many of these loops are buried in scripts which are easy enough to restructure to place the loop in the trigger and pass in the loop parameter.
There are situations for which one shouldn't apply this technique.
1. The function depends on some sort of aggregation/summary/averaging/initializing. This is typical of AI functions, where data on all the actors is collected first.
2. The function causes lag even when split up. We found this true of faction AI. Instead of one big lag every six hours, we were getting 21 small lags every .29 hours. We decided the one big lag was better.
3. Minimum interval seems to be one game minute (.017 hours). Beyond that level, one must apply loops.
4. For some other reason, one would like all instances to be considered together. Wages for example. But then, is it really a problem that one lord collects four wages one particular week while another collects none for four weeks? Or is that something that adds variety and interest to the game?
Results...
Before: https://www.adoberevel.com/shares/61c959a60ddd4b64bf9b368aac4b796a/albums/8240968356ff4dd8bd8ac2d111b42681/assets/6c83b1852b2c4017a839f66a0e722969
After:
https://www.adoberevel.com/shares/61c959a60ddd4b64bf9b368aac4b796a/albums/8240968356ff4dd8bd8ac2d111b42681/assets/2441e2fe38994982b3416060ad7af0ce
There is a problem with stochastic triggers: team members without a degree in something requiring statistics have trouble fully understanding or trusting them.
For this inauguration, I present stochastic triggers, a probabilistic technique for reducing lag. It helps with larger mods, as many triggers require exponentially more processing power as material grows. For example, faction AI requires each faction to consider every other -- an unavoidable nested loop. In Native, with six factions, this meant under forty loops. This is run seventy times to randomize the start for new games, or about 2500 loops. In Brytenwalda, with thirty two factions, the same script looped about 72000 times. Hence the infamous "wait five minutes" message in early versions of that mod.
The idea in triggers is, instead of running all instances once every so many frames, spread the processing evenly over all frames. An observant coder would notice this doesn't reduce processing, but it keeps it from happening all at once and potentially causing a lag.
Stochastic triggers have the added benefit of injecting a bit of controlled chaos into the game. With them, one never knows the exact result, but can control the expected, overall result.
Take for example, this weekly trigger:
Code:
# Refresh merchant inventories
(168,
[
(try_for_range, ":village_no", villages_begin, villages_end),
(call_script, "script_refresh_village_merchant_inventory", ":village_no"),
(try_end),
]),
Instead of looping through all the villages every week, we'll split them over the entire week. If we want every village to take an average of a week to refresh its inventories, we simply divide that period by the number of villages. So say one has 110 villages, as in VC. We simply replace the loop for a random function and put in our new interval.
Code:
# Refresh merchant inventories weekly ON AVERAGE
(1.5,
[
(store_random_in_range, ":village_no", villages_begin, villages_end),
(call_script, "script_refresh_village_merchant_inventory", ":village_no"),
# (try_end),
]),
Now neither we nor the players will know exactly WHEN a village might have new items. It might be in 1.5 hours. It might be in four weeks. But on AVERAGE it will be in one week. Plus all the villages are not processed together at the very moment every OTHER weekly trigger in the game is firing. In fact, the same technique applied to the other triggers of the same period virtually guarantees they will no longer be doing their processing at the same time.
Many of these loops are buried in scripts which are easy enough to restructure to place the loop in the trigger and pass in the loop parameter.
There are situations for which one shouldn't apply this technique.
1. The function depends on some sort of aggregation/summary/averaging/initializing. This is typical of AI functions, where data on all the actors is collected first.
2. The function causes lag even when split up. We found this true of faction AI. Instead of one big lag every six hours, we were getting 21 small lags every .29 hours. We decided the one big lag was better.
3. Minimum interval seems to be one game minute (.017 hours). Beyond that level, one must apply loops.
4. For some other reason, one would like all instances to be considered together. Wages for example. But then, is it really a problem that one lord collects four wages one particular week while another collects none for four weeks? Or is that something that adds variety and interest to the game?
Results...
Before: https://www.adoberevel.com/shares/61c959a60ddd4b64bf9b368aac4b796a/albums/8240968356ff4dd8bd8ac2d111b42681/assets/6c83b1852b2c4017a839f66a0e722969
After:
https://www.adoberevel.com/shares/61c959a60ddd4b64bf9b368aac4b796a/albums/8240968356ff4dd8bd8ac2d111b42681/assets/2441e2fe38994982b3416060ad7af0ce
There is a problem with stochastic triggers: team members without a degree in something requiring statistics have trouble fully understanding or trusting them.