Design Talks - Custom Runtime Events

Very nice Dave!  :)  Corona’s method perhaps seems weird because it’s (effectively) a Publish-Subscribe pattern (where Runtime is the broker) – so it’s similar, but not quite same as observer.  Some pub-subs are “hybrid”, allowing you to target a specific recipient for example (or other features - categorical topics, etc), and thus can more closely mimic observer, but as it stands, there’s still a void for something more “direct” like your approach.  But I (too) rarely go to that much trouble when simpler approaches will do.

Thanks Dave, nice name!  :smiley:

I suppose you could consider Corona’s messaging mechanism as Pub/Sub if you implement an intermediate message filtering broker using Runtime:dispatchEvent() or other methods.

As for hybrid approaches, my EventDispatcher is also not a pure Observer implementation because it handles dispatching different methods and not just update().

Btw, I enjoyed watching you and your son programming together!

Cheers,

Dave

@sporkfin Yep, I use a nifty little passthrough to send mouse movements to the touch even on objects to do simple mouseOvers… The hard part with touches is getting the event to the right object. Usually that means a separate event listener for each object.

Not yet but i get your points and might for my next project

  1. Yes. I do now, after reading a while back something you must have posted about custom events…  until then I knew nothing about them, but as soon as I saw what you posted about them,  I thought, this could be a great tool.  I just wish I knew about these when I made my tower defense game; way back when.

  2. I recently put a game on google play, my last game I published, called ‘Mad Pirates’. A  slot machine app.  I use them extensively in that game.  And I will have to look but I am fairly sure I also used them in another game I am ‘days’ away from publishing.

Had to do a lot of ‘hack code’ and work a rounds in my ‘Tanks & Towers’ tower defense game to communicate between all the objects created by a hierarchy of modules. The custom events would have saved a lot of time and frustration… if I had known about them back then.

‘finalize’ is something I have ‘stupidly’ avoided for some unknown reason. I have it on my very long list of ‘to follow up on’ stuff, because I wasn’t sure of it’s exact purpose and proper use.  Your note above about finalize, is very helpful. thanks.

I have not given it a lot of thought to it, but I do wonder, what is the breaking point… at what point is there too many custom events.

Bob

I use a similar mechanism in DC2.  So I show an overlay (my select a building screen) which allows the player to drag a building off a ribbon.  This then terminates the overlay and then in my main scene I create a new object that is the building they dragged in the overlay.  To make this seamless I dispatch an event ( a begin touch) to the new created object so it appears like you’ve dragged an image from the overlay into the parent scene.

Note: to achieve this I’ve had to make major changes to the open source storyboard code and this is, AFAIK, impossibile in composer.

It actually a little cleaner using the colon “:” notation, instead of passing self…  I use this almost exclusively and add enterFrame listeners to the same object.  I’ll also use a new() function to return an instance so you can spin up as many coinLabels as you want.

local coinsLabel = display.newText( "0", display.contentCenterX, 100 ) function coinsLabel:onCoins( event ) self.text = event.coins end Runtime:addEventListener( "onCoins", coinsLabel ) function coinsLabel:finalize() Runtime:removeEventListener("onCoins", coinsLabel) end coinsLabel:addEventListener("finalize")

For fun, here’s an example of a “life bar” module that can have multiple instances and has functions like damage() and heal()… Adding EventListener code to this would be pretty easy…

-- Heart bar module -- Define module local M = {} function M.new( options ) -- Default options for instance options = options or {} local max = options.max or 3 local spacing = options.spacing or 8 local w, h = options.width or 64, options.height or 64 -- Create display group to hold visuals local instance = display.newGroup() local hearts = {} for i = 1, max do hearts[i] = display.newImageRect( "scene/game/img/icon/heart.png", w, h ) hearts[i].x = (i-1) \* ( (w/2) + spacing ) hearts[i].y = 0 instance:insert( hearts[i] ) end instance.count = max instance.anchorChildren = true function instance:damage( amount ) instance.count = math.min( max, math.max( 0, instance.count - ( amount or 1 ) ) ) for i = 1, max do if i \<= instance.count then hearts[i].alpha = 1 else hearts[i].alpha = 0.2 end end return instance.count end function instance:alterMax(newMax) local nextHeartIndex = #hearts+1 hearts[nextHeartIndex] = display.newImageRect( "scene/game/img/icon/heart.png", w, h ) hearts[nextHeartIndex].x = (nextHeartIndex-1) \* ( (w/2) + spacing ) hearts[nextHeartIndex].y = 0 instance:insert( hearts[nextHeartIndex] ) max = newMax end function instance:fullHeal() instance.count = max for i = 1, max do hearts[i].alpha = 1 end end function instance:heal( amount ) self:damage( -( amount or 1 ) ) end function instance:finalize() -- On remove, cleanup instance end instance:addEventListener( "finalize" ) -- Return instance return instance end return M

Great topic @roaminggamer

I am not currently using custom events but I plan to start. . . now!

I used them in the past. They look fantastic and allow us to save code.

My thoughts are only two:

1.Do they affect performance?

2.Entering too many could make the code less understandable(I do not think, however, another opinion is always a pleasure)

Hello,

I have read somewhere but I don’t remember where. We can create listener on value. (a function is call when a value is updated).

If someone has seen something like this, can he reply in this topic?

1 - I never used custom listener, all my function, all my value can be access from everywhere (expect for useless value) but it’s not a good solution if many people work on the same code.

2 - I will use custom listener because you convince me and I discover that with this topic. It will help me to win a lot of time. To dispatch information it’s look very interesting 

Can someone tell me if both listener we be call with this event?

local coinsLabel = display.newText( "0", display.contentCenterX, 100 ) function coinsLabel:onCoins( event ) self.text = event.coins end ; Runtime:addEventListener( "onCoins", coinsLabel ) local myObject2={} function myObject2:onCoins( event ) print("Something") end ; Runtime:addEventListener( "onCoins", myObject) Runtime:dispatchEvent( { name = "onCoins", coins = 20 } )

There is one aspect to all of this i’d like to mention, time spent coding.

Every coder has his own habits and techniques, and while it is great to learn new techniques and see new ways to do things, it can come at the cost of spending more time coding. Some more complex setups can make the code less readable for the given coder, for others maybe not. 

What I try to say is that unless the new way brings better performance and/or solves a problem, sometimes its just faster to code the way we are used to, often at the cost of typing more code, but not neccessarily slower development or performance.

In the end its about the product we make, not the code structure in itself.

I am sure many much more proficient than me can detect and fine tune methods and techniques to squeeze out extra performance but its not always worth if the products runs fine on all required devices. Not every project pushes device limitsand needs to be coded in the most efficient way.

There I said it…waiting for the s*storm  :slight_smile:

Here it comes  :slight_smile:

Depend of what game you make. At the beginning of my project a TD, with 30 units on the map, the game run at 5fps at the end of the dev, I add a lot of effects, interactions and a lot of other stuf the game can run at 30fps with a few hundreds units on the map

I agree that the most of the time we don’t care about code optimization. But for critical function who are call thousand of time per seconde need to be optimize. To keep a good productivity, we have to keep in mind how many time it will be call, does improvement provide a major time execution?

i’d favor “judicious use” of such techniques (either Runtime.dispatchEvent or a DIY pub/sub system).

there’s a decision point in here somewhere, else you run the risk of making EVERYTHING message-based and calling NOTHING directly.  (though, heck, maybe some would even prefer that)

i’d probably not use it for the “coin label” scenario, instead i’d have a “hud.coins” class/instance and just call it’s “update” method directly, as the code that acquires coins is probably probably relatively “close” to the code that owns the hud in the hierarchy, and ought to have direct access to it.

but i might use it for something like a long-running (async) network request - say i’m querying a NIST server for real time, or awaiting the result of an IAP purchase, then broadcast with result when it returns, for whoever wanted it - potentially cleaner than keeping a reference to some callback function fe.

in short, i’d consider a message system when the publisher can’t be reasonably expected to “know” who the subscriber(s) are, otherwise it’s just unnecessary overhead.

  1. Everything has a performance impact and this could to if not used carefully.   That said, I’ve tested into the thousands of distinct listeners that just wake and do nothing as a test and saw no problems.  So, the mechanism itself is pretty efficient.

  2. I would disagree with this point.  Properly architected, a game using this technique is easier to understand for me.   As long as you avoid bad names, cascading events, and over kill I think you’ll be fine.

Note: @davebollinger has some great insights above (below your post) on this that I agree with.

Answers INLINE

@anaquim - You are correct and I’ve said it before.  You should stick with what you know and are comfortable with.

That said, this is more of an exploratory discussion.  My hope is to learn something from other community members while sharing something about the way I do things.

Knowing if what I’m doing is foreign is helpful too as I hope to produce more templates, starters, etc. and if I do something that users find ‘weird’ I may end up not helping them or myself.

Sorry for the re-quote.  I try to avoid this, but sometimes it is easier to be sure the OP knows what I’m replying to.

Responses INLINE

>>I didn’t understand this last part.  In this case the publisher is entirely ignorant of the subscribers. 

Read my “publisher” as whoever did Runtime:dispatchEvent, read my “subscriber” as whoever did corresponding Runtime:addEventListener.  (just a habit i’ve adopted, as almost everyone else in the gamedev world uses pub/sub design pattern terms)

The useful case imo is when the “publisher” is abstracted to the degree that it doesn’t “know” (ie, does not maintain state) of who originally initiated the request.  Because, otoh, if you “know” the direct path to a method, then use it.

Network operations make a reasonable example – let’s say you have a generic NetworkSupport module (don’t care what you call it), capable of submitting network requests and returning network results on behalf any generic “customer” (ie, any random code that needs network data is a “customer” of the NetworkSupport module as “provider”).

All the network module “knows” (internally, across an async request/receive) is it that it got a network response, no idea “who” originally requested it.  So it wants to relay it (by “publishing” a message) to whoever initiated the original request (who has “subscribed” to that topic, aka “is listening for that event” in Corona-speak).

The  function lookup and calling overhead would start to get too high otherwise.

Exactly.  reductio ad absurdem:

local EntireCoronaAPIEvented = { requestNewRect = function(self, event) local rect = display.newRect(unpack(event.params)) Runtime:dispatchEvent({ name="receiveNewRect", value=rect }) end, requestNewImage = function(self, event) local image = display.newImage(unpack(event.params)) Runtime:dispatchEvent({ name="receiveNewImage", value=image }) end, -- et al, ad nauseum, for the entire api } Runtime:addEventListener("requestNewRect", EntireCoronaAPIEvented) Runtime:addEventListener("requestNewImage", EntireCoronaAPIEvented) -- et al, ad nauseum, for the entire api local AGameEntityClass = {} AGameEntityClass.\_\_index = AGameEntityClass function AGameEntityClass:new() local instance = setmetatable({},self) -- aka: instance.view = display.newRect(100,100,50,50) Runtime:addEventListener("receiveNewRect", instance) Runtime:dispatchEvent({name="requestNewRect", params={100,100,50,50}}) return instance end function AGameEntityClass:receiveNewRect(event) self.view = event.value Runtime:removeEventListener("receiveNewRect", self) end local entity = AGameEntityClass:new()