Каллбековые функции

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #1
Все наверное знают или пользовались такими функциями, как SQL_TConnect() или SQL_TQuery(), которые по завершению работы вызывают переданную им функцию. Вот мне на днях понадобилось сделать что-то подобное.
Есть три плагина:
  • sourcebans.smx
  • sourcecomms.smx
  • sbchecker.smx
Все они поднимают отдельные соединения к базе SourceBans.
Меня это не устраивало, и я решил, что надо в плагин sourcebans.smx (т. н. ядро) добавить натив, который будет делать запрос к БД, и возвращать результат в переданный каллбек. Подобно функции SQL_TQuery(), да, но без хендла БД (оно и ясно почему).
В этой небольшой статье я распишу, как можно это сделать.

Для начала, в include-файле своего плагина надо написать "слепок" каллбека. Чтобы это сделать, надо приблизительно представить, что на каллбек должно передаваться. Поскольку у меня ф-ия выполнения запроса к БД, то я решил, что каллбек должен вызываться с тремя аргументами:
  • DBResultSet. Собственно, сами данные из БД, если это SELECT-запрос.
  • char[]. Строка с ошибкой, если такая появилась при выполнении.
  • any. Некоторые данные, которые были переданы на натив выполнения запроса, и должны вернуться в каллбек для идентификации запроса.
Пишем в inc-файле, снабжая комментарием для других скриптеров:
PHP:
// Каллбек для запросов к БД СБ.
//
// @param hDBResults            Результаты запроса. null, если нечего вернуть.
// @param szError               Строка с текстом ошибки, если таковая произошла.
// @param data                  Кастомные параметры, переданные из вызванного натива.
typedef SBDBCallback = function void(DBResultSet hDBResults, const char[] error, any data);
Вообще, желательно такое писать на английском, но для примера, я буду писать всё на русском.

Теперь, нам нужен натив, вызывая который, плагины будут передавать свой каллбек, кастомные данные и, собственно, запрос. Ещё можно разрешить передавать приоритет. У меня получился такой натив:
PHP:
/*********************************************************
 * Выполняет запрос к БД SourceBans в отдельном потоке.
 *
 * @param szQuery   Строка с запросом. Используйте в нём {{prefix}}
 *                  в тех местах, где необходимо подставить
 *                  префикс таблиц.
 * @param funcCall  Каллбек.
 * @param prio      Приоритет для запроса.
 * @param data      Кастомные данные. Вернутся в каллбеке.
 * @noreturn
 *********************************************************/
native void SBProcessQuery(char[] szQuery, SBDBCallback funcCall, DBPriority prio = DBPrio_Normal, any data = 0);
Теперь можно писать, собственно, сам натив :)

Открываем свой плагин (у меня sourcebans.sp), ищем форвард AskPluginLoad2() (или создаём вручную, если он до сих пор не существует) и добавляём в нём строчку создания Вашего натива.
PHP:
CreateNative("SBProcessQuery", Native_DBQuery);
Теперь, нам необходимо создать функцию, которая, собственно, будет обрабатывать запрос.
PHP:
// сам натив
public int Native_DBQuery(Handle hPlugin, int iNumParams) {
    char szQuery[4096];
    GetNativeString(1, szQuery, sizeof(szQuery));
    ReplaceString(szQuery, sizeof(szQuery), "{{prefix}}", DatabasePrefix, true);

    DataPack hDatapack = CreateDataPack();
    WritePackCell(hDatapack, view_as<int>(hPlugin)); // Хендл плагина нам пригодится при вызове функции.
    WritePackFunction(hDatapack, GetNativeFunction(2));
    WritePackCell(hDatapack, GetNativeCell(4));

#if defined DEBUG
    char szPluginName[PLATFORM_MAX_PATH];
    GetPluginFilename(hPlugin, szPluginName, sizeof(szPluginName));
    LogToFile(logFile, "Processing custom query for plugin %s: %s", szPluginName, szQuery);
#endif

    SQL_TQuery(DB, PluginQueriesCallback, szQuery, hDatapack, view_as<DBPriority>(GetNativeCell(3)));
}

// каллбек запроса
public void PluginQueriesCallback(Handle owner, Handle hndl, char[] error, any data) {
    ResetPack(data);
    Handle hPlugin = view_as<Handle>(ReadPackCell(data));

    Call_StartFunction(hPlugin, ReadPackFunction(data));
    Call_PushCell(view_as<int>(hndl));
    Call_PushString(error);
    Call_PushCell(ReadPackCell(data));
    Call_Finish();

    delete data;
}
Вот так просто можно создать натив, в который передаётся функция, которая рано или будет вызвана. Можно придумать множество использований таких ф-ий. Как пример, достаточно посмотреть на VIP-ядро от R1KO. В него тоже передаются функции-каллбеки при создании VIP-функции.
 

FurFur

Участник
Сообщения
153
Реакции
85

3 одновременных вызова -> stack overflow.
Я бы заменил на локальный динамический массив, размер которого нужно знать заранее, либо путем вычесления размера в самой функции.
 
Последнее редактирование:

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #3
@Крабос, хм, не знал. Спасибо. У себя поправлю...
Три запроса одновременных навряд ли будет. Разве что 2 только. От sbchecker и от sourcecomms в момент подключения игрока. 1
 

Reiko1231

AlexTheRegent
Сообщения
508
Реакции
1,335
@Kruzya, ваше решение для предложенной задачи не является лучшим. Всё, что в таком случае нужно сделать - создать клон коннектора MySQL с помощью функции CloneHandle. Тогда натив будет выглядеть как
C-подобный:
public Native_GetConnector(Handle:hPlugin, iNumParams)
{
    return CloneHandle(DB, hPlugin);
}
После обращения к этому нативу плагин получает копию коннектора и может делать с ним что угодно (если он его закроет, то оригинал не закроется. Если оригинал закроется, то копия не будет закрыта). Но это по-прежнему будет одно и то же соединение.
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #5
@Reiko1231, я считаю, что лучше будет сделать так, потому что:
  • клонирование хендла соединения с БД, насколько мне известно, создаёт ещё одно соединение;
  • другому плагину придётся вдобавок:
    • Либо читать конфиг SourceBans, и вытягивать из него префикс таблиц
    • Либо создавать ещё один натив для сторонних плагинов, который тупо вернёт префикс
 
Сверху Снизу