[Tutorial] y_als
#1

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:

Код:
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);
Using y_als:

Код:
#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
Explanation

Assuming you already understand ALS, I'll go through every line of the second version in turn.

Код:
#define ALS_PREFIX Hook
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).

Код:
#include <YSI\y_als>
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.

Код:
ALS_DATA<>
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.

Код:
public OnScriptInit()
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!

Код:
ALS_DETECT<ScriptInit>
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.

Код:
ALS_DETECT<PlayerConnect>
This checks to see if "Hook_PlayerConnect" exists, just the same as the "funcidx" code did in the original ALS version.

Код:
ALS_CALL<ScriptInit>
If "Hook_ScriptInit" exists, call it and return the result. If it doesn't then just return the default value (1 for "OnScriptInit").

Код:
#undef 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.

Код:
#define OnScriptInit Hook_ScriptInit
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.

Код:
ALS_FORWARD<ScriptInit>
Forward the new version of the callback (i.e. "Hook_ScriptInit").

Код:
public OnPlayerConnect(playerid)
This is just a normal (ALS hooked) callback WITH A PARAMETER.

Код:
ALS_CALL<PlayerConnect>
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.

Код:
#if defined _ALS_OnPlayerConnect
This is just the standard ALS hook macro set, which is unfortunately needed still. This line sees if another hook already exists.

Код:
#undef OnPlayerConnect
If another version of the hook already exists, remove the old hook renaming.

Код:
#define _ALS_OnPlayerConnect
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".

Код:
#define OnPlayerConnect Hook_PlayerConnect
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.

Код:
ALS_FORWARD<PlayerConnect>
Again, this forwards the callback correctly, without needing to know anything about the parameters of the function and thus possibly getting them wrong.

Код:
#undef ALS_PREFIX
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:

Код:
public OnPlayerCommandText(playerid)
{
	printf("OnPlayerCommandText(%d)", playerid);
	return 1;
}
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:

Код:
#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_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:

Код:
#define ALS_DO_DialogResponse<%0>
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.

Код:
%0<...>(...)
This calls the "%0" macro, passing all the data available about this callback. The "<>"s enclose callback information, the "()"s enclose the callback parameters.

Код:
DialogResponse,iiiis
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.

Код:
more:playerid
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.

Код:
,more:dialogid,more:response,more:listitem
These are also not the last parameter so are prefixed by "more:" to indicate this fact.

Код:
end_string:inputtext[]
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:
  • "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.
Again note the lack of spaces. Because these are internal macros that users never need to see (or understand, this was really a waste of time for you to read ), the format is tightly controlled to make other code simpler.

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)
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:

Код:
#define DO_PRINT<%0,%1>(%2) printf("On"#%0"()");
// OR
#define DO_PRINT<%0,%1>(%2) printf(#On%0"()");
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:

Код:
#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"
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:

Код:
#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);
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:

Код:
#define PRINT<%0> ALS_DO:DO_PRINT<%0>
And we can now write:

Код:
PRINT<PlayerConnect>
And have it just work. Same for any other callback. For reference, this generates the following code using "-l":

Код:
printf(#Hook_OnPlayerConnect "(""%d"")",playerid);
And in "OnDialogResponse":

Код:
printf(#S@@_OnDialogResponse "(""%d, ""%d, ""%d, ""%d, ""%s"")",playerid dialogid,response,listitem,((inputtext[0])?(inputtext):NULL));
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:

Код:
#define q,ALS_RS_none:) )
This is a macro that is used so:

Код:
#%1#q,ALS_RS_%2)
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.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)