Skip to content

Instantly share code, notes, and snippets.

@XanDDemoX
Last active December 18, 2015 17:09
Show Gist options
  • Save XanDDemoX/5816769 to your computer and use it in GitHub Desktop.
Save XanDDemoX/5816769 to your computer and use it in GitHub Desktop.
AppEvents Release v1.0.2 -AppEvents - An easy to use events framework for Codea
AppEvents Tab Order
------------------------------
This file should not be included in the Codea project.
#Dispatcher
#Event
#Events
#AppEvents
#ExampleEventObject
#Main
#Tower
-------------------------------------------------------------------------------------------
-- AppEvents --
-- An easy to use events framework built for Codea with Codea --
-- Version:1.0.2 --
-- Written by: XanDDemoX --
-------------------------------------------------------------------------------------------
AppEvents = class()
-- setup object to represent the app and create events object for it
-- app will always be the sender when using AppEvents.Global
AppEvents.App = {}
AppEvents.Global = Events(AppEvents.App)
--setup handlers table to track object instance - Events instance key pairs
AppEvents.__handlers = {}
AppEvents.__handlers[AppEvents.App] = AppEvents.Global
-- check if an object is being tracked by the handlers collection
function AppEvents.isTracked(obj)
if obj == nil then return false end
return AppEvents.__handlers[obj] ~= nil
end
-- track an object - optionally passing a custom Events instance
-- otherwise creates an Events instance
-- returns the Events instance for the given object
function AppEvents.track(obj,eventsobj)
if obj == nil then return false end
if not AppEvents.isTracked(obj) then
AppEvents.__handlers[obj] = eventsobj or Events(obj)
end
return AppEvents.__handlers[obj]
end
-- clears all Event instances from the Events instance for the given object and
-- removes the Events instance from the handlers collection
function AppEvents.untrack(obj)
if obj == nil then return end
if AppEvents.isTracked(obj) then
AppEvents.__handlers[obj]:dispose()
AppEvents.__handlers[obj] = nil
end
end
-- returns a table of Event instances from the Events instance for a given tracked object
function AppEvents.getEvents(obj)
if obj == nil then return {} end
if AppEvents.__handlers[obj] == nil then return {} end
return AppEvents.__handlers[obj]:getEvents()
end
-- returns true if an event with the given name is defined within a tracked object Events instance
function AppEvents.isdefined(name,obj)
if name== nil or obj == nil then return false end
if AppEvents.__handlers[obj] == nil then return false end
return AppEvents.__handlers[obj]:isdefined(name)
end
--defines an event of the given name optionally setting a custom Event instance
-- if the object is not tracked this will track it and then define the event
-- returns the Event instance
function AppEvents.define(name,obj,eventobj)
if name== nil or obj == nil then return end
local objEvents = AppEvents.track(obj)
return objEvents:define(name,eventobj)
end
-- undefines the event of the given name from the given tracked object
function AppEvents.undefine(name,obj)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
objEvents:undefine(name)
end
end
-- binds an event handler function to the event of the given name on the given object
-- if the event is not defined then it will be
function AppEvents.bind(name,obj,callback,selfcontext)
if name == nil or obj == nil or callback == nil then return end
local objEvents = AppEvents.track(obj)
objEvents:bind(name,callback,selfcontext)
end
-- unbinds the event handler function from the event with the given name from the given object
-- if no callback is passed then then entire event is undefined
function AppEvents.unbind(name,obj,callback)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
if callback == nil then objEvents:undefine(name) return end
objEvents:unbind(name,callback)
end
end
-- raises the event of the given name on the given object
function AppEvents.raise(name,obj,...)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
objEvents:raise(name,...)
end
end
-- gets the Event instance of the given name from the given object
function AppEvents.get(name,obj)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
return objEvents:get(name)
end
end
-- supresses the Event instance of the given name on the given object untill unsuppress is called.
-- this will prevent calls to raise on the instance from raisng any callback functions
function AppEvents.suppress(name,obj)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
objEvents:suppress(name)
end
end
-- unsuppresses the Event instance of the given name on the given object
function AppEvents.unsuppress(name,obj)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
objEvents:unsuppress(name)
end
end
--stops the propagation of the Event instane with the given name on the given object
-- this prevents any further callbacks from being called during until the event is raised again
function AppEvents.stop(name,obj)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
objEvents:stop(name)
end
end
-- clears the callbacks of the event with the given name on the given object
function AppEvents.clear(name,obj)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
objEvents:clear(name)
end
end
-- disposes of the event with the given name on the given object
function AppEvents.dispose(name,obj)
if AppEvents.isdefined(name,obj) then
local objEvents = AppEvents.__handlers[obj]
objEvents:dispose(name)
end
end
-- helper function to see if the sender is AppEvents.App (which is the object /
-- table reference used to represent the application) this will indicate whether the event is defined
-- in AppEvents.Global
function AppEvents.isGlobal(sender)
if sender == nil then return false end
return sender == AppEvents.App
end
-------------------------------------------------------------------------------------------
-- AppEvents --
-- An easy to use events framework built for Codea with Codea --
-- Version:1.0.2 --
-- Written by: XanDDemoX --
-------------------------------------------------------------------------------------------
Dispatcher = class()
-- creates a dispatcher which consistis of a queue of dispatch functions to call
-- a coroutine and a function to add functions to to the queue to call then resuming the coroutine "dispatches"
-- by calling all of the functions in the queue
-- additinally has a disposed flag to send the coroutine to the dead state and reset the dispatcher to its
-- initial state if and when required
-- with a little refactoring this could actually be a generic "Worker" which just executes functions
-- but ive kept it more specific to events incase any special functionality is required later
function Dispatcher:init(recursive)
self.recursive = recursive or false
-- internal fields
-- queue of functions to dispatch
self._queue = Tower()
-- pointer to coroutine
self._routine = nil
-- disposed flag
self._disposed = false
end
-- internal function which creates the coroutine that does the work.
-- this is essentially an infinate loop which checks for callbacks to call in the queue
-- if there are queued callbacks it calls them all (removing them from the queue)
-- if there isnt any callbacks to call the coroutine yields (so other stuff can happen)
function Dispatcher:_create()
return coroutine.create(function()
-- loop whilst dispatcher is not disposed
while self._disposed == false do
while self._queue:count() > 0 do
-- get top item (first item added is first out)
-- items are added at top of internal
-- tower table (via poke in self:enqueue)
-- so items can be popped here to prevent
-- the internal tower table shifting when calling
-- callbacks. So the first item added is at the bottom.
local dispatch = self._queue:pop()
-- call the dispatch function
-- wrapped in xpcall for stacktrace if stuff goes wrong :)
xpcall(dispatch, function()print(debug.traceback())end)
end
--yield or pay XD
coroutine.yield()
end
-- at this point the dispacher is disposed
-- so the coroutine can exit (goto the dead state)
-- loose references when dead (reset)
self:init(self.recursive)
end)
end
-- enqueues functions for the dispatcher to call upon resume
-- accepts a single function or a table of functions (other types are ignored)
-- if the recursive flag is set then a table of functions of any depth will be recurively enqueued
-- it proably wont like classes passed here though (because they are essentially tables ;))
function Dispatcher:enqueue(func)
if func ~= nil then
local funcType = type(func)
-- if function just enqueue with poke
-- enqueuing at the top of the towers internal table offsets any shifting of the internal
-- table to before the the event is raised rather than during
if funcType == "function" then
self._queue:poke(func)
elseif funcType == "table" then
-- if we have a table of functions insert all functions
-- if we have a multidimensional table and we are recursive then
-- recurse until all functions have been added
for i,v in ipairs(func) do
local vt = type(v)
if vt == "function" then
self._queue:poke(v)
elseif vt == "table" and self.recursive == true then
self:_enqueue(v)
end
end
end
end
end
-- resumes the internal coroutine and calls all functions in the queue
function Dispatcher:resume()
-- lazy create coroutine
if self._routine == nil then
self._routine = self:_create()
end
coroutine.resume(self._routine)
end
-- enqueues the given function or table of functions and resumes the coroutine
-- to execute the
function Dispatcher:dispatch(func)
if func ~= nil then
local funcType = type(func)
if funcType == "function" or funcType == "table" then
-- enqueue function or table of functions then resume coroutine
self:enqueue(func)
self:resume()
end
end
end
-- this function allows the dispatcher to be "disposed" - reset to its initial state
function Dispatcher:dispose()
--self._disposed = true
if self._routine ~= nil then
self:resume()
end
end
--older method for creating a dispatcher. this dispatcher class is based on the below (which is abit dirtier):
-- factory method: creates a dispatcher which consistis of a queue of dispatch functions to call
-- a coroutine and a function to add functions to dispatch - it acts as a message pump
-- additinally has a disposed flag to send the coroutine to the dead state and reset the dispatcher to its
-- initial state
function Dispatcher._createDispatcher()
local dispatcher = {
queue = {},
disposed = false,
routine = -1,
recursive = false,
dispatch = function(self,func,_recursing)
if _recursing == nil then _recursing = false end
-- lazy create routine
if self.routine == -1 then
self.routine = self._create()
end
-- insert func to queue if it is a function
-- or insert list of functions
if func ~= nil then
local funcType = type(func)
if funcType == "function" then
table.insert(self.queue,1,func)
elseif funcType == "table" then
for i,v in ipairs(func) do
local vt = type(v)
if vt == "function" then
table.insert(self.queue,1,v)
elseif vt == "table" and self.recursive == true then
self.dispatch(self,v,true)
end
end
end
end
-- resume coroutine
if _recursing == false then
coroutine.resume(self.routine)
end
end,
-- set disposed flag and resume coroutine allowing it to die
dispose = function(self) self.disposed = true coroutine.resume(self.routine) end
}
-- creates a coroutine to do the dispatchers pumping ;)
dispatcher._create = function()
return coroutine.create(function()
-- loop whilst not disposed
while not dispatcher.disposed do
while #dispatcher.queue > 0 do
-- get top item (first item added is first out)
-- items are added at top of table to prevent shifting
-- table items when calling callbacks so the first item added
-- is at the bottom
local dispatch = dispatcher.queue[#dispatcher.queue]
-- remove the bottom item (probably wants testing for race conditions :/)
table.remove(dispatcher.queue)
-- call the dispatch function
dispatch()
end
--yield or pay XD
coroutine.yield()
end
-- loose references when dead (reset)
dispatcher.routine = -1
dispatcher.queue = {}
dispatcher.disposed = false
end)
end
return dispatcher
end
-------------------------------------------------------------------------------------------
-- AppEvents --
-- An easy to use events framework built for Codea with Codea --
-- Version:1.0.2 --
-- Written by: XanDDemoX --
-------------------------------------------------------------------------------------------
Event = class()
-- global prevents dispatchers being created for all events when false
Event.DispatcherEnabled = true
-- represents an event
-- be sure to give the event a nice unique name for identifying and consuming it later
-- and be sure to assotiate the event to an object so you will know "who" (which object)
-- is trying to notify you of something (and you can make them do things when things happen :) )
--
-- name : The name of the event
--
function Event:init(name,obj)
-- event properties
self._name = name or self
self._obj = obj or self
-- callbacks list
self._callbacks = Tower()
self._selfctxcbs = {}
-- stop propagation and suppression flags
self._stop = false
self._supressed = false
-- create a dispatcher to send out the notifications (call all callbacks)
-- of when the event happens
if Event.DispatcherEnabled == true then
self._dispatcher = Dispatcher()
end
end
-- returns the name of the event
function Event:getName()
return self._name
end
-- returns the object to which the event belongs
function Event:getObject()
return self._obj
end
--returns the function signature required for Events:define
function Event:shortdef()
return self._name,self
end
-- returns the function signature required for AppEvents.define
function Event:longdef()
return self._name,self._obj,self
end
-- binds the given callback function to be called when the event is raised
function Event:bind(callback,selfcontext)
if callback == nil then return end
local cb = callback
if selfcontext ~= nil then
-- need to keep track of these callbacks so we dont duplicate things and unbinding works correctly
if self._selfctxcbs[callback] == nil then
-- wrap callback function and self context in a function which passes through arguments
cb = function(...) callback(selfcontext,...) end
self._selfctxcbs[callback] = cb
else
cb = self._selfctxcbs[callback]
end
end
if not self._callbacks:contains(cb) then
self._callbacks:add(cb)
return cb
end
end
-- unbinds the given callback function
function Event:unbind(callback)
if callback == nil then self._callbacks:clear() return end
local cb = callback
-- check if callback is looked up "self" wrapping function
if self._selfctxcbs[callback] ~= nil then
cb = self._selfctxcbs[callback]
self._selfctxcbs[callback] = nil
end
if self._callbacks:contains(cb) then
self._callbacks:remove(cb)
end
end
-- raises the event with the given parameters
function Event:raise(...)
-- ignore calls to raise if suppressed
-- events which are suppressed during being raised will continue to be raised
if self._supressed == true then return end
-- reset stop flag
self._stop = false
-- copy callbacks list
local callbacksCopy = Tower(self._callbacks:getItems())
-- copy args and insert sender
local argsCopy = Tower(arg)
argsCopy:insertAt(self._obj,1)
-- create dispatch function to raise the event
local eventDispatch = function()
for i,v in callbacksCopy:items() do
-- if not stopped map params to callback and call
if self._stop == false then
local cb = argsCopy:paramMap(v)
cb()
else
-- if stopped return immediately and reset flag
self._stop = false
return
end
end
end
-- enqueue the dispatch function
if Event.DispatcherEnabled == true then
if self._dispatcher == nil then self._dispatcher = Dispatcher() end
self._dispatcher:dispatch(eventDispatch)
else
-- if dispatchers are disabled just call.
eventDispatch()
end
end
-- stops propagation of the event
function Event:stop()
self._stop = true
end
-- suppress the event so calls to raise have no effect
function Event:suppress()
self._supressed = true
end
-- unsupress the event so calls to raise have effect
function Event:unsuppress()
self._supressed = false
end
-- clear the callbacks list.
function Event:clear()
self._callbacks:clear()
end
-- dipose of the dispatcher and clear the callbacks list
function Event:dispose()
if self._dispatcher ~= nil then
self._dispatcher:dispose()
end
self:clear()
end
-------------------------------------------------------------------------------------------
-- AppEvents --
-- An easy to use events framework built for Codea with Codea --
-- Version:1.0.2 --
-- Written by: XanDDemoX --
-------------------------------------------------------------------------------------------
-- represents a collection of events / the events of an object instance
Events = class()
function Events:init(obj)
-- the object the events contained within are associated too
self._obj = obj or self
-- internal key value lookup of events by name
self._handlers = {}
end
-- returns a table of the Event instances
function Events:getEvents()
local namesTable ={}
for k,v in pairs(self._handlers) do
table.insert(namesTable,k)
end
return namesTable
end
-- returns true if the event of the given name is defined
function Events:isdefined(name)
if name == nil then return false end
return self._handlers[name] ~= nil
end
-- defines an event of the given name optionally with a custom Event instance
-- returns the defined Event instance
function Events:define(name,eventobj)
if name == nil then return end
if not self:isdefined(name) then
if eventobj ~= nil and eventobj.getObject and eventobj:getObject() == self._obj then
self._handlers[name] = eventobj
else
self._handlers[name] = Event(name,self._obj)
end
--self._handlers[name] = eventobj or Event(name,self._obj)
end
return self._handlers[name]
end
-- undefines an event of the given name
function Events:undefine(name)
if name == nil then return end
if self:isdefined(name) then
self._handlers[name]:dispose()
self._handlers[name] = nil
end
end
-- binds the given event handler function to the Event instance of the given name
-- if the event is not defined it will be
function Events:bind(name,callback,selfcontext)
if name == nil or callback == nil then return end
local evt = self:define(name)
return evt:bind(callback,selfcontext)
end
-- unbinds the given event handler function from the Event instance of the given name
function Events:unbind(name,callback)
if callback == nil then self:undefine(name) return end
if self:isdefined(name) then self._handlers[name]:unbind(callback) end
end
-- raises the event of the given name with the given parameters
function Events:raise(name,...)
if self:isdefined(name) then self._handlers[name]:raise(...) end
end
-- returns the event of the given name or nil if undefined
function Events:get(name)
if self:isdefined(name) then return self._handlers[name] end
return nil
end
-- suppresses the event of the given name
function Events:suppress(name)
if self:isdefined(name) then self._handlers[name]:suppress() end
end
-- unsuppresses the event of the given name
function Events:unsuppress(name)
if self:isdefined(name) then self._handlers[name]:unsuppress() end
end
-- stops the propagation of the event of the given name until the next raise
function Events:stop(name)
if self:isdefined(name) then self._handlers[name]:stop() end
end
-- clears all callbacks from an event or all defined events
function Events:clear(name)
if name ~= nil then
if self:isdefined(name) then self._handlers[name]:clear() end
else
for i,v in pairs(self._handlers) do if v ~= nil then v:clear() end end
end
end
-- disposes of an event or all events
function Events:dispose(name)
if name ~= nil then
if self:isdefined(name) then
self._handlers[name]:dispose()
self._handlers[name] = nil
end
else
for i,v in pairs(self._handlers) do if v ~= nil then v:dispose() end end
self._handlers = {}
end
end
-------------------------------------------------------------------------------------------
-- AppEvents --
-- An easy to use events framework built for Codea with Codea --
-- Version:1.0.2 --
-- Written by: XanDDemoX --
-------------------------------------------------------------------------------------------
ExampleEventObject = class()
-- this is an example class to show how to setup events for the classes you create.
function ExampleEventObject:init()
-------------------------------------------------------------------------------------------
-- single event setup --
-------------------------------------------------------------------------------------------
-- classes which require its instances to have a single or a small number of events which are accessible
-- globally can define single globally accessible events for the current instance with AppEvents using by
-- passing the event name and self to the define function
self.exampleEvent = AppEvents.define("Example_event",self)
-- you can now bind / raise and unbind events by using the exampleEvent field or with AppEvents
-- you can call any method in the Event class. you can also pass a custom derrivative (child / inherited)
-- of the Event class as the third parameter to the define method for more advanced usage
-- self.exampleEvent = AppEvents.define("Example_event",self,self.CustomEvent)
-- if you dont want your events to be accessible globally then you can create localised events
-- by creating Event instances like below.
self.exampleEventLocal = Event("Example_event_local",self)
-------------------------------------------------------------------------------------------
-- Elevation --
-------------------------------------------------------------------------------------------
-- you can elevate this event at any time to a globally accesible event with the AppEvents.define and
-- Event:definition() functions
--self.exampleEventLocal = AppEvents.define(self.exampleEventLocal:definition())
-------------------------------------------------------------------------------------------
-- multiple events setup --
-------------------------------------------------------------------------------------------
-- classes which require its instances have multiple events which are accessible globally should
-- define a globally accessible events collection for the current instance with AppEvents by passing
-- self to the track function on initialisation
self.events = AppEvents.track(self)
-- you can now define globally accessible events for the current instance directly with self.events
self.collectionEvent = self.events:define("Example_global_collection_event")
-- just like single events you can define local events collections by creating an instance of the Events class
self.localEvents = Events(self)
-- and again just like above you can define locally accessible events on your local instance
self.collectionEventLocal = self.localEvents:define("Example_local_collection_event")
-------------------------------------------------------------------------------------------
-- Elevation (again) --
-------------------------------------------------------------------------------------------
-- simular to Event objects you can also elevate entire collections /
-- specify custom event collection derrivatives
-- with AppEvents.track
-- self.localEvents = AppEvents.track(self,self.localEvents)
-- if the collection is elevated all events contained within are also elevated to be globally accessible
-- note: if an object is allready "tracked" by app AppEvents then it must be untracked before the
-- track method will accept a custom Events object.
-- note: the above also applies to the "define" method
-- if an object is allready tracked or an event is allready defined then the tracked Events instance or defined
-- Event instance will be returned
-- binding event handlers:
-- you can bind event handlers as public or locals (private)
-- bind publically (probably for special use)
self.events:bind("example_public_handler", ExampleEventObject.publicEventHandler,self)
-- local publicWithSelf = function(...) return self.publicEventHandler(self,...) end
-- or function(...) return self:publicEventHandler(...) end
-- these local functions pass through the current instance when the handler is called
-- but they also prevent the sender and any custom arguments from being shifted too the left by 1
-- because self.publicEventHandler has the signature: self.publicEventHandler(self,sender,arg)
-- instead of self:publicEventHandler(sender,arg) which passes self for us
-- bind privately
local privateEventHandler = function(sender,arg)
print("private handler called")
print("sender: ",sender)
print("arg: ",arg)
end
self.events:bind("example_private_handler",privateEventHandler)
end
function ExampleEventObject:raiseEvents()
-- bind for example purposes
local fn = function(sender,arg) print(arg) end
self.exampleEvent:bind(fn)
self.exampleEventLocal:bind(fn)
self.collectionEvent:bind(fn)
self.collectionEventLocal:bind(fn)
-- raise events:
self.exampleEvent:raise("exampleEvent called")
self.exampleEventLocal:raise("exampleEventLocal called")
print()
self.collectionEvent:raise("collectionEvent called")
self.collectionEventLocal:raise("collectionEventLocal called")
print()
self.events:raise("example_public_handler","arg")
print()
self.events:raise("example_private_handler","arg")
print()
print("Raising with AppEvents:")
-- raising with appevents:
AppEvents.raise("Example_event",self,"exampleEvent called")
AppEvents.raise("Example_event_local",self,"exampleEventLocal not called")
AppEvents.raise("Example_global_collection_event",self,"collectionEvent called")
AppEvents.raise("Example_local_collection_event",self,"collectionEventLocal not called")
end
function ExampleEventObject:publicEventHandler(sender,arg)
--local s = sender or "nil"
print("public handler called")
print("sender: ",sender)
print("arg: ",arg)
end
-------------------------------------------------------------------------------------------
-- AppEvents --
-- An easy to use events framework built for Codea with Codea --
-- Version:1.0.2 --
-- Written by: XanDDemoX --
-------------------------------------------------------------------------------------------
-- Use this function to perform your initial setup
function setup()
saveProjectInfo("Description","AppEvents - An easy to use events framework for Codea")
saveProjectInfo("Author","XanDDemoX")
autoGist = AutoGist("AppEvents","AppEvents - An easy to use events framework for Codea","1.0.2")
autoGist:backup(true)
-- Example AppEvents usage--
-------------------------------------------------------------------------------------------
---- Global Events Object (AppEvents) and Events Collections (Events) ----
-------------------------------------------------------------------------------------------
-- the global AppEvents.Global is a collection of events which is shared across the app --
-------------------------------------------------------------------------------------------
print("Global Events Output:")
-- statically bind, raise and unbind shared events anywhere with the Global Events Object instance
-- setup handler - handlers should always be specified with a sender as the first parameter
-- this allows you to always know what object the event is associated to
-- sender = reference the object instance that owns the event
-- testText = optional custom parameter (you can have as few or as many of these as you like)
testHandler = function(sender,testText) print(testText) end
-- bind handler / callback (testHandler) to an event with the given name.
-- if no event is defined by the given name ("test_event") then one is created
AppEvents.Global:bind("test_event",testHandler)
-- trigger / raise event -- passing through any custom values
AppEvents.Global:raise("test_event","test")
-- unbind handler / callback
AppEvents.Global:unbind("test_event",testHandler)
-- this wont be printed because there are no handlers / callbacks bound
AppEvents.Global:raise("test_event","not printed")
-- bind a permenant handler - this cannot be unbound without resolving the function pointer
-- via getEvents or clearing the callbacks or disposing the event
AppEvents.Global:bind("test_event",function(sender,testText,concatFunc,tbl)
print(concatFunc(tbl[1],tbl[2]))
end)
-- send any number of arguments (of any type) to a raised event
AppEvents.Global:raise("test_event","test",function(x,y) return x..y end,{"conca","tenated"})
-------------------------------------------------------------------------------------------
---- Event Suppression ----
-------------------------------------------------------------------------------------------
-- suppress propagtion of event (prevent callbacks from being called)
-- ( when suppressing events they must be suppressed before they are raised!)
AppEvents.Global:suppress("test_event")
-- nothing printed:
AppEvents.Global:raise("test_event","test",function(x,y) return x..y end,{"not","printed"})
-- allow callbacks to be called again
AppEvents.Global:unsuppress("test_event")
--printed:
AppEvents.Global:raise("test_event","test",function(x,y) return x..y end,{"not ","suppressed now!"})
-- Stop Event Propagation early within handlers --
AppEvents.Global:bind("test_event",function(sender,testText,concatFunc,tbl)
print(concatFunc(tbl[1],tbl[2]))
-- stop any further callbacks being called
AppEvents.Global:stop("test_event")
end)
-- this callback will never be raised because the handler above prevents the event from continuing!
AppEvents.Global:bind("test_event",function(sender,testText,concatFunc,tbl)
print(concatFunc(tbl[1],tbl[2]))
end)
-- raise the event whos propagtion will be stopped early
AppEvents.Global:raise("test_event","test",function(x,y) return x..y end,{"print two of me ","only!"})
-------------------------------------------------------------------------------------------
---- Event Cleanup ----
-------------------------------------------------------------------------------------------
-- clear all callbacks for an event
AppEvents.Global:clear("test_event")
-- nothing printed:
AppEvents.Global:raise("test_event","test",function(x,y) return x..y end,{"not","printed"})
-- rebind for illustration:
AppEvents.Global:bind("test_event",function(sender,testText,concatFunc,tbl)
print(concatFunc(tbl[1],tbl[2]))
end)
-- completely dispose of an event
AppEvents.Global:dispose("test_event")
-- nothing printed:
AppEvents.Global:raise("test_event","test",function(x,y) return x..y end,{"not","printed"})
-------------------------------------------------------------------------------------------
------- Virtual Events (via AppEvents) ----------
-------------------------------------------------------------------------------------------
-- Bind events to any object instance AppEvents allows the event to be tracked globally
print()
print("Virtual Events Output:")
objhandler = function(sender,arg) print(arg..sender.x.."," ..sender.y) end
obj = vec2(1,2)
-- bind the event of the given name (virtual test) to the given object pointer (obj)
-- with the given handler (objhandler)
AppEvents.bind("virtual_test",obj,objhandler)
--raise the event
AppEvents.raise("virtual_test",obj,"vec2 coords are: ")
-- unbind it
AppEvents.unbind("virtual_test",obj,objhandler)
-- show event isnt bound:
AppEvents.raise("virtual_test",obj,"not printed")
-- supression
-- rebind
AppEvents.bind("virtual_test",obj,objhandler)
-- suppress event
AppEvents.suppress("virtual_test",obj)
AppEvents.raise("virtual_test",obj,"not printed")
AppEvents.unsuppress("virtual_test",obj)
-- stop propagation
AppEvents.raise("virtual_test",obj,"unsuppressed virtual vec2: ")
AppEvents.bind("virtual_test",obj,function(sender,arg) print(arg..sender.x.."," ..sender.y)
AppEvents.stop("virtual_test",sender)
end)
AppEvents.bind("virtual_test",obj,function(sender,arg) print(arg..sender.x.."," ..sender.y) end)
AppEvents.raise("virtual_test",obj,"print two of me! virtual vec2: ")
-- cleanup
-- clear all handlers
AppEvents.clear("virtual_test",obj)
AppEvents.raise("virtual_test",obj,"not printed")
-- rebind for illustration
AppEvents.bind("virtual_test",obj,objhandler)
-- dispose event
AppEvents.dispose("virtual_test",obj)
AppEvents.raise("virtual_test",obj,"not printed")
-------------------------------------------------------------------------------------------
-- Advanced usage --
-------------------------------------------------------------------------------------------
-- you can also bind events to an object by creating an event instance
-- but this is not tracked globally until tracked/defined in AppEvents
print()
print("Advanced output:")
virtualEvt = Event("virtual_test",obj)
virtualEvt:bind(function(sender,arg) print(arg) end)
-- undefined event so wont be printed
AppEvents.raise("virtual_test",obj,"not printed")
-- define event with custom event object (potentially inherited from Event ;) )
AppEvents.define("virtual_test",obj,virtualEvt)
AppEvents.raise("virtual_test",obj,"custom defined printed")
-------------------------------------------------------------------------------------------
---------------- Event Variables (Event) ------------------
-------------------------------------------------------------------------------------------
-- Object instances can have their own Events collections too. --
-- also see ExampleEventObject for instance based usage --
-------------------------------------------------------------------------------------------
--create / get references to shared global events as variables. Then bind / raise and unbind
print()
print("Event Variables output:")
-- define a global event
-- define always returns an Event object
testEventVar = AppEvents.Global:define("test_var_event")
-- then bind handler only because object is known (in this case it is AppEvents.App)
testEventVar:bind(testHandler)
testEventVar:raise("test variable event")
testEventVar:unbind(testHandler)
testEventVar:raise("not printed")
-- suppression
testEventVar:bind(testHandler)
testEventVar:suppress()
-- not printed:
testEventVar:raise("not printed")
testEventVar:unsuppress()
-- stop propagtion
testEventVar:bind(function(sender,testText)
print(testText)
testEventVar:stop()
end)
-- this callback will never be raised because the handler above prevents the event from continuing!
testEventVar:bind(function(sender,testText) print(testText) end)
testEventVar:raise("print two of me: test variable event")
-- cleanup
testEventVar:clear()
testEventVar:raise("not printed")
testEventVar:bind(function(sender,testText) print(testText) end)
testEventVar:dispose()
testEventVar:raise("not printed")
-------------------------------------------------------------------------------------------
-- Example Event Object --
-------------------------------------------------------------------------------------------
print()
print("Example event object:")
evtObj = ExampleEventObject()
evtObj:raiseEvents()
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)
-- This sets the line thickness
strokeWidth(5)
-- Do your drawing here
end
-------------------------------------------------------------------------------------------
-- AppEvents --
-- An easy to use events framework built for Codea with Codea --
-- Version:1.0.2 --
-- Written by: XanDDemoX --
-------------------------------------------------------------------------------------------
Tower = class()
-- An all in one index based table manipulation (array manipulation) class which takes some inspiration from
-- .net and linq functions.
-- this can also operate, based on usage, as a stack (Last in first out LiFo), queue (First in First out FiFo) or -- Dequeue / (pronounced "deck") which is also called a double ended queue. e.g stack operates normally with push -
-- and pop, queue operates with push and pull. and poke inserts items at the top by default poke is equivalent to
-- table.insert(self._items,1)
-- note: The implementation of dequeue and queue is not as efficient as it could be because it
-- invites the internal table to shift its items to close gaps >_<
-- the user can select when to optimise the different associated functions (push,pop/poke,pull)
-- which inverts the internal table and switches push and pop from inserting and removing from the bottom
-- of the table to the top
-- this also provides powerful manipulation for using the contents of the collection with dynamic functions :D
-- (which is used for calling event callbacks)
function Tower:init(items,indexed)
self._itemCount = 0
self._items = {}
self._optimisePushPop = true
self:addrange(items,indexed)
end
-- internal function to increment / decrement the item count
function Tower:_incCount(amount)
self._itemCount = self._itemCount + amount
end
-- internal property returns the current index to pop items from
function Tower:_popIndex()
if self._optimisePushPop == true then
return self._itemCount
else
return 1
end
end
-- internal property returns the current index to pull items from
function Tower:_pullIndex()
if self._optimisePushPop == false then
return self._itemCount
else
return 1
end
end
-- returns the item at a given index or nil
function Tower:item(index)
if index < 1 or index > self._itemCount then
return nil
else
return self._items[index]
end
end
function Tower:items()
return ipairs(self._items)
end
-- returns a shallow copy of the internal items collection
function Tower:getItems()
local cloneItems = {}
for i,v in ipairs(self._items) do
table.insert(cloneItems,v)
end
return cloneItems
end
-- returns a clone with a shallow copy of the internal items collection
function Tower:clone()
return Tower(self._items)
end
-- inserts an item at the top of the stack (bottom of the table)
function Tower:push(...)
if #arg > 0 then
for i,v in ipairs(arg) do
if self._optimisePushPop == true then
self:insertAt(v)
else
self:insertAt(v,1)
end
end
end
end
function Tower:pushArray(items)
if #items > 0 then
self:push(unpack(items))
end
end
-- removes and returns the first item at the top of the stack / bottom of the queue (bottom of the table)
-- or returns nil
function Tower:pop()
local index = self:_popIndex()
local item = self:item(index)
if item then
self:removeAt(index)
end
return item
end
-- inserts the given item at the the bottom of the stack / top of the queue (top of the table)
function Tower:poke(...)
if #arg > 0 then
for i,v in ipairs(arg) do
if self._optimisePushPop == false then
self:insertAt(v)
else
self:insertAt(v,1)
end
end
end
end
function Tower:pokeArray(items)
if #items > 0 then
self:poke(unpack(items))
end
end
-- removes and returns the first item at the bottom of the stack / top of the queue (top of the table)
function Tower:pull()
local index = self:_pullIndex()
local item = self:item(index)
if item then
self:removeAt(index)
end
return item
end
-- returns the first item at the top of the stack / bottom of the queue (top of the table)
-- or returns nil
function Tower:peek()
return self:item(self:_popIndex())
end
-- returns the first item at the bottom of the stack / top of the queue (bottom of the table)
function Tower:peer()
return self:item(self:_pullIndex())
end
-- swaps push and pop from using the top of the table too the bottom
-- giving cheaper calls when pushing/poping items.
-- but more expensive calls to pull and poke.
function Tower:optimisePushPop()
if self._optimisePushPop == false then
self:invert()
self._optimisePushPop = true
end
end
-- swaps pull and poke from using the top of the table to the bottom
-- giving cheaper calls when pulling / poking items.
-- but more expensive calls to push and pop.
function Tower:optimisePullPoke()
if self._optimisePushPop == true then
self:invert()
self._optimisePushPop = false
end
end
-- example optimise use case: using tower as a large queue
-- tower populated with push initially then call optimisePullPoke
-- this has an initial cost of inverting the table but yeilds cheeper calls to
-- pull because the items are removed from the bottom of the table
-- circomventing the table shifting items up on removal or down on insert
-- (via poke). after using optimisePullPoke calls to push and pop become more
-- expensive as they are switched to use the top of the table instead
-- returns the count of items currently in the internal collection
function Tower:count()
return self._itemCount
end
function Tower:invert()
if self._itemCount > 0 then
local items = self:getItems()
local count = self._itemCount
self:clear()
-- counter for decrementing index
local c = count
for i=1, count do
self:insertAt(items[c])
c = c - 1
end
end
end
-- clears all items from the tower
function Tower:clear()
self._items = {}
self._itemCount = 0
end
-- returns the first index from the top within the internal collection of the given item
-- otherwise returns -1
function Tower:indexOf(item)
local items = self._items
for i,v in ipairs(items) do
if v == item then return i end
end
return -1
end
-- inserts the given item at the given index or the bottom of the collection (if index is > count)
-- returns the true if the item was inserted or false if the index is < 0
-- if index is nil item is inserted at the bottom of the collection
function Tower:insertAt(item, index)
if item == nil then return end
if index then
if index > self._itemCount then
table.insert(self._items,item)
elseif index < 0 then
table.insert(self._items,1,item)
else
table.insert(self._items,index,item)
end
else
table.insert(self._items,item)
end
self:_incCount(1)
return true
end
-- updates the item at the given index with the given new value
-- returns true if the item was updated or false if the index is out of range
function Tower:updateAt(index,newValue)
if index < 0 or index > self._itemCount then
return false
else
self._items[index] = newValue
return true
end
end
-- returns true if the item at the given index was removed from the internal collection
-- otherwise returns false
function Tower:removeAt(index)
if index < 0 or index > self._itemCount then
return false
else
table.remove(self._items,index)
self:_incCount(-1)
return true
end
end
-- returns true if the given item is contained within the internal collection
-- otherwise returns false
function Tower:contains(item)
for i,v in ipairs(self._items) do
if v == item then return true end
end
return false
end
function Tower:addrange(items,indexed)
if items == nil then return end
if #items > 0 then
if indexed == nil then indexed = true end
if indexed then
for i,v in ipairs(items) do
self:insertAt(v)
end
else
for i,v in pairs(items) do
self:insertAt(v)
end
end
end
end
function Tower:add(item)
self:insertAt(item)
end
-- returns true if the given item was removed from the internal collection
-- otherwise returns false
function Tower:remove(item)
local index = self:indexOf(item)
if index > 0 then
return self:removeAt(index)
end
return false
end
-- returns the first item matched by the given function(item) from the top of the internal collection
-- otherwise returns nil
function Tower:find(findDelegate)
local items = self._items
for i,v in ipairs(items) do
if findDelegate(v) then return v end
end
return nil
end
-- returns the first index from the top of the item matched by the given function(item)
-- otherwise returns -1
function Tower:findIndex(findDelegate)
local items = self._items
for i,v in ipairs(items) do
if findDelegate(v) == true then return i end
end
return -1
end
-- returns all indexs matched by the given function(item)
-- otherwise returns nil
function Tower:findIndexes(findDelegate)
local items = self._items
local foundItems = {}
for i,v in ipairs(items) do
if findDelegate(v) then
table.insert(foundItems,i)
end
end
return foundItems
end
-- returns all items matched by the given function(item)
-- otherwise returns nil
function Tower:findAll(findDelegate)
local items = self._items
local foundItems = {}
for i,v in ipairs(items) do
if findDelegate(v) then
table.insert(foundItems,v)
end
end
return foundItems
end
-- updates the first item from the top of the colection
-- which is matched by the given function(item) with the given new value
-- returns true if an item was updated otherwise false
function Tower:update(findDelegate,newValue)
local index = self:findIndex(findDelegate)
if index > 0 then
return self:updateAt(index,newValue)
end
return false
end
-- updates all items that match the given function(item) with the given new value
-- returns the count of items updated
function Tower:updateAll(findDelegate,newValue)
local indexes = self:findIndexes(findDelegate)
local count = 0
for i,v in ipairs(indexes) do
if self:updateAt(index,newValue) then count = count + 1 end
end
return count
end
-- removes the first item from the top of the collection which is matched by the given function(item)
function Tower:delete(findDelegate)
local item = self:find(findDelegate)
if item then
return self:remove(item)
end
return false
end
-- removes the all items from the collection which are matched by the given function(item)
-- returns the count of items removed
function Tower:deleteAll(findDelegate)
local indexes = self:findIndexes(findDelegate)
local count = 0
for i,v in ipairs(indexes) do
if self:removeAt((v-count)) then count = count + 1 end
end
return count
end
-- maps the items contained in the internal collection to the given function
function Tower:paramMap(func)
if func == nil then return function() end end
if self._itemCount < 1 then return function() end end
local funcType = type(func)
if funcType == "function" then
local items = self:getItems()
return function () return func(unpack(items)) end
elseif funcType == "table" then
local items = self:clone()
local functable = Tower(func)
return function()
local result = Tower()
functable:foreach(function(f) if f then result:add(items:paramMap(f)()) end end)
return result
end
else
return function() end
end
end
-- maps an item to the parameter of a given function for each item contained in the within the internal collection
function Tower:map(func)
if self._itemCount < 1 then return nil end
local funcs = {}
local items = self:getItems()
for i,v in ipairs(items) do
funcs[i] = func(v)
end
return funcs
end
-- maps an item as the parameters to a given function for each item within the internal collection
function Tower:mapn(func)
if self._itemCount < 1 then return nil end
local result = {}
local items = self:getItems()
local count = self._itemCount
local n = #self:peer()[1]
for i=1,n do
local args = {}
for k,v in ipairs(items) do
table.insert(args,v[i])
end
local f = func(unpack(args))
table.insert(result,f)
end
return result
end
-- iterates the internal collection calling a user defined function
function Tower:foreach(func)
if func then
for k,v in self:items() do
func(v)
end
end
end
-- sorts a (shallow) cloned insance of self and returns
function Tower:clonesort(func)
if func then
local cloneobj = self:clone()
cloneobj:sort(func)
return cloneobj
else
return nil
end
end
-- sorts the internal collection with a user defined function using table.sort
function Tower:sort(func)
if func then
table.sort(self._items,func)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment