ID:2611039
 
Hello, I just had a quick question for anyone with a better understanding on performance.

I want to have a passive regeneration for player health. Would it be better to create a single world loop that checks everyone's health every second and updates it, or to create it as a loop for each individual client upon joining the game?
Depends on the design. I think this would be better served on a per client basis. This is under the assumption that there will be systems(passives) in place that determine how much an individual can regenerate, how fast, if you can regenerate at all at that given time(impairments like bleeding/poisoning comes into mind), etc. It doesn't make much sense to me having one loop that handles all players with those considerations in mind.
I see, that is a good point; it would make more since for a per client basis in case of individual factors like that. Thanks!
world
New()
Loop()

var list/players = new

mob/var/tmp
health = 100
health_max = 100
regen_health_timer
regen_health_delay = 15
regen_health_flat = 1
regen_health_percent

proc/Loop()
set waitfor = FALSE
var tick = world.tick_lag
var mob/m,tod
for()
sleep(tick)
tod = world.time
for(m in players)
if(tod >= m.regen_health_timer)
m.regen_health_timer = tod + m.regen_health_delay
if(m.health_max > m.health)
m.health += (m.health_max * (m.regen_health_percent/100)) + m.regen_health_flat
if(m.health >= m.health_max)
m.health = m.health_max
else
m.health = m.health_max

mob/Login()
players |= src
With ticker loops, there's merits to both approaches.

The advantage of a per-world loop approach is that you wind up with events all being scheduled at roughly the same time, regardless of any jiggery pokery with when a client logged in or out.

On the other hand, the per-player loop allows you to take advantage of polymorphic behavior that gives your project more flexibility at the cost of a little bit of overhead in the form of proc invocation.

There's no reason you can't do both at the same time though, and get some of the advantages and some of the disadvantages of both. Because of overhead, I'd strongly recommend making your regen loop tie into a sort of global ticker loop in the first place.

world
New()
..()
TickLoop()
var
list/tick_actors = list()
ticking = 0
proc
wake(datum/d)
if(islist(d))
for(var/datum/i in d)
tick_actors[d] = 1
else
tick_actors[d] = 1

suspend(datum/d)
if(islist(d))
for(var/datum/i in d)
tick_actors -= d
else
tick_actors -= d

TickLoop()
set waitfor = 0
ticking = 1
var/client/c, datum/d
while(ticking)
for(c)
c.onTick()
for(d in tick_actors)
d.onTick()
c = null
d = null
sleep(world.tick_lag)

datum
proc
onTick()
client //because client isn't a datum for reasons?
proc
onTick()
mob?.onTick()


For any tasks that are so heavy that they cannot deal with per-object tick overhead, you can embed them in TickLoop() directly using a single global proc. For any objects that you need to actually have individualized, polymorphic tick responses, you can just overload onTick() and embed your new behavior.

Again, there are merits to both approaches, so use both approaches.
Thank you both for the examples, I hadn't even considered trying to take advantage of both ways at the same time.
MLAAS did something of a hybrid as well, except the world ticker called the tick() of every tickable object in the world. It kept track of the last call of tick() and would only call it if object.tick_delay ticks had elapsed in the meantime.

It was actually surprisingly efficient for the era when it was written. MLAAS used to handle quite a bit going on during a time when the engine wasn't at all optimized like it is now. (the game runs using almost zero overhead on the current version of the engine, solely due to WHEN it was written and how)