Log In  

I've seen varying techniques for update looping and am looking for insight/feedback on if one is any better than the other in terms of speed/efficiency.

Method #1 I see a lot is a global table of all the actors on the screen. So everything goes here and then the system _update() function is something like this.

actors={}

function _update()
   foreach(actors, function(obj)
      obj.update()
   end
end

This method makes sense. Every object then has its own update() method...but that feels kind of inefficient, especially if the update would be the same for like-actors.

Method #2 is grouping like-actors with each group have its own update() method, which is then called in the system update loop.

bullets={}
bullets.actors={}
bullets.update=function()
   foreach(bullets.actors, function(obj)
      ...do stuff to obj...
   end
end

badguys={}
badguys.actors={}
badguys.update=function()
   foreach(badguys.actors, function(obj)
      ...do stuff to obj...
   end
end

function _update()
   bullets.update()
   badguys.update()
end

This makes sense to me too and feels a little better. I like the idea of having independent updates for each group so I can turn them on/off as needed within different views. But this also feels like it's a lot of foreach() loops which could be a problem...?

And then I see this same pattern being used for the draw() methods too.

Both of these methods work, so I'm just wondering if one has any performance advantages over the other.

P#20654 2016-05-16 13:42 ( Edited 2016-05-16 17:42)

1

The first approach involves a property lookup for each object to determine which update function to call. All other things being equal, the latter approach is more efficient as the function is known in advance.

If this were a modern game, and you were writing in something like C++ where you can ensure your data is stored contiguously, and you were processing tens of thousands of game entities per frame and trying to hit 60fps, then the choice would have a meaningful impact on performance and you would probably organize your game data around the latter approach.

But this is lua, and pico-8, and your program will be dealing with hundreds of entities per frame at most, and it will spend almost all of its frame time drawing. Talking to the API and writing to the framebuffer have significant costs imposed on them to give them retro performance characteristics, while iterating lua data structures does not. Everything you do in lua happens at modern speed; it's just when you touch the pico-8 API that the speed of operations becomes a factor.

Rather than optimising for program efficiency, optimise for your own efficiency: structure your update code in a way that makes it easiest and quickest for you to build your game. That might be option #1, or it might be option #2, or it might be some other approach altogether.

P#20658 2016-05-16 14:27 ( Edited 2016-05-16 18:27)

Another consideration is that you may want to update or draw your entities in a different order than you'd naturally group them: e.g. if your game has z depth, you may have to draw the bad guys behind the player, then the player, then the bad guys in front of the player. In this situation it may make more sense to sort all your entities of any type into a single list for drawing.

P#20659 2016-05-16 14:36 ( Edited 2016-05-16 18:36)

I've been using PICO-8 for only a little over a month, so I don't know how useful my opinion is, but I'm using the second method in my current project. I'm largely doing this simply because it seems more logical to me.

After thinking about it, though, I realize it gives more efficient control over the order in which events are processed. If all objects were in the same table, I don't think it would be very efficient to iterate over the table multiple times, processing only certain objects on each pass. With objects separated by type, the tables can be iterated over only once in the order that makes the most sense for the game.

This may be a bit of a tangent, but since you posted example code, I'm curious about the differences between foreach() and all(). I'm using the latter. Are there benefits to one or the other? I believe I tried using foreach() but ran into problems when deleting items from a table. Using all() doesn't seem to have this problem (that I've encountered yet, anyway).

One of my update loops, for example:

  for i in all(shots) do
    i.x = i.x + i.xvel
    i.y = i.y + i.yvel
    if collide(i.x, i.y, i.w, i.h, p.x, p.y, p.w, p.h) and p.blink == 1 then
      p.hp = p.hp - 1
      p.blink = 50
    end     
    if i.x > camx + 128 or i.x < camx then
      del(shots, i)
    end   
  end
P#20661 2016-05-16 16:07 ( Edited 2016-05-16 20:07)

Just for the record, I used Method #2 for my most recent project. I liked that it let me put the update/draw methods for each group where I want because in some screens I needed different combinations.

@binaryeye - I'm using the foreach() because that's probably the first thing I found, haha! I guess I'm just used to having a foreach() in other languages so it was more natural to me. And I guess because I didn't need the iterator var in my cases. I haven't had any trouble using foreach() at all, even when deleting. But hey, whatever works.

This works for me:

foreach(actors, function(obj)
   ...some stuff...

   if obj.health<=0 then del(actors,obj) end
end

I think because del() deletes based on the value passed, and since the whole object is passed in, it finds it and removes it.

P#20664 2016-05-16 16:23 ( Edited 2016-05-16 20:23)

Why am I having errors using the first method trying the Dom8verse tutorial?

function _update()
foreach(objects, function(obj)
obj.update(obj)
end
end

P#41897 2017-06-23 18:47 ( Edited 2017-06-23 22:47)

You're missing an 'end' - your foreach needs an end, the passed function needs an end and the _update needs an end. At least that's what it looks like from here...didn't try it.

function _update()
   foreach(objects, function(obj)
       ...do something...
      end
   end
end
P#41899 2017-06-23 20:35 ( Edited 2017-06-24 00:35)

As pasted, I see a missing paren as well

P#41905 2017-06-23 22:17 ( Edited 2017-06-24 02:17)

As a point of interest,

obj.update(obj)

can be shortened to

obj:update()

The ":" implies that the object should be passed in as the first parameter of the method. (To sort-of simulate object oriented behavior.)

Handy, even if it feels backwards to C++ programmers.

P#41983 2017-06-28 17:40 ( Edited 2017-06-28 21:40)

You're missing an 'end' - your foreach needs an end, the passed function needs an end and the _update needs an end.

Actually, he's missing a closing parenthesis for the foreach function. I noticed all your code examples were missing it as well, and it was also missing from the example in the PICO-8 fanzine.

It should be:

function _update()
  foreach(objects, function(obj)
    obj:update()
  end) -- <- closing parens
end
P#46553 2017-11-21 14:19 ( Edited 2017-11-21 19:20)

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-04-18 20:50:50 | 0.012s | Q:25