Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
scenemanager and managing memory — Gideros Forum

scenemanager and managing memory

petecpetec Member
edited April 2012 in General questions
Some VERY basic questions below, but I'd like to check my understanding of scenemanager as I'm having a memory problem when changing scenes repeatedly with something I'm coding and it eventually cripples its performance. I'm very much a suck it and see hobby programmer and so my descriptions will be very non-technical I'm afraid.

I've sort of assumed that:

(1) If graphics and text are added using self rather than stage, then when the scene changes they will automatically be removed without me doing anything else.
(2) If I add an eventListener to a display object, then the listener will be removed when the object is.

Is that correct or will those leave something in memory?

Also, if I have tables of data in a scene, do those need to be cleared in any way? And what about sounds?

As a test, I've built by a simple thing that has almost identical code in two scenes and uses identical assets. Each scene has the option to either go to the other scene (e.g. scene1 to scene2) or to refresh itself by using scenemanager to go to itself again (e.g. scene1 to scene1). I'm using collectgarbage("count") to print out the memory use as each transition is completed.

I have found that whenever I go from a scene to itself (e.g. scene1 to scene1) the memory use stays pretty well constant. But, whenever I go back and forward between the two scenes, the memory use gradually increases. I've tried adding an enterFrame collectgarbage function and that made no difference. I've tried removing listeners as the transition is initiated and that made no difference! And I think it's the same problem that I'm experiencing in the proper app I'm coding.

Sorry for the long post, but any ideas welcomed!

Thanks, Pete

Likes: MoKaLux

+1 -1 (+1 / -0 )Share on Facebook
«1

Comments

  • It sounds like you are struggling with memory management. I'm not exactly sure how to train someone how to do memory management. Some things create memory, some things release the memory. If you have a global variable that points to something, it wont be released by the garbage collector unless you reassign the variable to nil. If you tell something (e.g. stage) to pay attention to something (e.g. addChild or addEventListener) then it wont be garbage collected.

    You need to watch for actions that increase memory usage (e.g. Texture.new()) and make sure you are only doing it once for each shared resource. I'm guessing that every time a scene is displayed on the stage it is increase memory usage.

    This is all project management and I don't think it's the hardest part of making a game, since doing it wrong just makes the game buggy and it usually is the hardest thing to learn because it's so boring, complex, and unrewarding (lack of crashes is rewarding but it's a backwards incentive).

    You should check out the game template since it's a larger project that does all of this stuff.

    http://www.giderosmobile.com/forum/discussion/480/gideros-game-template
  • ar2rsawseenar2rsawseen Maintainer
    Hey, if it's a simple thing, post your code here, and will try to comment on it. ;)
  • petecpetec Member
    Hi Magnusviri, I think I have struggled with memory management for many years ;)
    But you're right, that's what my problem is. Thanks for all your suggestions.

    ar2rsawseen, I've (hopefully) attached a zip file that contains the code and the assets for my test. If you could take a look at that, it would be great. It's two scenes, both of which show the set of numbers 0-9 one per screen if you scroll sideways using my rudimentary swipe code or tapping the edges of the screen. The red button on each screen swaps you between the two scenes. I've put a print statement in main.lua that prints out the memory use at the end of each transition. I find that the memory stays about the same as I scroll left and right but steadily and relentlessly increases if I keep swapping screens.

    Thanks to both, Pete
    zip
    zip
    memory_test.zip
    191K
  • I think you're just missing the following in the two places where you "'tap' changeSceneBtn" in your endSwipe function:
    self:removeEventListener(Event.MOUSE_DOWN,startSwipe)
    After adding this line to both scene1 and scene2 the memory leak seemed to go away for me.
  • The thing I don't quite understand is why you have to remove the event listener? Since scene manager gets rid of it's reference to "self", shouldn't the object be freed up? Does the code that handles event management keep a reference to all objects that have registered a listener?
  • avoavo Member
    edited April 2012
    @ndoss My current theory is that unless you remove the enter frame/touch/mouse events before the scene change it keeps all the references to objects even when they should be destroyed. Or at least I think they should be destroyed, since the scene itself is set to nil.

    I'm not sure why this is exactly, but I just spent more hours than I'd want to admit figuring out a similar memory leak that related to a self: enter frame event that would just pile up references to all the local images that were referenced inside the function it called, everytime the scene loaded. The function itself was also a local.

    Another thing I have trouble with sometimes is I will need to remove an event listener as in this case, but unless the function is global it will be out of scope of the removeEventListener if I'm trying to call it in exitBegin for example. It might be nice if you didn't have to supply a function to removeEventListener and it just removed all listeners attached to that object (like self:removeEventListener(Event.ENTER_FRAME)

    Anyway I'm pretty new at this still so every week I learn something that makes me realize I was doing it all wrong before, that's probably the case here :P

    and sorry petec for hijacking your thread I hope ndoss' fix worked for you, I tested it and it worked as well.
  • avoavo Member
    I just realized that since you don't remove the event listener, the next time the scene is called you have 2x, 3x, 4x etc event listeners, which creates the multiple references, not objects themselves.

    I'm pretty sure that's whats actually happening. Maybe typing all that out made my brain work again... going to test it real quick.
  • Anyway I'm pretty new at this still so every week I learn something that makes me realize I was doing it all wrong before, that's probably the case here
    You & me both! :)

    Likes: avo

    +1 -1 (+1 / -0 )Share on Facebook
  • avoavo Member
    Well just checking to see if the ENTER_FRAME exists using hasEventListener doesn't work in this case. It actually doesn't exist and is deleted when the scene is apparently, which really makes me ??? what is keeping references to these local objects on the scene.

    I think what I mentioned can be the case sometimes though since I ran into that problem with my own click events I was dispatching (things would happen twice). Yes I need to learn lessons multiple times before I remember anything apparently :P
  • avoavo Member
    edited April 2012
    ah of course every time the scene is loaded self is a new table, so the old enter frame is lost in the abyss or something, and what I put before is exactly what happens.

    Lets see if I can drill it in to my brain this time, remove event listeners every time all the time :P
  • petecpetec Member
    edited April 2012
    Thanks avo and ndoss (no idea how to do those @names...).

    So my assumption that the event listener would be removed when the scene was removed was wrong. Removing the listener certainly does the trick. I double checked by not removing the event listener in my swipeRight function and that produced a memory leak, so it obviously is that.

    Now to go back to the proper thing I'm coding and hunt out all those event listeners.

    Thanks, Pete

    Edit: Oh look, just putting @ in front of a word does it.
  • atilimatilim Maintainer
    edited April 2012
    Hi all,

    I've just looked at the source code quickly and at first sight I think there shouldn't be any memory leak even if you don't remove the event listeners (but there is memory leak). I'll do some tests and try to find why these objects aren't GCed.
  • petecpetec Member
    Thanks atilim. I'll be interested in what you find.
  • petecpetec Member
    Thanks atilim. I'll be interested in what you find.
  • petecpetec Member
    edited April 2012
    I think I must be incontinent (or incompetent or both). I've been doing my best to get rid of memory leaks with some success thanks to the help above but I've now come to another brick wall.

    In fenceScreen.lua in attached file there are two lines (234 and 252) that are apparently causing memory leaks and I really don't know why. One sets the y scale of an object and the other sets alpha to 0. When the lines are commented out, going from the start screen to the fence screen and back again repeatedly causes the memory use to increase very slightly (why?) but if either of the two lines are uncommented, then they cause the memory use to increase more rapidly and keep going up, even if the function that they are in isn't called, which I don't understand.

    I do wonder if it's something wrong that I'm doing with the way that I am generating multiple copies of objects as both the lines are referring to that type of object. The only thing that makes me wonder if that is the case is that if I change line 234 to
    splat[total]:setScaleY(1.15)
    then the larger leak doesn't happen, even though the splats are generated in the same way. The only difference that I can see is that lines 234 and 252 are both in the function that generates the splats, but I don't undersatnd if or why that would make a difference.

    The code is quite flakey at the moment as I've taken out all my safeguards to make it easier to read, so it can lock up if you go backwards and forwards too quickly. Oh, and clicking the numbers splats the fence. With line 234 in action you can see where I'm going!

    Any help welcomed, thanks
    Pete
    zip
    zip
    Fence_splat.zip
    185K
  • I can't do a detailed analysis, but here is what I would do if I were going to. I'd note everything in fenceScreen:init() that creates memory. I don't know the API well enough off the top of my head but I would guess that Sound.new("sounds/balloonBurst.wav") consumes memory. I know Texture.new("fenceAssets/bg.jpg" does. I bet TTFont.new("ComickBook_Simple.ttf", 30) does.

    Just a small gripe about tabs, I can't easily see where fenceScreen:init() ends. After looking a little more I'm guessing the whole file is that function?

    It looks like gotoMenuScreen is where all memory created by init should be cleared. Although I don't know for sure if you have to do this, I'd at least test it. In gotoMenuScreen I'd assign every variable that points to textures, sounds, fonts, etc to nil. See if that fixes it.
  • petecpetec Member
    edited April 2012
    Thanks. Sorry about the tabs - because I'm using scenemanager and know that the whole thing is in the init function, I didn't do the 'first level' of tabs.
    I'll give your suggestion a go, although if nilling is needed I'm not sure why it would behave so differently when the commented lines are included or excluded. Also, I thought that when scenemanager removes the scene the variables were set to nil in that process, but I could well be wrong.
    Thanks, Pete

    Edit: Just to say that I've tried setting all suggested variables to nil before exiting the screen and it made no difference.
  • I'd have to look at it in more depth, which I can't do right now to know for sure. But basically, if anything outside of fenceScreen (scenemanager, the event framework, etc) has a pointer to anything allocated inside of fenceScreen then that reference stays around even after fenceScreen is removed, the garbage collector wont release the memory.

    Memory management is complex but I think the hardest thing is knowing what types of behaviors should just be avoided and using discipline to allocate and release memory in consistent and predictable ways.

    For example, a function like getWidth() should never ever allocate memory that isn't released, even if you have a function like cleanupGetWidth(). In 6 months you'll forget all about cleanupGetWidth() and you'll forget you need to call it.

    As a rule of thumb, when I see .new() I always assume that memory is allocated and I need to nil it out. I don't know if lua or Gideros has any other functions that have memory allocation side effects, but that's what you are looking for.

    Global variables could also be a culprit. The Lua garbage collector will only release something that has no variables pointing to it. So if you create a scene and store it in an global array and you just add scenes to that array then memory will increase. You obviously wont do that intentionally, but addEventListener might just do exactly this.

    It's possible to go poking around all of the global variables. I had a script that did a great job printing the global namespace but I can't find it. Here is something simple to give you an idea of what you can do.
    for key,value in pairs( _G ) do
         print( key.."=" ..tostring(value))
    end
  • petecpetec Member
    Thanks for all your help.
  • ndossndoss Guru
    edited April 2012
    @atilim, after looking at the first memory leak example, I think it might not be caused by a memory leak within Gideros -- it might be @petec's code.

    @petec, I changed scene1.lua from your original problem and got rid of the removeEventListener that we had added to get rid of the memory leak -- the new version doesn't seem to have a memory leak even though it no longer removes the event listener. To do this, I converted all of your functions to scene1 class methods and changed some of the variables you'd defined as locals to be class variables. I believe that doing this in your second example would also fix its problems.

    I couldn't quite get my head around the exact problem, but I think the problem you have with both posted examples is related to closures and the following pattern in your code:
        local func1
        local func2
        local otherVars = ....
     
        function func1()
             ... do something with func2 and otherVars ...
        end
     
        function func2()
             ... do something with func1 and otherVars ...
        end
    Back to your second example, you asked why would the following line cause a memory leak even though it's not called?
    splat[total]:setScaleY(1.15)
    I believe the reason is that even though it's not being called, the "splat" table is still being captured in a closure and is therefore increasing the amount of memory that's being lost.

    Here's the changed version of scene1 that doesn't require the explicit removal of the event listener.
    scene1 = gideros.class(Sprite)
    function scene1:init()
     
    ---------------------------------------------------------------
    -- DISPLAY SIZE VARIABLES
    ---------------------------------------------------------------
    self._H= application:getLogicalWidth()  -- H and W swapped for landscape
    self._W= application:getLogicalHeight()
    local centreX= self._W/2
    local centreY= self._H/2
     
    --------------------------------------------------------------
    -- SOUND VARIABLES AND FUNCTIONS
    ---------------------------------------------------------------
    self.picChangeSnd= Sound.new("sounds/picChangeSnd.wav")
    self.clickSnd= Sound.new("sounds/click.wav")
     
    --------------------------------------------------------------
    -- VARIABLES
    ---------------------------------------------------------------
    local word={"zero","one","two","three","four","five","six","seven","eight","nine"}
     
    ---------------------------------------------------------------
    -- INITIAL SCENE DISPLAY OBJECTS
    ---------------------------------------------------------------
    local bg=Bitmap.new(Texture.new("commonAssets/bg.jpg",true))
    self:addChild(bg)
     
    local numeral=Bitmap.new(Texture.new("commonAssets/number"..countNumber..".png",true))
    numeral:setAnchorPoint(0.5, 1)
    numeral:setX(centreX-150)
    numeral:setY(self._H-120)
    bg:addChild(numeral)
     
    local font = TTFont.new("TektonPro-Bold.otf", 72)
    local numeralText=TextField.new(font,"")
    numeralText:setTextColor(24,24,150)
    numeralText:setText(word[countNumber+1])
    numeralText:setX(numeral:getX()-(numeralText:getWidth()/2))
    numeralText:setY(numeral:getY()+64)
    bg:addChild(numeralText)
     
    local sceneText=TextField.new(font,"scene 1")
    sceneText:setTextColor(24,24,150)
    sceneText:setX(centreX+50)
    sceneText:setY(centreY)
    sceneText:setScale(0.7,0.7)
    self:addChild(sceneText)
     
    ----------------------
    -- changeSceneBtn
    ----------------------
    self.changeSceneBtn =Bitmap.new(Texture.new("commonAssets/arrowDownBtn.png",true))
    self.changeSceneBtn:setAnchorPoint(1, 0)
    local changeSceneBtnX=self._W-10
    local changeSceneBtnY=10
    self.changeSceneBtn:setX(changeSceneBtnX)
    self.changeSceneBtn:setY(changeSceneBtnY)
    self.changeSceneBtn.clickable=true
    self:addChild(self.changeSceneBtn)
     
    ---------------------------------------------------------------
    -- GOTO NEXT SCREEN
    ---------------------------------------------------------------
     
    self:delayedAddSwiping()
     
    end -- of scene1:init()
     
    function scene1:delayedAddSwiping()
       local swipeTimer = Timer.new(200, 1)
       swipeTimer:addEventListener(Event.TIMER, function() self:addEventListener(Event.MOUSE_DOWN, self.startSwipe, self) end)
       swipeTimer:start()
    end
     
    function scene1:startSwipe(e)
       self.swipeSX = e.x
       self.swipeSY = e.y
       self:addEventListener(Event.MOUSE_UP, self.endSwipe, self)
    end
     
    function scene1:endSwipe(e)
       self:removeEventListener(Event.MOUSE_UP, self.endSwipe, self)
       local swipeX= e.x - self.swipeSX
       ------------------
       --  swipe screen
       ------------------
       if swipeX>75 then
          self:swipeRight()
       end
       if swipeX<-75 then
          self:swipeLeft()
       end
       ------------------
       --  tap something
       ------------------
       if swipeX<=75 and swipeX>=-75 then
          -- 'tap' left  border of screen
          if e.x<80 and e.y>100 then
             self:swipeRight()
          end
          -- 'tap' right  border of screen
          if e.x>self._W-80 and e.y>100 then
             self:swipeLeft()
          end
          -- 'tap' changeSceneBtn
          if e.x>self.changeSceneBtn:getX()-self.changeSceneBtn:getWidth() and e.x<self.changeSceneBtn:getX() and e.y>self.changeSceneBtn:getY() and e.y<self.changeSceneBtn:getY()+self.changeSceneBtn:getHeight() then
             self.clickSnd:play()
             --self:removeEventListener(Event.MOUSE_DOWN,startSwipe) -- BEFORE CHANGE, HAD TO HAVE THIS
             sceneManager:changeScene("scene2", 0.3, SceneManager.moveFromBottom)
          end
     
       end
    end
     
    function scene1:swipeRight()
       if countNumber>minNum then
          self:removeEventListener(Event.MOUSE_DOWN,self.startSwipe, self)
          self.picChangeSnd:play()
          countNumber=countNumber-1
          sceneManager:changeScene("scene1", 0.2, SceneManager.moveFromLeft)
       end
    end
     
    function scene1:swipeLeft()
       if countNumber<maxNum then
          self:removeEventListener(Event.MOUSE_DOWN,self.startSwipe, self)
          self.picChangeSnd:play()
          countNumber=countNumber+1
          sceneManager:changeScene("scene1", 0.2, SceneManager.moveFromRight)
       end
    end
  • petecpetec Member
    Wow @ndoss, thank you so much for taking the time to do all of this!

    I've got a lot to learn, starting with trying to get my head round classes. I can see that your code above actually makes it easier to deal with the various functions than the way I have been writing it.

    The one thing I'm not 100% sure of in the above is why the changeSceneBtn is added as
    self.changeSceneBtn=Bitmap.new(Texture.new("commonAssets/arrowDownBtn.png",true))
    but things like the numeral are added as
    local numeral=Bitmap.new(Texture.new("commonAssets/number"..countNumber..".png",true))
    Is that because the changeSceneBtn is the only one that is referred to in any of the functions outside the init() function? And if I wanted to attach a listener to the numeral with a related function, then the numeral would then have to be a class variable?

    Thanks, Pete
  • ar2rsawseenar2rsawseen Maintainer
    edited April 2012
    local means this variable can be used only in this scope, where it is defined.
    if you store variable in self.someprop as example, then you can access it in any other class method. Say it's a way to use value in class scope - in all it's methods.

    It would be possible to add event handler to both variables
  • ndossndoss Guru
    edited April 2012
    @petec, I took your code and kept reducing it until I came up w/ a small example that still lost memory.

    In the following code, scene1 has a circular reference which I think Gideros should be able to handle deleting so I'm back to thinking it might be a problem in Gideros ... @atilim would have to say for sure.
    -- ========================================================================
    -- scene1
    -- ========================================================================
    scene1 = Core.class(EventDispatcher)
    function scene1:init()
     
       local startSwipe = function()
          local a = self
       end
     
       self:addEventListener(Event.MOUSE_DOWN,startSwipe) 
    end
     
    -- ========================================================================
    -- Free & allocate "scene1" a bunch of times
    -- ========================================================================
    local s = scene1.new()
    for i=1,20 do
       s = scene1.new()
       collectgarbage()
       print('mem',collectgarbage("count"))
    end
    +1 -1 (+3 / -0 )Share on Facebook
  • avoavo Member
    edited April 2012
    I think Gideros keeps the Mouse/Touch/Enter Frame listeners around even though self doesn't exist anymore. As long as you know this you can prepare ahead of time, but it would probably make more sense for Gideros to clear them out since I don't think there's anyway to access them yourself after deleting self, and they can't do anything since that self doesn't exist anymore.

    thank you @ndoss for working on this to get such a simple example of the potential issue.
  • atilimatilim Maintainer
    @ndoss ohh thanks a lot. Still I couldn't look at the memory issue but this code will be a life saver while debugging.
  • petecpetec Member
    @ar2rsawseen thanks for the clear description - got it!
    @ndoss thanks for carrying on looking at the problem.
  • petecpetec Member
    @ndoss Just to let you know that I rewrote the fenceScreen code following the pattern you set with your rewrite of my code, converting all the functions to fenceScreen class methods and the necessary variables to class variables. Once I'd got my head round when to use getParent() to access some of the values I eventually got it working (was a bit of a struggle with things like the onComplete bits in the GTweens).

    The outcome is as you predicted - it got rid of the large memory leak I had before and it behaves just the same regardless of whether the two previously troublesome lines are commented out or included. It is still creeping up about 1 unit per visit, but that's what it did before with the lines commented out, so I guess that might be the other problem you have identified(?).

    Anyway, thanks again for all of your help.
    Pete

  • Thanks @petec, glad it's working!
  • avoavo Member
    Does anyone know what the performance difference would be between using local variables and class variables? Wouldn't class variables be essentially global since they are part of a global table? Or does the fact that they are part of the class table make it faster?
  • atilimatilim Maintainer
    edited April 2012
    Hi all,

    I've finally found the bug and corrected it. It only occurs when a listener function refers to the self as an upvalue (exactly as @ndoss's code)

    Thank you :)
    +1 -1 (+4 / -0 )Share on Facebook
Sign In or Register to comment.