Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
need help improving performance of a real time game — Gideros Forum

need help improving performance of a real time game

piepie Member
edited January 2019 in Game & application design
Hi, it's the first time I am in this situation and I am not sure if my approach is the right one:

I have some guys roaming across the stage, who are being targeted by turrets as in any tower defence game.

A bit of overall lag when they're many would be acceptable, but at some point I lose control of their "status" (like burn, or freeze).
I suppose due to the lag because when they are only a few it's not an issue.

I tried keeping everything simple:
almost everything is handled by a single enterframe function in the level file, that sets a "tick" each second (frame based) and changes values.
Later in the same function those values are read, and with a bunch of conditions based on these values things happen.
I am now playing with profiler to lighten the heaviest functions, but I think I used all the tricks I know: from localizing anything to loading the fewest texturepacks.
I still see some potential by pooling status sprites (everything else is already in a pool) but I am not sure it would be enough.

To make a practical example: every actor has a property to define its status, which is a table made by 2 entries: the first one is a counter that starts from 0 and is increased on every new "tick", the second one is the limit it should reach before annihilation (this is also handled by the same function that increase it)

Pseudocode goes like this:
function onTick()
 --increase status time
  if actor.status[1]<=actor.status[2] then
      actor.status[1] = actor.status[1]+1
  else
       actor.status = nil
  end
 
  --remove graphics relative to status
  if actor.status == nil then
     actor.statusSprite:removeFromParent()
     actor.statusSprite = nil
  else
 --apply status damage
    actor.hp = actor.hp -1
   end
 
-- ... and so on for almost anything
 
end</pre>


What I don't understand is how is it possible that some lines of code are executed, while others seems to be skipped: I mean, even if I lose "tick number 34", shouldn't this thing be executed on "tick number 35" and following "ticks"?
So why there is a random condition when I still see flames on a guy who doesn't get any damage from them and they won't ever disappear?
I may avoid adding flames as a new movieClip, but if I have to draw any guy in flames in 3 frames I would exponentially increase the number of textures I need to load... :/

Thank you

Comments

  • maybe you could also experiment about what kind of device does it lag on.
  • Maybe Luajit will help. Also you can use our magic alternative math operations that @SinisterSoft defined. I never experimented rhose myself also just saw in the forum so they are only wild guess.
  • SinisterSoftSinisterSoft Maintainer
    edited January 2019
    You could do things like:
    function onTick(e)
      local skip=e.deltaTime*60 -- assuming framerate set to 60hz, this would normally be around 1 if no frames skipped
     
      local a=actor
      local s=a.status
      if s[1]<=s[2] then s[1}+=skip else a.status=nil end -- not sure if s=nil would work or not!!!
     
      if not s then -- again, I'm not sure if this would work - you may need to set s to nil
          a.statusSprite:removeFromParent()
          a.statusSprite=nil
      else
          a.hp-=skip
      end
    end</pre>
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • Also removeFromParent might be expensive...
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • Apollo14Apollo14 Member
    edited January 2019
    @pie I don't know if you tried Gideros' mutation operators instead of vanilla Lua.
    If you didn't, then it's definitely worth trying.
    Simple operations like +- don't make great difference. But for math.pow/math.sqrt/etc mutation operators are many times faster.
    actor.hp = actor.hp -1
    actor.hp-=1 --faster, but not dramatically
     
    --slow:
    function getDistance( x1, y1, x2, y2 )
    	return math.sqrt ( math.pow ( x2 - x1, 2 ) + math.pow ( y2 - y1, 2 ) )
    end
     
    --5x faster:
    function getDistance2( x1, y1, x2, y2 )
    	return ((x1 - x2)^2 + (y1 - y2)^2)^0.5
    end

    Also I've seen somewhere (maybe in 'Programming in Lua' book), it was said that perfomance is different when we use short lua ternary operators instead of if/else blocks.
    (but I'm not sure if I remember it correctly, and I don't remember what was faster, now I'm also curious about it)
    > Newcomers roadmap: from where to start learning Gideros
    "What one programmer can do in one month, two programmers can do in two months." - Fred Brooks
    “The more you do coding stuff, the better you get at it.” - Aristotle (322 BC)
  • hgy29hgy29 Maintainer
    Gideros won’t skip code, once the frame event is started it is ran entirely, effectively stalling the game if taking too much time. You must be using other kind of mechanisms that hold your sprites on screen
  • piepie Member
    edited January 2019
    It lags on windows player, so I assume it woud lag anywhere else :)
    I am planning to switch to sinistersoft math operators, but I am not using too many math operators there.

    I will try with zerobrane's watch panel to check if the conditions are met or not / what happens there

    @hgy29 would it be the same even if the onTick function is local inside the enterframe function? I mean
    function level:enterframe()
     
    local function onTick()
    ...
    end
     
      if e.frameCount%30 ==1 then
        onTick()
      end
    end
    thank you


    [edit: sorry confirmed message too early by mistake]
  • it's strange that it lags already on your pc, your scenario does not seem like something that should pose a problem to a current pc, even if it is not optimized at all.
  • hgy29hgy29 Maintainer
    yes, it will be the same
  • It seems to me that you are creating and destroying a lot of sprites on the fly which is terrible from a performance perspective because..
    - The garbage collector will be going crazy.
    - There is a lot of overhead creating new sprites.

    The best approach would be to create a bunch of actors and reuse them as required. With the status sprite (and possibly others you might have), just have them attached to the actor all the time and make them not visible when not required.

    With MovieClips.. dump them. For a single MovieClip with 32 frames of animation (unique graphics) you are creating 32 bitmap objects! It might be a little harder to code yourself but use ONE bitmap object and then fetch TextureRegions from a TexturePack and setTextureRegion() on the bitmap object.

    Also think about things that don't need to be processed every frame too. If an actor is always aiming at the player.. could they update their direction every second frame without it not looking or performing bad?

    Likes: Apollo14

    +1 -1 (+1 / -0 )Share on Facebook
  • piepie Member
    @antix I had those sprites attached to my actors and I was setting those visible/not visible but I had the issue, so I went for another option (using removeFromParent as I wrote here) but it's not worth it. I have to say, the overall performance doesn't seem to be affected this much by that change though.
    Everything is already pooled and reused, except the status sprites because they were part of the actors (and they will be there again because I liked it more).

    You sound right when you say to drop movieclips: maybe I am skilled enough to code an alternative, but it will take time and I was trying to focus on the game.
    If I don't catch a bug somewhere, I suppose it's time to think at this option too.. :(

    Thank you
  • hgy29hgy29 Maintainer
    I have a feeling that MovieClips might be the culprit here. Knowing that the animation complete event doesn’t always fire in Movieclip, that they manage they own life cycle, they can well go crazy in your case. I have stopped using them, I use Core.asyncCall and do my own frame by frame animation in fakethreads thanks to Core.yield(true)

    Likes: Apollo14

    +1 -1 (+1 / -0 )Share on Facebook
  • I don't have a problem with movieclip - but I just use the for things like UI appearing, etc - not actual game objects.
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • i also don't use movieclips within game, so it could be the culprit.
  • piepie Member
    Allright, I have to do this then.. Is there an example of "fake movieclip" handled by fake threads?
    How do you handle timings?

    Thank you
  • Apollo14Apollo14 Member
    edited January 2019
    @pie, I'm also curious about benchmarking 'movieclip' vs 'setTextureRegion' perfomance.
    Here's very basic example (maybe there're better ways):
    *and definitely it would be better to not have dedicated rooster:run() function for every rooster instance. We should manage all instances with array somehow. Maybe somebody from Lua Gurus will suggest elegant solution :smile:
    local rooster=Bitmap.new(rooster_tpack:getTextureRegion("rooster_1.png"))
    stage:addChild(rooster)
     
    function rooster:run()
    	for i=1,17 do
    		rooster:setTextureRegion(rooster_tpack:getTextureRegion("rooster_"..i..".png"))
    		Core.yield(true) --we can set delay Core.yield(.02) for example
    	end
    	rooster:run() --repeat again after one cycle of animation finished
    end
     
    Core.asyncCall(rooster.run)

    Likes: hgy29, pie

    > Newcomers roadmap: from where to start learning Gideros
    "What one programmer can do in one month, two programmers can do in two months." - Fred Brooks
    “The more you do coding stuff, the better you get at it.” - Aristotle (322 BC)
    +1 -1 (+2 / -0 )Share on Facebook
  • Apollo14Apollo14 Member
    edited January 2019
    btw async call animation truly is more perfomant
    with movieclip I have ~50fps when I spawn 300 roosters
    while with asynccalls I can afford to spawn 570 roosters and still have same ~50fps

    I didn't even optimize code, there's dedicated rooster:run() function for every instance, sort of lame practice :D

    After optimization asynccall animation will be 2x faster or even more.

    Likes: pie

    > Newcomers roadmap: from where to start learning Gideros
    "What one programmer can do in one month, two programmers can do in two months." - Fred Brooks
    “The more you do coding stuff, the better you get at it.” - Aristotle (322 BC)
    +1 -1 (+1 / -0 )Share on Facebook
  • Interesting, I never thought of using fake threads to animate things, I've always just used frame based animations running from EnterFrame and they work pretty fast.
  • I've been messing about with homing missiles and such nonsense in the last few days and now that stuff is working I'm beginning to optimize. I remembered you posted some code above @Apollo14 for a faster distance() function. After some testing I discovered it is not as fast as using math.sqrt however. Doing a little more testing I came up with a mix of multiplication and exponent which outperforms both pure eponent and sqrt based functions. Here's a test..
    -- simple function timer
     
    local REPS = 1000000
     
    local dx = 5
    local dy = 5
     
    local data = {
      {
        'sqrt',
        function()
          local sqrt = math.sqrt
          local dist = sqrt(dx * dx + dy * dy)
        end,
      },
     
      {
        'exponent',
        function()
          local dist = ((dx ^ 2) + (dy ^ 2)) ^ 0.5
        end,
      },
     
      {
        'multiply/exponent',
        function()
          local dist = ((dx * dx) + (dy * dy)) ^ 0.5
        end,
      },
    }
     
    -- run all functions
    for i = 1, #data do
      local block = data[i]
      local func = block[2]
      local start = os.timer()
      for i = 1, REPS do
        func()
      end
      local elapsed = math.floor((os.timer() - start) * 1000)
      print(block[1] ..  ' (' .. elapsed .. 'ms)')
    end
    The 'multiply/exponent' method seems to be the fastest and I've been using it in my prototype without anything breaking (currently). Thought you might be interested :)
    +1 -1 (+3 / -0 )Share on Facebook
  • That's the way I do it, if you only want to find out which is nearest though (don't need the actual amount), etc then you don't need the ^0.5 at the end.

    Likes: Apollo14, antix

    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
    +1 -1 (+2 / -0 )Share on Facebook
  • @SinisterSoft ahh that is very useful.. thanks!
  • And considering the ^0.5 will be the slowest bit then it should save some time. :)

    Likes: antix

    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
    +1 -1 (+1 / -0 )Share on Facebook
  • Yes! So if you have a bunch of objects by the player you use this method to find the closest one, and then use ^0.5 on the closest one to get the actual distance between the two objects.. magic :)
    +1 -1 (+3 / -0 )Share on Facebook
  • MoKaLuxMoKaLux Member
    edited October 28
    FYI I added without ^0.5 (no real difference) and math.distance (the really bad guy :p )
    -- simple function timer by <a href="https://forum.giderosmobile.com/profile/antix" rel="nofollow">@antix</a>
    local x1, y1, x2, y2 = math.random(-100000, 100000), math.random(-100000, 100000), math.random(-100000, 100000), math.random(-100000, 100000)
    local dx, dy
     
    -- init
    local dist = math.distance(x1, y1, x2, y2)
    print("*** POINT A: "..x1, y1, "POINT B: "..x2, y2)
    print("*** DISTANCE: "..dist)
     
    -- tests
    local sqrt = math.sqrt
     
    local data = {
    	{
    		"sqrt",
    		function()
    			dx, dy = x2-x1, y2-y1
    			dist = sqrt(dx*dx + dy*dy)
    		end, -- 56ms
    	},
    	{
    		"exponent",
    		function()
    			dx, dy = x2-x1, y2-y1
    			dist = (dx^2 + dy^2)^0.5
    		end, -- 40ms
    	},
    	{
    		"multiply/exponent",
    		function()
    			dx, dy = x2-x1, y2-y1
    			dist = (dx*dx + dy*dy)^0.5
    		end, -- 43ms
    	},
    	{
    		"multiply/exponent without power",
    		function()
    			dx, dy = x2-x1, y2-y1
    			dist = dx*dx + dy*dy
    		end, -- 40ms but wrong result!
    	},
    	{
    		"math.distance",
    		function()
    			dist = math.distance(x1, y1, x2, y2)
    		end, -- 73ms
    	},
    }
     
    -- run all functions
    for i = 1, #data do
    	local block = data[i]
    	local func = block[2]
    	local start = os.timer()
    	for i = 1, 1000000 do -- 1 million repetitions!
    		func()
    	end
    	local elapsed = math.floor((os.timer() - start) * 1000)
    	print(block[1].." ("..elapsed.."ms)", "", "", "", "", "distance: "..dist)
    end
     
    --[[ RESULTS
    *** POINT A: -95201	388	POINT B: -75997	76396
    *** DISTANCE: 78396.49022756057
    sqrt (56ms)					distance: 78396.49022756057
    exponent (40ms)					distance: 78396.49022756057
    multiply/exponent (43ms)					distance: 78396.49022756057
    multiply/exponent without power (40ms)					distance: 6146009680
    math.distance (73ms)					distance: 78396.49022756057
    ]]
    my growING GIDEROS github repositories: https://github.com/mokalux?tab=repositories
Sign In or Register to comment.