Last active
December 18, 2015 17:09
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AppEvents Tab Order | |
------------------------------ | |
This file should not be included in the Codea project. | |
#Dispatcher | |
#Event | |
#Events | |
#AppEvents | |
#ExampleEventObject | |
#Main | |
#Tower |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------------------- | |
-- 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------------------- | |
-- 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------------------- | |
-- 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------------------- | |
-- 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------------------- | |
-- 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------------------- | |
-- 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------------------- | |
-- 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