Man hooks (mechanism)
From LSWiki
Revision as of 16:55, 11 June 2007
Contents |
Synopsis
Ain Soph supports a generalized mechanism for allowing developer-defined code to alter the course of and be notified of lib-handled events. This is referred to as the "hooks" mechanism.
Files
/mod/basic/hooks.c /lib/hooks.h
Description
A hook is a way of getting the core mechanics of the game to talk to your code when some event it handles occurs, like an object moving or an item being equipped or a lock being picked. The term "hook" comes from the idea that the lib is providing a "hook" that you can "hang" your code on. There are many types of hooks, each with a different function; some allow you to determine whether an event will be allowed to occur, others allow you to alter numerical values like damage amounts and success chances, and others simply provide you with notification that an event has occurred. All hooks currently supported by the lib are listed in 'man hook_list'.
When you define a hook, you send some particular object a hook type and the code that the lib should talk to -- generally in the form of a function pointer (see 'man function'). You are saying to the lib, "when this event happens to the object I am manipulating, talk to this piece of code about it".
The two basic functions required to interact with hooks from a perspective of project building are add_hook() and remove_hook().
status add_hook(mixed hook, mixed call)
This function inserts a hook call into the object's list of hooks. Since add_hook() exists in all characters, items, rooms, and standard daemons, any hook can be defined in nearly any object, but only certain object types will make use of them, as described in 'man hook_list'.
The first argument to add_hook() is a hook macro from hooks.h; the hook macros and their effects are listed in 'man hook_list'. While the hook macros resolve to integer values, do not ever use a literal integer for defining a hook.
The second argument defines the call you wish to be performed by the hook mechanism. The most common thing to pass here is a closure; e.g. add_hook(Can_Unlock, #'my_unlock_hook). A closure will be simply passed the arguments described for the hook in 'man <hook>'. You may also pass an object or a string object filename; using these will result in the function resolve_hook() being called in the specified object and passed the integer hook type followed by the standard arguments for the hook. With improvements in the save mechanics, there is no really good reason to use the object/string hook functionality, and it may be removed at some point in the future; consider it deprecated.
add_hook() returns true if the hook was successfully defined.
set_hook() performs the same function as add_hook(), as a backward compatibility function.
status remove_hook(mixed hook, mixed call)
remove_hook() is used to remove a previously defined hook from an object. The arguments must be identical to the ones originally used to define the hook.
closure hit_hook; int hits;
void weapon_hit() { hits++; }
void start_counting_hits() { add_hook(Mod_Inflict_Damage, #'weapon_hit) }
void stop_counting_hits() { remove_hook(Mod_Inflict_Damage, #'weapon_hit) }
void create() { start_counting_hits() call_out("stop_counting_hits", 300) }
remove_hook() returns true if the hook was successfully removed.
The other functions related to hooks are query_hook(), any_hook(), query_hooks(), and check_hook().
mixed query_hook(mixed hook)
query_hook() returns the call information associated with the specified hook. If none are defined, this will be 0. Otherwise, it may return a closure, an object, a string, or an array composed of two or more of these.
status any_hook(mixed hook)
Returns true if the object has any hook information for the hook specified. This is intended as the fastest possible test to see if it is useful to do a full check of the hook. The reason to do this is that the mapping construction used in specifying hook arguments is fairly expensive.
mapping query_hooks()
Returns the mapping containing the object's hook information. A mapping is used rather than an array despite the integer hook definitions because the hook information is expected to be very sparse in nearly all objects.
varargs mixed check_hook(mixed hook, mixed args, mixed working_value)
This is the function used by lib modules to determine the effects a hook should have. The first two arguments are the hook to check and the arguments to pass to the hook calls. The third argument is optional and is only used by Poll hooks. The exact behavior of check_hook() depends on the type of hook being analyzed:
For Can hooks, each call is checked in turn until one returns a value other than 1 or 0, at which point that value is immediately returned and no other calls are checked. If no such return value is encountered, check_hook() returns 0 if any call returned 0, or 1 otherwise.
For Do, Fail, and At hooks, all calls are checked. If any call returned a value other than 1 or 0, the first such value found is returned. If no such return values are encountered, check_hook() returns 1 if any call returned 1, or 0 otherwise.
For Mod hooks, all calls are checked and their return values are accumulated (which will result in an error if any values are of types which are not addition-compatible). The accumulated value is returned.
For Value hooks, each call is checked in turn until one returns a value other than 0, at which points that value is immediately returned and no other calls are checked. If no such return value is encountered, check_hook() returns 0.
For Poll hooks, all calls are checked, and those with nonzero return values are considered "votes" for their value. A return value of ({ 0 }) is considered a vote for zero. Also, if a two-element array with an integer second element is returned it is considered a number of votes for the first element equal to the second element; i.e. ({ "foo", 3 }) is three votes for the value "foo". For these hooks, the third argument to check_hook(), working_value, specifies a value which will be returned if no values are obtained from hooks (allowing a result of zero to be distinguished from no result).
For Revise hooks, all calls are checked, with whatever value is returned by each hook in turn considered as the desired revised value. The current version of this continually-revised value is passed as the first argument to the hook functions, in addition to any normal argument set for the hook.
All lib-level hooks, and nearly all hooks in general, use a mapping for their arguments, with various keys as appropriate to their function. Note, however, that if check_hook() receives an array argument, the array will be expanded into an argument list as per apply().
See Also
hook_list(mechanisms)