Currently I am developing a mod that registers 4 globalsteps with different timings (every tick, every 0.5 seconds, every 2 seconds and every 60 seconds by counting a timer value and checking it via if like described in the dev wiki). All of the globalsteps functions iterate over all connected players and perform different smaller actions with every player.
My question is: Is it better to have four different small globalsteps performing their actions or would it better to have one large globalstep with 4 different timers performing all of the actions within one iteration over all players every tick?
Best practice? multiple small globalsteps vs one large one
- rubenwardy
- Moderator
- Posts: 6978
- Joined: Tue Jun 12, 2012 18:11
- GitHub: rubenwardy
- IRC: rubenwardy
- In-game: rubenwardy
- Location: Bristol, United Kingdom
- Contact:
Re: Best practice? multiple small globalsteps vs one large o
You should use minetest.after for timed events, it uses a priority queue.
You should spread intensive actions out across ticks to reduce server jitter. If the tasks are small it's fine to run in a single tick however
You should spread intensive actions out across ticks to reduce server jitter. If the tasks are small it's fine to run in a single tick however
- Linuxdirk
- Member
- Posts: 3219
- Joined: Wed Sep 17, 2014 11:21
- In-game: Linuxdirk
- Location: Germany
- Contact:
Re: Best practice? multiple small globalsteps vs one large o
I am not quite sure about this … Is minetest.after more or less consistent/secure than using globalsteps? The latest release version (0.4.16 as of today) is my target platform.rubenwardy wrote:You should use minetest.after
So it is better to run multiple small things instead of one giant piece of code checking and doing everything? The actions I perform do not depend on each other. So having a registered globalstep being skipped a few ticks isn’t that important.rubenwardy wrote:You should spread intensive actions out across ticks to reduce server jitter.
As said all tasks iterate over all players, let me extend that. The task gets the name from the player object and passes it and a second value (a localized entry from a global table) to a function.rubenwardy wrote:If the tasks are small it's fine to run in a single tick however
Code: Select all
local timer = 0
local interval = my_global_table.interval
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer >= interval then
timer = 0
for _,player in ipairs(minetest.get_connected_players()) do
-- do stuff here
end
end
end)
Code: Select all
local m = player:get_player_control()
if m.up or m.down or m.left or m.right then
-- do stuff here
end
For me it is important to not affect performance too much.
- rubenwardy
- Moderator
- Posts: 6978
- Joined: Tue Jun 12, 2012 18:11
- GitHub: rubenwardy
- IRC: rubenwardy
- In-game: rubenwardy
- Location: Bristol, United Kingdom
- Contact:
Re: Best practice? multiple small globalsteps vs one large o
Most of those also apply to global steps. For the case of running something every N seconds, minetest.after is better. Minetest.after isn't precise on older versions however.Linuxdirk wrote:I am not quite sure about this … Is minetest.after more or less consistent/secure than using globalsteps? The latest release version (0.4.16 as of today) is my target platform.rubenwardy wrote:You should use minetest.after
I'd tend to put related things that run in the same interval together, and only split them up if it causes dropped server steps when the timer runs.Linuxdirk wrote:So it is better to run multiple small things instead of one giant piece of code checking and doing everything? The actions I perform do not depend on each other. So having a registered globalstep being skipped a few ticks isn’t that important.rubenwardy wrote:You should spread intensive actions out across ticks to reduce server jitter.
Looping through players isn't that slow, it's the content that matters. Anything that sends packets - such as HUD, player physics modifiers, and object modifiers - should be spread out as much as possible, as they don't tend to be batched and so can end up with a lot of packet sending. For example, on CTF there's 5*3 hud elements which are updated every time someone is killed - that's 30*5*3=450 packets. It's vital that those elements aren't updated every loop, but only when needed.Linuxdirk wrote:As said all tasks iterate over all players, let me extend that. The task gets the name from the player object and passes it and a second value (a localized entry from a global table) to a function.rubenwardy wrote:If the tasks are small it's fine to run in a single tick however
*snip*
(There are some more localized values, this is just a minimal working example of what I do.) Basically all tasks check a single thing. The heaviest might be checking for player movement.
The function that will be executed when all checks passed gets the player object (as I recently learned passing around player objects is considered inconsistent), gets some custom player attributes, does some calculations and writes the result as custom player attribute and modifies a HUD element.
For me it is important to not affect performance too much.
It's still worth avoiding running stuff every tick if you can.
At the end of the day, the best way to optimise is to use a profiler. I'd go with the simple implementation first, then if you see dropped steps every time the interval function returns then you know it's taking too long.
- Linuxdirk
- Member
- Posts: 3219
- Joined: Wed Sep 17, 2014 11:21
- In-game: Linuxdirk
- Location: Germany
- Contact:
Re: Best practice? multiple small globalsteps vs one large o
What older versions? <0.4.16? As I develop for the latest release I’m fine with it if it is less precise in older versions. What’s the best way to use it? something like this?rubenwardy wrote:For the case of running something every N seconds, minetest.after is better. Minetest.after isn't precise on older versions however.
Code: Select all
local do_stuff = function ()
minetest.after(60, do_stuff)
for _,player in ipairs(minetest.get_connected_players()) do
-- do stuff here
end
end
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Best practice? multiple small globalsteps vs one large o
I don't think that's true. Based on my research, minetest.after does not perform any prioritization or optimization. It simply loops through the job queue in its entirety every server tick.rubenwardy wrote:You should use minetest.after for timed events, it uses a priority queue.
https://github.com/minetest/minetest/bl ... ter.lua#L4
In previous implementations, minetest.after was actually more precise because the job expiration was determined with microsecond precision by a monotonic clock.rubenwardy wrote:Minetest.after isn't precise on older versions however.
https://github.com/minetest/minetest/bl ... /after.lua
Now, rather than the precision of the monotonic clock, a local "time" variable is being incremented by dtime each globalstep. To my understanding, dtime is relatively inaccurate and imprecise. Moreover, performing hundreds of floating point calculations every minute on a single variable is likely to induce gradual rounding errors and therefore perpetual drift of the calculated time.
In my view, minetest.after is really only suitable for one shot events. It's not a particularly robust facility for high-scale, routine job control. Unlike node timers, that can be stopped, restarted, and even validated, none of that is possible with minetest.after. Also native support for periodic tasks is lacking, which necessitates recursive calls to minetest.after, which is not stylistically clean nor intuitive.rubenwardy wrote:For the case of running something every N seconds, minetest.after is better.
I personally would recommend consolidating all of your tasks into the fewest number of globalstep registrations possible (preferably one or two, if possible), and then apportioning them accordingly. For example, this is the abbreviated code for my weather simulator mod.
Code: Select all
local timer = 0
minetest.register_globalstep( function( dtime )
-- Multiple lightning strikes can occur randomly in a series after thunder
-- (but only only one lightning strike at a time of course)
if cur_thunder and cur_thunder > 0 then
:
:
end
-- Update sky to reflect degree of precipitation every 3 seconds, offset by 0.7 seconds
-- (this should help distribute the CPU and network load)
if v_lightning == 0 or ( timer + 7 ) % 30 == 0 then
:
:
-- During lightning, recalculate sky every 0.1 seconds to allow realistic visual effects
-- (should have negligible performance impact due to randomness and rarity)
elseif v_lightning then
:
:
end
-- Get current weather conditions via perlin noise function every 2.0 seconds
-- Calculate temp fade and sky fade relative to offset from middway
if timer % 20 == 0 then
:
:
end
-- Iterate through all players and start/stop environment sounds and particle fx
-- only when weather conditions change or player moves indoors/outdoors
if timer % 10 == 0 then
:
:
end
-- Export weather data at 10 second intervals offset by 0.3 seconds for charting of
-- weather conditions via polygraph mod
if ( timer + 3 ) % 100 == 0 then
:
:
end
timer = timer + 1
end )
Who is online
Users browsing this forum: No registered users and 5 guests