Categories
Corona SDK Lua OOP Tutorials

Simple demo of implementing Undo and Redo in Corona / Lua

Just posted a simple demo of HistoryStack, to show how Undo and Redo can be implemented in Corona SDK / Lua. It also demonstrates using EventDispatcher to dispatch events to update button states.

https://github.com/daveyang/HistoryStack

 

Categories
Corona SDK Lua OOP

EventDispatcher docs

Documentation has been added for EventDispatcher in LDoc format, generated from the source file.

Here’s the basic usage:

local EvtD = require "EventDispatcher"

local dispatcher = EvtD()

-- listener as table
local listener = {
   eventName = function(event, ...)
      print(event.name, event.target, event.source)
   end
}

-- listener as function
local function listener(event, ...)
    print(event.name, event.target, event.source)
end

dispatcher:addEventListener( "eventName", listener ) -- or
dispatcher:on( "eventName", listener )

dispatcher:once( "eventName", listener )

dispatcher:hasEventListener( "eventName", listener )

dispatcher:dispatchEvent( { name="eventName" } ) -- or
dispatcher:dispatchEvent( "eventName" ) -- or
dispatcher:emit( { name="eventName" } ) -- or
dispatcher:emit( "eventName" )

dispatcher:removeEventListener( "eventName", listener )

dispatcher:removeAllListeners( "eventName" ) -- or
dispatcher:removeAllListeners()

dispatcher:printListeners()

All listeners receive the following fields in the parameter event table:

  • event.name (name of the event)
  • event.target (the listener itself)
  • event.source (the dispatcher)

Grab the latest code at Github.

Categories
Corona SDK Lua OOP

EventDispatcher update

Just a quick post that EventDispatcher has been updated with new features including new methods on(), emit(), once() that are similar to Node.js, a new method removeAllListeners(), and a debug function printListeners(). The on() and emit() methods are actually aliases to the addEventListener() and dispatchEvent() methods.

Chris Byerley (aka develephant) who developed Coronium.io and Coronium.gs is using EventDispatcher under the hood for the game server client code. Check out his projects if you’re a Lua / Corona developer.

Here’s the original post for EventDispatcher if you wish to read about it.

Grab the latest code at Github.

Categories
Corona SDK Lua OOP

Send custom events with EventDispatcher for Corona SDK / Lua

In Corona SDK, event listeners can only be added to the global Runtime object or to display objects. In order to broadcast messages to event listeners, the event dispatcher is limited to either one or the other. This limitation places messaging in the two scopes instead of between objects that are supposed to be talking to each other. I’ve seen many examples with display objects that are created solely for the purpose of dispatching events. This just doesn’t feel right to me; so I’m releasing my EventDispatcher, perhaps other developers may find it useful too.

Those who came from the good old Flash 5 days may remember FLEM (FLash Event Model). It was the first listener event model for Flash and ActionScript, and was created by Branden Hall and further developed and maintained by me. I’ve adapted the basic event model mechanism found in ActionScript 2/3 to Lua. This EventDispatcher has a similar interface as the listener model in Corona SDK, with some extra features thrown in (such as optional extra parameters when dispatching events, and returning status).

EventDispatcher provides custom event broadcaster/listener mechanism to regular Lua objects, it works as regular Lua 5.1 / 5.2 code, in Corona SDK, and likely other Lua-based frameworks.

[ Update: See http://swfoo.com/?p=718 ]

Basic usage:

    local EvtD = require "EventDispatcher"

    local listener = {
        eventName = function(event)
            print(event.name)
        end
    }

    local broadcaster = EvtD()

    broadcaster:addEventListener( "eventName", listener )
    broadcaster:hasEventListener( "eventName", listener )
    broadcaster:dispatchEvent( { name="eventName" } )
    broadcaster:removeEventListener( "eventName", listener )

Sample code below demonstrates how it can be used:

local EvtD = require "EventDispatcher"

---------------------------------------------------------------------------

-- shared function for cowboys; shows the use of event.target
local function cowboyDraw(event, ...)
	if event.subject then
		print(event.target.name .." is ".. event.name .."ing a gun and shooting a ".. event.subject)
	else
		print(event.target.name .." is ".. event.name .."ing a gun")
	end
end

---------------------------------------------------------------------------

-- table listeners

local cowboy1 = {
	name = "Cowboy1",
	draw = cowboyDraw
}

local cowboy2 = {
	name = "Cowboy2",
	draw = cowboyDraw
}

---------------------------------------------------------------------------

-- listener as table; shows the use of event.source
local iPad = {
	turnOn = function(event, ...)
		print("iPad is turned on by ".. event.source.name .." (table)")
	end,

	turnOff = function(event, ...)
		print("iPad is turned off by ".. event.source.name .." (table)")
	end
}

-- listener as function
local function turnOniPad(event, ...)
	print("iPad is turned on by ".. event.source.name .." (function)")
end

---------------------------------------------------------------------------

-- basic artist draw function
local function artistDraw(event, ...)
	print(event.target.name .." is ".. event.name .."ing a picture")
end

---------------------------------------------------------------------------

-- artist1 is both a listener and a event dispatcher
local artist1 = EvtD{
	name = "Artist1",
	draw = artistDraw,

	-- responds to the 'rest' message, and sends a message to the iPad
	rest = function(event, ...)
		print(event.target.name .." is ".. event.name .."ing")

		-- event.target is artist1
		event.target:dispatchEvent( { name="turnOff" } )
	end
}

-- artist1 tells iPad to listen to the 'turnOff' message
artist1:addEventListener( "turnOff", iPad)

---------------------------------------------------------------------------

-- artist2 is both a listener and a event dispatcher
local artist2 = EvtD{
	name = "Artist2",

	draw = function(event, ...)
		-- event.target is artist2
		event.target:dispatchEvent( { name="turnOn" } )

		if event.subject then
			print(event.target.name .." is ".. event.name .."ing a ".. event.subject .." on the iPad")

			-- shows the use of extra arguments
			local func,pieces,name = ...
			func(pieces,name)
		else
			print(event.target.name .." is ".. event.name .."ing on the iPad")
		end
	end,

	rest = function(event, ...)
		event.target:dispatchEvent( { name="turnOff" } )
		print(event.target.name .." is ".. event.name .."ing")
	end
}

-- shows the use of table and function listeners
artist2:addEventListener( "turnOff", iPad)
artist2:addEventListener( "turnOn", turnOniPad)

---------------------------------------------------------------------------

-- mayor is a event dispatcher who tells others what to do
local mayor = EvtD()

-- mayor shows how much gold is collected
mayor.collectGold = function(nPieces, fromName)
	print("Mayor collected ".. nPieces .." pieces of gold from ".. fromName)
end

-- mayor tells these four people to pay attention to different messages
mayor:addEventListener( "draw", cowboy1 )
mayor:addEventListener( "draw", cowboy2 )
mayor:addEventListener( "draw", artist1 )
mayor:addEventListener( "draw", artist2 )
mayor:addEventListener( "rest", artist2 )

-- mayor sends the 'rest' message
mayor:dispatchEvent( { name="rest" } )
print("Rested 1")

-- mayor tells everyone to draw
mayor:dispatchEvent( { name="draw" } )

-- mayor tells these people to stop listening to the 'draw' message
mayor:removeEventListener( "draw", cowboy1 )
mayor:removeEventListener( "draw", artist1 )
print("Removed")

-- mayor tells artist1 to listen to the 'rest' message
mayor:addEventListener( "rest", artist1 )

-- mayor tells whoever is still listening to rest
mayor:dispatchEvent( { name="rest" } )
print("Rested 2")

-- mayor tells whoever is still listening to draw, with a subject and extra parameters
mayor:dispatchEvent( { name="draw", subject="bandit" }, mayor.collectGold, 42, "Dave" )

Here is the output from the code:

iPad is turned off by Artist2 (table)
Artist2 is resting
Rested 1
Cowboy1 is drawing a gun
Cowboy2 is drawing a gun
Artist1 is drawing a picture
iPad is turned on by Artist2 (function)
Artist2 is drawing on the iPad
Removed
iPad is turned off by Artist2 (table)
Artist2 is resting
Artist1 is resting
iPad is turned off by Artist1 (table)
Rested 2
Cowboy2 is drawing a gun and shooting a bandit
iPad is turned on by Artist2 (function)
Artist2 is drawing a bandit on the iPad
Mayor collected 42 pieces of gold from Dave

And here is the EventDispatcher module:

Update (Sept 12, 2014): Added aliases ‘on’ and ’emit’ for ‘addEventListener’ and ‘dispatchEvent’ for Coronium GS.


-- EventDispatcher.lua
--
-- Provides custom event broadcaster/listener mechanism to regular Lua objects.
--
-- Created by: Dave Yang / Quantumwave Interactive Inc.
--
-- http://qwmobile.com  |  http://swfoo.com/?p=632
--
-- Version: 1.1.4
--
-- Basic usage:
--		local EvtD = require "EventDispatcher"
--		local listener = {
--			eventName = function(event)
--				print(event.name)
--			end
--		}
--		local broadcaster = EvtD()
--		broadcaster:addEventListener( "eventName", listener ) or broadcaster:on( "eventName", listener )
--		broadcaster:hasEventListener( "eventName", listener )
--		broadcaster:dispatchEvent( { name="eventName" } ) or broadcaster:emit( { name="eventName" } )
--		broadcaster:removeEventListener( "eventName", listener )
---------------------------------------------------------------------------

local EventDispatcher = {}

function EventDispatcher:init(o)
	local o = o or {}
	o._listeners = {}
	self.__index = self
	return setmetatable(o, self)
end

---------------------------------------------------------------------------

-- Check if the event dispatcher has registered listener for the event eventName
-- Return boolean (true/false), and if found also return the index of listener object
function EventDispatcher:hasEventListener(eventName, listener)
	if eventName==nil or #eventName==0 or listener==nil then return false end

	local a = self._listeners
	if a==nil then return false end

	for i,o in next,a do
		if o~=nil and o.evt==eventName and o.obj==listener then
			return true, i
		end
	end
	return false
end

---------------------------------------------------------------------------

-- Add a listener for event eventName (a string).
-- Return addition status (true/false), position of listener is also returned if false; position=0 if failed
function EventDispatcher:addEventListener(eventName, listener)
	local found,pos = self:hasEventListener(eventName, listener)
	if found then return false,pos end

	local a = self._listeners
	if a==nil then return false,0 end

	a[#a+1] = { evt=eventName, obj=listener }
	return true
end

-- 'on' is an alias of 'addEventListener'
EventDispatcher.on = EventDispatcher.addEventListener

---------------------------------------------------------------------------

-- Dispatch event (a table, must have a 'name' key), with optional extra parameters.
-- Return dispatched status (true/false).
function EventDispatcher:dispatchEvent(event, ...)
	if event==nil or event.name==nil or type(event.name)~="string" or #event.name==0 then return false end

	local a = self._listeners
	if a==nil then return false end

	local dispatched = false
	for i,o in next,a do
		if o~=nil and o.obj~=nil and o.evt==event.name then
			event.target = o.obj
			event.source = self

			if type(o.obj)=="function" then
				o.obj(event, ...)
				dispatched = true
			elseif type(o.obj)=="table" then
				local f = o.obj[event.name]
				if f~= nil then
					f(event, ...)
					dispatched = true
				end
			end
		end
	end
	return dispatched
end

-- 'emit' is an alias of 'dispatchEvent'
EventDispatcher.emit = EventDispatcher.dispatchEvent

---------------------------------------------------------------------------

-- Remove listener with eventName event from the event dispatcher.
-- Return removal status (true/false).
function EventDispatcher:removeEventListener(eventName, listener)
	local found,pos = self:hasEventListener(eventName, listener)
	if found then
		table.remove(self._listeners, pos)
	end
	return found
end

---------------------------------------------------------------------------

-- create syntactic sugar to automatically call init()
setmetatable(EventDispatcher, { __call = function(_, ...) return EventDispatcher:init(...) end })

return EventDispatcher

EventDispatcher provides a broadcaster/listener event mechanism to regular Lua objects. Corona developers can write cleaner object-oriented messaging code that doesn’t rely on display objects or send messages from the global Runtime.

For the latest code, check out EventDispatcher at github.

Categories
ActionScript OOP

Inheritance in ActionScript 2.0

Back in the good old days of Flash 5 and MX, trying to do inheritance in ActionScript required some knowledge of what goes on behind the scene. Also, the infamous superclass invocation bug haunted developers for almost three years.

Now in ActionScript 2.0, with the new OOP-specific keywords, inheritance is much simpler:

class Parent {

}

class Child extends Parent {

}

That’s all. And the keyword “super()” in the Child class constructor is not even mandatory because the compiler inserts it for you!

As for the superclass invocation bug, it is now fix for Flash player 7!

Categories
ActionScript OOP Tips & Tricks

abstract class

Since ActionScript 2.0 does not have the “abstract” modifier, here’s one way to create a class that acts like an abstract class, by making the constructor private:

class PretendToBeAbstractClass {
    // private constructor
    private function PretendToBeAbstractClass() {}
}

When the following statement is compiled, the compiler will complain that one can’t instantiate from this class because the constructor is private:

var o:PretendToBeAbstractClass = new PretendToBeAbstractClass();

Although the constructor is private, but because it behaves as protected, you can still extend this class:

class MyClass extends PretendToBeAbstractClass {
}

What is missing is the compiler does not actually know what an abstract class is; therefore there won’t be any warning messages.

As I mentioned in the last post, you can get around all the type-checking at runtime, and even instantiating an “abstract” class at runtime like this:

var o = new _global["PretendToBeAbstractClass"]();

Or even access “private” properties from it:

trace(o.somePrivateVar);

Obviously these actions defeat the purpose of strict-typing, but it is possible (until runtime type-checking is implemented).

Categories
ActionScript Design Patterns OOP Tips & Tricks

Singleton

Here’s one way to implement a Singleton in ActionScript 2.0:

class Singleton {
	// the only instance of this class
	private static var inst:Singleton;

	// keep count of the number of Singleton objects referenced
	private static var refCount:Number = 0;

	// constructor is private
	private function Singleton() {}

	// get an instance of the class
	public static function get instance():Singleton {
		if (inst == null) inst = new Singleton();
			++refCount;
			return inst;
		}

		// return the number of instance references
		public static function get referenceCount():Number {
		return refCount;
	}
}

The count property is for keeping count of the number of Singleton objects referenced (obviously it is not aware of destroyed objects), and is not really a necessary member of the Singleton pattern.

To use this class, one would write something like this:

var s1:Singleton = Singleton.instance;
var s2:Singleton = Singleton.instance;

Now s1 and s2 are the same instance of the Singleton class.

Categories
ActionScript OOP

private, protected & public

In ActionScript 2.0, there are “private” and “public” modifiers, but there is no “protected”. However, “private” behaves like “protected” – i.e. subclasses and instances can access private members; so there is no true private scope.

To make sure the compiler catches access to private properties/functions, strict-typing has to be used:

class SomeClass {
     private var:somePrivateProperty:String;
 }

// without specifying the type of the instance, the compiler will not
// prevent statements from accessing private properties; therefore,
// o.somePrivateProperty is accessible

var o = new SomeClass();
trace(o.somePrivateProperty);

// by specifying the type, the compiler will catch acess to
// private members:
var o:SomeClass = new SomeClass();

Because type-checking is only performed at compile time, there is no guarantee that private members cannot be accessed at runtime.

By default, if the “public” modifier is left out, the member is assumed to be public.