14.04.2015, 21:21
Introduction
y_als is a core module in YSI, but I've never really documented what it does as it is quite an internal one. However, it does provide a number of useful features that can make working with callbacks (especially large numbers of them) simpler. For one thing, it allows you to do things "for all" callbacks simply, mostly without having to worry about parameters and call conventions and specifiers etc. Secondly, it makes using the ALS hook system slightly simpler (mostly for the same reason).
Some of the "improvements" here may seem minimal, but in a large library like YSI which can hook any given callback multiple times (and it does hook them all in at least one place), it makes a big time saving.
Example
Standard ALS hook:
Using y_als:
Explanation
Assuming you already understand ALS, I'll go through every line of the second version in turn.
In the original version "Hook_OnPlayerConnect" was used, this is the same required prefix as there, but defined only once in a file to reduce typos mistakes (which are not detectable in strings).
Just include the ALS code. Frankly you don't even need this if you've already included at least one YSI file (any of them - y_als is one of the globaly incldued "core" files because it makes hooks simpler.
This is similar to the "gHasOPC" variable in the original code, providing memory in which to store wether or not another callback exists. The difference between this and the original version is that this is designed to efficiently handle all the default SA:MP callbacks (plus a few YSI ones) - currently it uses only 2 cells for them all.
This is a YSI abstraction, it is either "OnGameModeInit" or "OnFilterScriptInit", depending on what the script is running as - basically it is called at the start of a script regardless of what the script is. What's more, you no longer need the "FILTERSCRIPT" definition for this to work - it figures it all out at runtime and so always works even if a user gets something wrong. This is frankly very useful for writing libraries!
This checks if there are any more of this callback that need chaining (as in the standard version of ALS). There is a slight limitation of the system in that you must do this even for callbacks that are only ever called once (such as "OnScriptInit"). Note the lack of "On" in this text for simplicitly.
This checks to see if "Hook_PlayerConnect" exists, just the same as the "funcidx" code did in the original ALS version.
If "Hook_ScriptInit" exists, call it and return the result. If it doesn't then just return the default value (1 for "OnScriptInit").
"OnScriptInit" is a YSI callback, which were all designed to be "ALS protected", as in you don't need the complex ALS macros to hook them, you just undefine the old version and define a new version.
This renames the next version of the callback so that the calls can be chained (again as in ALS). Note again the lack of "On", this is to reduce symbol lengths for some longer callbacks.
Forward the new version of the callback (i.e. "Hook_ScriptInit").
This is just a normal (ALS hooked) callback WITH A PARAMETER.
This chains the current callback just as in the old version of ALS code, but with NO MENTION OF THE PARAMETER. Things like the "CallLocalFunction" specifier are abstracted - the system just knows to use "i" not "ssi" for example, it also doesn't chain the call if the other hooked function doesn't exist instead it returns the default return value. Again this is "1" for "OnPlayerConnect", but its "0" for "OnPlayerCommandText" and again the system knows this and does things properly.
This is just the standard ALS hook macro set, which is unfortunately needed still. This line sees if another hook already exists.
If another version of the hook already exists, remove the old hook renaming.
If this is the first hook on "OnPlayerConnect", tell subsequent libraries (if any) that at least one hook now exists, and has defined a pre-processor symbol called "OnPlayerConnect".
Create a pre-processor symbol called "OnPlayerConnect". This may be the same as a callback name, but the two symbol sets are entirely separate. Thus, if the pre-processor symbol with this name DOESN'T exist and we try "#undef" it, the compiler will give an error; however, if the pre-processor symbol with this name DOES exist and we don't "#undef" it, the compiler will give a warning - that's why the "_ALS_OnPlayerConnect" symbol is required.
Again, this forwards the callback correctly, without needing to know anything about the parameters of the function and thus possibly getting them wrong.
At the end of your file using "y_als", this is required for technical reasons (otherwise you can end up with multiple callbacks with the same name). Note that if you miss the "#define ALS_PREFIX" line but not the "#include <YSI\y_als>" line, you will get the default prefix of "Mode", but you still need to "#undef" it.
Justification
Ok, so now you've see how to use y_als for a few callbacks and had a detailed explanation (I hope), now I'll explain WHY this exists in the first place, as on first glance it does seem quite pointless. The basic reason is that the code generation can be automated, in fact that's pretty much the only reason. All you need to change between "OnPlayerConnect" and "OnPlayerCommandText" is the function name for much of the code. You don't need to worry about parameters, specifiers ("i" or "is"), brackets (should there be a "[]" here or not), or tags. Plus it is a little quicker once you get used to it. This autmation becomes even more apparent when we use the library to extend its own functionality.
Prints
To show some more advanced features of the library, let's develop a system to print out a callback and all its parameters. At first glance this seems easy:
And yes, that is easy, until you want to do it for ALL callbacks using (say) regular expressions. This may not be something you ever do, but is something I do with surprising regularity; not just printing callbacks, but just doing SOMETHING for ALL callbacks, in those cases I'd like the compiler to worry about tags, brackets, and strings, for me.
For this we need to look in to the library a little deeper. Opening up y_als shows multiple lines like this:
The "ALS_R_" line is very simply just the default return value for that callback (note again the lack of "On" in all these lines for symbol length limit reasons). Nothing more really needs to be said about that, it's "1" in all but two places (one of those being a YSI callback).
The "ALS_DO_" line is a little more complex and will be broken down further:
This defines the "ALS_DO_" line for the "OnDialogResponse" callback. This macro is actually an internal one. The value of the parameter "%0" will become another macro to get called.
This calls the "%0" macro, passing all the data available about this callback. The "<>"s enclose callback information, the "()"s enclose the callback parameters.
This is the name of the callback again (so that is gets passed to the macro, though I have just figured out another way of doing this so you don't need it twice, but since this is all internal data that's already written it doesn't really matter), followed by the specifier for "CallLocalFunction" and similar functions. Note no spaces.
This is the first parameter, or more to the point this is not the last parameter. Because it is not the last, there are "more" to come and the parameter is marked as such.
These are also not the last parameter so are prefixed by "more:" to indicate this fact.
This IS the last parameter, so uses "end", it is also a string so uses "string". Although this example doesn't show them all, the full set (with examples from real callbacks) is:
Speaking of other code, let's go back to our "print" macro, this will be an example of a "%0" macro above, so will have the following format:
Here "%0" is our callback's name, "%1" is our callback's specifier, and "%2" is a DESCRIPTION of our callback's parameters. We can thus write part of our code already:
Now those two lines LOOK similar, but they're NOT! The first one will just print "OnPlayerConnect" (for example) all the time. The second one will print the current callback's name AFTER ALS substitutions have been done, so it could print "OnPlayerConnect" if it is the first callback in a chain or something else such as "Mode_PlayerConnect" depending on what came before. Which one you want is up to you, but I'll stick to the second one for now as it can make debugging slightly simpler.
Unfortunately, now we need to generate "%d %s" (or whatever) from "is" (or whatever). However, it turns out that generating that data from the parameters themselves (due to the descriptions) is actually easier to do:
Using that, and a variant on the inbuilt "ALS_RS_" macro (which converts parameters to their "call" equivalents; unlike the "ALS_KS_" macro, which converts them to their "function declaration" equivalents) we get:
Here I should point out the extra ")", which is detected and removed by the "end:" and "none:" "ALS_PS_" macros.
And a final piece of boilerplate to make the macro easy to use:
And we can now write:
And have it just work. Same for any other callback. For reference, this generates the following code using "-l":
And in "OnDialogResponse":
Here you can see the effect of the ALS expansion discussed earlier to give the final names, not the typed names. You can also see all the parameters nicely (and safely) printed correctly.
I should also at this point mention the "q" macro:
This is a macro that is used so:
Here "%1" is obviously the function specifier, followed by a comma, then followed by the parameters. However, if there are no parameters then the comma should not be there and the special "q" specifier (which is ignored normally) removes it. Unfortunately as we didn't use specifiers in our print example, we had to write the "ALS_R2_" macro instead to do a similar thing.
Conclusion
This tutorial looked at the y_als library and hopefully showed you how it can make hooked code shorter. The second section was also hopfully of interest to people developing large modes and who want to do the same thing repeatedly for different callbacks, with almost no effort porting the code between them. What use it is beyond that is beyond me.
Note that a few points in this tutorial were tweaked slightly from the current version of YSI so are only accurate for the very latest version. Most notably the "none:" parameter type is new and was previously just "end:" with no data.
Credits
I am reposting this tutorial (originally made by ******) with the thought of what he said to several members of the community. Everything that could help someone should not be deleted from the forums, which is something I agree with since I learned a lot by reading the tutorials on here. He made a lot of things that are used by lots of servers and that knowledge should not be lost for present and future developers.
y_als is a core module in YSI, but I've never really documented what it does as it is quite an internal one. However, it does provide a number of useful features that can make working with callbacks (especially large numbers of them) simpler. For one thing, it allows you to do things "for all" callbacks simply, mostly without having to worry about parameters and call conventions and specifiers etc. Secondly, it makes using the ALS hook system slightly simpler (mostly for the same reason).
Some of the "improvements" here may seem minimal, but in a large library like YSI which can hook any given callback multiple times (and it does hook them all in at least one place), it makes a big time saving.
Example
Standard ALS hook:
Код:
new bool:gHasOPC; public OnGameModeInit() { gHasOPC = funcidx("Hook_OnPlayerConnect") != -1; return CallLocalFunction("Hook_OnGameModeInit", ""); } #if defined _ALS_OnGameModeInit #undef OnGameModeInit #else #define _ALS_OnGameModeInit #endif #define OnGameModeInit Hook_OnGameModeInit forward OnGameModeInit(); public OnPlayerConnect(playerid) { if (gHasOPC) { return CallLocalFunction("Hook_OnPlayerConnect", "i", playerid); } return 1; } #if defined _ALS_OnPlayerConnect #undef OnPlayerConnect #else #define _ALS_OnPlayerConnect #endif #define OnPlayerConnect Hook_OnPlayerConnect forward OnPlayerConnect(playerid);
Код:
#define ALS_PREFIX Hook #include <YSI\y_als> ALS_DATA<> public OnScriptInit() { ALS_DETECT<ScriptInit> ALS_DETECT<PlayerConnect> ALS_CALL<ScriptInit> } #undef OnScriptInit #define OnScriptInit Hook_ScriptInit ALS_FORWARD<ScriptInit> public OnPlayerConnect(playerid) { ALS_CALL<PlayerConnect> } #if defined _ALS_OnPlayerConnect #undef OnPlayerConnect #else #define _ALS_OnPlayerConnect #endif #define OnPlayerConnect Hook_PlayerConnect ALS_FORWARD<PlayerConnect> #undef ALS_PREFIX
Assuming you already understand ALS, I'll go through every line of the second version in turn.
Код:
#define ALS_PREFIX Hook
Код:
#include <YSI\y_als>
Код:
ALS_DATA<>
Код:
public OnScriptInit()
Код:
ALS_DETECT<ScriptInit>
Код:
ALS_DETECT<PlayerConnect>
Код:
ALS_CALL<ScriptInit>
Код:
#undef OnScriptInit
Код:
#define OnScriptInit Hook_ScriptInit
Код:
ALS_FORWARD<ScriptInit>
Код:
public OnPlayerConnect(playerid)
Код:
ALS_CALL<PlayerConnect>
Код:
#if defined _ALS_OnPlayerConnect
Код:
#undef OnPlayerConnect
Код:
#define _ALS_OnPlayerConnect
Код:
#define OnPlayerConnect Hook_PlayerConnect
Код:
ALS_FORWARD<PlayerConnect>
Код:
#undef ALS_PREFIX
Justification
Ok, so now you've see how to use y_als for a few callbacks and had a detailed explanation (I hope), now I'll explain WHY this exists in the first place, as on first glance it does seem quite pointless. The basic reason is that the code generation can be automated, in fact that's pretty much the only reason. All you need to change between "OnPlayerConnect" and "OnPlayerCommandText" is the function name for much of the code. You don't need to worry about parameters, specifiers ("i" or "is"), brackets (should there be a "[]" here or not), or tags. Plus it is a little quicker once you get used to it. This autmation becomes even more apparent when we use the library to extend its own functionality.
Prints
To show some more advanced features of the library, let's develop a system to print out a callback and all its parameters. At first glance this seems easy:
Код:
public OnPlayerCommandText(playerid) { printf("OnPlayerCommandText(%d)", playerid); return 1; }
For this we need to look in to the library a little deeper. Opening up y_als shows multiple lines like this:
Код:
#define ALS_R_DialogResponse 1 #define ALS_DO_DialogResponse<%0> %0<DialogResponse,iiiis>(more:playerid,more:dialogid,more:response,more:listitem,end_string:inputtext[])
The "ALS_DO_" line is a little more complex and will be broken down further:
Код:
#define ALS_DO_DialogResponse<%0>
Код:
%0<...>(...)
Код:
DialogResponse,iiiis
Код:
more:playerid
Код:
,more:dialogid,more:response,more:listitem
Код:
end_string:inputtext[]
- "more:playerid" - A parameter called "playerid" that isn't the last in the list.
- "tag:Float:fRotX" - A parameter called "fRotX" that isn't the last in the list and has a tag of "Float".
- "string:password[]" - A parameter called "password" that isn't the last in the list and is a string.
- "end:playerid" - A parameter called "playerid" that is the last in the list.
- "end_tag:PlayerText:playertextid" - A parameter called "playertextid" that is the last in the list and has a tag of "PlayerText".
- "end_string:inputtext[]" - A parameter called "inputtext" that is the last in the list and is a string.
- "none:" - A callback with no parameters.
Speaking of other code, let's go back to our "print" macro, this will be an example of a "%0" macro above, so will have the following format:
Код:
#define DO_PRINT<%0,%1>(%2)
Код:
#define DO_PRINT<%0,%1>(%2) printf("On"#%0"()"); // OR #define DO_PRINT<%0,%1>(%2) printf(#On%0"()");
Unfortunately, now we need to generate "%d %s" (or whatever) from "is" (or whatever). However, it turns out that generating that data from the parameters themselves (due to the descriptions) is actually easier to do:
Код:
#define ALS_PS_more:%0, "%d, "ALS_PS_ #define ALS_PS_string:%0[], "%s, "ALS_PS_ #define ALS_PS_tag:%3:%0, "%f, "ALS_PS_ #define ALS_PS_end:%0) "%d" #define ALS_PS_none:%0) #define ALS_PS_end_string:%0[]) "%s" #define ALS_PS_end_tag:%3:%0) "%f"
Код:
#define ALS_R2_more:%0, ,%0 ALS_R2_ #define ALS_R2_string:%0[], ,((%0[0])?(%0):NULL) ALS_R2_ #define ALS_R2_tag:%3:%0, ,(_:%0) ALS_R2_ #define ALS_R2_end:%0) ,%0) #define ALS_R2_none:) ) #define ALS_R2_end_string:%0[]) ,((%0[0])?(%0):NULL)) #define ALS_R2_end_tag:%3:%0) ,(_:%0)) #define DO_PRINT<%0,%1>(%2) printf(#On%0"("ALS_PS_%2)")"ALS_R2_%2);
And a final piece of boilerplate to make the macro easy to use:
Код:
#define PRINT<%0> ALS_DO:DO_PRINT<%0>
Код:
PRINT<PlayerConnect>
Код:
printf(#Hook_OnPlayerConnect "(""%d"")",playerid);
Код:
printf(#S@@_OnDialogResponse "(""%d, ""%d, ""%d, ""%d, ""%s"")",playerid dialogid,response,listitem,((inputtext[0])?(inputtext):NULL));
I should also at this point mention the "q" macro:
Код:
#define q,ALS_RS_none:) )
Код:
#%1#q,ALS_RS_%2)
Conclusion
This tutorial looked at the y_als library and hopefully showed you how it can make hooked code shorter. The second section was also hopfully of interest to people developing large modes and who want to do the same thing repeatedly for different callbacks, with almost no effort porting the code between them. What use it is beyond that is beyond me.
Note that a few points in this tutorial were tweaked slightly from the current version of YSI so are only accurate for the very latest version. Most notably the "none:" parameter type is new and was previously just "end:" with no data.
Credits
I am reposting this tutorial (originally made by ******) with the thought of what he said to several members of the community. Everything that could help someone should not be deleted from the forums, which is something I agree with since I learned a lot by reading the tutorials on here. He made a lot of things that are used by lots of servers and that knowledge should not be lost for present and future developers.