Design Talks - Custom Runtime Events

In this post I’d like to talk about what I call ‘custom’ Runtime Events.
 
This is an example of what I mean:

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

Later, in any module or un-connected piece of code, I can do this to update the ‘coinsLabel’:
 

Runtime:dispatchEvent( { name = "onCoins", coins = 20 } )

After this code runs, the ‘coinsLabel’ will show 20.

Note: The ‘finalize’ listener is used to ensure the Runtime event listener is removed when the label is destroyed. Otherwise, the ‘dead’ object might receive ‘onCoins’ events incorrectly at a later time causing a crash or error.

 
SSK2 Sidenote
Please note, I don’t actually write my code as shown above.  Instead, I use SSK2 helpers: 
https://roaminggamer.github.io/RGDocs/pages/SSK2/globals/#runtime-improvements
 
Using SSK2, my code really looks like this:

local coinsLabel = display.newText( "0", display.contentCenterX, 100 ) function coinsLabel.onCoins( self, event )    self.text = event.coins end; listen( "onCoins", coinsLabel ) function coinsLabel.finalize(self) -- SSK2's "ignoreList()" removes all listed events at once and safely ignoreList( { "onCoins", "enterFrame" }, self ) end; coinsLabel:addEventListener("finalize") ... then later post( "onCoins", { coins = 20 } ) -- shorthand for "Runtime:dispatchEvent()"

This code is not significantly different, but I find it much easier to type and easier to read.  Additionally, the use of SSK2’s ignoreList() function is safer and better than a series of calls to Runtime:removeEventListener().

Why Do I Use Custom Events?

So, what is the big deal?  

The big deal (for me) is this allows me to:

  • Make much of my non-core code ‘event’ based.
    • Core code is basically game logic.
    • Non-core is stuff like scoring, playing sounds, dispatching ads requests, etc.
  • Keep my code separated.  

The last reason is most important.  As I noted above, once the event listener is set up, code anywhere else in my game can dispatch an event and it doesn’t have to know about the label.  There is no need for a reference to it or any such ugliness.  Simply dispatch the event and ‘Violla!’ the label is updated.

While the application of this concept to a ‘score label’ is vaguely interesting, it becomes much more interesting when applied to complex pieces of code like your ad manager, the sound manager, etc.

I will talk more about ads later, but for completeness I’ll give a more interesting example of sound code now.

SSK2 Sound Manager Example

SSK2 includes a basic “Sound Manager”.  It automatically listens for the event ‘onSound’

I generally use it like this in my games:

-- Register sounds; Uusually in main.lua ssk.soundMgr.addEffect( "coin", "sounds/sfx/coin.wav") ssk.soundMgr.addEffect( "gate", "sounds/sfx/gate.wav") ssk.soundMgr.addEffect( "died", "sounds/sfx/died.wav") ...

Later, I can dispatch sound events anywhere I need them:

post( "onSound", { sound = "coin" } )

As you can probably tell, this equates to super easy sound management and coding.

What About You?

I’d like to close this (long) post with some questions for you, the readers:

  1. Do  you use ‘custom’ events?

  2. If, “Yes”, what do you use them for?

  3. If, “No”, are you thinking about using them now?

Please answer if you can and share any other thoughts you may have.  

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()

@Dave
Now that async example makes sense and is definitivly something i will use in my current project cause not being able to just return the value on answer received can be quite a troublemaker when the app uses alot of them
Thanks!

Hi,

This is a topic close to my heart. Almost all my projects are event driven (a throwback from my Flash days). I don’t use Runtime events though, I build event dispatching directly into my modules using this technique:

Module:

--############################################################################# --# Evented Module --############################################################################# local \_M = {} function \_M:doSomething() self:dispatchEvent({name="SomeEvent", value=20, target=self}) end function \_M:somethingElse() print("Hi!") end return setmetatable(\_M, { \_\_index = system.newEventDispatcher() })

Usage:

--############################################################################# --# Contrived Evented Module Test --############################################################################# local eventedMod = require("eventedMod") --add listener local function onEvent(evt) print(evt.value) evt.target:somethingElse() end eventedMod:addEventListener("SomeEvent", onEvent) --test call eventedMod:doSomething() --remove listener eventedMod:removeEventListener("SomeEvent", onEvent)

Another technique is using a “master” dispatcher:

--############################################################################# --# Main Controller --############################################################################# local events = system.newEventDispatcher() local score = require("score") local entity = require("entity") --pass master dispatcher to modules (internals not shown) score:init(events) entity:init(events) --something dispatched from entity events:addEventListener("EntityScored", function(evt) --dispatch to anyone listening for the event events:dispatchEvent({name=evt.name, value=evt.value}) end) -- ...

Using this technique it’s simple to listen for/dispatch events from any module, anywhere, without them needing to know about each other intimately. This is of course a pseudo example.

Reference: https://docs.coronalabs.com/api/library/system/newEventDispatcher.html

-dev