PDA

View Full Version : Working with Lua events using the Classes.Publisher object



Tieske8
July 22nd, 2011, 01:37 PM
While going through the lua files trying to figure out how stuff works I created some test code to see how the Girder internals use the Classes.Publisher object to provide lua with an event capbility (in code, not the regular Girder events)

So here is my experiment, with details comments on how it works, both firing events and consuming events


--[[
Example to explain the working of events using the Classes.Publisher object.
This class is used in many Girder objects to inform other pieces of lua code
of changes in/to an object.
]]--

-- define the events our SampleObject will be generating
-- using a local for easier access ('Events' instead of 'self.Events')
local Events = table.makeset ( {
'Add',
'Stopped',
'Started',
'SomeOther',
'OneMore',
} )

-- This is where we start our SampleObject with events
local SampleObject = {

-- copy local event list into our object so consumers can examine available events
Events = Events,

-- We require our own Publisher object to handle the event subscriptions
Publisher = Classes.Publisher:New(),

-- this is an internal function which can be called to raise an event
-- event must be a value from the Events list, all extra parameters will be passed
-- on to the eventhandlers of our subscribers
Event = function (self,event,...)
-- Check if an event is provided, also make sure its a valid event
assert(event, "No event provided")
assert(Events[event] == event, "Event " .. event .. " is not a supported event")

-- now let our publisher spread the word of our event
self.Publisher:Event (event, unpack (arg))
end,

-- anyone can subscribe to our events by calling this function
-- parameter f should be a function to be called when an event occurs
Subscribe = function (self, f)
return self.Publisher:Subscribe(f)
end,

-- Once subscribed, anyone can unsubscribe from our events by calling this function
-- parameter f should be the same function that was used to subscribe.
Unsubscribe = function (self, f)
return self.Publisher:Unsubscribe (f)
end,

-- sample methods to start our object and raise an event
Start = function (self)
-- this is where actual code would go

-- Now raise the event that we started our object
self:Event(self.Events.Started, "parameter1", 2, "and parameter3")
end,

Other = function (self)
-- this is where actual code would go

-- Now raise the event that we did something
self:Event(self.Events.SomeOther, "No parameters")
end,

} -- SampleObject

-- Lets try our SampleObject here...
-- create a handler function
local myEventHandler = function (event, ...)
if event == SampleObject.Events.Started then
print ("Object informed us that it started, with parameters: ", unpack(arg) )
else
print ("Object informed us about some other event ('" .. event .. "'), with parameters: ", unpack(arg))
end
end

-- now register for events
SampleObject:Subscribe(myEventHandler)

-- go call Start method so we get an event, and see whether our handler prints them in the lua console
SampleObject:Start() --> Object informed us that it started, with parameters: parameter1 2 and parameter3
SampleObject:Other() --> Object informed us about some other event ('SomeOther'), with parameters: No parameters

-- Now unregister
SampleObject:Unsubscribe(myEventHandler)

Tieske8
July 22nd, 2011, 01:47 PM
Along the lines of the DRY principle (Don't Repeat Yourself), here's another method with the exact same result, but no need to repeat the code for every object.



--[[
Here is another approach to the same events methodology
Lets create a utility function that adds the event capability to any object/table you provide.
]]--

-- Here's the utility function; (its a global !!)
-- target is the object/table to which event capabilities will be added
-- eventlist is a 1 dimensional list (input to table.makeset()) with event names
-- The function returns the target object, with event capability added
table.AddEvents = function ( target, eventlist )
-- check our inputs
assert( type(target) == 'table', "Can't enable events; the target must be a table.")
assert( type(eventlist) == 'table', "Can't enable events; the eventlist must be a table.")
assert( target.Publisher == nil, "Can't add event capbility, the target already has an event publisher")

-- create event list into our object so consumers can examine available events
local Events = table.makeset(eventlist)

-- We require our own Publisher object to handle the event subscriptions
local Publisher = Classes.Publisher:New()

-- this is an internal function which can be called to raise an event
-- event must be a value from the Events list, all extra parameters will be passed
-- on to the eventhandlers of our subscribers
local Event = function (self,event,...)
-- Check if an event is provided, also make sure its a valid event
assert(event, "No event provided")
assert(Events[event] == event, "Event " .. event .. " is not a supported event")

-- now let our publisher spread the word of our event
self.Publisher:Event (event, unpack (arg))
end

-- anyone can subscribe to our events by calling this function
-- parameter f should be a function to be called when an event occurs
local Subscribe = function (self, f)
return self.Publisher:Subscribe(f)
end

-- Once subscribed, anyone can unsubscribe from our events by calling this function
-- parameter f should be the same function that was used to subscribe.
local Unsubscribe = function (self, f)
return self.Publisher:Unsubscribe (f)
end

-- Now lets add the created locals to our target object
target.Publisher = Publisher
target.Events = Events
target.Event = Event
target.Subscribe = Subscribe
target.Unsubscribe = Unsubscribe

return target
end -- table.AddEvents

print()
print("Lets try our second approach...")

-- create a table/object with just the functional code;
local TestObject = {
-- sample methods to start our object and raise an event
Start = function (self)
-- this is where actual code would go
-- Now raise the event that we started our object
self:Event(self.Events.Started, "parameter1", 2, "and parameter3")
end,

Other = function (self)
-- this is where actual code would go
-- Now raise the event that we did something
self:Event(self.Events.SomeOther, "No parameters")
end,
}

-- Call AddEvents, to add the Event capability to our TestObject
table.AddEvents (
TestObject,
{
'Add',
'Stopped',
'Started',
'SomeOther',
'OneMore',
} )

-- Here is our test event handler function
local myEventHandler = function (event, ...)
if event == TestObject.Events.Started then
print ("Object informed us that it started, with parameters: ", unpack(arg) )
else
print ("Object informed us about some other event ('" .. event .. "'), with parameters: ", unpack(arg))
end
end

-- Now test again, the results should be identical;
-- now register for events, using the same test eventhandler.
TestObject:Subscribe(myEventHandler)

-- go call Start method so we get an event, and see whether our handler prints them in the lua console
TestObject:Start() --> Object informed us that it started, with parameters: parameter1 2 and parameter3
TestObject:Other() --> Object informed us about some other event ('SomeOther'), with parameters: No parameters

-- Now unregister
TestObject:Unsubscribe(myEventHandler)


Additional remark; the event capability can also be added inline like this;


local TestObject = table.AddEvents(
-- create target
{
Start = function (self)
-- bla bla
end,

Other = function (self)
-- bla bla
end,
},
-- create eventlist
{
'Add',
'Stopped',
}
)