global to local event forwarding in table-listener

I’d like to solicit feedback about this “tableRuntimeLocalEventDispatcher()” (code attached), which tries to solve the following problem:

When you have a system event like “enterFrame”, then you can either register a event handler with the Runtime target that then gets called but doesn’t have any context to any object. Or you can use a table event handler, which is attached to the object’s table and a self parameter value will give you its reference, or you can use a closure where you wrap the object inside of an normal event handler function. The advantage of the last two are that you have the event handler running within the context of an object without the need for global variables, which is a very common use case.

The disadvantage of the table-event handlers are that you can only install a single one at the event-name slot of the object-table. This is a big limitation if you want to modularize your app and have multiple event handlers taking care of different aspects of the object’s behavior.

The closure approach works well, but then you need a factory-function that wraps the object in a closure. It will make a new function instance for each object, which makes it almost impossible to identify those handlers by name because they get created during runtime - just one indirection too many.

The alternative I came up with is different, and I haven’t seen it before, which makes me a little nervous. The idea is to use a generic table-event-handler, which simply and only forwards/dispatches, for example, the “enterFrame” events to the object itself. This allows you to write normal multiple “enterFrame” event handlers and register those with the object itself. The table-event-handler gets registered with Runtime for “enterFrame” events, and before forwarding the event on, it adds an event.target property that points to self such that the local event handler can find its local context.

It seems like a trick and costs one extra indirection, but it also seems to work well; you can have multiple “enterFrame” local event handlers registered with an object without a problem, and all handlers can easily be “named” such that we can wire them up thru Tiled-property-values.

Note that this trick is not necessary for “collision” events as you already can register those locally…unfortunately for Runtime’s “enterFrame” events we do not seem to be that lucky.

Please take a look and let me know whether there are easier, maybe a little less contrived ways to do this.

Thanks, Frank.
[lua]-------------------------------------------------------------------------------
— Generic system table-event to local object event dispatcher.
– A table-event handler that assigns the object-self to the event.target and
– then redispatches to the object itself. In that way, we can have mutiple
– system event handlers that are local to an object as the table-mechanism
– only allows us to register a single system event handler with an object.
– Usage for “enterFrame” events with a display object “anObject”:
– anObject[enterFrame] = NamedEventHandlers.tableRuntimeLocalEventDispatcher
– Then register object-table event handler with Runtime like:
– Runtime:addEventListener( “enterFrame”, anObject ).
– This table-event-handler will then forward “enterFrame” event to registered
– object’s event handlers like for example “myEnterFrameHandler(event)”, which
– has been registered like:
– anObject:addEventListener(“enterFrame”, myEnterFrameHandler)
NamedEventHandlers.tableRuntimeLocalEventDispatcher = function(self, event)
if (not self.dispatchEvent) then
– poor-man’s test for display object existence
– if we’re here, then only skeleton object exists and we should clean-up
Runtime:removeEventListener( event.name, self )
return true
else
– dispatch event to object itself
– copy as we cannot annotate and reuse the event table as bad things will happen
local event2 = {}
for k,v in pairs(event)do event2[k]=v end
– add self as the target such that the handler can find the self-state
event2.target = self
self:dispatchEvent(event2)
return false
end
end[/lua] [import]uid: 8093 topic_id: 7439 reply_id: 307439[/import]

Looks like a pretty smart approach, using Corona’s built-in event cueing.

The only thought I’d have is, for a large number of handlers when speed is important, there will be some penalty creating a new table each dispatch.

In that case I’d tend to go with a table of handlers. A bit more work, because you have to manage adding and inserting yourself. Something like:

[code]

local myRect = display.newRect(0, 0, 100, 100)

myRect.handlers = {}

function myRect:callHandlers(event)
for _, v in pairs(self.handlers) do v(event) end
end

myRect.enterFrame = myRect.callHandlers

function myRect:addHandler(func)
self.handlers[#self.handlers+1] = func
end

function myRect:removeHandler(func)
for k, v in pairs(self.handlers) do
if v == func then
table.remove(self.handlers, k)
print("removed item ", k)
end
end
end

function handler1(event)
print("handler1: ", event.name)
end

function handler2(event)
print("handler2: ", event.name)
end

Runtime:addEventListener(“enterFrame”, myRect)

myRect:addHandler(handler1)
myRect:addHandler(handler2)

– check removal
timer.performWithDelay(2000, function() myRect:removeHandler(handler1) end)

[/code] [import]uid: 3953 topic_id: 7439 reply_id: 26396[/import]

That looks very much like an equivalent approach.

You could probably benefit from that display-object existence test also to ensure that you’re not working with some crippled orphan-display-object.

When you write “some penalty creating a new table each dispatch”, you’re probably referring to the copy of the event-table that I have to make. When I try to add that target attribute to the originally passed event-table, Corona barfs for some reason… not sure why, but the event structure seems sacred. Anyway, it’s only a two entry table (name & time), so the overhead of the copy should be negligible.

Just to make it easier to compare your example with that tableRuntimeLocalEventDispatcher() approach, I’ve tried to rewrite it like:
[lua]local myRect = display.newRect(0, 0, 100, 100)

myRect.enterFrame = NamedEventHandlers.tableRuntimeLocalEventDispatcher

function handler1(event)
print("handler1: ", event.name)
end

function handler2(event)
print("handler2: ", event.name)
end

Runtime:addEventListener(“enterFrame”, myRect)

myRect:addEventListener(“enterFrame”, handler1)
myRect:addEventListener(“enterFrame”, handler2)

– check removal
timer.performWithDelay(2000, function() myRect:removeEventListener( “enterFrame”, handler1 ) end)[/lua]

As you mentioned, my approach seems to reuse more of the existing Corona event handling framework, but besides that, they both do the same thing.

Good to see others come up with similar solutions.
Thanks for sharing, Frank. [import]uid: 8093 topic_id: 7439 reply_id: 26403[/import]

I’m guessing that the event passed by the Corona API gets cleaned up by the time the explicit dispatchEvent is received. That’s the only advantage to using a table of functions - the event isn’t cleaned up until the main event receiver function returns. If, as you say, you’re only running a couple of event handlers simultaneously, your method is tidier and, I guess, more conformant to Corona’s model.

I wonder if there’s a way to prevent cleanup (if that’s what it is) of the event. Though then you’d have the problem of having to figure out the last usage of it - then cleaning it up - or get mem leakage.

Come to think of it, your table copy looks the safest bet :slight_smile: [import]uid: 3953 topic_id: 7439 reply_id: 26494[/import]

Yup - too bad that we have to keep guessing about a lot of the exact behavior and semantics of Corona’s APIs - better documentation goes a long way if you cannot see the code itself and let’s just say that Ansca could improve a little on that part…

The description of the event-releated APIs, the events them selves, and their semantics is abysmal - when are handlers called, in what sequence, is the dispatch call done inline/synchronous or after the return of the handler, is the event-table reused by the calling dispatcher such that we cannot change it it we pass it along asynchronously, or is it passed by value (as I believe it should) such that there are no synchronization issues, why doesn’t every event-table include a reference to the target and the function it is calling such that you can generically remove the handler without having to maintain state, no introspection capabilities of what handlers are registered, unless you dig under the covers; why are handlers not automatically removed when display objects are removeSelf’ed - or are they sometimes (?), …

Sorry for the rant, but it takes me far too much debugging-time to figure out what’s happening and much of it is still guessing…

-Regards, Frank. [import]uid: 8093 topic_id: 7439 reply_id: 26526[/import]

Hi guys!

I think your code could help to solve my problem.
I am a newbie and what i want is to drag and scale an object, i.e., I have two listeners for the same object. I can do both individually by commenting out one of the actions, but can not have both active in the same build.
Could you tell me if your sample could help to solve this?
I don’t understand well english, so I don’t understand well your explanation.

Thanks in advance! [import]uid: 44013 topic_id: 7439 reply_id: 28292[/import]