PDA

Просмотр полной версии : [Урок] Эффективная реализация API инклуда с возможностью вызова функций из фильтрскриптов



Nexius_Tailer
16.11.2019, 20:05
В этом уроке я расскажу, как умно сделать функционал (API) вашего инклуда, чтобы пользователи могли подключать инклуд к фильтрскриптам/моду и вызывать его внешние функции как из фильтрскриптов (но сами функции обрабатывались бы лишь в инклуде, подключенному к моду), так и из мода, но в этом случае с такой скоростью, как если бы они вызывались без поддержки их вызова из фильтрскриптов.

В общем-то, вся суть алгоритма уже разложена в первом абзаце и при желании понимающим людям можно сразу переходить к последнему примеру реализации, а для всех остальных начну с самого начала.

Прежде всего стоит прояснить, зачем вообще инклуду иметь API и для чего всё это нужно. API (внешние функции) некой системы в инклуде представляют собой функции, которые делаются, чтобы их могли использовать за пределами этого инклуда (в моде, фильтрскриптах, других инклудах). Простой пример - mxINI, easyDialogs/mdialog и т.д. Все эти инклуды имеют внутри себя функции, например ini_openFile или Dialog_Close, созданные специально для их использования извне. Таким образом, мы имеем движок нашего алгоритма (реализацию некой системы, её части) в отдельном файле, а из мода или других инклудов уже просто вызываем готовые функции в одну строчку. Либо же мы имеем в инклуде независимую систему, а функции нужны другим скриптам для доступа к её переменным (их узнавания/изменения) или любым другим данным. В случае с доступом к переменным можно, конечно, и не делать отдельных функций, а изменять эти переменные вне инклуда напрямую, но так обычно не принято по ряду причин и потому выделяемую память держат в зоне видимости лишь файла инклуда, давая к ней доступ другим скриптам через API функции.

Хорошо. С тем, что такое и зачем нужно API нашему инклуду (если оно вам действительно нужно) разобрались. Теперь рассмотрим то, как на практике оно реализуется в большинстве случаев. Самый простой пример - в нашем инклуде создаётся stock функция -> в ней нужный нам код -> всё, функция готова для вызова из мода или любых других инклудов, также к нему подключенных. Однако в данном случае у нас не получится каким-либо образом вызывать эту функцию из любого другого фильтрскрипта. Вернее получится, если вы подключите этот же инклуд отдельно и к фильтрскрипту, но тем самым вы просто продублируете весь код инклуда ещё и для этого фильтрскрипта и в итоге будете иметь параллельную работу двух (всё равно не связанных между собой) систем из вашего инклуда в фильтрскрипте и в моде.

Если в качестве api функций вы имеете что-то вроде этого:

stock IsValidVehicleModel(skinid) return (400 <= skinid <= 611);

stock IsValidSkinModel(skinid) return (0 <= skinid <= 73 || 75 <= skinid <= 311);

stock IsValidMapIconType(markertype) return (0 <= markertype <= 63);

//И любые другие несложные функции, которые не обращаются к памяти и результат выполнения которых определяется проверками/вычислениями только лишь относительно заданных ей аргументов и ничего больше


То в данном случае, конечно, нет ничего критичного в том, что они будут продублированы отдельно (при подключении этого инклуда) для каждого фильтрскрипта, поскольку нет нужды выполнения этих функций в каком-то одном месте (например в моде), а остальным фильтрскриптам также нет нужды перенаправлять их вызовы в мод.

Однако если вы имеете какую-либо более-менее полноценную систему в вашем инклуде с подобным функционалом:

new bool:IsChatEnabled[MAX_PLAYERS];

public OnPlayerConnect(playerid)
{
IsChatEnabled[playerid] = true;

//Hook
#if defined myinc_OnPlayerConnect
return myinc_OnPlayerConnect(playerid);
#else
return 1;
#endif
}

//Hook
#if defined _ALS_OnPlayerConnect
#undef OnPlayerConnect
#else
#define _ALS_OnPlayerConnect
#endif
#define OnPlayerConnect myinc_OnPlayerConnect
#if defined myinc_OnPlayerConnect
forward myinc_OnPlayerConnect(playerid);
#endif

public OnPlayerText(playerid, text[])
{
//Если IsChatEnabled равна false - блокируем отправку сообщений игрока в чат
if(!IsChatEnabled[playerid]) return 0;

//Hook
#if defined myinc_OnPlayerText
return myinc_OnPlayerText(playerid, text);
#else
return 1;
#endif
}

//Hook
#if defined _ALS_OnPlayerText
#undef OnPlayerText
#else
#define _ALS_OnPlayerText
#endif
#define OnPlayerText myinc_OnPlayerText
#if defined myinc_OnPlayerText
forward myinc_OnPlayerText(playerid, text[]);
#endif

stock EnableChatForPlayer(playerid, bool:enable)
{
if(!IsPlayerConnected(playerid)) return 0;
IsChatEnabled[playerid] = enable;
return 1;
}

stock IsChatEnabledForPlayer(playerid)
return (IsPlayerConnected(playerid) ? IsChatEnabled[playerid] : 0);


То вы уже наверняка понимаете, что выполняться эти функции должны из одного места, иначе при параллельном выполнении инклуда в фильтрскриптах и в моде функция EnableChatForPlayer будет иметь эффект на блокировку чата лишь там, где выполняется копия такого инклуда (например, установив через неё запрет на отправку сообщий игроку в чат в одном из фильтрскриптов, а потом сняв запрет ею же в моде, блокировка чата продолжится, потому что это будут две параллельно работающие блокировки в фильтрскрипте и в моде, а по итогу вы отключаете её только в моде). Что же делать в таком случае - ответ ниже.

Второй, более сложный пример реализации API функций уже с возможностью перенаправления их вызовов из фильтрскриптов в мод (т.е. наши функции, вызываясь в фильтрскриптах, не выполняются тут же в них, а всего лишь шлют удалённый вызов в инклуд, подключенный к моду, в котором находится уже непосредственно код этих функций) происходит следующим образом: мы создаём stock функции-"обёртки", в которых есть всего лишь функция CallRemoteFunction (клик) (https://team.sa-mp.com/wiki/CallRemoteFunction) с вызовом другой паблик функции -> далее мы создаём эти самые паблик функции, которые в себе уже и содержат фактический код наших функций. CallRemoteFunction позволяет вызывать именно паблик функции (и именно поэтому нам приходится делать реальную функцию пабликом) всего лишь по её названию/указанным аргументам, ища и вызывая её по всем фильтрскриптам/моду уже в процессе работы сервера. Таким образом, благодаря CallRemoteFunction мы имеем возможность, вызвав функцию-обёртку в фильтрскрипте, послать сигнал на выполнение паблик функции с реальным кодом нашей функции уже в моде, тем самым выполняя код системы лишь в одном, едином месте. Удобно, но взамен мы жертвуем скоростью вызова этих функций, потому как CallRemoteFunction, выполняя вызов функции не напрямую, делает это достаточно медленно. Тем не менее, вот сам способ реализации этого варианта кодом, который используют почти во всех инклудах, где есть возможность вызывать функции инклуда из фильтрскриптов, выполняя их при этом в моде:

//На этот раз пример содержит не сами api функции некого инклуда,
//а хуки обычных самповских нативов, которые также нам часто нужно перехватить и в моде и в скриптах,
//однако код хука выполнить только в моде, перенаправляя вызовы из фильтрскриптов

//Для начала давайте добавим те функции-обёртки, которые будут перенаправлять все вызовы в мод
stock myinc_SetPlayerHealth(playerid, Float:health)
return CallRemoteFunction("myGMinc_SetPlayerHealth", "if", playerid, health);

//Hook
#if defined _ALS_SetPlayerHealth
#undef SetPlayerHealth
#else
#define _ALS_SetPlayerHealth
#endif
#define SetPlayerHealth myinc_SetPlayerHealth

stock myinc_SetPlayerArmour(playerid, Float:armour)
return CallRemoteFunction("myGMinc_SetPlayerArmour", "if", playerid, armour);

//Hook
#if defined _ALS_SetPlayerArmour
#undef SetPlayerArmour
#else
#define _ALS_SetPlayerArmour
#endif
#define SetPlayerArmour myinc_SetPlayerArmour

//Теперь нам нужна часть инклуда, которая будет выполняться только в моде (т.е. только если не задефайнен FILTERSCRIPT)
//В ней мы и объявим логику самой системы и наши паблик функции с фактическим кодом,
//которые будут удалённо вызываться хукнутыми выше функциями

#if !defined FILTERSCRIPT

//Код системы, который должен выполняться только из мода
new
Float:PlayerHealth[MAX_PLAYERS],
Float:PlayerArmour[MAX_PLAYERS];

//Паблики с фактическим кодом перехваченных функций

forward myGMinc_SetPlayerHealth(playerid, Float:health);
public myGMinc_SetPlayerHealth(playerid, Float:health)
{
if(!SetPlayerHealth(playerid, health)) return 0; //Валидация аргументов самой самповской функцией + её выполнение, если валидация прошла
PlayerHealth[playerid] = health; //Весь наш код
return 1;
}

forward myGMinc_SetPlayerArmour(playerid, Float:armour);
public myGMinc_SetPlayerArmour(playerid, Float:armour)
{
if(!SetPlayerArmour(playerid, armour)) return 0; //Валидация аргументов самой самповской функцией + её выполнение, если валидация прошла
PlayerArmour[playerid] = armour; //Весь наш код
return 1;
}

//Ну а далее уже обнуление хп/брони при коннекте просто для демонстрации целостности системы из этого примера
//Однако стоит заметить, что эта часть системы (как и любые другие, если таковые бы были далее)
//также должна выполняться в части инклуда, доступной только из мода
public OnPlayerConnect(playerid)
{
PlayerHealth[playerid] = 100.0;
PlayerArmour[playerid] = 0.0;

//Hook
#if defined myinc_OnPlayerConnect
return myinc_OnPlayerConnect(playerid);
#else
return 1;
#endif
}

//Hook
#if defined _ALS_OnPlayerConnect
#undef OnPlayerConnect
#else
#define _ALS_OnPlayerConnect
#endif
#define OnPlayerConnect myinc_OnPlayerConnect
#if defined myinc_OnPlayerConnect
forward myinc_OnPlayerConnect(playerid);
#endif

#endif // !defined FILTERSCRIPT


Заметили, что реализация такого способа не потребовала создания двух отдельных инклудов: одного только для фильтрскриптов, а другого исключительно для мода? Всё в данном случае решила "#if !defined FILTERSCRIPT", тем самым один и тот же инклуд нам нужно просто подключить к моду и всем фильтрскриптам. Это тоже удобно и также используется многими достаточно давно, но при этом в таком случае важно иметь во всех фильтрскриптах перед подключением каких-либо инклудов дефайн "FILTERSCRIPT" в самом начале, таким образом давая этой системе работать правильно.

И, вроде как, вот он выход из ситуации и решение проблемы, которым все пользуются уже достаточно долгое время: если нам нужны функции, которые должны мочь вызываться из фильтрскриптов, то мы делаем их вторым способом, а если нет - первым. Но что, если мы имеем достаточно объёмную систему, которая например, перехватывает большинство стандартных самповских нативов или имеет десятки/сотни API функций, для которых просто необходимо иметь возможность вызова из FS'а, но при этом, используя для них всех медленный CallRemoteFunction, мы совсем не можем быть уверенными, что конечный пользователь нашего инклуда имеет у себя вообще хоть какие-то фильтрскрипты? Это достаточно серьёзная проблема, потому как активное использование таких "перенаправляемых" функций из самого мода будет впустую замедлять весь сервер, а фича из второго способа, тем самым, превращается в абузу.

Именно поэтому (+-) пару лет назад я сделал своё собственное решение на основе второго варианта, которое также позволяет вызывать наши API функции инклуда из фильтрскриптов (подключая инклуд к фильтрскриптам и перенаправляя функции в инклуд из мода) и при этом вызывая их напрямую из самого мода. Реальная оптимизация ощущается гораздо больше на практике, потому как чаще всего люди абсолютно не используют никакие фильтрскрипты либо используют их непродолжительное время, тем самым в таком большинстве случаев мы полностью снимаем нагрузку от CallRemoteFunction, хотя сама возможность вызова из фильтрскриптов никуда не пропадает и может быть добавлена в любом из подключенных фильтрскриптов даже прямо во время работы сервера.

Итак, вот сама реализация такого метода на примере всё тех же хуков стандартных функций сампа:

//Для начала давайте снова добавим функции-обёртки, которые будут перенаправлять все вызовы в мод, но на этот раз...
stock myinc_SetPlayerHealth(playerid, Float:health)
{
//мы тут же проверяем, где именно вызвалась наша функция-обёртка
#if defined FILTERSCRIPT
//и если это фильтрскрипт, то (куда деваться) перенаправляем вызов в мод через CallRemoteFunction
return CallRemoteFunction("myGMinc_SetPlayerHealth", "if", playerid, health);
#else
//а вот если мод, то вызов паблика делаем напрямую, тем самым не теряя в скорости
return myGMinc_SetPlayerHealth(playerid, health);
#endif
}

//Hook
#if defined _ALS_SetPlayerHealth
#undef SetPlayerHealth
#else
#define _ALS_SetPlayerHealth
#endif
#define SetPlayerHealth myinc_SetPlayerHealth

//Здесь всё то же самое
stock myinc_SetPlayerArmour(playerid, Float:armour)
{
#if defined FILTERSCRIPT
return CallRemoteFunction("myGMinc_SetPlayerArmour", "if", playerid, armour);
#else
return myGMinc_SetPlayerArmour(playerid, armour);
#endif
}

//Hook
#if defined _ALS_SetPlayerArmour
#undef SetPlayerArmour
#else
#define _ALS_SetPlayerArmour
#endif
#define SetPlayerArmour myinc_SetPlayerArmour

//Теперь нам нужна часть инклуда, которая будет выполняться только в моде (т.е. только если не задефайнен FILTERSCRIPT)
//В ней мы и объявим наши паблик функции с фактическим кодом и логику самой системы

#if !defined FILTERSCRIPT

//Код системы, который должен выполняться только из мода
new
Float:PlayerHealth[MAX_PLAYERS],
Float:PlayerArmour[MAX_PLAYERS];

//Паблики с фактическим кодом перехваченных функций

forward myGMinc_SetPlayerHealth(playerid, Float:health);
public myGMinc_SetPlayerHealth(playerid, Float:health)
{
if(!SetPlayerHealth(playerid, health)) return 0; //Валидация аргументов самой самповской функцией + её выполнение, если валидация прошла
PlayerHealth[playerid] = health; //Весь наш код
return 1;
}

forward myGMinc_SetPlayerArmour(playerid, Float:armour);
public myGMinc_SetPlayerArmour(playerid, Float:armour)
{
if(!SetPlayerArmour(playerid, armour)) return 0; //Валидация аргументов самой самповской функцией + её выполнение, если валидация прошла
PlayerArmour[playerid] = armour; //Весь наш код
return 1;
}

//Ну а далее уже обнуление хп/брони при коннекте просто для демонстрации целостности системы из этого примера
//Однако стоит заметить, что эта часть системы (как и любые другие, если таковые бы были далее)
//также должна выполняться в части инклуда, доступной только из мода
public OnPlayerConnect(playerid)
{
PlayerHealth[playerid] = 100.0;
PlayerArmour[playerid] = 0.0;

//Hook
#if defined myinc_OnPlayerConnect
return myinc_OnPlayerConnect(playerid);
#else
return 1;
#endif
}

//Hook
#if defined _ALS_OnPlayerConnect
#undef OnPlayerConnect
#else
#define _ALS_OnPlayerConnect
#endif
#define OnPlayerConnect myinc_OnPlayerConnect
#if defined myinc_OnPlayerConnect
forward myinc_OnPlayerConnect(playerid);
#endif

#endif // !defined FILTERSCRIPT


Как видите, изменения относительно примера из второго варианта реализации, по большому счёту, всего лишь в самих функциях-обёртках-хуках, где просто была добавлена одна дефайн проверка с двумя вариантами вызова каждой из наших функций. Ничего необычного, однако именно этот вариант следовало бы использовать большинству разработчиков инклудов, которые до сих пор в ста процентах случаев используют удалённый вызов даже там, где этого можно избежать и вызывать напрямую.

А вот ещё один пример, только уже с собственными API функциями:

//И снова добавим функции-обёртки

stock EnableChatForPlayer(playerid, bool:enable)
{
//Валидацию аргументов на этот раз можно вынести сразу сюда,
//чтобы не затрачивать время на перенаправление из фильтрскрипта функции, которая всё равно не выполнится
if(!IsPlayerConnected(playerid)) return 0;
#if defined FILTERSCRIPT
return CallRemoteFunction("gm_EnableChatForPlayer", "ii", playerid, enable);
#else
return gm_EnableChatForPlayer(playerid, enable);
#endif
}

stock IsChatEnabledForPlayer(playerid)
{
//Валидацию аргументов на этот раз можно вынести сразу сюда,
//чтобы не затрачивать время на перенаправление из фильтрскрипта функции, которая всё равно не выполнится
if(!IsPlayerConnected(playerid)) return 0;
#if defined FILTERSCRIPT
return CallRemoteFunction("gm_IsChatEnabledForPlayer", "i", playerid);
#else
return gm_IsChatEnabledForPlayer(playerid);
#endif
}

//Часть инклуда, которая будет выполняться только в моде (т.е. только если не задефайнен FILTERSCRIPT)

#if !defined FILTERSCRIPT

//Код системы, который должен выполняться только из мода
new bool:IsChatEnabled[MAX_PLAYERS];

//Паблики с фактическим кодом API функций

forward gm_EnableChatForPlayer(playerid, bool:enable);
public gm_EnableChatForPlayer(playerid, bool:enable)
{
IsChatEnabled[playerid] = enable;
return 1;
}

forward gm_IsChatEnabledForPlayer(playerid);
public gm_IsChatEnabledForPlayer(playerid) return IsChatEnabled[playerid];

//Продолжение основного кода системы

public OnPlayerConnect(playerid)
{
IsChatEnabled[playerid] = true;

//Hook
#if defined myinc_OnPlayerConnect
return myinc_OnPlayerConnect(playerid);
#else
return 1;
#endif
}

//Hook
#if defined _ALS_OnPlayerConnect
#undef OnPlayerConnect
#else
#define _ALS_OnPlayerConnect
#endif
#define OnPlayerConnect myinc_OnPlayerConnect
#if defined myinc_OnPlayerConnect
forward myinc_OnPlayerConnect(playerid);
#endif

public OnPlayerText(playerid, text[])
{
//Если IsChatEnabled равна false - блокируем отправку сообщений игрока в чат
if(!IsChatEnabled[playerid]) return 0;

//Hook
#if defined myinc_OnPlayerText
return myinc_OnPlayerText(playerid, text);
#else
return 1;
#endif
}

//Hook
#if defined _ALS_OnPlayerText
#undef OnPlayerText
#else
#define _ALS_OnPlayerText
#endif
#define OnPlayerText myinc_OnPlayerText
#if defined myinc_OnPlayerText
forward myinc_OnPlayerText(playerid, text[]);
#endif

#endif // !defined FILTERSCRIPT


Ну а вместо итога я хотел бы привести плюсы и минусы реализации API последним (моим) вариантом.

Плюсы:
* Все функции в вашем инклуде доступны для вызова как из мода, так и из фильтрскриптов (как в предыдущем методе);
* При этом нет большой нагрузки от вызова функций инклуда из мода, поскольку в этом случае они вызываются напрямую.

Минусы:
* Зависимость корректной работы всего этого алгоритма от того, добавил ли пользователь "#define FILTERSCRIPT" в начало каждого фильтрскрипта (как и в предыдущем методе);
* Для каждой функции вы должны иметь её "обёртку" и паблик с фактическим её кодом, т.е. в сумме технически две функции для одной фактической (как в предыдущем методе);
* Код становится ещё более некомпактным, чем в предыдущем методе, из-за постоянного дублирования проверок на фильтрскрипт внутри каждой функции-обёртки.

Тем не менее, как мне кажется, это вполне того стоит и эффективность с лихвой перевешивают косметические недостатки.

DeimoS
17.11.2019, 02:28
Чтоб всё не упиралось в макрос FILTERSCRIPT, можно сделать глобальную переменную и перехват OnGameModeInit. В перехвате устанавливаем для глобальной переменной единицу/true и уже вместо
#if defined FILTERSCRIPT
return CallRemoteFunction("gm_IsChatEnabledForPlayer", "i", playerid);
#else
return gm_IsChatEnabledForPlayer(playerid);
#endif
делаем
if(глобальная_переменная == 0)
return CallRemoteFunction("gm_IsChatEnabledForPlayer", "i", playerid);
else
return gm_IsChatEnabledForPlayer(playerid);

Так как OnGameModeInit не вызывается для скриптов, у скриптов она будет равна нулю, а для мода - единице. Как итог, получится всё тот же принцип работы и независимость от наличия макроса FILTERSCRIPT, но в .amx будет попадать чуть больше кода + будет тратится чуть больше времени на проверки (хотя сравнение обычной переменной происходит настолько быстро, что это можно и не учитывать).

Nexius_Tailer
17.11.2019, 16:49
Чтоб всё не упиралось в макрос FILTERSCRIPT, можно сделать глобальную переменную и перехват OnGameModeInit. В перехвате устанавливаем для глобальной переменной единицу/true и уже вместо
#if defined FILTERSCRIPT
return CallRemoteFunction("gm_IsChatEnabledForPlayer", "i", playerid);
#else
return gm_IsChatEnabledForPlayer(playerid);
#endif
делаем
if(глобальная_переменная == 0)
return CallRemoteFunction("gm_IsChatEnabledForPlayer", "i", playerid);
else
return gm_IsChatEnabledForPlayer(playerid);

Так как OnGameModeInit не вызывается для скриптов, у скриптов она будет равна нулю, а для мода - единице. Как итог, получится всё тот же принцип работы и независимость от наличия макроса FILTERSCRIPT, но в .amx будет попадать чуть больше кода + будет тратится чуть больше времени на проверки (хотя сравнение обычной переменной происходит настолько быстро, что это можно и не учитывать).
Да, тоже думал добавить такой вариант как альтернативу, но лично в своих скриптах это использовать не рискую. Проверки, переносящиеся в рантайм хоть и мизерные сами по себе, но в конечном итоге они будут присутствовать в самых частовызываемых событиях и функциях (а по факту вообще везде, куда дотянется инклуд: как в пабликах, так и в каждой api/хукнутой самп функции) и уже в глобальном плане, думаю, нагрузка будет более заметная. Тем более и выполняются эти проверки каждый раз почти в любом случае, что делает эту нагрузку постоянной на протяжении всей работы сервера.

DeimoS
18.11.2019, 16:40
Да, тоже думал добавить такой вариант как альтернативу, но лично в своих скриптах это использовать не рискую. Проверки, переносящиеся в рантайм хоть и мизерные сами по себе, но в конечном итоге они будут присутствовать в самых частовызываемых событиях и функциях (а по факту вообще везде, куда дотянется инклуд: как в пабликах, так и в каждой api/хукнутой самп функции) и уже в глобальном плане, думаю, нагрузка будет более заметная. Тем более и выполняются эти проверки каждый раз почти в любом случае, что делает эту нагрузку постоянной на протяжении всей работы сервера.

Если сравнивать код без проверок и код с проверками, то да, нагрузка возрастёт на целых 100%! Но если говорить о нагрузке в целом, то она всё ещё будет крайне мала и несущественна (вся проверка будет генерировать около 5 инструкций). Даже в OnPlayerUpdate такой код никак не отразится на скорости работы сервера. Особенно относительно вызова CallRemoteFunction.


Ну и чтоб не быть голословным, вот настройки для профайлера (http://pro-pawn.ru/showthread.php?12585)
// Profiler v1.3 (copyright (c) 2014-2017 Daniel_Cortez) \\ Pro-Pawn.ru
// Условия использования данного кода: http://pro-pawn.ru/showthread.php?12585

/*======== Настройки =========================================================*/
// Кол-во итераций в циклах.
const PROFILER_ITERATIONS_MAJOR = 1_000_000;
const PROFILER_ITERATIONS_MINOR = 10;

new const code_snippets_names[2][] =
{
{"if"},
{"без if"}
};


#define Prerequisites();\
new pRaceActive;


#define CodeSnippet0(); \
if(pRaceActive == 1){}else{}

#define CodeSnippet1();



/*======== Конец настроек ===================================================*/

Результаты:

https://i.imgur.com/6n1kjeh.png

Как видно, разница составляет около 100 тиков для 10 миллионов вызовов. Разделяем 100 на 10 миллионов и получаем 0.00001 тика. Повлияет ли это хоть как-то на сервер? Сомневаюсь. Даже если подключить 100 инклудов, в которых будут подобного рода проверки, нагрузка заметной никак не станет.

Nexius_Tailer
18.11.2019, 22:40
Если сравнивать код без проверок и код с проверками, то да, нагрузка возрастёт на целых 100%!
Я говорил совсем про другое.


Результаты:

https://i.imgur.com/6n1kjeh.png

Как видно, разница составляет около 100 тиков для 10 миллионов вызовов. Разделяем 100 на 10 миллионов и получаем 0.00001 тика. Повлияет ли это хоть как-то на сервер? Сомневаюсь. Даже если подключить 100 инклудов, в которых будут подобного рода проверки, нагрузка заметной никак не станет.
За тесты спасибо, но лично я бы всё равно предпочёл дефайн проверки даже просто чтобы не нести этот мусор в amx. Как-никак, наличие FILTERSCRIPT в начале любого fs'а это норма, которая, во-первых, присутствует в том числе и в new.pwn, ну и во-вторых, в интересах самого же пользователя для правильной работы всей той кучи остальных скриптов, которые уже его используют и вряд ли когда-то перестанут.

А, и да, на практике есть проблема ещё с выделением глобальных переменных в части мода, имел бы такой инклуд рантайм проверки на фильтрскрипт. Дефайн-проверка в этом плане тупо отсечёт код независимо от того, находится ли он в теле функции/паблика или нет.

DeimoS
18.11.2019, 23:12
Не знаю про какое повсеместное наличие макроса FILTERSCRIPT ты говоришь, ибо если зайти, например, в раздел скриптов этого форума и начать смотреть исходники, то можно удивиться тому, что скриптов без макроса, в лучшем случае, столько же, сколько и с макросом. Просто потому что большая часть скриптеров не очень понимает зачем он нужен.

Но вообще я нигде не утверждал, что мой вариант лучше. Я просто указал на то, как реализовать всё иначе, озвучив основные плюсы и минусы такого подхода.

Nexius_Tailer
19.11.2019, 00:20
Не знаю про какое повсеместное наличие макроса FILTERSCRIPT ты говоришь, ибо если зайти, например, в раздел скриптов этого форума и начать смотреть исходники, то можно удивиться тому, что скриптов без макроса, в лучшем случае, столько же, сколько и с макросом. Просто потому что большая часть скриптеров не очень понимает зачем он нужен.
Не думаю, что на этом форуме есть достаточное количество релизов инклудов, которые имеют возможность подключаться к фильтрскриптам в принципе, так что лучше смотреть на официальном. А так, вариант вполне себе, тут уже даже скорее личный выбор каждого, что и как ему использовать для каких ситуаций, учитывая, что любая из альтернатив имеет свои нюансы.

DeimoS
19.11.2019, 04:12
Не думаю, что на этом форуме есть достаточное количество релизов инклудов, которые имеют возможность подключаться к фильтрскриптам в принципе, так что лучше смотреть на официальном. А так, вариант вполне себе, тут уже даже скорее личный выбор каждого, что и как ему использовать для каких ситуаций, учитывая, что любая из альтернатив имеет свои нюансы.

Про данный форум я сказал сугубо в качестве примера, за которым не нужно "далеко ходить" :) Да и речь ведь не об инклудах, а о скриптах.

К слову, зашёл сейчас ради интереса на официальный форум, перешёл в раздел скриптов и из первых десяти тем только в двух был макрос (+ещё в двух были мёртвые ссылки). Так что ситуация с этим макросом особо не изменилась, судя по всему) В любом случае, как я уже говорил, я ни на чём не настаиваю.

Nexius_Tailer
19.11.2019, 09:53
А, ну под использованием дефайна я имел в виду именно инклуды, которые учитывая его меняют своё поведение. Если какой-то обычный рядовой нубо-скрипт вроде "мой первый фс Vip system" или ещё чего подобного его не имеет, то это и не удивительно.

DeimoS
20.11.2019, 04:19
А, ну под использованием дефайна я имел в виду именно инклуды, которые учитывая его меняют своё поведение. Если какой-то обычный рядовой нубо-скрипт вроде "мой первый фс Vip system" или ещё чего подобного его не имеет, то это и не удивительно.

Так речь ведь о том, что этот макрос отсутствует далеко не только в "рядовых нубо-скриптах", а, скорее, чуть ли не повсеместно. Просто потому что мало кто понимает его предназначение.
Собственно, для того я и предложил вариант без привязки к макросу, который относительно мало скриптеров использует.

Впрочем, я уже раз третий пишу про одно и то же, поэтому предлагаю закончить. Кто заинтересуется данной темой, тот найдёт для себя всю информацию и в комментариях.

Nexius_Tailer
20.11.2019, 11:51
Просто я изначально имел в виду то, что в инклудах (своего рода тоже скрипты, но почему-то забыл уточнить это) везде, где юзается фича поддержки фильтрскриптов (будь то внешние функции/своё собственное поведение инклуда в них/что-либо ещё), везде обычно и юзается проверка на дефайн. И именно поэтому в интересах разрабов/пользователей фильтрскриптов будет её иметь, чтобы все эти остальные инклуды не включали код мода в их скрипты, даже если два из трёх этих инклудов имели бы возможность проверять всё при выполнении сервера.

DeimoS
20.11.2019, 13:01
Просто я изначально имел в виду то, что в инклудах (своего рода тоже скрипты, но почему-то забыл уточнить это) везде, где юзается фича поддержки фильтрскриптов (будь то внешние функции/своё собственное поведение инклуда в них/что-либо ещё), везде обычно и юзается проверка на дефайн. И именно поэтому в интересах разрабов/пользователей фильтрскриптов будет её иметь, чтобы все эти остальные инклуды не включали код мода в их скрипты, даже если два из трёх этих инклудов имели бы возможность проверять всё при выполнении сервера.

Ну, во-первых, использовать такой метод написания инклуда только ради того, чтоб в FS попало меньше кода - это, мягко говоря, не очень рационально. Ибо шанс словить проблемы с быстродействием кода гораздо более выше, нежели шанс словить проблемы нехватки памяти. И, в данном случае, любое экономие памяти теряет свой смысл, ибо затраты на срабатывание CallRemoteFunction будут слабо соизмеримы с этой самой экономией.
Использовать такой метод стоит только в случае крайней нужды и чёткого понимания своих действий (ну когда, собственно, для правильной работы инклуда требуется синхронизировать данные FS и мода. Всякие античиты и т.п.), о чём бы я крупными буквами написал бы в самом начале статьи, на твоём месте, дабы мамкины оптимизаторы не начали творить дичь.

Во-вторых, про "везде юзается" и "в интересах" - это слабый аргумент. Просто потому что в интересах разрабов есть много всего, чего они не делают (например, банальное написание кода в одном стиле, дабы повысить совместимость своего кода с чужим и упростить чтение как своего кода другими людьми, так и чужого кода себе). Так же много где юзается то, что юзаться не должно или юзается неправильно (яркий пример - уровень работы с MySQL в большинстве модов и скриптов, который, мягко говоря, оставляет желать лучшего).
Отталкиваться нужно, в первую очередь, от здравого смысла, а не от слепой статистики без анализа. И тут, как мне кажется, гораздо логичнее написать более универсальный код, потратив на тысячную долю тика больше, нежели пытаться экономить то, что экономить особо не нужно, делая код более зависимым от кода пользователя. Но я это уже и раньше писал, поэтому повторюсь ещё раз: тут уж каждому своё.

Nexius_Tailer
20.11.2019, 14:33
Ну, во-первых, использовать такой метод написания инклуда только ради того, чтоб в FS попало меньше кода - это, мягко говоря, не очень рационально. Ибо шанс словить проблемы с быстродействием кода гораздо более выше, нежели шанс словить проблемы нехватки памяти. И, в данном случае, любое экономие памяти теряет свой смысл, ибо затраты на срабатывание CallRemoteFunction будут слабо соизмеримы с этой самой экономией.
Экономие памяти я и не рассматриваю как цель в данном случае, т.к. здесь это вообще ни на что по факту не повлияет. Лишний код, который бы включался в фс с рантайм проверками, меня заботил больше всего именно в плане алгоритма, который бы почти впустую проверял одно и то же каждый тик сервера. Но разумеется, в сравнении с медленностью вызова CallRemoteFunction это в любом случае гораздо менее существенно.


Использовать такой метод стоит только в случае крайней нужды и чёткого понимания своих действий (ну когда, собственно, для правильной работы инклуда требуется синхронизировать данные FS и мода. Всякие античиты и т.п.), о чём бы я крупными буквами написал бы в самом начале статьи, на твоём месте, дабы мамкины оптимизаторы не начали творить дичь.
Не очень понимаю, что можно натворить при условии, что далеко не все инклуды имеют надобность в api между скриптами и в каком-либо api в принципе. Хукнутые самп нативы приводятся в примерах ближе к концу в абсолютно том же контексте, что инклуд должен как минимум иметь систему с доступом к неким переменным, которые нужно изменить не локально в конкретном скрипте.


Во-вторых, про "везде юзается" и "в интересах" - это слабый аргумент. Просто потому что в интересах разрабов есть много всего, чего они не делают (например, банальное написание кода в одном стиле, дабы повысить совместимость своего кода с чужим и упростить чтение как своего кода другими людьми, так и чужого кода себе). Так же много где юзается то, что юзаться не должно или юзается неправильно (яркий пример - уровень работы с MySQL в большинстве модов и скриптов, который, мягко говоря, оставляет желать лучшего).
Отталкиваться нужно, в первую очередь, от здравого смысла, а не от слепой статистики без анализа. И тут, как мне кажется, гораздо логичнее написать более универсальный код, потратив на тысячную долю тика больше, нежели пытаться экономить то, что экономить особо не нужно, делая код более зависимым от кода пользователя. Но я это уже и раньше писал, поэтому повторюсь ещё раз: тут уж каждому своё.
Писать код в одном стиле это, по большому счету, в интересах комьюнити, а не в их собственных; уровень интеграции с бд, раз в их модах она кое-как работает, видимо также их вполне устраивает. Здесь же речь о работоспособности всей системы в принципе, ибо если эти люди не добавят тот дефайн в своих фсах и подключат инклуд, который его учитывает, они практически в любом случае столкнутся с проблемами и некорректной работой, а уже при попытке её исправить так или иначе дойдут до этого решения. Так что в данном случае это действительно в их интересах, в отличие от примеров выше.
Ну а повторяться насчет последнего я и сам не вижу смысла, т.к. все выводы уже написаны здесь (http://pro-pawn.ru/showthread.php?16978-%D0%AD%D1%84%D1%84%D0%B5%D0%BA%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F-%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-API-%D0%B8%D0%BD%D0%BA%D0%BB%D1%83%D0%B4%D0%B0-%D1%81-%D0%B2%D0%BE%D0%B7%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C%D1%8E-%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%D0%B0-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9-%D0%B8%D0%B7-%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B2&p=95576&viewfull=1#post95576).

MassonNN
27.12.2019, 14:41
public OnPlayerClickTextDraw(playerid, Text:clickedid)
{

// код


#if defined autoschool_OnPlayerClickTextDraw
return autoschool_OnPlayerClickTextDraw(playerid, Text:clickedid);
#else
return 1;
#endif
}

#if defined _ALS_OnPlayerClickTextDraw
#undef OnPlayerClickTextDraw
#else
#define _ALS_OnPlayerClickTextDraw
#endif
#define OnPlayerClickTextDraw autoschool_OnPlayerClickTextDraw
#if defined autoschool_OnPlayerClickTextDraw
forward autoschool_OnPlayerClickTextDraw(playerid, Text:clickedid);
#endif





error 021: symbol already defined: "OnPlayerClickTextDraw"

Nexius_Tailer
28.12.2019, 14:46
public OnPlayerClickTextDraw(playerid, Text:clickedid)
{

// код


#if defined autoschool_OnPlayerClickTextDraw
return autoschool_OnPlayerClickTextDraw(playerid, Text:clickedid);
#else
return 1;
#endif
}

#if defined _ALS_OnPlayerClickTextDraw
#undef OnPlayerClickTextDraw
#else
#define _ALS_OnPlayerClickTextDraw
#endif
#define OnPlayerClickTextDraw autoschool_OnPlayerClickTextDraw
#if defined autoschool_OnPlayerClickTextDraw
forward autoschool_OnPlayerClickTextDraw(playerid, Text:clickedid);
#endif





error 021: symbol already defined: "OnPlayerClickTextDraw"
Видимо у тебя уже до этого хука где-то выше объявлен этот же паблик.

MassonNN
31.12.2019, 00:21
Видимо у тебя уже до этого хука где-то выше объявлен этот же паблик.

Да, походу из-за того. А нельзя интересно как-то после объявления хук сделать?) Просто, наверное, это было бы удобнее... Особенно, если у тебя структурированный проект

DeimoS
31.12.2019, 05:25
Да, походу из-за того. А нельзя интересно как-то после объявления хук сделать?) Просто, наверное, это было бы удобнее... Особенно, если у тебя структурированный проект

Делать хуки не для античитов или публичных библиотек - такая себе идея. Обсуждалось это многократно и тут (http://pro-pawn.ru/showthread.php?16576-%D0%A1%D0%BE%D0%B2%D0%B5%D1%82-%D0%BF%D0%BE-%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B5-%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B&p=93323&viewfull=1#post93323), и тут (http://pro-pawn.ru/showthread.php?15165-Geebrox&p=84116&viewfull=1#post84116), и где-то ещё явно.

Ну а если всё же хочешь стрелять в собственную ногу - y_hook в помощь.