OOP and listeners

Hi guys, I’m quite new to Corona SDK and having an issue with managing classes and modules.

Here is my implementation of the square module:

local square = {} local squareMt = { \_\_index = square } local globals = require( "globals" ) function square.new( coords, params) image = display.newRect( coords.x, coords.y, coords.w, coords.h ) image.strokeWidth = params.strokeWidth or globals.strokeWidth image:setFillColor( params.squareColor or globals.squareColor.clear ) image:setStrokeColor( globals.strokeR, globals.strokeG, globals.strokeB ) image:addEventListener( "touch", touch ) local newSquare = { img = image, pos = params.position, status = globals.clear } return setmetatable( newSquare, squareMt ) end function touch(event) print event.target.pos --does not work obviously end return square

I’d want to access the square attributes (pos and status) from the listener. How can I achieve it?

I believe this will give you the behavior you want (look for EFM to see my changes):

local square = {} local squareMt =  { \_\_index = square } local globals = require( "globals" ) -- EFM begin local function touch( self, event) -- 'self' is the 'square' table -- event.target is the 'image'    local target = event.target    local params = self.params or {}    for k,v in pairs( params ) do       print( tostring(k) .. " == " .. tostring( v ) )    end end -- EFM end function square.new( coords, params) --EFM (you made image a global in your last sample)...bad... :) local image = display.newRect( coords.x, coords.y, coords.w, coords.h )         image.strokeWidth = params.strokeWidth or globals.strokeWidth         image:setFillColor(  params.squareColor or globals.squareColor.clear )     image:setStrokeColor( globals.strokeR, globals.strokeG, globals.strokeB )     image:addEventListener( "touch", touch )     local newSquare = {         img    = image,         pos    = params.position,         status = globals.clear, touch = touch -- EFM     }     newSquare:addEventListener( "touch", image )-- EFM     return setmetatable( newSquare, squareMt ) end return square 

Hi again.  Having responded to your question.  I have an alternative suggestion.

Unless you’re interested in OOP and not game development, I would suggest abandoning the concept of OOP with Lua and Corona.  

At least, don’t get carried away and spend a lot of time setting up metatables, etc.

I would write the above sample as follows instead:

-- This entire piece of code gets placed in a file and is 'required' as a module. local globals = require( "globals" ) local function touch( self, event)    local params = self.params    for k,v in pairs( params ) do       print( tostring(k) .. " == " .. tostring( v ) )    end end local function newSquare( x, y, params ) -- Yes, the creation of this many 'locals' is somewhat inefficient, but -- the benefit is orderly easily modified code. So, unless you're creating -- 10's of thousands of squares a second, no worries on perf. --     local params  = params or {}     local width   = params.w or 40     local height  = params.h or width or 40     local group   = params.group or display.currentStage local fill = params.fill or or globals.squareColor.clear or {1,1,1,0} local stroke = params.stroke or globals.stroke or { 1,1,1,1 } local strokeW = params.strokeW or globals.strokeW or 0 local strokeC = params.strokeC or globals.strokeC or {1,1,1,1} local status = params.status or globals.clear     local aSquare = display.newRect( group, x, y, width, height )     aSquare.strokeWidth = strokeW     aSquare:setFillColor(  unpack( fill ) )     aSquare:setStrokeColor( unpack( strokeC ) )     aSquare:addEventListener( "touch", touch )     aSquare.status = status     aSquare.touch = touch     aSquare:addEventListener( "touch" )  return aSquare end public = {} public.new = newSquare   return public 

Then later:

local square = require "square" local mySquare = square.new( 100, 100, { w = 100, h = 20, fill = { 1, 0, 0 }, strokeW = 3, strokeC = { 0, 1, 0 }  } ) 

Cheers,

Ed

Hey.  One more thing.  If you are actually interested in using Lua in an OOP fashion, then I suggest reading some of the stuff here:

http://www.omidahourai.com/from-zero-to-oo-ardentkids-guide-to-object-oriented-lua-with-corona-sdk

I don’t have an issue with Lua and OOP, except that I sometimes see folks get so caught up in ‘OOP’-ing their code they never get their app/game done.

Cheers (for the last time I think!) 

Ed

Hi roaminggamer, thanks for the help, really helpful and very well explained!

Just a few things:

  • the first example is not working for me, it seems you’re calling ‘addEventListener’ from a table (newSquare) and that is not permitted from what I understand.

  • What is the difference between :

    aSquare:addEventListener( “touch”, touch )

            and

             

aSquare.touch = touch aSquare:addEventListener( "touch" ) 

            ?

  • Shouldn’t I add ‘aSquare.params = params’ in the second example to let the touch function print them?

Thanks a lot for the help!

D.

@Diego,

Sorry about that.  I type up most of my answers and don’t actually run them.  i.e. I put them through the Corona Simulator in my brain and sometimes, my brain fails to throw an error. :slight_smile:

I’ll look over the code and your questions and answer back shortly.

-Ed

What I do for OOP is have this one-liner.

\_G.Base =  \_G.Base or { new = function(s,...) local o = { } setmetatable(o,s) s.\_\_index = s s:initialise(...) return o end, initialise = function() end }

So you can create a class as follows

local MyClass = Base:new() function MyClass:initialise(someparam) -- constructor    self.myparam = someparam    self.otherstuff = {} end function MyClass:printStuff()   print(self.myParam) end

and create instances as

inst = MyClass:new("Eric") inst:printStuff()

Classes in lua are not classes, really, they are prototypes - you create a class instance, and then ‘copy’ it (sort of) using metatables.

RoamingGamer is partially right. OOP or any other methodology (component/entity might suit more ?) is not the whole answer in itself, though your approach is good because you are mirroring Coronas. What is important is to think about it, to structure things in some sort of logical format.

OOP people to get carried away and create quintillions of classes and hierarchies sometimes. You can do a lot with structure and organisation of code and data.

@Diego

Re: First examples and Listeners

You can attach a listener to anything, but I probably typoed part of my code.  Let me explain how listeners work and the different varieties you will see.

Tables vs. Other Objects

Please be aware, that all ‘objects’ in Corona are Lua Tables.  i.e. display.newImageRect() returns an object which is a Lua Table + extra code attached for Corona.  However, it behaves and is treated just like a table.

Function Listener vs. Table Listener

I found these terms very confusing when I first started working with Corona.  However, at the end of the day, the thing to remember is that function listeners are standalone functions, not associated with an object.  They can be local or global.  On the other hand, table listeners are functions/methods associated with some object (a table).

Runtime vs Object Events

To further confuse things, you have Runtime and Object originated events.  The prior are generally system events (GPS, orientation, etc.) but also includes the global touch event.  The latter are specific to objects and include touch, collision, preCollision, etc.

Now, let me show you some syntax rules and terms to help you understand how things work in Corona.

Example #1 - Runtime Function Listener

local function theListener( event )     print(event.x,event.y) end Runtime:addEventListener( "touch", theListener ) -- Read the above as, -- -- Send the Runtime event "touch" to the function 'theListener' for processing. -- The function 'theListener' will be passed a single table containing the event information. 

Example #2 - Runtime Table Listener

local function theListener( self, event )     -- Self is the object this function is attached to.     print(event.x,event.y) end local someObject = display.newCircle( 100, 100, 10 ) someObject.touch = theListener -- Notice how name of method field matches the name of the event. Runtime:addEventListener( "touch", someObject ) -- Read the above as, -- -- Send the Runtime event "touch" to the method 'touch' attached to the object 'someObject'. -- The method 'touch' references the local function 'theListener' and will -- automatically be passed a reference to 'someObject' (as argument self), and the event table -- (as argument event.)

Example #3 -Object Function Listener

local function theListener( event )     print(event.x,event.y) end local someObject = display.newCircle( 100, 100, 10 ) someObject:addEventListener( "touch", theListener ) -- Read the above as, -- -- Send the object event "touch" to the local function 'theListener'. -- Automatically passed an event table (as argument event.) --  -- Note: event.target will be a reference to the touched object which is 'someObject'. --

Example #4 -Object Table Listener

local function theListener( self, event )     -- Self is the object this function is attached to.     print(event.x,event.y) end local someObject = display.newCircle( 100, 100, 10 ) someObject.touch = theListener -- Notice how name of method field matches the name of the event. someObject:addEventListener( "touch", someObject ) -- Second argument is optional in this case -- and implied if left blank. -- Read the above as, -- -- Send the object event "touch" to the method 'touch' attached to the object 'someObject'. -- The method 'touch' references the local function 'theListener' and will -- automatically be passed a reference to 'someObject' (as argument self), and the event table -- (as argument event.) --

Example #5 -Object Table Listener - Using a Proxy Object (Very Rare)

local function theListener( self, event )     -- self is the 'proxy object'     -- event.target is the object we touched.     print(event.x,event.y) end local someObject = display.newCircle( 100, 100, 10 ) local someObject2 = display.newCircle( 200, 200, 10 ) -- Create a 'proxy' object to process the touch events for my two objects local proxyObject = {}   proxyObject.touch = theListener  someObject:addEventListener( "touch", proxyObject ) someObject2:addEventListener( "touch", proxyObject ) -- Read the above as, -- -- Send the object event "touch" to the method 'touch'attached to the proxy object 'proxyObject'. -- -- The method 'touch' which refers to the local function 'theListener' and will -- automatically be passed a reference to 'proxyObject' (as argument self), -- To get a reference to the touched object, look at the field event.target, -- in the event table (argument event).

Benefits and Drawbacks Of Variations

  • Benefits
    • Example 1 - Catches touch events anywhere.  Processes after all object touches in that touch location.  Cannot be blocked by another listener returning true.
    • Example 2 - Wonderful for enterFrame and other system events that you want to funnel to specific objects.
    • Example 3 - Centralizes processing to one function (personally I don’t like this however).  Saves memory.
    • Example 4 - Also saves memory if you maintain proper scope when defining the listener.  Has added benefit of clearly providing a reference to the touched object.  Lastly, it self-cleans when you delete the object.  i.e. You don’t need to call removeEventListener()
    • Example 5 - Useful for advanced techniques like creating behavior libraries, etc.
  • Drawbacks
    • Example 1 - You must clean this up yourself.  i.e. Must later call removeEventListener()
    • Example 2 - You must clean this up yourself.  i.e. Must later call removeEventListener()
    • Example 3 - You must clean this up yourself.  i.e. Must later call removeEventListener()
    • Example 4 - If done wrong, this wastes a lot of space. i.e. If you define a new function for every object, you use up the memory for that function in addition to the object.  So, scope is key.
    • Example 5 - Confusing to grok and error prone on the cleanup.

@All readers,

If you see something I messed up, please comment so I can respond and fix it.  Again, all this code went through the brain simulator only.

FANTASTIC thread! learned a lot.

Mo

@roaminggamer

Nice brain simulator you have :wink: Anyway, very good read, it made me understand better how Corona and Lua work.

I believe this will give you the behavior you want (look for EFM to see my changes):

local square = {} local squareMt =  { \_\_index = square } local globals = require( "globals" ) -- EFM begin local function touch( self, event) -- 'self' is the 'square' table -- event.target is the 'image'    local target = event.target    local params = self.params or {}    for k,v in pairs( params ) do       print( tostring(k) .. " == " .. tostring( v ) )    end end -- EFM end function square.new( coords, params) --EFM (you made image a global in your last sample)...bad... :) local image = display.newRect( coords.x, coords.y, coords.w, coords.h )         image.strokeWidth = params.strokeWidth or globals.strokeWidth         image:setFillColor(  params.squareColor or globals.squareColor.clear )     image:setStrokeColor( globals.strokeR, globals.strokeG, globals.strokeB )     image:addEventListener( "touch", touch )     local newSquare = {         img    = image,         pos    = params.position,         status = globals.clear, touch = touch -- EFM     }     newSquare:addEventListener( "touch", image )-- EFM     return setmetatable( newSquare, squareMt ) end return square 

Hi again.  Having responded to your question.  I have an alternative suggestion.

Unless you’re interested in OOP and not game development, I would suggest abandoning the concept of OOP with Lua and Corona.  

At least, don’t get carried away and spend a lot of time setting up metatables, etc.

I would write the above sample as follows instead:

-- This entire piece of code gets placed in a file and is 'required' as a module. local globals = require( "globals" ) local function touch( self, event)    local params = self.params    for k,v in pairs( params ) do       print( tostring(k) .. " == " .. tostring( v ) )    end end local function newSquare( x, y, params ) -- Yes, the creation of this many 'locals' is somewhat inefficient, but -- the benefit is orderly easily modified code. So, unless you're creating -- 10's of thousands of squares a second, no worries on perf. --     local params  = params or {}     local width   = params.w or 40     local height  = params.h or width or 40     local group   = params.group or display.currentStage local fill = params.fill or or globals.squareColor.clear or {1,1,1,0} local stroke = params.stroke or globals.stroke or { 1,1,1,1 } local strokeW = params.strokeW or globals.strokeW or 0 local strokeC = params.strokeC or globals.strokeC or {1,1,1,1} local status = params.status or globals.clear     local aSquare = display.newRect( group, x, y, width, height )     aSquare.strokeWidth = strokeW     aSquare:setFillColor(  unpack( fill ) )     aSquare:setStrokeColor( unpack( strokeC ) )     aSquare:addEventListener( "touch", touch )     aSquare.status = status     aSquare.touch = touch     aSquare:addEventListener( "touch" )  return aSquare end public = {} public.new = newSquare   return public 

Then later:

local square = require "square" local mySquare = square.new( 100, 100, { w = 100, h = 20, fill = { 1, 0, 0 }, strokeW = 3, strokeC = { 0, 1, 0 }  } ) 

Cheers,

Ed

Hey.  One more thing.  If you are actually interested in using Lua in an OOP fashion, then I suggest reading some of the stuff here:

http://www.omidahourai.com/from-zero-to-oo-ardentkids-guide-to-object-oriented-lua-with-corona-sdk

I don’t have an issue with Lua and OOP, except that I sometimes see folks get so caught up in ‘OOP’-ing their code they never get their app/game done.

Cheers (for the last time I think!) 

Ed

Hi roaminggamer, thanks for the help, really helpful and very well explained!

Just a few things:

  • the first example is not working for me, it seems you’re calling ‘addEventListener’ from a table (newSquare) and that is not permitted from what I understand.

  • What is the difference between :

    aSquare:addEventListener( “touch”, touch )

            and

             

aSquare.touch = touch aSquare:addEventListener( "touch" ) 

            ?

  • Shouldn’t I add ‘aSquare.params = params’ in the second example to let the touch function print them?

Thanks a lot for the help!

D.

@Diego,

Sorry about that.  I type up most of my answers and don’t actually run them.  i.e. I put them through the Corona Simulator in my brain and sometimes, my brain fails to throw an error. :slight_smile:

I’ll look over the code and your questions and answer back shortly.

-Ed

What I do for OOP is have this one-liner.

\_G.Base =  \_G.Base or { new = function(s,...) local o = { } setmetatable(o,s) s.\_\_index = s s:initialise(...) return o end, initialise = function() end }

So you can create a class as follows

local MyClass = Base:new() function MyClass:initialise(someparam) -- constructor    self.myparam = someparam    self.otherstuff = {} end function MyClass:printStuff()   print(self.myParam) end

and create instances as

inst = MyClass:new("Eric") inst:printStuff()

Classes in lua are not classes, really, they are prototypes - you create a class instance, and then ‘copy’ it (sort of) using metatables.

RoamingGamer is partially right. OOP or any other methodology (component/entity might suit more ?) is not the whole answer in itself, though your approach is good because you are mirroring Coronas. What is important is to think about it, to structure things in some sort of logical format.

OOP people to get carried away and create quintillions of classes and hierarchies sometimes. You can do a lot with structure and organisation of code and data.

@Diego

Re: First examples and Listeners

You can attach a listener to anything, but I probably typoed part of my code.  Let me explain how listeners work and the different varieties you will see.

Tables vs. Other Objects

Please be aware, that all ‘objects’ in Corona are Lua Tables.  i.e. display.newImageRect() returns an object which is a Lua Table + extra code attached for Corona.  However, it behaves and is treated just like a table.

Function Listener vs. Table Listener

I found these terms very confusing when I first started working with Corona.  However, at the end of the day, the thing to remember is that function listeners are standalone functions, not associated with an object.  They can be local or global.  On the other hand, table listeners are functions/methods associated with some object (a table).

Runtime vs Object Events

To further confuse things, you have Runtime and Object originated events.  The prior are generally system events (GPS, orientation, etc.) but also includes the global touch event.  The latter are specific to objects and include touch, collision, preCollision, etc.

Now, let me show you some syntax rules and terms to help you understand how things work in Corona.

Example #1 - Runtime Function Listener

local function theListener( event )     print(event.x,event.y) end Runtime:addEventListener( "touch", theListener ) -- Read the above as, -- -- Send the Runtime event "touch" to the function 'theListener' for processing. -- The function 'theListener' will be passed a single table containing the event information. 

Example #2 - Runtime Table Listener

local function theListener( self, event )     -- Self is the object this function is attached to.     print(event.x,event.y) end local someObject = display.newCircle( 100, 100, 10 ) someObject.touch = theListener -- Notice how name of method field matches the name of the event. Runtime:addEventListener( "touch", someObject ) -- Read the above as, -- -- Send the Runtime event "touch" to the method 'touch' attached to the object 'someObject'. -- The method 'touch' references the local function 'theListener' and will -- automatically be passed a reference to 'someObject' (as argument self), and the event table -- (as argument event.)

Example #3 -Object Function Listener

local function theListener( event )     print(event.x,event.y) end local someObject = display.newCircle( 100, 100, 10 ) someObject:addEventListener( "touch", theListener ) -- Read the above as, -- -- Send the object event "touch" to the local function 'theListener'. -- Automatically passed an event table (as argument event.) --  -- Note: event.target will be a reference to the touched object which is 'someObject'. --

Example #4 -Object Table Listener

local function theListener( self, event )     -- Self is the object this function is attached to.     print(event.x,event.y) end local someObject = display.newCircle( 100, 100, 10 ) someObject.touch = theListener -- Notice how name of method field matches the name of the event. someObject:addEventListener( "touch", someObject ) -- Second argument is optional in this case -- and implied if left blank. -- Read the above as, -- -- Send the object event "touch" to the method 'touch' attached to the object 'someObject'. -- The method 'touch' references the local function 'theListener' and will -- automatically be passed a reference to 'someObject' (as argument self), and the event table -- (as argument event.) --

Example #5 -Object Table Listener - Using a Proxy Object (Very Rare)

local function theListener( self, event )     -- self is the 'proxy object'     -- event.target is the object we touched.     print(event.x,event.y) end local someObject = display.newCircle( 100, 100, 10 ) local someObject2 = display.newCircle( 200, 200, 10 ) -- Create a 'proxy' object to process the touch events for my two objects local proxyObject = {}   proxyObject.touch = theListener  someObject:addEventListener( "touch", proxyObject ) someObject2:addEventListener( "touch", proxyObject ) -- Read the above as, -- -- Send the object event "touch" to the method 'touch'attached to the proxy object 'proxyObject'. -- -- The method 'touch' which refers to the local function 'theListener' and will -- automatically be passed a reference to 'proxyObject' (as argument self), -- To get a reference to the touched object, look at the field event.target, -- in the event table (argument event).

Benefits and Drawbacks Of Variations

  • Benefits
    • Example 1 - Catches touch events anywhere.  Processes after all object touches in that touch location.  Cannot be blocked by another listener returning true.
    • Example 2 - Wonderful for enterFrame and other system events that you want to funnel to specific objects.
    • Example 3 - Centralizes processing to one function (personally I don’t like this however).  Saves memory.
    • Example 4 - Also saves memory if you maintain proper scope when defining the listener.  Has added benefit of clearly providing a reference to the touched object.  Lastly, it self-cleans when you delete the object.  i.e. You don’t need to call removeEventListener()
    • Example 5 - Useful for advanced techniques like creating behavior libraries, etc.
  • Drawbacks
    • Example 1 - You must clean this up yourself.  i.e. Must later call removeEventListener()
    • Example 2 - You must clean this up yourself.  i.e. Must later call removeEventListener()
    • Example 3 - You must clean this up yourself.  i.e. Must later call removeEventListener()
    • Example 4 - If done wrong, this wastes a lot of space. i.e. If you define a new function for every object, you use up the memory for that function in addition to the object.  So, scope is key.
    • Example 5 - Confusing to grok and error prone on the cleanup.

@All readers,

If you see something I messed up, please comment so I can respond and fix it.  Again, all this code went through the brain simulator only.

FANTASTIC thread! learned a lot.

Mo