How to properly pool assets?

I’m trying to implement object pooling but am running into a memory issue. Every time I’m using an object from a pool and sending it back to the pool, the memory increases. But that shouldn’t be the case, should it? Since I’m reusing the same object?

Pool.lua

local pool = {} local poolmt = {\_\_index = pool} local Piece = require "Piece" local mydata = require "mydata" function pool.create(parameters)     local function newObject()             local obj = display.newSprite(Base, seq)             obj:setFrame(newO)             obj.isVisible = false             function obj:reset()                 obj.alpha = 1                 obj.rotation = 0                 obj.anchorX = 0.5                 obj.anchorY = 0.5                 obj.x = 0                 obj.y = 0             end                          return obj             end         poolSize = poolSize or 16     assert(newObject, "A function that returns new objects for the pool is required.")     local freeObjects = {}     for \_ = 1, poolSize do         table.insert(freeObjects, newObject())     end     return setmetatable({             freeObjects = freeObjects,             newObject = newObject         },         poolmt     ) end function pool:obtain()     return #self.freeObjects == 0 and self.newObject() or table.remove(self.freeObjects) end function pool:free(obj)     assert(obj, "An object to be freed must be passed.")     obj:scale(1, 1)     obj.isVisible = false     table.insert(self.freeObjects, obj)      end function pool:clear()     for k in pairs(self.freeObjects) do         print("clear "..k)         self.freeObjects[k] = nil     end end return pool

Implement pool in actual code…

A button…

            local closeBtn = display.newGroup()             sceneGroup:insert(closeBtn)             local close\_btn\_s = pools[86]:obtain()             close\_btn\_s.pool = pools[86]             closeBtn:insert(close\_btn\_s)             close\_btn\_s.isVisible = true                          local close\_btn = pools[98]:obtain()             close\_btn.pool =  pools[98]             close\_btn.isVisible = true                         closeBtn:insert(close\_btn)  ...add touch listener to closeBtn          

Removing button using a function clearSprite()

clearSprite(closeBtn)

closeBtn = nil

local function clearSprite(group)         if not group then return false end         if group.touch then             group:removeEventListener( "touch")             group.touch = nil         end         local n = group.numChildren         if (not n) or n == 0 then             if group.pool then                                 group.pool:free(group)             else                 group:removeSelf( )             end             group = nil                     else             for i = n, 1, -1 do                 local child = group[i]                 clearSprite(child)             end         end     end

 

Hope someone could spare the time to take a look at all this stuff. I can’t figure out where I’m going wrong with this. Thanks.

 

 

 

 

First,
 
Are you doing this for fun or as an experiment?
 
If you’re doing this to ‘optimize’… don’t.   It is probably a waste of time and effort that will only lead to bugs.  
 
If this is for fun… continue reading.
 
 
Second,  
 
While I appreciate the code post (too many blank lines… which makes it hard to see w/o lots of scrolling back and forth), in this a case a downloadable mini-demo that can be run and examined would be easier to help with.
  
 
I played around with your code and made some changes:

  • changed it to a basic module and got rid of metatable stuff…If you end up  using more than one pool there are easier ways to do this.

  • split tracking of objects into three tables: all, free, used

  • reduced external pool functions to get() and destroy()
    The following may contain typos
     

    – Pool.lua local pool = {} local Piece = require “Piece” local mydata = require “mydata” local all_objects = {} local used_objects = {} local free_objects = {} – =============================================== – Internal module helper functions – ================================================ local function free( self ) used_objects[self] = nil free_objects[self] = self – self.alpha = 1 self.rotation = 0 self.anchorX = 0.5 self.anchorY = 0.5 self.x = 0 self.y = 0 self.xScale = 1 self.yScale = 1 self.isVisible = false end local function newObject( params ) params = params or {} local obj = display.newSprite( … add proper values as needed … ) obj:setFrame(newO) – may want to add more code here … – Attach free helper obj.free = free – put object in all and used tables. all_objects[obj] = obj used_objects[obj] = obj return obj end – =============================================== – Module functions (exposed externally) – ================================================ – Get free object or create new one. function pool.get() for k,v in pairs( free_objects ) do free_objects[v] = nil used_objects[v] = v v.isVisible = true return v end return newObject( params ) end – Destroy all objects in pool – Warning: If objects are refered to elsewhere, their garbage collection will be defered – till that reference is cleared. function pool.destroy() for k,v in pairs( all_objects ) do display.remove(v) end all_objects = {} used_objects = {} free_objects = {} end return pool

To use this simplified pool module:

-- Getting pool objects (created as needed) local obj1 = pool.get() local obj2 = pool.get() local obj3 = pool.get()

Later, you can free an object:

obj2:free() obj2 = nil

Then you can get new objects (which will re-use the freed object and create one new one):

local objA = pool.get() -- re-used object from before local objB = pool.get() -- new objects

At this point there are 5 pooled objects.
 
Finally, at any time you can destroy them all as follows:

pool.destroy() obj1 = nil obj3 = nil objA = nil objB = nil

One more note.

I’ve experimented with this before for fun and found that pooling was actually slower than the alternative. 

The slowness comes from all the ‘reset object state’ work.  Accessing the object’s fields is slow and wasteful.

I looked through my old answers and found code and a video from back in Feb 2017:

https://www.youtube.com/watch?v=AdzG43DYWkY

answer to this thread: https://forums.coronalabs.com/topic/67730-spawning/#entry350563

( be sure to read my response in the thread )

The code: https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2017/02/pooling.zip

Object pooling is always faster and more memory efficient than create/destroy.  Especially considering memory leakage in Corona framework.

@SGS

I did show, with my simple example, that objects with bodies got a slight advantage from pooling, so I will believe there are cases where it is more efficient, but to assert this is always this case… I don’t agree. 

I also demonstrated that simple cases measure as ‘the same’ as pooling in terms of creation speed and memory usage.

Having said that, your games are on the extreme end of what you can do with Corona, so if you found that pooling helped you, I’ll buy that.

I’d just say that, this user should create prototypes to measure pooling versus non-pooling for his or her usage and see if there is a benefit before signing up for the extra headache of creating and maintaining the system for a full game. 

Curiosity Question:    What memory leakage?  Do you run into memory leaks in your games?  Have you identified specific features of Corona that are leaky?  That would be a nice list to have for anyone considering a large project like yours.

Curiosity Question 2:   I know you’re busy, but would you care to share a high-level view/summary of your pooling system architecture?  That might help this user if s/he is making a complex one.

@alternatacc,

Be aware, SGS has created a number of highly successful and incredibly sophisticated games.  So, if you’re planning to create something highly complex and/or large you should heed his advice on this.  

Most of my games and apps have been very simple by comparison, and I tend to think most users starting out are creating smaller games, thus my advice on pooling is different.

Hopefully he sees this post and answers back with additional details.

Hi Ed how’s things going?

I will give a simple example, suppose you have space invaders and you are firing bullets.  To create a new bullet (load an image and attach a pyhsics body) each time a player fires is incredibly wasteful.  If a ship can have 10 bullets live at any one time then instantiate 10 bullets, with physics, and show/hide/reposition as necessary.  I am sure you will agree here?

When you scale it is even more important to optimise memory usage.

Another example is texture memory management.  I preload only the required textures into a custom texture buffer (i made) on game load.  Each asset in my game requires 2 different textures and one mask. I only “draw them” (and maybe many copies of) when the objects actually becomes visible.  It is not possible for my games to load every texture “just in case”.  Having tens of thousands of newImageRects from pooled textures halved my load speed and almost doubled my frame rate.  This is only a bit of the optimisations I have to do to actually get my games to run at a decent speed on low/medium spec devices.

I am more referring to images rather than generic objects.  There is an known issue with display objects leaking memory over time when you have thousands of create/destroy cycles.  I will admit, for a lot of cases and especially when running on a PC that pooling can not make much difference.  But for mobile targets it is definitely worth it.

I agree with @SGS in that it doesn’t even have to be a particularly big app in order for it to benefit from smart object pooling, especially on mobile devices.

Many years ago, when my friends and I were just starting out with game development, one of the projects that we worked on was an endless runner. We honestly didn’t have a clue about optimisation or even about general good practices. We kept creating new platform pieces to the right of the screen and we kept removing them as soon as they were no longer visible. As the game progressed, the tiles kept moving faster and faster. Needless to say, the performance was horrible on older mobile devices and it just kept getting worse the longer you got in each game/play session.

We naturally stumbled upon object pooling and began to utilise it, even though we didn’t even know the term at the time.

As an endless runner, the game consisted on pseudo-randomly generating fixed size tiles, enemies & traps, power ups, coins, etc. on predetermined intervals. It wasn’t exactly rocket science to know that, if every tile is… say 48 pixels wide, then there is always a set maximum number of tiles that can be displayed at any given time. Same with all other objects.

And so we simply calculated the maximum number of every object that could ever be visible at the same time and we just created tables for them. Everything was reused and nothing was ever removed. We used sprites for our display objects and whenever a tile, for instance, moved off screen, we just changed the sprite’s frame, moved it back to the right side of the screen and enabled or disabled some parts of the tile’s physics body.

The performance between pooling and not pooling assets were night and day for old devices, and the game’s performance stayed the same regardless of how fast the tiles were moving.

@SGS - Thanks for the response!  I am doing OK.  I am back doing electrical and computer engineering work, but I stop in here occasionally to check on questions.

Bullets, and other similar multi-use in a short time-span objects. - Definitely agree.  That is an excellent case for pooling.  

PC versus mobile - Excellent distinction!  It is easy to get used to good performance on the dev-machine/simulator and forget the hard realities of mobile devices (especially the older targets).

@XeduR @Spyric - Thanks for your feedback on this too.

I’m sure @alternatacc and others now have lots to think about and take away from the topic.