ID:1365722
 
I'm a pretty lazy guy. I don't like looking through my code to replace add remove procedures that get called on a specific procedure chain, so I came up with a system that copies the C# event listeners.

Not sure if I've seen it anywhere else in dm, so I figured I would post it here for people to blow holes in it and see if there are improvements to make.

//events can be inhertied on atoms and datums. They must be defined direcltly on world and client.

//inhert atoms to allow events
atom/parent_type = /edatum

edatum //must inherit from datum, not define on datum. Defining on datum would cause a bad recusion on New()
{
var/list/subscribers //do not initiate them, the subscribe creates and removes as needed.

proc/subscribe(event,object,callback,async = 1) //subsriber to event, async will cause a spawn() on the command.
if(!src.subscribers)//check the list exists and create if not.
src.subscribers = list()
if(!src.subscribers[event])
src.subscribers[event] = list()
src.subscribers[event] += new/subscriber(object,callback,async) //add the item to the list.

proc/unsubscribe(event,object,callback,async = 1) //remove item if it is in the list.
if(src.subscribers && src.subscribers[event])
for(var/subscriber/subscriber in src.subscribers[event])
if(subscriber.isCopy(object,callback,async))
src.subscribers[event] -= subscriber
if(!length(src.subscribers[event]))
src.subscribers -= event
if(!length(src.subscribers))
src.subscribers = null

proc/isSubscribed(event,object,callback,async = 1)
if(src.subscribers && src.subscribers[event]) //if lists exist
for(var/subscriber/subscriber in src.subscribers[event]) //loop every entry
if(subscriber.isCopy(object,callback,async)) //ask the item if it is a copy.
return 1
return 0

proc/fire(event,list/arguments) //triggers an event
if(src.subscribers && src.subscribers[event]) //if list exists
for(var/subscriber/subscriber in src.subscribers[event]) //loop through entries
if(subscriber.isAsync()) //fire the async entries
subscriber.fire(src,arguments) //tell them to fire, async will return right away, sync will wait until complete, so lets do async first.
for(var/subscriber/subscriber in src.subscribers[event]) //loop through entries (again)
if(!subscriber.isAsync()) //fire the sync entries
subscriber.fire(src,arguments)
}

client //duplication as we can't use inheritance here.
{
var/list/subscribers

proc/subscribe(event,object,callback,async = 1)
if(!src.subscribers)
src.subscribers = list()
if(!src.subscribers[event])
src.subscribers[event] = list()
src.subscribers[event] += new/subscriber(object,callback,async)

proc/unsubscribe(event,object,callback,async = 1)
if(src.subscribers && src.subscribers[event])
for(var/subscriber/subscriber in src.subscribers[event])
if(subscriber.isCopy(object,callback,async))
src.subscribers[event] -= subscriber
if(!length(src.subscribers[event]))
src.subscribers -= event
if(!length(src.subscribers))
src.subscribers = null

proc/isSubscribed(event,object,callback,async = 1)
if(src.subscribers && src.subscribers[event])
for(var/subscriber/subscriber in src.subscribers[event])
if(subscriber.isCopy(object,callback,async))
return 1
return 0

proc/fire(event,list/arguments) //triggers an event
if(src.subscribers && src.subscribers[event]) //if list exists
for(var/subscriber/subscriber in src.subscribers[event]) //loop through entries
if(subscriber.isAsync()) //fire the async entries
subscriber.fire(src,arguments) //tell them to fire, async will return right away, sync will wait until complete, so lets do async first.
for(var/subscriber/subscriber in src.subscribers[event]) //loop through entries (again)
if(!subscriber.isAsync()) //fire the sync entries
subscriber.fire(src,arguments)

}


var/list/wsubscribers //can't use vars on world, only procedures.
world //duplication as we can't use inheritance here.
{

proc/subscribe(event,object,callback,async = 1)
if(!wsubscribers)
wsubscribers = list()
if(!wsubscribers[event])
wsubscribers[event] = list()
wsubscribers[event] += new/subscriber(object,callback,async)

proc/unsubscribe(event,object,callback,async = 1)
if(wsubscribers && wsubscribers[event])
for(var/subscriber/subscriber in wsubscribers[event])
if(subscriber.isCopy(object,callback,async))
wsubscribers[event] -= subscriber
if(!length(wsubscribers[event]))
wsubscribers -= event
if(!length(wsubscribers))
wsubscribers = null

proc/isSubscribed(event,object,callback,async = 1)
if(wsubscribers && wsubscribers[event])
for(var/subscriber/subscriber in wsubscribers[event])
if(subscriber.isCopy(object,callback,async))
return 1
return 0

proc/fire(event,list/arguments) //triggers an event
if(wsubscribers && wsubscribers[event]) //if list exists
for(var/subscriber/subscriber in wsubscribers[event]) //loop through entries
if(subscriber.isAsync()) //fire the async entries
subscriber.fire(src,arguments) //tell them to fire, async will return right away, sync will wait until complete, so lets do async first.
for(var/subscriber/subscriber in wsubscribers[event]) //loop through entries (again)
if(!subscriber.isAsync()) //fire the sync entries
subscriber.fire(src,arguments)
}


subscriber //our datum of data. This inheirts datum.
{
var/object
var/callback
var/async

New(_object,_callback,_async)
object = _object
callback = _callback
async = _async

proc/fire(sender,list/arguments)
if(src.async)
spawn(0)
call(src.object,src.callback)(sender,arguments)
else
call(src.object,src.callback)(sender,arguments)

proc/isCopy(_object,_callback,_async)
if(object == _object && callback == _callback && async == _async)
return 1
return 0

proc/isAsync()
return src.async
}


And here is the testing I completed on it.
/* All testing sucessful 08/30/2013 */

mob
Login()
..()
sleep(15)
world.subscribe("login",src,"messages")
client.subscribe("login",src,"messages")
src.subscribe("login",src,"messages")
world << "Subscribed";

verb/fireWorldLogin()
world.fire("login",list("Source"="/world"))

verb/fireClientLogin()
client.fire("login",list("Source"="/client"))

verb/fireMobLogin()
src.fire("login",list("Source"="/mob"))

verb/fireBadLogin()
src.fire("login",null)

verb/unsubscribeFromAll()
world.unsubscribe("login",src,"messages")
client.unsubscribe("login",src,"messages")
src.unsubscribe("login",src,"messages")

verb/subscribeToAll()
world.subscribe("login",src,"messages")
client.subscribe("login",src,"messages")
src.subscribe("login",src,"messages")

verb/ShowStatus()
if(wsubscribers) world << "wsubsribers exists."
if(client.subscribers) world << "wsubsribers exists."
if(src.subscribers) world << "wsubsribers exists."

proc/messages(sender,list/arguments)
if(!arguments || !arguments["Source"])
CRASH("The argument 'Source' is required in the messages procedure.")
world << "New message from [arguments["Source"]]"
I wrote an article on hooks that you might find quite enlightening.

It's a bit... Well, different from your approach, but attacks the same ideology in a fairly efficient manner.

I take advantage of BYOND's lightning-fast associative lists to ensure about as fast and memory efficient an approach that I was able to imagine.

To top it off, it doesn't use extra objects, which to my knowledge, have to hunt for an available refid, upon initialization (which can be slow).

http://www.byond.com/forum/?post=1226646
You should probably turn this into a library. It's too lengthy to be a snippet.
I agree with Magnum. Plus it would serve as a somewhat interesting library to have for some if you added detailed documentation for everything.