Object recycling tips
Posted: Mon May 09, 2016 10:35 pm
Okay, some of you may have heard that it is a good practice to recycle/reuse unused object instead of creating new one. That approach is easy to implement in Lua since any table can be refilled with new instance values, and in the end you are going to create no excess overhead (except for minimal utility bytes like locals, temporary references etc).
However, in Love2d not everything is tables, since host application (the engine) manipulates userdata objects in its own way - physics, images, sound data etc, so it becomes a bit far from trivial to reuse objects that use multiple userdata objects, like, if your supercool monster has its own instance of shape-body-fixture, it's probably also has a texture, a quad or a sprite id and everything that it is composed of is going to be instantiated when you create one similar to this way
Your code ofcourse can be different, however general approach is going to be similar anyway. You also can use some kind of ECS helper that does everything for you and minimizes all the hassle, however i tend to invent my own wheels for the sake of education and complete control over things that are going on in my projects.
Now, when we talk about recycling instead of destroying, when you don't need an object instance anymore, you are going to place it into a "bin", which is probably some other table named unusedObjectTable or something. When object is "dead" in a way that it is no longer useful (monster's hp is zero, bullet's timer is zero or it collided with something...), it's reference is going to be deleted from main draw/update loop and is going to be insterted into said table for future use, and when time comes, instead of creating a new instance, you just pop a table reference out of unusedObjectTable, refill its values like if they represent your freshly created monster, and put it into your main processing loops and game goes on. Bear in mind that in the case of userdata objects like physics (and unlike textures which can be stored somewhere as a single instance and can be passed as reference wherever it needed), you should not create new instance of shape-body-fixture, since you are going to waste memory, and you have to check if it exists already before creation, and make inactive in some way when your object is not needed anymore so it will not affect other objects.
And my question is: how should i write object creation function, so that it checks if i create new instance, or use already existent (and unused) ? I don't want to write separate "Object:reinstantiate()" function and i want to use all OOP capabilities that Lua offers (metatables), while refraining from any ECS/helper libs as much as possible.
My object creation code usually looks similar to this one (projectile's example)
I've omitted functions like update and draw since they are irrelevant. This code will just create a new instance of a projectile each time it is called and returns it wherever it was called from for future manipulations.
I ask this because i am not sure how should i pass existent instance's data. I've tried it this way
HOWEVER. In lua, when you call
with a colon (:) sign, it is equivalent to
so above example approach is kinda reduntant.
If someone still reads this - you're my hero. Every approach that comes to mind either gives table setting loops, or two different objects look like if they have variable conflict (visually they "fight" for a position, for timer values etc.), so i guess everything that i do to implement "recycling" just messes up table metaindexes and everything falls apart.
Now, sorry, i am not going to attach .love file of where i'm trying to glue this whole thing to - code is too much of a mess to torture your eyes with, but if anyone is interesed, i am more than willing to discuss missing details.
Edit. Well, actually - i'm going to be fine if answer is going to be "just write Instance:reinstantiate() function and call it on every reuse", however i'm looking for less redundant practice.
Edit2. Dude here implemented somewhat similar to what i'm trying to, however his code is, as far as i can tell, is not self sufficient and dependant on some other module (middleclass), and for my constructs its is going to be a rather painful integration.
However, in Love2d not everything is tables, since host application (the engine) manipulates userdata objects in its own way - physics, images, sound data etc, so it becomes a bit far from trivial to reuse objects that use multiple userdata objects, like, if your supercool monster has its own instance of shape-body-fixture, it's probably also has a texture, a quad or a sprite id and everything that it is composed of is going to be instantiated when you create one similar to this way
Code: Select all
function Monster:new()
local self = setmetatable({},Monster) -- if you use OOPish approach in your lua code...
self.body = ...
self.shape = ...
self.fixture = ...
self.hp = 100
self.sprite = ...
return self
end
Now, when we talk about recycling instead of destroying, when you don't need an object instance anymore, you are going to place it into a "bin", which is probably some other table named unusedObjectTable or something. When object is "dead" in a way that it is no longer useful (monster's hp is zero, bullet's timer is zero or it collided with something...), it's reference is going to be deleted from main draw/update loop and is going to be insterted into said table for future use, and when time comes, instead of creating a new instance, you just pop a table reference out of unusedObjectTable, refill its values like if they represent your freshly created monster, and put it into your main processing loops and game goes on. Bear in mind that in the case of userdata objects like physics (and unlike textures which can be stored somewhere as a single instance and can be passed as reference wherever it needed), you should not create new instance of shape-body-fixture, since you are going to waste memory, and you have to check if it exists already before creation, and make inactive in some way when your object is not needed anymore so it will not affect other objects.
And my question is: how should i write object creation function, so that it checks if i create new instance, or use already existent (and unused) ? I don't want to write separate "Object:reinstantiate()" function and i want to use all OOP capabilities that Lua offers (metatables), while refraining from any ECS/helper libs as much as possible.
My object creation code usually looks similar to this one (projectile's example)
Code: Select all
Projectile = {}
Projectile.__index = Projectile
Projectile.timer_max = 5 -- these varaibles are global and unchanged for every instance and serve as public constants
function Projectile:new(id)
local self = setmetatable({},Projectile)
self.id = id
self.timer = self.timer_max
self.body = -- vanilla body creation code
self.shape = -- vanilla shape creation code
self.fixture = love.physics.newFixture(self.shape,self.body)
return self
end
I ask this because i am not sure how should i pass existent instance's data. I've tried it this way
Code: Select all
function Projectile:new(id,instance)
local self
if instance then
self = instance -- if existent instance is passed then we use it
else
self = setmetatable({},Projectile) -- otherwise we create new table
end
-- this is important since we either use existent body instance or create new one
-- for reasons listed above
-- shape and fixture code is similar
-- then, every time object is considered "dead"
-- i'm just going to call self.body:setActive(false)
-- and self.body:setActive(true) on next instantiation
-- if existent instance is used
self.body = self.body or love.physics.newBody(...)
self.shape = self.shape or love.physics.newShape(...)
self.fixture = self.fixture or love.physics.newFixture(...)
return self
end
Code: Select all
someObjectInstance:someMethod(some_argument)
-- and now you can use semi-keyword "self" to manipulate instance's data
Code: Select all
someObjectInstance.someMethod(someObjectInstance, some_argument)
If someone still reads this - you're my hero. Every approach that comes to mind either gives table setting loops, or two different objects look like if they have variable conflict (visually they "fight" for a position, for timer values etc.), so i guess everything that i do to implement "recycling" just messes up table metaindexes and everything falls apart.
Now, sorry, i am not going to attach .love file of where i'm trying to glue this whole thing to - code is too much of a mess to torture your eyes with, but if anyone is interesed, i am more than willing to discuss missing details.
Edit. Well, actually - i'm going to be fine if answer is going to be "just write Instance:reinstantiate() function and call it on every reuse", however i'm looking for less redundant practice.
Edit2. Dude here implemented somewhat similar to what i'm trying to, however his code is, as far as i can tell, is not self sufficient and dependant on some other module (middleclass), and for my constructs its is going to be a rather painful integration.