14.04.2015, 17:11
ALS 4 (Hook Method 7)
Introduction This technique is entirely thanks to ipsBruno. They proposed it in a post in the previous ALS topic and it took me a long time to figure out WHY it worked (because it shouldn't have - at least not with a standard PRE-processor). All my work up till that point had assumed that the pre-processor runs first linearly, but this method seemed to have knowledge of the future, which it turns out is because "#if defined" is handled specially in a second round of pre-processing. I frequently tell people not to treat C and PAWN as the same or they will have problems, but I fell foul of exactly that here. Anyway, the only place this was documented was a random conversation in a random topic, despite being the best ALS method to date.
This method makes callback hooking exactly as fast as a regular function call. That means that creating a function called, say "MyLib_Init" and telling people to place it in their "OnGameModeInit" function is no longer faster than ALS and (as ever) very awkward. As I said, this is the best ALS method there is, but the latest version of y_hooks is actually even faster than standard function calls because of extensive code rewriting. However, ALS is still supported because some people seem to think that using well tested efficient libraries is a bad thing...
Update: I thought this wasn't in a tutorial already, but it turns out it was:
https://sampforum.blast.hk/showthread.php?tid=402760
Sorry.
Review
Technically "ALS" (Advanced Library System) is not a hook, it is a method of determining that another function has already been hooked, but hook methods using this macro set are collectively referred to as ALS anyway.
The basic structure is:
pawn Code:
<function>()
{
<chained call>();
}
<does a hook exist?>
<yes - remove the old one>
<else>
<no - it does now>
<end>
<rename chain>
<forward the function>
- LS v1 overcame this problem by using "CallLocalFunction" - that takes a function that may or may not exist as a string, so has no compile-time checks. Ideal but slow.
- ALS v2 was not widely used, but involved run-time code rewriting to replace a call to a generic library function with a call to the next chain element. This was fast but OS dependent and never ported to Linux (methods for code rewriting on Linux have since been developed).
- ALS v3 used states - you defined a fallback state and gave the next chained function a normal state. If it exists it is called, if it doesn't the fallback function is called instead. This had compile-time checking and very little run-time overhead, but was even harder to write than the regular ALS.
No hooks:
pawn Code:
public OnGameModeInit()
{
MyLib_Init();
Streamer_Init();
// etc...
}
pawn Code:
// Replaces "public OnGameModeInit" and calls all YSI internal functions.
Script_OnGameModeInit()
{
}
To chain callbacks, simply check if the next function in the chain exists, and if it does then call it:
pawn Code:
public OnGameModeInit()
{
#if defined MyLib_OnGameModeInit
MyLib_OnGameModeInit();
#endif
return 1;
}
pawn Code:
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif
#define OnGameModeInit MyLib_OnGameModeInit
pawn Code:
#if defined MyLib_OnGameModeInit
forward MyLib_OnGameModeInit();
#endif
pawn Code:
public OnPlayerConnect(playerid)
{
#if defined MyLib_OnPlayerConnect
MyLib_OnPlayerConnect(playerid);
#endif
return 1;
}
#if defined _ALS_OnPlayerConnect
#undef OnPlayerConnect
#else
#define _ALS_OnPlayerConnect
#endif
#define OnPlayerConnect MyLib_OnPlayerConnect
#if defined MyLib_OnPlayerConnect
forward MyLib_OnPlayerConnect(playerid);
#endif
Functions
Function hooking is not affected by this new version - it has actually never changed because it wasn't hard in the first place, but it was never documented in a tutorial just sort of known by some. In this case, the hook does NOT have the same name as the function being hooked, so to hook GivePlayerMoney looks like:
pawn Code:
stock MyLib_GivePlayerMoney(playerid, amount)
{
// Call the old version, no need to check if it exists.
GivePlayerMoney(playerid, amount);
return 1;
}
// Has this been hooked already?
#if defined _ALS_GivePlayerMoney
#undef GivePlayerMoney
#else
#define _ALS_GivePlayerMoney
#endif
// Reroute future calls to our function.
#define GivePlayerMoney MyLib_GivePlayerMoney