[Tutorial] State-based hooks (aka pre-hooks)
#1

This is a hook method I came up with long ago (before hook method 7, i.e. modern ALS). It uses states to determine if a hook exists or not. This was previously documented, but that documentation is both deleted and obsolete - the technique is the same, but the implementation has evolved a little bit.

PHP Code:
#include <a_samp>
/*
First define several states for "hooked" and "unhooked".  These functions are
purely to define states, they are never used.  The states only need to be
defined once per mode, and both fixes.inc and YSI already define them.  The
function name is unimportant, but the state specifiers must be exactly as below:
*/
static stock Library_IncludeStates() <_ALS _ALS_x0_ALS _ALS_x1_ALS _ALS_x2_ALS _ALS_x3>
{
}
static 
stock Library_IncludeStates() <_ALS _ALS_go>
{
}
/*
Here is our first hooked function, it is always a good idea to hook a ScriptInit
function so you can set "_ALS_" state, but you can also do that in any other
callback, this is just merely a small optimisation.
*/
public OnGameModeInit()
{
    
/*
    Set the `_ALS_` state to `_ALS_go`.  The basic idea is that this state is
    ALWAYS `_ALS_go`, if the next callback in a callback chain exists it will
    have an implementation defined for the `_ALS_go` state; if there is no next
    callback, only the fallback will exist.  This should be done before the
    first chain call - it only NEEDs to be done once but multiple times is fine,
    and multiple libraries can (and do) do this.
    */
    
state _ALS _ALS_go;
    
/*
    Now call the next callback in the chain.  With the normal ALS method this
    requires a pre-processor `#if` directive to protect against the case where
    the next item in the chain doesn't exist.  With this version, the next
    chain callback always exists in some form even if the user didn't define it.
    */
    
return Library_OnGameModeInit();
}
/*
Forward the next callback in the chain.
*/
forward Library_OnGameModeInit();
/*
Normal ALS redefinition checks.
*/
#if defined _ALS_OnGameModeInit
    #undef OnGameModeInit
#else
    #define _ALS_OnGameModeInit
#endif
/*
Now this is where it starts getting interesting.  Because we unconditionally
call `Library_OnGameModeInit`, the code MUST include this function, but because
we are hooking it, we don't know if it will or not - so we create it!  But if we
create it, then what's in the next library (or mode) in the chain?  The answer
is they are BOTH the next function in the chain thanks to the magic of states.
The first line here defines `OnGameModeInit` when `_ALS` is set to two of the
other values - this is actually NEVER the case, but is important as it tells the
compiler which automata our `Library_OnGameModeInit` function is controlled by
(i.e. `_ALS`).  The second line is the "fallback" function - if `_ALS` is in a
state for which there is no specific implementation, this one will get called
instead and just instantly return (maybe you can now see the trick).  If `_ALS`
is set to `_ALS_go` and there is no other function, then the compiler will
identify this fallback function as the correct one to call (or rather the
runtime will).  If the hooked function DOES exist, then that more specialised
version will be called instead.
If we didn't have the first function to define states, then the fallback
wouldn't compile.
*/
public Library_OnGameModeInit() <_ALS _ALS_x0_ALS _ALS_x1> { return 1; }
public 
Library_OnGameModeInit() <> { return 1; }
/*
Because we are now using states, we need a slightly more complex redefinition of
the next callback's function definition - we need to transparently add the
`_ALS : _ALS_go` state to it, so that's exactly what this line does.
Remember that `_ALS` only needs to be defined once in a mode, and `Library_` is
just the prefix I've used in this example - it needs to be unique.
*/
#define OnGameModeInit(%0) Library_OnGameModeInit(%0) <_ALS : _ALS_go> 
PHP Code:
/*
This is just another `OnGameModeInit` that will be correctly called if it
exists.  It shouldn't be part of your library (i.e. this is user code).
*/
public OnGameModeInit()
{
    
printf("Hello");
    return 
1;

y_hooks are simpler to write, and faster to run. Normal ALS hooks are slightly faster, but lack two properties. Because this version uses states the next item in the chain always exists, and the entry point to the chain function is in a known format and location in the AMX. These properties together allow us to detect and intercept these calls at run-time. And so introduces "pre-hooks". The standard call order for hooks is as follows:
  1. y_hooks, in include order.
  2. ALS hooks, in include order.
  3. User code.
However, there are some libraries (notably fixes.inc) which must be called before y_hooks hooks, but can't rely on y_hooks to use `hook` and thus come first. For these, we have pre-hooks, defined by these state hooks, and natively supported by y_hooks (which leads to the interesting property that including y_hooks actually optimises these functions. The new order thus becomes:
  1. Pre-hooks.
  2. y_hooks, in include order.
  3. ALS hooks, in include order.
  4. User code.
Pre-hooks are called in include order, but y_hooks can not determine which is the last one, so we need to explicitly inform it of the order:

PHP Code:
CHAIN_ORDER(Library);
#undef CHAIN_ORDER
#define CHAIN_ORDER CHAIN_NEXT(Library) 
This assumes you have YSI (and thus y_prehook, part of YSI_Core). Otherwise, check the file for the macro definitions:

https://github.com/pawn-lang/YSI-Inc.../y_prehook.inc

It also defines macros to simplify the forwarding, and add custom return values:

PHP Code:
/*
Normal ALS definitions.
*/
#if defined _ALS_OnGameModeInit
    #undef OnGameModeInit
#else
    #define _ALS_OnGameModeInit
#endif
/*
All that is required for forwarding.  The `= 1` is to define the default return
value, so in `OnPlayerCommandText` this would be `= 0`.
*/
CHAIN_FORWARD:Library_OnGameModeInit() = 1;
/*
Now the redefinition.
*/
#define OnGameModeInit(%0) CHAIN_PUBLIC:Library_OnGameModeInit(%0) 
Again, check y_prehook for more information. These macros are also mostly in fixes.inc, but currently misnamed.
Reply
#2

Nice guide!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)