• 2 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Pawn] YSI Daily Tips
#1
YSI Daily Tips

Almost every time I mention something about YSI someone says "oh, I didn't know it could do that".? This is maybe my fault for incomplete/deleted documentation, so I'm going to start writing one random snippet about YSI every day.? Hopefully these will all help you simplify your modes and improve your scripting.? I've also ported over the tips from the old YSI Secrets thread on the SA:MP forum. If you have any suggestions, questions, or your own YSI hints, feel free to share them here.

WARNING:? Don't copy and paste the examples

In order to get both indentation and syntax highlighting showing up, the "tabs" you see are actually underscores with the same colour as the background.? If you copy text verbatim from this thread, you'll come away with extra characters you don't want.
#2
2021-04-14: GLOBAL_TAG_TYPES

This is used in place of tags for variable argument functions. For example, ?printf? is defined as:

Quote:
native printf(const format[], {Float,_}:...);

Using {Float, _}: means that this function can accept a range of Float and _ (normal) variables. But what about Text: or Group: or Menu:? You could write:

Quote:
native printf(const format[], {Float, Text, Menu, _}:...);

To avoid getting tag mismatch warnings when trying to print those variable types, or you can do:

Quote:
native printf(const format[], GLOBAL_TAG_TYPES:...);

Which is defined in YSI as:

Quote:
{_,Bit,Text,Group,File,Float,Text3D}
#3
2021-04-15: PP_LOOP

PP_LOOP is a pre-processor loop, so generates multiple blocks of code. For example:

Quote:
PP_LOOP<5>(printf("hi");)()

Will compile as:

Quote:
printf("hi");printf("hi");printf("hi");printf("hi");printf("hi");

The definition of this macro is:

Quote:
PP_LOOP<loop count>(output)(separator)

The separator goes BETWEEN instances of ?output?, but not at the end. So you can make a list as:

Quote:
PP_LOOP<10>(55)(, )

And that will compile as:

Quote:
55, 55, 55, 55, 55, 55, 55, 55, 55, 55

Note the lack of ?, ? on the end because that?s not separating anything.
#4
2021-04-16: __COMPILER_PASS

The PAWN compiler does TWO pre-processing stages. It?s rare that this is a problem, but if you need to know which stage is being run you can do:

Quote:
#if __COMPILER_PASS == 0
#else // 1 for second pass.
#endif

Alternatively:

Quote:
#if COMPILER_1ST_PASS
#else // Or COMPILER_2ND_PASS
#endif
#5
2021-04-17: y_cell

This is a library for fast bit-manipulation of cells:

Quote:
Cell_ReverseBits(cell);

Reverse all the bits in a cell:

Example: 0b11110000000000000000000000000000 Becomes: 0b00000000000000000000000000001111

Example: 0b10110011100011110000111110000010 Becomes: 0b01000001111100001111000111001101

Example: 0b01010101010101010101010101010101 Becomes: 0b10101010101010101010101010101010

Quote:
Cell_ReverseNibbles(cell);

Reverse all the nibbles in a cell:

Example: 0x12345678 Becomes: 0x87654321

Example: 0x010F0703 Becomes: 0x3070F010

Example: 0xF0F0F0F0 Becomes: 0x0F0F0F0F

Quote:
Cell_ReverseBytes(cell);

Reverse all the bytes in a cell:

Example: 0x12345678 Becomes: 0x78563412

Example: 0x01020304 Becomes: 0x04030201

Example: 0xFF00FF00 Becomes: 0x00FF00FF

Quote:
Cell_CountBits(cell);

Count all the 1s in a cell.

Example: 0 Returns: 0

Example: 1 Returns: 1

Example: 0x01010101 Returns: 4

Quote:
Cell_GetLowestBit(cell);

Returns a number between 0 and 31, representing the least significant set bit in a cell:

Example: 0b00000000000000000000000000000001 Returns: 0

Example: 0b00000000000000000000000000001000 Returns: 3

Example: 0b00010001100011000011100010001000 Returns: 3

WARNING: This function returns 0 if there are no bits set AND if the lowest bit is 1

Quote:
Cell_GetLowestComponent(cell);

Returns the lowest set bit in a cell:

Example: 0b00000000000000000000000000000001 Returns: 0b00000000000000000000000000000001

Example: 0b00000000000000000000000000001000 Returns: 0b00000000000000000000000000001000

Example: 0b00010001100011000011100010001000 Returns: 0b00000000000000000000000000001000
#6
2021-04-18: Keywords

Most people know that YSI adds foreach to the PAWN language, but here is a list of other keywords added by it:
  • foreach - Efficiently looping over sparse arrays and iterators.

Quote:
foreach (new i : Player) printf("Player %d", i);
  • task - Timer function that is always running as long as the server is up.

Quote:
task Ping[1000]()
{
____printf("Ping");
}
  • ptask - Similar to ?task?, but has a ?playerid? parameter.

Quote:
ptask PlayerPing[1000](playerid)
{
____printf("Ping %d", playerid);
}
  • loadtext - Load localised string data for displaying text.

Quote:
loadtext core[ysi_properties];
  • remote - A public function that can be called from other scripts with compile-time parameter checking.

Quote:
remote MyFunc(a, b[])
{
}
  • broadcastfunc - Call a remote function in all other scripts.

Quote:
broadcastfunc MyFunc(42, "Hello World.");
  • inline - Declare a function inside another function, and get access to the enclosing function?s variables.

Quote:
stock Outer(a)
{
____inline Inner(b)
____{
________printf("%d %d", a, b);
____}
}
  • using - Pass an inline function to another function as a callback.

Quote:
INI_Parse(file, using inline Local_Parse);
  • hook - Use a callback multiple times in multiple files without conflict.

Quote:
hook OnPlayerConnect(playerid)
{
}
  • global - When running multiple scripts, only one should be in charge of, say, the anti-cheat. This declares a function as ?global?, such that all scripts can use it but only one script will be in charge.

Quote:
global AC_GiveWeapon(playerid, weaponid, ammo)
{
}

remote functions are called in ALL other scripts, global functions are called in just one.
  • foreign - Goes with global to inform a script that this function is in a different script.

Quote:
foreign AC_GiveWeapon(playerid, weaponid, ammo);
#7
2021-04-19: OnScriptInit

Quote:
public OnScriptInit()
{
}

This is like OnGameModeInit or OnFilterScriptInit, but works the same in both (i.e. is called once at the start, regardless of the script type). It can't do anything those callbacks can't do, just makes writing libraries more consistent. Basically it is a good option for initialisation code in an include as then it doesn?t matter where your include is used.
#8
2021-04-20: OnScriptExit

Quote:
public OnScriptExit()
{
}

This is like OnGameModeExit and OnFilterScriptExit, but works the same in both (i.e. is called once at the end, regardless of the script type). It can't do anything those callbacks can't do, just makes writing libraries more consistent.
#9
2021-04-21: Dynamic Memory Allocation



Using YSI you can allocate and free memory when you like (and as much as you like):



Quote:

func(arr[], size)

{

____for (new i = 0; i != size; ) printf("%d", arr[i]);

}



main()

{

____new Alloc:a = malloc(1000); // Allocate 1000 cells.



____mset(a, 0, 50); // Write "50" to the first slot.



____// Pass our newly allocated array to a function.

____func(mget(a, 0), Malloc_SlotSize(a));



____// Free the memory (or we will eventually run out).

____free(a);

}

#10
2021-04-22: Restricting Connections



You can limit how many people can connect to your server from the same IP:



Quote:

SetMaxConnections(3);




That lets 3 people connect from the same IP. If any more try join they will fail to connect. You can also change what happens when too many people join:



Quote:

SetMaxConnections(5, e_FLOOD_ACTION_BAN);




That will ban an IP if more than 5 people try to connect from it. The available actions are:


  • e_FLOOD_ACTION_BLOCK - Don?t let any more connect (default).

  • e_FLOOD_ACTION_KICK - Kick everyone using that IP.

  • e_FLOOD_ACTION_BAN - Ban the IP (the players will time out).

  • e_FLOOD_ACTION_FBAN - Ban the IP and instantly kick all the players on that IP.

#11
2021-04-23: Temp Variables

Sometimes you need a variable for a fraction of a second (or just a few expressions). YSI has 3 of these ?temp? variables already to avoid creating more:I@, J@, and Q@[YSI_MAX_STRING].

Quote:
#define ReturnPlayerHealth(%0) (GetPlayerHealth((%0), Float:I@), Float:I@)

Quote:
#define ReturnPlayerName(%0) (GetPlayerName((%0), Q@, 24), Q@)

These variables can NOT be relied upon over function calls, so this might not work:

Quote:
J@ = j;
GetSomeData();
printf("%d", J@);

GetSomeData might modify J@ and you would never know. These are purely for getting some data then using it pretty much straight away.

There is also another one of these short variables: @_ (yes, the variable is called <at><underscore>). This is the Master ID for a script. All currently running scripts have an ID.
#12
2021-04-24: Short Functions

Speaking of short variables, there are some short functions too. These are used in complex macros to reduce the length of the generated code to fit in tight line-length restrictions:
  • U@ - setproperty
  • V@ - getproperty
  • W@ - CallRemoteFunction
  • O@ - SetTimerEx
#13
2021-04-25: Debug Prints

If you compile a YSI mode with ?_DEBUG? set, you get a load of information in the compiler window. This macro means that the prints are ONLY included at the correct level and not any other time, so using ?_DEBUG 0? (or none at all) will strip all the print statements from the mode for the best performance.

Quote:
#define _DEBUG 3

#include <YSI_Library\y_whatever>

The number after _DEBUG is the debug level (default 0) and there are 8 levels:
  • 0 - No debugging.
  • 1 - Show public function calls (callbacks and timers).
  • 2 - Show API function calls (main YSI functions).
  • 3 - Show stock function calls (secondary YSI functions).
  • 4 - Show internal function calls.
  • 5 - Show function branches (extra debug information within functions).
  • 6 - Show loop repeats (prints within loops).
  • 7 - Show extra loops (even more prints in loops).

I will warn you - use level 7 VERY rarely, even I try to avoid it as it generates several Mb of log data just for STARTING your server!

To output your own debug prints use P:<number>(message)

Quote:
P:5("This will be printed at debug levels 5 and higher.");
#14
2021-04-26: Special Prints



Besides the 8 levels mentioned above, there are some special levels:



Quote:

P:F("Fatal error message");

// Prints "* YSI Fatal Error: <your message>"

// Beeps 5 times to alert the user.




Quote:

P:E("Error message");

// Prints "* YSI Error: <your message>"

// Beeps 3 times to alert the user.




Quote:

P:W("Warning message");

// Prints "* YSI Warning: <your message>"

// Beeps 1 time to alert the user.




Quote:

P:I("Info message");

// Prints "* YSI Info: <your message>"

// Very similar to doing "P:0" (which is a thing).




These special prints are ALWAYS compiled because they give the user important information about errors in code, and not just random debug information. However, they can be suppressed by doing:



Quote:

state ysi_debug : off;

#15
2021-04-27: Dynamic Prints



A recent addition to y_debug added level -1:



Quote:

#define _DEBUG -1




This activates run-time debug level selection, so doing:



Quote:

P:3("Message");




Compiles as:



Quote:

if (gDebugLevel >= 3) printf("Message");




You can then change the level at run-time with:



Quote:

DebugLevel(3);




Disable most printing (except special ones - see above) with:



Quote:

DebugLevel(0);




And get the current debug level with:



Quote:

new level = DebugLevel();

#16
2021-04-28: Special Tags



YSI introduces two special tags for functions. These can be used to optimise code in some instances:


  • void - People familiar with ?C? might recognise this as ?no return?, i.e. the function doesn?t return anything:




Quote:

void:Func()

{

____// No return.

}




When using y_master, this will actually make the generated code faster, and give an error if you put ?return? in your function and try use that return value. Technically you can still have ?return?, you just can?t use the value.


  • string - This is used all over YSI, not just as a return type. Anything with a ?string:? tag is just a normal string, and entirely compatible with regular strings, but there are times when it is useful for the compiler to know the difference between an array and a string:




Quote:

string:Func(string:a[], b[])

{

____// Function that takes a string (a) and an array (b), and returns a string.

}




This is used extensively with YSI keywords - timer, global, inline, and remotefunc all rely on string: to generate code that uses "s" instead of "a" (arrays must be followed by a length parameter, strings needn?t be).
#17
2021-04-29: IS_IN_RANGE



If you have some code like this:



Quote:

if (a >= 0 && a < 10)




Or the much better:



Quote:

if (0 <= a < 10)




You can now use YSI to do it an even better way:



Quote:

if (IS_IN_RANGE(a, 0, 10))




This checks that the first parameter is AT LEAST the second parameter, and SMALLER than the third parameter, and is faster than the other two methods. If you want to change the ranges, add or subtract 1. For example to check if something is a numeric character do:



Quote:

if (IS_IN_RANGE(character, '0', '9' 1))




That will do:



Quote:

if ('0' <= character <= '9')




Or more strictly it will do:



Quote:

if ('0' <= character < '9' 1)




These being integers, those are the same (they aren?t for floats).
#18
2021-04-30: RUN_TIMING



This is a simplified way to get many statistics comparing two bits of [pawn]



Quote:

RUN_TIMING("Version 1 ()")

{

____a = b 5;

}



RUN_TIMING("Version 2 (=)")

{

____a = b;

____a = 5;

}




Will output something like:



Timing "Version 1 ()"...

__________Mean = 78.00ns

__________Mode = 76.00ns

________Median = 78.00ns

_________Range = 9.00ns

Timing "Version 2 (=)"...

__________Mean = 94.00ns

__________Mode = 94.00ns

________Median = 94.00ns

_________Range = 15.00ns





If you don?t understand statistics, pick the one with the lowest Mean, or better yet learn statistics.



There is an optional second parameter to control the number of loops if the experiments run too quickly or slowly:



Quote:

RUN_TIMING("Version 2 (=)", 100)

#19
2021-05-01: y_unique



Most people know about y_hooks, and that if you use it in multiple files you need to include it in every one.? But why?? The reason is that every time you include it, it generates a new unique name for the hooked functions so that two hook OnPlayerConnects in a row don?t have the same name for the compiler.? But how?? y_unique



Including y_hooks after the first time just in turn includes y_unique.? This library exports two symbols - UNIQUE_SYMBOL and UNIQUE_FUNCTION.


  • UNIQUE_SYMBOL is just a number:




Quote:

#include <YSI_Coding\y_unique>

printf("Value: %d", UNIQUE_SYMBOL);

#include <YSI_Coding\y_unique>

printf("Value: %d", UNIQUE_SYMBOL);




That will print two numbers (probably sequential, but that?s not a guarantee).


  • UNIQUE_FUNCTION is a macro that takes a symbol name and injects a number in to it:




Quote:

#include <YSI_Coding\y_unique>

UNIQUE_FUNCTION<My...thFunction>()

{

}



#include <YSI_Coding\y_unique>

UNIQUE_FUNCTION<My...thFunction>()

{

}




That will make two functions with names such as My008thFunction and My009thFunction.? The zeros are by design, and the numbers are injected in to the symbol in place of the given ...s.? The <Function...Name> pattern is used in a few other places in YSI as well, most notably y_groups.? If you?re wondering why it is done this way, the following code won?t work:



Quote:

#include <YSI_Coding\y_unique>

MyUNIQUE_SYMBOLthFunction()

{

}



#include <YSI_Coding\y_unique>

MyUNIQUE_SYMBOLthFunction()

{

}




That will just produce two functions both called exactly MyUNIQUE_SYMBOLthFunction, and the following code is a syntax error:



Quote:

#include <YSI_Coding\y_unique>

My UNIQUE_SYMBOL thFunction()

{

}



#include <YSI_Coding\y_unique>

My UNIQUE_SYMBOL thFunction()

{

}




In C this could be done with the ## operator, but while there are proposals to add it to pawn, they haven?t materialised yet.



How does y_unique generate a new number every time you include it? It?s just a massive massive string of #if UNIQUE_SYMBOL == 4s etc. Its split up in to files by 100s, and within those in to groups of 10 for speed, but there?s really nothing more to it than that. This also means the numbers are limited to as high as the defines go (currently 999).
#20
2021-05-02: Command Prefix



Commands are normally /command; however, with y_commands you can change this symbol to anything you want per-command:



Quote:

YCMD:ban(playerid, params[], help)

{

____// Admin "!ban" command.

}



YCMD:kick(playerid, params[], help)

{

____// Admin "!kick" command.

}



YCMD:me(playerid, params[], help)

{

____// Player "#me" command.

}



YCMD:help(playerid, params[], help)

{

____// Player "/help" command.

}



public OnScriptInit()

{

____Command_SetPrefix(YCMD:ban, '!');? ? // /ban? -> !ban

____Command_SetPrefixNamed("kick", '!'); // /kick -> !kick

____Command_SetPrefix(YCMD:me, '#');? ?? // /me? -> #me

____// "/help" doesn't need setting.

}




There?s also Command_GetPrefix, Command_GetPrefixNamed, Command_IsPrefixUsed, and Command_IsValidPrefix to get meta-data on prefixes.



There is no global prefix, but if you want to change it for every command you can use a simple loop:



Quote:

foreach (new c : Command)

{

____Command_SetPrefix(c, '!');

}

#21
2021-05-03: Dependencies



YSI usually needs md-sort and amx-assembly, but only if you use certain parts.? If you just include the very core of YSI you don?t even need a_samp.? y_utils and a few other libraries will work with just the core pawn includes - core, file, float, string, time, and optionally console if you have it.? So you can use YSI for projects in no way related to SA:MP.
#22
2021-05-04: y_zonepulse



This is a tiny little library that does just one thing - makes zones on the mini-map flash.? Make a zone, pass it to the GangZonePulse function, and watch it flash.



Wait, can?t zones already flash with GangZoneFlashForPlayer and GangZoneFlashForAll?? Yes, but that just turns them on and off at a set rate.? This include allows you to specify the two colours they alternate between, how long to take fading between the colours, and how long to pause at each colour:



Quote:

new zone = GangZoneCreate(-10.0, -10.0, 10.0, 10.0);



// Boring.

GangZoneFlashForPlayer(playerid, zone, COLOUR_RED);



// Better:

//? - Change from red to green over 2 seconds.

//? - Pause at green for half a second.

//? - Change back from green to red over 2 seconds.

//? - Pause at red for half a second.

//? - Repeat.

GangZonePulseForPlayer(playerid, zone, COLOUR_RED, COLOUR_GREEN, 2000, 500);



// Best:

//? - Change from blue to yellow over 5 seconds.

//? - Pause at yellow for 2 seconds.

//? - Change back from yellow to blue almost instanly (1 ms).

//? - Pause at blue for 1 second.

//? - Repeat.

GangZonePulseForPlayer(playerid, zone, COLOUR_RED, COLOUR_GREEN, 5000, 2000, 1, 1000);




The first parameter is also a PlayerSet, so can take players, groups, or arrays.
#23
2021-05-05: y_testing Assertions



When writing tests with y_testing, most checks use ASSERT:



Quote:

ASSERT(x == 2);




It?s simple and clear.? However, if x isn?t 2 it doesn?t give you much information - all that?s known is that some boolean check was false.? For most common operations there are more specific tests that give you far more useful information.? In all these assertions, if they fail, the failed test and the value of testee are printed to help with debugging.


  • ASSERT_EQ(testee, comparison) - Checks if the testee value equals the given constant comparison.

  • ASSERT_NE(testee, comparison) - Checks if the testee value doesn?t equal the given constant comparison.

  • ASSERT_LE(testee, comparison) - Checks if the testee value is less than or equal to the given constant comparison.

  • ASSERT_LT(testee, comparison) - Checks if the testee value is less than the given constant comparison.

  • ASSERT_GE(testee, comparison) - Checks if the testee value is greater than or equal to the given constant comparison.

  • ASSERT_GT(testee, comparison) - Checks if the testee value is greater than the given constant comparison.

  • ASSERT_ZE(testee) - Checks if the testee value is zero.

  • ASSERT_NZ(testee) - Checks if the testee value is not zero.

  • ASSERT_SAME(testee, string) - Checks if testee is the given string.

  • ASSERT_DIFF(testee, string) - Checks if testee is not the given string.

  • ASSERT_NEAR(testee, string) - Checks if testee is the given string, ignoring case.

#24
2021-05-06: Plugins



YSI is written to support many plugins in different ways, all without any direct dependencies.? If you call the native from a plugin in a function that is itself never called, you won't get errors for the natives not existing, so YSI can use plugin functions even when they aren't included or loaded.



MySQL



Inline query functions built in directly:



Quote:

inline const Result()

{

____// Closure variables work here, negating the need for extra parameters.

}

// This also calls `mysql_format`, so optional parameters are merged with `query`.

MySQL_PQueryInline(gSQL, using inline Result, query);




There are also equivalent functions for `TQuery` and ORM functions.



BCrypt



Inline comparisons functions:



Quote:

inline const Same(bool:match)

{

____// Closure variables work here, negating the need for extra parameters.

}

BCrypt_CheckInline(text, hash, using inline Same);




Quote:

inline const Hashed(string:hass[])

{

____// Closure variables work here, negating the need for extra parameters.

}

// `_` means `default` for the `cost` parameter.

BCrypt_HashInline(text, _, using inline Hashed);




Requests



RequestCallback and RequestJSONCallback as inline versions again.



Streamer



Inline callback versions of almost all the streamer functions it makes sense for - OnPlayerEnterDynamicArea, OnPlayerEnterDynamicRaceCP, OnPlayerEnterDynamicCP, OnPlayerPickUpDynamicPickup, and OnDynamicObjectMoved are all wrapped, to enable calls such as:



Quote:

cb_MoveDynamicObject(using inline OnMoved, objectid, 10.0, 10.0, 10.0, 10.0);




However, this feature isn't quite complete yet as it relies on streamer plugin v2.9.5 and its "extra" extra IDs (to store callback IDs), which has only just been released.
#25
2021-05-07: Bug Bounty



YSI has a bug bounty.? It's not well defined or known, but it exists.? In general it is for fixing bugs, not just reporting them, though there are some exceptions if the report actually details the exact cause rather than just the effects.? The bigger the bug, the more you get paid.? It also only applies to 5.x, not 4.x, but includes fixing breaking changes between 4.x and 5.x.? Some examples of other things that are covered include:


  • Significant test cases.

  • Documentation.

  • Compatibility with other (popular) includes.

  • const-correctness.

  • Bug fixes.

  • Compiler version compatibility.

#26
2021-05-08: Resize 2D Arrays



You can resize the slots in a 2D array, as long as the sum remains the same.? For example this array:



Quote:

new arr[5][5];




Has a grand total of 25 cells in which to store data.? Those can be reallocated to one slot of 21, and 4 slots of 1.? This is called a jagged array:



Quote:

new arr[5][5];

// Make "arr[0]" 21 cells big, and all the others just "1".

Jagged_Resize(array, {0, 21}, {1, 1}, {2, 1}, {3, 1}, {4, 1});




The compiler doesn?t know about these resizes, so you need to replace sizeof(arr[]) with jaggedsizeof(arr[0]); specifying an index because they?re all different.



You can also declare the array jagged initially:



Quote:

new Jagged:arr[5]<21, 1, 1, 1, 1>;

printf("Slot 0 size: %d", jaggedsizeof (arr[0]));

printf("Slot 1 size: %d", jaggedsizeof (arr[1]));

printf("Slot 2 size: %d", jaggedsizeof (arr[2]));

printf("Slot 3 size: %d", jaggedsizeof (arr[3]));

printf("Slot 4 size: %d", jaggedsizeof (arr[4]));

#27
2021-05-09: Startup Time And Size Solved



The longest standing complaints about YSI were the slow startup time and the huge resulting AMX size. Today I solved both of them at once by rewriting y_malloc. It now uses the heap again, but in a safe manner, thus vastly reducing file sizes and load times.
#28
2021-05-10: y_php



YSI has full 2-way communication with a PHP CLI, or any other server running in a persistent mode.
#29
2021-05-11: Origins of YSI



I started writing a race server (San Andreas Underground), it was going quite well with some unique features for the time, one of which was the ability to host multiple races in parallel. Unfortunately I got distracted writing the underlying core instead of the high-level gamemode, and eventually ended up releasing this core as a library - YSI. Thus y_races is the oldest component in YSI, and the main algorithm hasn?t changed at all since, because it was so solid to begin with.
#30
2021-05-12: Adding y_groups Support



Obviously various YSI libraries have y_groups support, via functions such as:



Quote:

Group_SetCommand(Group:g, coommand, allowed);

bool:Group_AreaAllowed(Group:g, area);

Group_SetClassDefault(Group:g, allowed);




But none of these functions actually exist anywhere in code - they?re all generated from a common template at compile-time.? And your own libraries can use the same pattern.? Assuming you are writing an Object library:



Quote:

// Just the name, used to generate all the group function names.

#define GROUP_LIBRARY_NAME<%0...%1> %0Object%1



// The maximum number of entities your library can hold.

#define GROUP_LIBRARY_SIZE 512



// The tag for your entity IDs.? Can be ommitted for `_:`.

#define GROUP_LIBRARY_TAG Obj



// And include y_groups after all the defines above (it will undefine them).

#include <YSI_Players\y_groups>




Then you just need a way for y_groups to enable and disable individual players.? This is done through a single function, all y_groups does is manage when to add and remove players by calling this one function:



Quote:

Object_SetPlayer(Obj:entity, playerid, bool:set)

{

____// Do whatever you want here to enable this entity for this player.

}




AFter that very little code you have:


  • Group_SetObject(Group:g, Obj:e, set);

  • Group_SetObjectDefault(Group:g, Obj:e, set);

  • Group_SetGlobalObject(Group:g, Obj:e, set);




And far more.? All the y_groups functions, with ... replaced by your library name.
#31
2021-05-13: y_dialog Inline Parameters



Most documentation on y_dialog callbacks show the following:



Quote:

inline const Response(pid, dialogid, response, listitem, string:inputtext[])

{

____// Code goes here.

}

Dialog_ShowCallback(playerid, using inline Response, DIALOG_STYLE_INPUT, "Title", "Contents", "OK");




However, when the closure also contains playerid, this gets cumbersome to constantly rename the playerid variable passed to the inline, then not use it.? Similarly the dialogid variable is redundant because y_dialog doesn?t use IDs and never provides them for comparison.? Thus, these two parameters are both optional and can be skipped in the inline:



Quote:

inline const Response(response, listitem, string:inputtext[])

{

____// Code goes here.

}

Dialog_ShowCallback(playerid, using inline Response, DIALOG_STYLE_INPUT, "Title", "Contents", "OK");




Dialog_ShowCallback takes either iis or iiiis functions.
#32
2021-05-14: y_stringhash Hash Algorithm

You can change which hash function is used by y_stringhash:
  • Bernstein (Default):

    Quote:
    switch (YHash(input))
    {
    ____case __H<hello>: {}
    ____case __H<world>: {}
    }

  • Bernstein (Explicit):

    Quote:
    switch (YHash(input, .type = hash_bernstein))
    {
    ____case __H@b<hello>: {}
    ____case __H@b<world>: {}
    }

  • FNV1:

    Quote:
    switch (YHash(input, .type = hash_fnv1))
    {
    ____case __H@f<hello>: {}
    ____case __H@f<world>: {}
    }

  • FNV1a:

    Quote:
    switch (YHash(input, .type = hash_fnv1a))
    {
    ____case __H@a<hello>: {}
    ____case __H@a<world>: {}
    }

You would only need this in the extremely rare event that you get a hash collision between two cases. It is also important to note that these are NOT cryptographic hashes - they are use to compare simple string, NOT to store passwords.
#33
2021-05-15: y_remote



This very simple library just wraps CallRemoteFunction, to generate all the specifiers for you and check that the parameters are correct at compile-time.? This code compiles but doesn't work:



Quote:

// FS

forward FunctionInFilterscript(a, b[]);



public FunctionInFilterscript(a, b[])

{

}



// GM

main()

{

____// Incorrect parameters, but no warning.

____CallRemoteFunction("FunctionInFilterscript", "ii", 0, 42);



____// Name spelt incorrectly, but no warning.

____CallRemoteFunction("FunctionInFilterScript", "is", 0, "hello");

}




This code gives an error:



Quote:

// FS

remote FunctionInFilterscript(a, string:b[])

{

}



// GM

remote FunctionInFilterscript(a, string:b[]);



main()

{

____// Type mismatch error.

____call FunctionInFilterscript(0, 42);



____// Undefined symbol error.

____call FunctionInFilterScript(0, "hello");

}




That's very useful for spotting bugs early.
#34
2021-05-16: CUSTOM_TAG_TYPES



An earlier tip mentioned GLOBAL_TAG_TYPES, a list of tags that all YSI functions will accept.? You can extend this list with your own tags by defining CUSTOM_TAG_TYPES.? This is a bare list (no braces) of tags, and from now on all YSI functions (formatters etc) will accept these tags with no warnings:



Quote:

#define CUSTOM_TAG_TYPES PlayerText3D, Object

#include <YSI_Group\y_library>

#35
2021-05-17: Passing Normal Functions As Callbacks



Functions that most people think take inline functions can also take public functions with using callback (or using public):



Quote:

public Response(playerid, dialogid, response, listitem, string:inputtext[])

{

}



main()

{

____Dialog_ShowCallback(playerid, using callback Response<iiiis>, DIALOG_STYLE_INPUT, "Title", "Contents", "OK");

}




This does need an explicit type (iiiis) because the types of publics can?t be automatically determined in the same way as for inlines.? With an inline the type is checked and if the parameters don?t match a warning is given.? However, with a public, you specify the type manually, so if it is wrong there should be no warning (you basically promise the compiler it is called his way).? Fortunately, this code uses addressof underneath, which generates code to check the call is correct - so if the specified type is wrong here the compiler actually gives an error, thus re-introducing lovely type-checking.



Because the call uses addressof, you can extend this to another type of function - bare functions. These are sometimes called stocks, but they are not stocks - that keyword is only used in some situations (mainly libraries):



Quote:

Response(playerid, dialogid, response, listitem, string:inputtext[])

{

}



main()

{

____Dialog_ShowCallback(playerid, using function Response<iiiis>, DIALOG_STYLE_INPUT, "Title", "Contents", "OK");

}




This has three advantages:


  • Reduced scope.? You can declare the functions as static if you need.

  • Reduced headers.? The Response function is no longer placed in the publics section of the AMX header.

  • No forward.? Some people really really hate writing this keyword, now you don?t need to.

#36
2021-05-18: CALL@



Technically this is a fact about amx-assembly, but it directly affects using YSI.



You saw in the previous tip how using using callback and using function take the parameter specifier after the function name (using function Response<iiiis>).? This is so that the underlying call to addressof can generate the correct code to actually GET the address.? Roughly speaking, a call to the function is generated, but the call is conditional on the result of a call to an internal addressof function, which always returns false.? This is so the internal function can read the next CALL OpCode, but never execute it.? Something like the following:



Quote:

new a = addressof (MyFunction<iiss>);




Becomes:



Quote:

new a = (GetNextCall() ? MyFunction(INT, INT, STRING, STRING) : gGetNextCallResult);




However, you can customise how addressof internally calls your function using CALL@Name:



Quote:

MyFunction(a, b, const string:c[] = "", const string:d[] = "hello")

{

}

#define CALL@MyFunction MyFunction(4, 5)




Now, addressof doesn?t need the explicit specifier, and by extension neither does using:



Quote:

Response(playerid, dialogid, response, listitem, string:inputtext[])

{

}



#define CALL@Response Response(0, 0, 0, 0, "")



main()

{

____Dialog_ShowCallback(playerid, using function Response, DIALOG_STYLE_INPUT, "Title", "Contents", "OK");

}




However, <iiiis> has the advantage that it is always const-correct, unlike that example?
#37
2021-05-19: Initialisation Order



I?m somewhat loathe to document this, as I know someone will abuse it, but this is the complete order of initialisation callbacks in YSI.? Unfortunately, over the years this has got slowly more complicated with more and more dependencies and restrictions.



Originally it was just either OnGameModeInit or OnFilterScriptInit, but no-one uses the FILTERSCRIPT macro correctly, so OnScriptInit was added, which can detect if the script is a GM or FS; and is called before either of their callbacks.



Then JIT support was added to the code, which requires generating assembly from OnJITInit, but that won?t be called if they aren?t using the JIT plugin.? Thus OnCodeInit was added, which is called from either OnJITInit, OnFilterScriptInit, or OnGameModeInit; whichever comes first. OnCodeInit is harder to use than any other callback because it actually generates the code used by y_hooks and other libraries, thus no advanced YSI features like hook or inline can be used within it.



Then I added caching.? This saves a copy of the mode after all OnCodeInit generation has run, and is called when the mode is loaded from cache, rather than loaded initially.? However, this entire feature was a dead-end and recently removed, thus this callback was also removed.? I don?t normally like removing features as it breaks backwards-compatibility, but this was an exception - it was an awful feature you should avoid.



In addition, there are the lightweight initialisation functions, which can be used in place of OnScriptInit.? PRE_INIT__ and POST_INIT__.? Instead of using hook with a normal init name, these have unique names with a custom declaration (meaning you can make initialisation hooks without needing to include y_hooks):



Quote:

POST_INIT__ CreateGroups()

{

____// Code goes here.

}




The most common example of POST_INIT__ is final*:



Quote:

final Group:gAdmin = Group_Create("Admin 1");




So the full intialisation order is:


  • OnCodeInit - no (very few) YSI features can be used.

  • OnJITInit - with JIT plugin.

  • PRE_INIT__ functions - old final location.

  • OnScriptInit - all scripts.

  • POST_INIT__ functions - new final location.

  • OnFilterScriptInit - filterscripts only.

  • OnGameModeInit - all scripts, no others are called in filterscripts.




* Its actually currently PRE_INIT__, but I?m changing that due to some conflicts, and it making the initialisation actually even more complicated than is listed here.
#38
2021-05-20: Yet Another Call Improvement



Three days ago introduced using function Name<spec>, and two days ago introduced CALL@Name. These are both methods use to specify what parameters a function expects, but they are used to pass the function as a pointer to another function that already knows what parameters the pointee should take.



Given:



Quote:

Caller(arg1, Func:cb<iii>, arg2)

{

____@.cb(arg1, arg2, arg1 arg2);

}



Callee(a, b, c)

{

}



main()

{

____Caller(4, addressof (Callee<iii>), 5);

}




The specifier is in this code twice - once in the parameters of Caller to say it wants a function that takes three parameters, and once in the addressof to say that Callee is indeed a function that meets the requirements of taking three parameters.? The same basic problem exists if you?re using using or CALL@ as well - repetition of information.



Because of how this all works, you actually can?t stop this information being duplicated, but you can hide it such that users of your function never need to worry about it.? This is normally done with &:



Quote:

Caller(arg1, Func:cb<iii>, arg2)

{

____@.cb(arg1, arg2, arg1 arg2);

}



Callee(a, b, c)

{

}



main()

{

____Caller(4, &Callee, 5);

}




Much cleaner than all other solutions, but how does it work?? That?s not a standard operator.? The answer is that oft-maligned feature - macros:



Quote:

#define Caller(%0,&%1,%2) Caller(%0,addressof(%1<iii>),%2)




Using this macro (placed almost anywhere) will detect when the second parameter (Nth parameter in other calls - just depends when you want the pointer) starts with &.? If it does it replaces that parameter with a typed call to addressof, as was done in earlier examples.? The fact that th function and macro have exactly the same name is important - this allows all the methods to work, not just one.



For Dialog_ShowCallback this would be:



Quote:

#define Dialog_ShowCallback(%0,&%1,%2) Dialog_ShowCallback(%0,addressof(%1<iiiis>),%2)




For BCrypt_CheckInline:



Quote:

#define BCrypt_HashInline(%0,%1,&%2) BCrypt_HashInline(%0,%1,addressof(%2<s>))




For cb_MoveDynamicObject:



Quote:

#define cb_MoveDynamicObject(&%0,%1) cb_MoveDynamicObject(addressof(%0<i>),%1)




The last macro parameter will detect ALL remaining function parameters after the & - you only need to explicitly specify the parameters before &.? Thus this technique works for vararg functions as well.
#39
2021-05-21: Pass An Iterator As A Parameter



Sometimes you want to pass an iterator to a function.? Iterators are often global, but they don't have to be, and there are still times when you might want them as generic parameters.? There's no simple way to do this, it wasn't really a design consideration, but it is possible.



Under the hood an iterator is two variables - an array containing the data, and a single variable containing the count (the count is also an array for multi-iterators).? For the iterator Player these are called Iterator@Player and Iter_Count@Player (so you get vaguely readable warnings).? Thus, to receive an iterator in a function as use it within that function you need the following two parameters:



Quote:

MyFunction(Iterator@iter[MAX_PLAYERS 1], Iter_Count@iter)

{

____foreach (new i : iter)

____{

____}

}




You can't use the `Iterator:` macro for this, because it also generates initialisation code, but you could create your own.? The size is %1 1 to hold some internal data.? This does mean you have to know what size of iterator you want to pass in advance, because currently foreach uses sizeof a lot internally.? This might be fixable, but isn't currently:



Quote:

#define IterParam:%0<%1> Iterator@%0[%1 1], Iter_Count@%0



MyFunction(IterParam:iter<MAX_PLAYERS>)

{

____foreach (new i : iter)

____{

____}

}




With this code, PASSING the iterator is again fairly straightforward:



Quote:

main()

{

____MyFunction(Iterator@Player, Iter_Count@Player);

}




But we've just changed one nice parameter in to two horrible parameters.? Fortunately the calling side of things is far better defined (since the y_iterate) API functions like Iter_Add already take plain iterators as parameters.? The functions might not be defined nicely, but they can be called nicely.? The macro for this is _ITER, which does all the work detecting which type of iterator was passed (normal or multi), the size and start, and calls an InternalA or InternalB version of another macro depending on normal- or multi-:



Quote:

#define MyFunction _ITER<MyFunction>

#define Iter_MyFunction_InternalA(%0,%1)? ? MyFunction_(_:%1,%0)

#define Iter_MyFunction_InternalB(%0,%2,%1) MyFunction_(_:%1,%0)





This renames MyFunction to MyFunction_, then defines MyFunction as a macro that takes an iterator.? InternalA has two parameters given - the count and array.? InternalB has three parameters given - the count, the start, and the array.? If you want to pass the size of the iterator it's F@s(%1)-F@s(%0).? You can also just call your function? Iter_MyFunction_InternalA and you don't need the extra macros.? If your function takes more parameters after the iterator, they are all passed to the macros/functions in an additional %9 at the end of the parameter list.



Now the call is very simply:



Quote:

main()

{

____MyFunction(Player);

}

#40
2021-05-21: bUt YsI hAs MaCrOs!

I?m a big advocate of NOT using macros for things, yet am famous for using macros - how can those two things be reconciled?? Easy, it depends on WHY you are using them, and I have some guidelines on when to use them and when not to use them.? Basically it depends on two things - how much work you?re saving yourself and how much you?re changing normal syntax (making it harder for other people to read your code).? As an aside, macros are very hard to get right, and while they may appear to work correctly in some cases, that doesn?t mean they always will, so there?s a good chance that without a thorough understanding of what?s going on you?re just opening yourself up to major future issues.

Some examples of pointless macros:

Quote:
#define SCM SendClientMessage
SCM(playerid, COLOUR_ERROR, "Why are you using SCM?");

The ultimate stupid macro.? How much work does it save?? None - you?re replacing a single function call with a single function call (and if you don?t have auto-completion in your editor get a better editor).

Quote:
#define Object:: Object_
Object::SetPosition(objectid, x, y, z);

Worse than not saving you any effort, this requires MORE effort, because not only is it actually more characters to write, but it breaks auto-completion so that time save is gone as well.? Plus it adds non-standard syntax to code that will cause people to stop and question what exactly is going on.? Is it REALLY just Object_ or is it doing something more behind the scenes?

Quote:
#define SendClientMessageEx(%0,%1,%2) \
____format(string, sizeof (string), %2); \
____SendClientMessage(%0, %1, string)

This example is broken, and the name is terrible - Ex tells you nothing about what the function actually does, it is only obvious because you?re looking at the code right now!? Plus, I hope you aren?t doing this:

Quote:
if (!HasAdminLevel(playerid, 5))
____return SendClientMessageEx(playerid, COLOUR_ERROR, "You need admin level %d", 5);

That code won?t work, but good luck figuring out why looking at the call site.

Quote:
#define SendToAdmin(%0,%1,%2) \
____if (IsPlayerAdmin(%0)) \
________SendClientMessage(%0, %1, %2)

This is also broken in several ways:

Quote:
SendToAdmin(playerid, COLOUR_ERROR, "I hope they're not an admin");

This code will randomly miss people.

Quote:
if (ThingShouldHappen())
____SendToAdmin(player, COLOUR_OK, "Yes");
else
____SendToAdmin(player, COLOUR_OK, "No");

This will never ever send "No" to anyone, not even admins.

So why is YSI ?allowed? macros?? The truth is some of them are pretty bad and shouldn?t exist, but for the most part they?re used when the code generated is complex and something that people shouldn?t need to worry about, AND that code can?t be moved to a function.? Most of the examples above are either macros for the sake of using macros, or would be more effective (and less buggy) as functions.? Take y_iterate for example:

Quote:
new Iterator:Admins<MAX_PLAYERS>;

The code generated by this, which you COULD type out yourself, is:

Quote:
new Iter_Size@Admins, Iterator@Admins[MAX_PLAYERS 1] = {
____MAX_PLAYERS * (1 - MAX_PLAYERS),
____MAX_PLAYERS * (2 - MAX_PLAYERS),
____...
};

WHY it is that is not something people need to know - it?s an implementation detail of how the library works.? Calling format to format a string is not an implementation detail, that?s something everyone knows, everyone knows that?s what your macro is doing, you aren?t saving confusion by hiding it.

The other rule I try to stick to is to always stick as closely as possible to existing syntax.? Why was :: considered useful syntax?? It adds nothing except something else to learn.? On the other hand macros like hook and inline closely mimic the syntax of stock - they look exactly the same as every other function declaration keyword, so nothing new to learn.? There are some macros that don?t - I?ve never liked the : in foreach as that has no precedence (; would be worse, because that implies the expressions are separate).? The <> ?special array? syntax as used in the Iterator: syntax above is not great - it has the advantage of making it clear that this is not just a normal array, but with no precedent.? CMD: has been repeatedly documented as very poor syntax, due to copying tags without being a tag.? timer Func[TIME]() has no precedence - what does the [] syntax mean there without context?? Show me a YSI macro and I can probably tell you why I hate it.

Note that I?m only talking about external macros here - those designed for users of the library, internal ones are irrelevant, they?re again implementation details (and the reason they?re almost unreadable is that a) that?s just how writing macros is, and b) compiler line length limits).


Forum Jump: