apologies ahead of time for the long post. though it’ll be good for future readers who might have questions.
for starters i just want to say that there’s only 1 way to do OOP in Lua. ( well, maybe 1.5 ways. this single way is by using the metatable. i totally understand why it seems like there are a ton of ways because there are many OO frameworks, but in the end they are all just *implementations* of the metatable method. this is the underlying mechanism put in place by the designers of the Lua language so we could implement OO and other cool stuff.
(for clarity i’m not counting the method i term “1/2” or situations where some software engineer might have hacked their own underlying Lua structure. i’m just referring to your run-of-the-mill OO which probably accounts for 95%. BTW, the Javascript world has a similar architecture as Lua and also a similar situation with a plethora of OO frameworks.)
i just wanted to clear that up.
now on to the show…
Event Listeners
as you all have discussed, each function and table in Lua has its own unique ID. to see some examples here is some Lua code and the resulting IDs:
[lua]print( function() print(“hello”) end )
print( function() print(“hello”) end )
print( { } )
print( { } )
– calling the above resulted in the following IDs.
– function: 0x10c6d6700
– function: 0x10c6d6790
– table: 0x10ed1a6d0
– table: 0x10ed1a770[/lua]
as you can see, each of the IDs is different even though the structures we’ve printed (our function and anonymous table) “look” the same. this is important to note because any time you call addEventListener() with a function or table (as the second argument), it must be unique or else things won’t work as expected.
to understand why, imagine that the Runtime object has a data structure where it keeps a list of functions and objects that it needs to notify. it keeps track of all of these things by using their unique ID. when it needs to send out a notification, it loops through each item in the structure and makes the event call using the ID.
the code and data structure could be something like the following, totally-oversimplified example:
[lua]-- table of IDs which we’re to notify
Runtime.things_to_notify = {
“function: 0x10c6d6700”,
“function: 0x10c6d6790”,
“table: 0x10ed1a6d0”
}
function Runtime.addEventListener( event_name, target_id )
– add target id to our list
table.insert( Runtime.things_to_notify, target_id )
end[/lua]
now, when you call removeEventListener() to remove the callback, you have to send in the same identifier that was used for the addEventListener() so it can find it in its data structure and properly remove it.
in a nutshell, this is the underlying principle which needs to be followed.
Code Review
so, the issue with original code from thomas6 was that the Runtime was getting different identifiers for the calls to add/remove. for the call to removeEventListener(), Runtime couldn’t find the ID to remove in its lookup table so the enterFrame callback continued to work.
[lua]-- original code
– each one of these is sending in a different ID
– because they’re not the same anonymous function
Runtime:addEventListener(“enterFrame”, function() self.frameLoop(self) end)
Runtime:removeEventListener(“enterFrame”, function() self.frameLoop(self) end)[/lua]
the example code from beckslash won’t work for two reasons:
- Runtime is given the same listener, so the ID isn’t unique
- the variable ‘selfInstance’ can only hold one ‘self’ at a time.
in both of these issues everything gets overwritten, so it’s only the last object that works. obviously this isn’t good for OO situations where you usually need to track more than one instance.
(to clarify, in a lot of Corona examples you do see a listener passed as beckslash has shown, but those are usually in non-OO situations, eg like an event callback for a single button, etc)
Object Oriented Callbacks
there are two methods to deal with this situation. neither is better than the other and which one you prefer will probably be a matter of taste.
following you’ll find your code with the necessary modifications for each method and notes in the comments. (i’ve tested each one of these to make sure it works.
Object Method
i think this one is the cleanest, however i don’t like “polluting” my Object Class namespace with names of events.
it also won’t work in every situation – once an object class needed to respond to the same *event name* from different objects (eg, “touch” event). each touch callback had different functionality to implement, obviously though my class only had a single method i could name ‘touch’.
[lua]local classBullet = {}
local classBullet_metatable = { __index = classBullet} – metatable
– the important thing here is that we have a method that has the
– same name as the event in which we’re interested
– in this case we’ve changed ‘frameLoop’ to ‘enterFrame’
function classBullet.enterFrame(self, event)
self.image.x = self.image.x + math.cos(math.rad(self.angle))
self.image.y = self.image.y + math.sin(math.rad(self.angle))
if self.image.x > 280 or self.image.x < 40 or self.image.y < 120 or self.image.y > 360 then
self.kill(self, event)
end
end
– then here we just send in ‘self’ to the event listener registration
– the event listener mechanism is smart enough to search
– for the method ‘enterFrame’ on the object referred to by ‘self’
– each ‘self’ is a Lua table and each also has a unique id
function classBullet.start(self)
Runtime:addEventListener( “enterFrame”, self )
end
function classBullet.kill(self, event)
Runtime:removeEventListener( “enterFrame”, self )
end
– CONSTRUCTOR –
classBullet.new = function(bulletList, position)
local newBullet = {}
newBullet.angle = math.random(360)
newBullet.image = display.newCircle(10, 10, 10)
newBullet.image.x = 160
newBullet.image.y = 240
newBullet.image.rotation = newBullet.angle
return setmetatable (newBullet, classBullet_metatable)
end
return classBullet[/lua]
Closure Method
this method isn’t very pretty, but it always gets the job done. here we’re taking advantage of the fact that Lua is a flexible scripting language so we can stash data on just about anything.
[lua]local classBullet = {}
local classBullet_metatable = { __index = classBullet} – metatable
function classBullet.frameLoop(self, event)
self.image.x = self.image.x + math.cos(math.rad(self.angle))
self.image.y = self.image.y + math.sin(math.rad(self.angle))
if self.image.x > 280 or self.image.x < 40 or self.image.y < 120 or self.image.y > 360 then
self.kill(self, event)
end
end
function classBullet.kill(self, event)
– let’s get back our unique anonymous function
local callback = self._f
self._f = nil
Runtime:removeEventListener( “enterFrame”, callback )
end
function classBullet.start(self)
– this is our unique anonymous function
– we need to give *this one* back to the event listener
– because this one has the unique ID we used to register our callback
local callback = function( event )
self.frameLoop(self, event)
end
– so let’s save the function for later
self._f = callback
Runtime:addEventListener( “enterFrame”, callback )
end
– CONSTRUCTOR –
classBullet.new = function(bulletList, position)
local newBullet = {}
newBullet.angle = math.random(360)
newBullet.image = display.newCircle(10, 10, 10)
newBullet.image.x = 160
newBullet.image.y = 240
newBullet.image.rotation = newBullet.angle
return setmetatable (newBullet, classBullet_metatable)
end
return classBullet[/lua]
Cheers,
dmc
btw, i’m one of the many who have created a framework to do OO in Lua/Corona so i’m partially to blame for the mess.
http://developer.coronalabs.com/code/dmc-corona-library
FYI, if you’re interested in seeing other OO code for tips, etc, there are a lot of examples in the library. among them are even some of the standard Corona examples reworked in OO.
for fun i also re-wrote Jonathan Beebe’s Ghost vs Monsters using OO:
https://developer.coronalabs.com/code/ghost-vs-monsters-oop [import]uid: 74908 topic_id: 35326 reply_id: 141317[/import]