Multiple Stages and resource destruction facilities

As for the documentation, the stage object is a “special” group where objects are referenced by default.
It would be very useful to have a built-in set of functions in order to manage different “stages” as “pages”, in order to easily free memory and transition from a page to the next.

As an example, think of an app with multiple “pages” where each page loads a set of animations. Since each page has its own set of anims, previous anims must be released and removed from the current stage… although having just one stage forces you to keep track of the resources referenced by each page.

While this is not extremely hard to do in Lua, IMHO each user implementation will be prone to memory leaks and needs some careful code planning.

Since I’m new to Corona - but not to Lua - please let me know if I’m missing something. [import]uid: 5750 topic_id: 1100 reply_id: 301100[/import]

Look into groups. The default stage is just that, a group. You can create as many groups as you want. Just watch out for that they are rendered in the order who you create them. [import]uid: 5712 topic_id: 1100 reply_id: 2798[/import]

Hi Mike,
yes, but it’s not clear what happens to objects nilled in a group. Are they removed from the stage as well ? Is memory released if I destroy a group but I don’t touch the stage group ? How much time is required for resources to be freed/loaded ? (object load/destroy functions could notify when the operation is complete)

Standard Lua behavior is that if there is a reference of a certain object in a global environment, the object isn’t marked for collection.

I shall check this with some tests…and see if there’s trace of nilled groups or objects in the stage group.

[import]uid: 5750 topic_id: 1100 reply_id: 2799[/import]

Could you share the results of your tests please as I am uncertain about how to delete an object correctly too. [import]uid: 5712 topic_id: 1100 reply_id: 2800[/import]

Sure, will do.

As of now, I’m sure objects are collected if removed from the stage group. In fact, by removing all objects from stage, Corona stops running automatically - i.e. no object to process anymore.

My current implementation keeps track of the resources loaded by each page/group and removes objects from stage once new ones are safely loaded. [import]uid: 5750 topic_id: 1100 reply_id: 2802[/import]

Afaik you can have an display object only in one group.

When you create an display object it will be initially in the stage group (always)

If you create your own group and insert your object into that group it will be removed from its current parent automatically.

I was also struggling when I first read about those stuff. It is not clear from the documentation for me neither.

Esp. those advices to obj=nil after a obj.parent:remove(obj) in the documentation is asking for confusion.

You do not need to nil the object … you need lose all references to the object. Having the object in the stage or any group is just having a reference to it somewhere.

If you use a display group for holding your objects … you won’t need to nil em.

grp=display.newGroup()  
  
r1=display.newRect(...)  
-- here r1 is child of stage  
grp:insert(r1)  
-- here r1 is only child of group  
r2=display.newRect(...)  
grp:insert(r2)  
  
-- at this state grp is child of stage  
-- r1 and r2 is child of grp  
-- and r2 is above r1 in the display  
  
parent.r1:insert(r1)  
-- grp:insert(r1) would be equivalent!  
  
-- now r1 is on top if r2 .. still child in grp  
  
r1.parent:remove(r1)  
  
-- now the rect object r1 is removed from the display  
-- but it is still allocated because r1 is referencing it  
  
r1=nil  
  
-- now the rect object is gone  

So far for those examples… BUT

In real life situations you may just add the display object to a group which was created with the use of a local variable. I do this a lot…

Imagine you create dice faces. I would create 6 groups and every one of this groups is getting dots.
But I would not keep track of the objects which represent the dots.

I just keep track of the groups they are in.

Now imagine you want to change the image you used for the dots with another one.

Now you iterate backwards through the group and call obj.parent.remove(obj) for every single dot and add the new ones. You do not “nil” anything. As there are no references to those objects.

Another example… I create my buttons roughly like this:

function Foo:createButtons()  
  
 self.butA = display.newGroup()  
  
 local tmp=display.newRect(0,0,...)  
 tmp:setFillColor(...)   
 butA:insert(tmp)  
 -- button has now a background  
  
 tmp=display.newText(5,5,...)  
 tmp.setTextColor(...)  
 butA:insert(tmp)  
 -- text is over background now  
  
 -- move the button somewhere (all stuff in the group)  
 butA.x=100  
 butA.y=120  
  
end  
  
-- to change the color of the button background  
  
self.butA[1]:setFillColor(..)  
  
-- to change the text color of the button  
  
self.butA[2]:setTextColor(..)  
  

You can see… the display group is at the same time the “storage” for the objects.

If you remove them… they are going to be deallocated because this is the only reference to them.

To implement two different pages you just

page1 = display.newGroup()  
page2 = display.newGroup()  
  
-- remove page2 from the stage...  
  
page2.parent:remove(page2)  
  
...  
  
-- Switch to page2  
  
display.getCurrentStage():insert(page2)  
page1.parent:remove(page1)  
  

Correct me if I am wrong somewhere please :slight_smile: [import]uid: 6928 topic_id: 1100 reply_id: 3241[/import]

Hi Oder,
thanks for the examples.

I noticed that if you physically remove any reference from stage (nilling them), the program ends. In your latest example, by removing page2 Corona should terminate its execution before loading page1, if effectively page2 and all its objects were collected as garbage. Otherwise it just did not collected those objects from page2, which are usually allocated images.

That’s why I was asking for either more documentation on how the system works wrt memory management and garbage collection (i.e. are group tables created with weak keys, values or both ?), or a dedicated system.

While testing my own implementation, which actually nilled each image object in a page table, Corona just terminated as soon as I nilled everything from the current page on stage (as in your last example). So, my concern is that you visually see page1 after page2 in your latest example, but you might not know whether resources from page2 are still wasting memory and video memory.

It is also true that one might want to keep page2 for subsequent page changes, but that’s not always the case… you might also want to physically free all the allocated resources from memory in order to load something more “heavy” than a simple menu page - and nilling images is actually an effective way.

Say you have a menu, that takes 5 - 6 Mbytes in video ram. Then you switch to your main “core” app (a game for instance) and you need almost all the available video ram (20 / 24 Mb, depending on iPhone model)… you must then physically free your menu resources…otherwise the iPhone OS will stop execution on the device (not the Corona simulator) for having exceeded memory allocation.

Till now, users are probably not experiencing this so much because the Apps they’re developing are using little graphic resources, and because Corona does not allocate the same image twice in memory even if you invoke newImage() twice for the same file.

I’d be curious to know if someone made an app with more than 30 Mbytes worth of graphics… and check if they experienced abnormal termination while running on the actual device - or if they ended up nilling images when not needed anymore.

[import]uid: 5750 topic_id: 1100 reply_id: 3245[/import]

Also, try this simple “stress-test”:

  • have a sequence of full resolution png images loaded with the movieclip library
  • the sequence should be larger than 24 Mbytes in memory (png size does not count… do width*height*4*n_frames)

Notice that until you do not play() the sequence, the App does not exceed video ram (i.e. does not crash)… which is odd…it should have overflown memory after the execution of all the newImage() instructions.
As soon as you play() the sequence instead, the app crashes after having displayed roughly the equivalent of 24 Mbytes worth of frames/textures.

One explaination could be that Corona is running at 30 FPS, and GL-ES is doing some texture management between system and video ram. When the underlying GL layer cannot cope with switching textures exceeding video ram at 30 FPS, it crashes. (Corona will request the next texture in memory which isn’t there yet)

…or Corona is managing GL texture creation through newImage() in a different way, rather than all at once.

[import]uid: 5750 topic_id: 1100 reply_id: 3247[/import]

It does not end for me… and my example does never remove all pages … it adds page2 before removing page1.

The whole image rendering stuff you talk about is something I am going to test in another application maybe. My current application does not use a single image so far. I construct everything out of vector objects so far.

What makes me wonder is that you still talk about “nil” something. I tried to explain that "nil"ing something is more or less just removing the reference to the object. It won’t free anything and if you use groups inside of groups or objects inside of groups you need to remove every single object itself … you can’t just nil something to do that.

I believe there are two things to do:

  1. Removing it from the renderer
  2. Freeing the “Lua memory” used

As the groups or the stage is a Lua variable too… it keeps a reference too. But removing those references wont remove the object. At least this is how I understand it.

do  
local a=display.newImage(...)  
local b=display.newRect(...)  
local c=display.newGroup()  
c:insert(a)  
c:insert(b)  
c.parent.remove(c)  
c=nil  
end  

will leak baaaaad (of course)!

do  
local a=display.newImage(...)  
local b=display.newRect(...)  
local c=display.newGroup()  
c:insert(a)  
c:insert(b)  
a.parent:remove(a)  
b.parent:remove(b)  
c.parent:remove(c)  
end  

should not leak… (because: do / local / end also the lua references are gone)

Maybe the devs can put some light at this topic?

Currently I believe that and display.something object gets deallocated when the lua collector finds it not referenced AND the display engine does not have it in any list anymore… but thats just a wild guess of myself :slight_smile:

I think this because I can create a group… remove it from the stage… and insert it back later… as long as I had a valid lua reference (which is needed to reinsert it anyway… hehe) [import]uid: 6928 topic_id: 1100 reply_id: 3253[/import]

Lua can work in different ways as far as garbage collector is concerned.
Have a look at Lua weak tables: http://www.lua.org/pil/17.html

I suppose Corona creates its table objects (like t = newImage, t is a table) with strong references, otherwise your objects would eventually be deleted by the garbage collector.

In a Runtime environment this is especially crucial. You must ensure that tables which are intended to be used are not collected…while you need an explicit method for mark them for collection when you’re done (which is “nil them”).

Take this example:

[lua]do
local mytable = {num = 1} – just to fill it some…

function testWeakness()
print(mytable)
end

Runtime:addEventListener(“enterFrame”, testWeakness)
end[/lua]

If you can see the value printed at each frame, it means that you need to nil “mytable” in order to have it collected from memory. The same goes for any other table returned by Corona constructors.
It simply means that those tables have strong references by default.

As I said, this is understandable in an engine like Corona. The user must be in control of objects in this way… but you also need efficient ways to destroy them - or mark for collection - that are convenient.

The example above should stop working if you make “mytable” a weak table by assigning a metatable to it:

[lua]mytable.__mode = “kv” – set it after you created the table[/lua]

This makes both keys and values “weak”… you’ll now notice that the Runtime will print “nil” with the previous code.

stage:remove() should not influence this mechanism, unless it makes the table given as its argument a weak table automatically. If it does… we go back to my point…you’d need multiple “stages”, working like this, in order to free memory before a subsequent big loading.
Since all objects removed from stage would become “weak”, and thus collectable, you should not use them anymore… that is, stage is empty and the program should exit (or wait for the next stage to load).

I hope you get my point.
[import]uid: 5750 topic_id: 1100 reply_id: 3264[/import]

Again… you can’t “nil” a table… you can assign something… to the last reference to a table!

You can assign anything to that to remove the reference to it…

t={}
t=nil

is the same as

t={}
t=‘anystring’

both times you remove the reference to the table an it can get collected
Using a weak table would make that you can store t inside of a table but it will not count as reference and vanish if the last “real” reference is gone.

That I can see “mytable” in your example is just how it works!? Why should this ever get collected? It is just “global” to the function and if you assign something different. This will be shown in the print and the table will be collected as there is no reference to it anymore.

Introducing weak tables does not help… really… I do not think they have anything to do with the topic we discuss. In addition your usage of __mode is wrong. You can’t just set __mode … you need to use this together with a metatable.

a = {}  
b = {\_\_mode = "kv"}  
setmetatable(a, b)  
-- a has weak keys and values now  

The other thing is that weak tables are something you need for very special things like management of created resources and you can’t use weak tables with display objects because you need to “free” them by removing them from the display manually.

What is this talk about that the program stops if you remove the last element from the stage?

Try this code please…

local frame=0  
  
function render(event)  
 frame=frame+1  
  
 if frame == 60 then  
 print("Adding a rect")  
 local r=display.newRect(0,0,100,100)  
 r:setFillColor(255,255,255)  
 elseif frame == 120 then  
 print("Removing the rect")  
 local s=display:getCurrentStage()  
 s:remove(s[1])  
 frame=0  
 collectgarbage("collect")  
 print(collectgarbage("count"))  
 end  
end  
  
Runtime:addEventListener( "enterFrame", render )  

Nothing stops … everything collects… just as it should. [import]uid: 6928 topic_id: 1100 reply_id: 3267[/import]

“global” means something different. The table is within the scope of the “do … end”, but it’s not “global”.
Anyway, you prove my point with ur latest code. Please use images instead of vector objects… They are not a concern for memory. What happens to video memory if you use an image instead of a vector object? Is it released?

As for stopping execution, try setting the vector object to nil after you remove it from stage. That should terminate execution.

(and yes, I skipped the table and setmetatable part…) [import]uid: 5750 topic_id: 1100 reply_id: 3269[/import]

Sorry… asking me to set that vector object to nil just makes me stop. Pretty pointless to continue. [import]uid: 6928 topic_id: 1100 reply_id: 3285[/import]

Well… As I noted in my first post I was going to experiment with image loading myself a bit!

You may move over to Bug Reports for the results…
[import]uid: 6928 topic_id: 1100 reply_id: 3293[/import]

Oder,
glad you discovered “images” in Corona, and (finally) found out what I was telling you 10 posts ago, i.e. you get a “memory leak” if you only remove() images from stage… you should NIL them too!

Ansca manuals are suggesting you this, I’m telling you this, Lua programming manuals will tell you this, still, you don’t seem to get it.

For the last time: http://lua-users.org/wiki/GarbageCollectionTutorial

Example:
[lua]do – creates a local scope
local mytable = display.newImage(“myImage”) – creates an image - i.e. a table with userdata attached
display:getCurrentStage():remove(mytable) – removes image from stage. This is not collected tho!
– It’s a table with strong reference and with an associated userdata (likely, the GL texture).
mytable = nil – this makes mytable seen as “unused” by Lua, i.e. ready for collection.
– Associated userdata should have a GC method for releasing texture memory…thus freeing physical memory as garbage collector collects “mytable”.
end[/lua]

Look, it’s very simple: newImage() tables can be removed and inserted to the stage… SO, stage:remove(table) doesn’t tell *anything* to Corona as to whether you want the table and its texture to be cleared from *memory* as well (destroyed!), or not!
If you just remove() an image from stage… can you still insert() it back to stage ? If you can, why should Corona DESTROY it when you remove() it from stage ?

In order to *clear memory* and dispose the image/table you must set it to nil! If it’s not referenced in other tables, it gets marked for collection.

You get it now ? If you don’t, I’m real sorry… I didn’t started the thread to convince you of anything, or explain garbage collection.
[import]uid: 5750 topic_id: 1100 reply_id: 3323[/import]

Show me how to do it. Correct my code and make it work! [import]uid: 6928 topic_id: 1100 reply_id: 3326[/import]

I don’t have Corona at hand now, so I cannot test it, but this should work.
Just replace the render function in your code, read the comments and let me know:

[lua] local r – this makes the new image “local” to the “elseif” scope as well

function render(event)
frame=frame+1

if frame == 1 then
local iname=createCopy(image)
print("Adding an image "…iname)
r=display.newImage(iname,system.DocumentsDirectory)
image=image+1
if image > max then
image=1
end
elseif frame == 30 then
print(“Removing the image in stage”)
local s=display:getCurrentStage()
s:remove® – same as s:remove(s[1]), if r is always the only image on stage
r = nil – nils r… this marks the image table for collection
– although, if you don’t load the next image first, Corona might exit and stop execution
frame=0
end
end[/lua]

Edit: changed scope for “r”. Previously it was likely collected immediately, as you registered the function to the Runtime. [import]uid: 5750 topic_id: 1100 reply_id: 3327[/import]

Can somebody jump in and tell him that there is no magic in “something=nil” please?

As I am always curious I ran his code… and his code without the nil line (so r gets assigned another value at the next frame) and everything is the same… of course.

But I found something different… and this is astonishing! This code does different things on different devices! Which I explain in the Bug section after finishing up here …

@erpy there is a nice saying about things you won’t say will not let you look dumb :wink: [import]uid: 6928 topic_id: 1100 reply_id: 3328[/import]

@OderWat,

I took some time to run the code on the Simulator with both setting the object to nil (after removal) and not nil’ing the object and I couldn’t see any difference. My conclusion was Lua did it’s “mark and sweep” garbage collection and removed the object’s resources since it was not referenced by any other variable. I didn’t try this on any actual device.

Tom [import]uid: 6119 topic_id: 1100 reply_id: 3330[/import]

@Fogview,
you really SHOULD know that the Corona simulator AND the Apple iPhone simulator DO NOT simulate memory restriction for the graphic processor.


APPLE --------------------------------------------------------------------
iPhone Simulator includes complete and conformant implementations of both OpenGL ES 1.1 and OpenGL ES 2.0 that you can use for your application development. Simulator differs from the PowerVR MBX and PowerVR SGX in a number of ways:

*

Simulator does not use a tile-based deferred renderer.
*

Simulator does not enforce the memory limitations of the PowerVR MBX.
*

Simulator does not support the same extensions on either the PowerVR MBX or the PowerVR SGX.
*

Simulator does not attempt to simulate either the PowerVR MBX or the PowerVR SGX. Images generated by the simulator are not a pixel-for-pixel match with those generated on the device.
/APPLE --------------------------------------------------------------------

The Corona simulator DO NOT simulate such limits as well.

@Oder,
that goes for you as well:

http://developer.apple.com/iphone/library/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/OpenGLESPlatforms/OpenGLESPlatforms.html#//apple_ref/doc/uid/TP40008793-CH106-SW3

That’s why you have different results on different devices.

[import]uid: 5750 topic_id: 1100 reply_id: 3341[/import]