Advanced OOP removeEventListener question

Hi there boys and girls,

I have a tricky situation here. I’m getting the hang of OOP and loving it, but I’m hitting a serious bump with eventListeners, so I was hoping you could look at my code, try it and help out. You only need the two lua files I’ll post below, no other files like graphics needed so it should be easy to run this in a minute or two.

The idea is that an eventListener (called frameLoop) in the bullet.lua file moves the bullets. When they reach a certain distance this eventListener should be removed, but apparently this doesn’t work. I’m well aware of forward referencing etcetera, but this is a bit different.

So, here’s the main.lua file that just creates a couple of circles (bullets):

-- main.lua --  
--------------  
  
local classBullet = require("bullet")  
  
local bulletList = {}  
  
local function createBullet()  
  
 if system.getTimer() \< 1000 then  
 bulletList[#bulletList+1] = classBullet.new(bulletList, #bulletList+1)  
 bulletList[#bulletList]:start()  
 end  
end  
  
timer.performWithDelay(1, createBullet, 0)  
  
local debugText = display.newText("test", 160,40, native.systemFont, 16)  
  
local function showMemory()  
  
 local memUsed = collectgarbage("count") / 1000  
 debugText.text = memUsed  
  
end  
  
Runtime:addEventListener("enterFrame", showMemory)  

and here’s the bullet class:

-- bullet.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  
 print("when this get's called the circles should stop moving!")  
 self.kill(self, event)  
 end  
end   
  
function classBullet.kill(self, event)  
 print("but they don't because this removeEventListener doesn't work!")  
 Runtime:removeEventListener("enterFrame", function() self.frameLoop(self) end)  
end  
  
function classBullet.start(self)  
 Runtime:addEventListener("enterFrame", function() self.frameLoop(self) end)   
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  

I would REALLY appreciate it if someone could help me out here.

Best regards,
Thomas [import]uid: 70134 topic_id: 35326 reply_id: 335326[/import]

Brent, Rob, anyone - just a friendly bump because I seem to have gotten ZERO views :slight_smile:

That’s what you get when you put “advanced OOP” in the title of a post. Noone dares to ventures into the murky realms of OOP in Lua :wink:

Cheers,
Thomas [import]uid: 70134 topic_id: 35326 reply_id: 141055[/import]

Only think I can think of is whether it’s struggling to remove it because you’re using an anonymous function.

Be interested to see if somebody has the solution - it’s things like this that stop me from fully going OOP with Corona, a lack of a clear direction. I understand that’s a complaint levelled at lua in general, but would really like to see Corona endorse a specific way to do OOP.

This could be something ‘under-the-hood’ and we might need somebody like Walter to explain why this will/won’t work with OOP design and runtime listeners. [import]uid: 33275 topic_id: 35326 reply_id: 141060[/import]

It’s very likely the anonymous function. Corona needs to know exactly what to kill and while your code looks identical in add/remove, the table hexcode for it would not be. So you’re basically saying add(table 0x1) but remove(0x2) or something similar.

Best to just declare the function as part of the object and call it from there, I imagine… [import]uid: 41884 topic_id: 35326 reply_id: 141062[/import]

Even though the body of the anonymous functions are the same, they are different functions (They each have different memory addresses).

Just make a function with a name and use that. [import]uid: 70003 topic_id: 35326 reply_id: 141063[/import]

Thanks SegaBoy, Richard9 and Beckslash!

Your answers are very valuable to me and gave me some good insights regarding the problem, but not solutions, unfortunately. Bear with me as I go over your replies and why I still can’t see how this should work.

You all mention the problem being the “anonymous” function. I get your remarks, but how do you use closures without making the function anonymous? Unfortunately the need to pass the parameter “self” to my frameLoop function means that I have to use a closure. Maybe it’s time to start looking at table listeners - I’ll keep you guys posted on that.

Regarding declaring the function as part of the object, it is being declared as part of the class, so it can be accessed via the metatable method, which is the way it should be done, right? If I make it part of the returned instance I lose the OOP aspect. Even then, I have tried restructuring the code so many times, but there’s always a problem popping up, no matter what I do.

Once again, Brent, Rob? :wink: [import]uid: 70134 topic_id: 35326 reply_id: 141177[/import]

[blockcode]
local selfInstance = nil

local function listener()
frameLoop(selfInstance)
end

function classBullet.kill(self, event)
print(“but they don’t because this removeEventListener doesn’t work!”)
Runtime:removeEventListener(“enterFrame”, listener)
end

function classBullet.start(self)
selfInstance = self
Runtime:addEventListener(“enterFrame”, listener)
end
[/blockcode]

This should do the trick. [import]uid: 70003 topic_id: 35326 reply_id: 141181[/import]

Hi Beckslash,

Thanks! I’ll definitely try this after work tonight. I’ll let you know how things work out!

Thomas [import]uid: 70134 topic_id: 35326 reply_id: 141185[/import]

Geat. Let me know how it goes. [import]uid: 70003 topic_id: 35326 reply_id: 141190[/import]

Dmccuskey, thanks and WOW! Wow, wow, wow, what a great and thorough explanation!

I didn’t have time to go over this material during the past few days but got some time last night to check this out and of course it works like a charm. Not only that but I actually understand a lot more about OOP in Lua and Corona.

Some points to make or agree on:

  1. Yes, you are very right. There is only one right way to do OOP in Lua, and that is using metatables. In my search for OOP-methods I have come across many methods and structures, and they all claim that there are “many ways” of OOP, but it is obvious that most of these methods are severely lacking in many aspects (such as overrides, base- and superclasses etc…). Most methods actually only deal with simple instancing as a method for spawning (note: which may very well be enough for a large part of Corona users).

  2. I understand what you mean about my adding and removing eventListeners not referring to the same function - this was an AHA-moment! It’s like printing the same text on two identical sheets of paper: they both look the same, they read the same things, but they are still two distinct and different sheets of paper (and taking away the second one does not mean the first one is removed). I use the second method where you put a reference to the listener function in the ._f key of the instance. Smart method! For people trying to get to terms with this method, I should note that you don’t need to use self._f per se, you can call it whatever you want, like self.listenerFunction. I was confused at first because ._G stands for “the global environment” in Lua, so I though that maybe ._f stood for a certain “functional environment”, but no, it’s just a name.

  3. I don’t understand why there is not yet a definitive tutorial on this by the good folks of Corona - and why some people use the excuses of “there are many ways to do OOP” when it is clear that Lua is designed from the ground up to use the metatable method. Yes, there are shortcuts, but please don’t state that these are equally valid ways to do OOP.

But most of all, wow, wow, wow and thanks again. You have helped me make the jump from intermediate to advanced Lua programmer, I feel! This should be a sticky thread!!!

Thomas [import]uid: 70134 topic_id: 35326 reply_id: 141494[/import]

Just wanted to agree with thomas6, thanks to Dmccuskey for such a thorough explanation on the subject - exactly what’s been needed.

I have followed the DMC libraries, but must admit got confused following the GvM OOP example - I must try again. [import]uid: 33275 topic_id: 35326 reply_id: 141499[/import]

@dmccuskey, thank you for this thorough explanation. I’ll need some while to fully digest this, but I totally appreciate it.

+1 for sticky’ing this thread. Or perhaps, dmccuskey, I wonder if you’d consider contributing to weekly blog/tutorial as a guest?

Naomi [import]uid: 67217 topic_id: 35326 reply_id: 141540[/import]

hey all,

thanks so much for the awesome kudos. they made my Monday ! :slight_smile:

@thomas6

re: #1. you perfectly describe the “1/2” i was referring to earlier – the class of frameworks which use only single-level objects (ie, no metatable). and i fully agree that this method probably works well for a large part of the Corona users.

just to add to the discussion regarding this method, i’ll say that just because there isn’t any hierarchy lookup via metatables, this doesn’t mean that functionality can’t be borrowed by other base classes. these frameworks could be architected so that methods and properties from different base classes are “layered” in one at a time on a newly constructed object. this process would essentially re-write any existing methods/properties which already existed, thereby giving the ability to borrow functionality (though this means “last one in wins !” ). this is fine, because in the end the only thing that is important is that a property lookup or method call on your object succeeds. for many projects there’s no need for more than this.
re: #2. yes, you are correct regarding the key name ‘_f’. there is absolutely nothing special about that name. thank you for clearing that up.
@SegaBoy

yeah, GvM OOP is a fairly complex project. it took me about 40 hours to convert, which was more time than i expected. there was a lot of functionality that had to be split up and re-packaged, including integrating a state machine to control the gameplay. i wouldn’t recommend it as an intro to OO in Corona. :slight_smile:
@Naomi

yeah, i’d be interested in writing a guest article. :slight_smile: what topic do you think would be good ? who should i contact to find out more about the process ?

btw, what is your relationship with Corona Labs ? i see you on the forums a lot.
cheers everyone,
dmc [import]uid: 74908 topic_id: 35326 reply_id: 141588[/import]

apologies ahead of time for the long post. though it’ll be good for future readers who might have questions. :smiley:
for starters i just want to say that there’s only 1 way to do OOP in Lua. ( well, maybe 1.5 ways. :slight_smile: 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. :slight_smile:

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 :slight_smile:

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:

  1. Runtime is given the same listener, so the ID isn’t unique
  2. 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. :slight_smile:

(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. :slight_smile:

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’. :slight_smile:
[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. :slight_smile: 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. :smiley:

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]

@dmccuskey, I have personally connected with people at Corona since I joined Corona community, but really, I’m just a Corona user, who is perhaps a little more active than the most? I think they’d be more than delighted to have someone so knowledgeable to contribute a guest blog post. If I were inside the company, I’d be – so why wouldn’t they? I’ll shoot Brent/Rob a recommendation email.

Cheers,
Naomi [import]uid: 67217 topic_id: 35326 reply_id: 141590[/import]

@Naomi,

that sounds perfect, thanks !

cheers,
dmc [import]uid: 74908 topic_id: 35326 reply_id: 141594[/import]

Brent, Rob, anyone - just a friendly bump because I seem to have gotten ZERO views :slight_smile:

That’s what you get when you put “advanced OOP” in the title of a post. Noone dares to ventures into the murky realms of OOP in Lua :wink:

Cheers,
Thomas [import]uid: 70134 topic_id: 35326 reply_id: 141055[/import]

Only think I can think of is whether it’s struggling to remove it because you’re using an anonymous function.

Be interested to see if somebody has the solution - it’s things like this that stop me from fully going OOP with Corona, a lack of a clear direction. I understand that’s a complaint levelled at lua in general, but would really like to see Corona endorse a specific way to do OOP.

This could be something ‘under-the-hood’ and we might need somebody like Walter to explain why this will/won’t work with OOP design and runtime listeners. [import]uid: 33275 topic_id: 35326 reply_id: 141060[/import]

It’s very likely the anonymous function. Corona needs to know exactly what to kill and while your code looks identical in add/remove, the table hexcode for it would not be. So you’re basically saying add(table 0x1) but remove(0x2) or something similar.

Best to just declare the function as part of the object and call it from there, I imagine… [import]uid: 41884 topic_id: 35326 reply_id: 141062[/import]

Even though the body of the anonymous functions are the same, they are different functions (They each have different memory addresses).

Just make a function with a name and use that. [import]uid: 70003 topic_id: 35326 reply_id: 141063[/import]