Briefly:
/delegate
var/datum/object
var/function
proc/Run()
/hook
var/list/delegates
var/return_condition = RETURN_NONZERO
proc/Run()
The idea being, most cleanly:
/mob/var/hook/on_death
/mob/proc/die()
on_death.Run()
/mob/poet/New()
on_death += delegate(src,/mob/poet/proc/dramatic_quote)
/mob/hook/on_death(var/timeofday, var/death_reason)
// Code before hooks are executed
. = ..()
// Code after hooks are executed
/mob/poet/proc/dramatic_quote(var/timeofday, var/death_reason)
say("Alas, [timeofday] I died, and all because of [death_reason]!")
mymob.on_death += delegate(mymob,/mob/poet/proc/dramatic_quote)
mymob/on_death("today","blood loss")
Where hook is a keyword like proc and verb, and the entire code block for the hook being optional, defaulting to .=..()
The return condition mentioned for hooks is a bitfield that specifies how to handle execution and return values. For example, when running ten procs, the third and seventh procs return a nonzero number while the rest return null. Depending on the actual circumstances, you may want execution to halt with the first zero/null or nonzero number (eg Cross()). You may want to run all of the procs, and return the largest return value (this also works if you don't care about the return, eg, Crossed()).
You may want to run all of the procs, and return a list of all return values. And you may only want one sentry value to cancel execution (in particular, null) while any other value is recorded in the list. You might also want a return result that is an associative array of (delegate=result), in case you need to locate the object that had a particular return value.
At minimum, any event hook will need to have the option of dying under certain (non-error) circumstances.
Delegates and/or Event hooks can also function as Topic() replacements. For example,
/mob/hook/click_me(var/why, var/how)
html_link = "<a href='?\ref[src.click_me]why=fun&how=html'>Click me!</a>
The facilities for this already exist and the above could in principle simply be done with library code:
/mob/var/hook/clickme_proc
/hook/Topic(href,href_list)
src.Run(arglist(href_list))
Still, it would be much better if this was formalized for a number of reasons.
It's far from an urgent feature request but I already mostly-wrote a library version of this, and it sort of... lacks. It isn't part of standard byond code, I have to workaround areas where I don't control the language. For example, compare:
/mob/hook/test(var/argument = null)
if(argument != null)
return ..()
else
src << "Argument required"
mob.test()
mob.test("a")
to:
/mob/var/hook/non_empty/test
/hook/non_empty // Run() calls RunList() with the args list
RunList(var/list/arguments)
if(!len(arguments) || arguments[0]==null)
usr << "Argument required"
return
..()
/mob/New()
test = new /hook/non_empty()
usr = src
test.Run()
test.Run("a")
...but that's not really your problem or anything, it's just that it could be a lot better.
Mostly I just think that delegates/function pointers and event hooks are a worthy addition to the toolkit. I haven't even run the well dry on things you could do. For example, the library version of hooks calls all procs defined on it, eg:
/hook/game_started/proc/notify_players()
for(var/client/C)
C << "The game has started!"
(This is implemented as a for(typesof("[type]/proc")) if you're curious.)
By adding that proc in code to the hook you guaranteed that it would be in the list of delegates. If hook is a keyword like proc, it would look really weird, sort of like
/proc/game_started/proc/notify_players()
...
game_started()
but I think it could work.
I think I am in danger of rambling on endlessly so I will wait for questions, feedback, or whatever.