Согласно официальной документации: SQLQueryCallbackpublic void SQL_Callback_SelectClient(Database hDatabase, DBResultSet results, const char[] sError, any iUserID) // Обратный вызов
{
if(sError[0]) // Если произошла ошибка
{
LogError("SQL_Callback_SelectClient: %s", sError); // Выводим в лог
return; // Прекращаем выполнение ф-и
}
int iClient = GetClientOfUserId(iUserID);
if(iClient)
{
// Игрок всё еще на сервере
}
}
error: Error string if there was an error. The error could be empty even if an error condition exists, so it is important to check the actual results value instead.
(Строка ошибки, если она произошла. Строка может быть пуста, даже если ошибка произошла, так что важно проверять значение results, вместо error.)
прочитал все, не увидел что ожидал увидеть.@Crocell, первый пост то читал?
public void ConnectCallBack (Database hDB, const char[] error, any data) // Пришел результат соеденения
{
if (hDB == null) // Соединение не удачное
{
SetFailState("Database failure: %s", sError); // Отключаем плагин
return;
}
g_hDatabase = hDB; // Присваиваем глобальной переменной соеденения значение текущего соеденения
CreateTables(); // Функция пока не реализована, но по имени, думаю, ясно что она делает
}
SetFailState("Database failure: %s", sError); // Отключаем плагин
@login, базу блокировать надо при выполнении непоточных запросов.
Отличие, собственно, вот в чём: поточные запросы выполняются в другом, отдельном от сервера, потоке, а потому не вызывают никаких фризов. Непоточные же выполняются в одном потоке с игровым тиком, а потому, если сервер будет очень долго думать, то будет фриз гарантированный.
Никак. Забудьте. Такие каллбеки требуют моментальной реакции.как правильно обработать задержку при получении данных, что бы это не начало вызывать фризы на сервере? Данные из БД нужны не для вывода в лог или показу пользователю, а для принятия решения при коннекте игрока к серверу.
OnClientAuthorized()
и использовать асинхронщину.Потому что иного, правильного способа обработать результат - нет.Как правило просто в функции обратного вызова заполняют переменные или DataPack и далее уже в других функциях сразу же обрабатывают эти данные.
Понятия "прерывания" в SourceMod нет.Каким образом в sourcemod реализуются очереди задач и выдаются прерывания?
Это посложнее объяснить, да и это выходит за рамки этого текста.Например, когда работает несколько плагинов, то каким образом между ними распределяется процессорное время внутри самого процесса Srcds.exe
Такого не произойдёт, конечно же. В один тик - навряд ли.При отслеживании событий форворда OnClientAuthorized() если сразу подклчюается несколько клиентов, то как будет обрабатывать их плагин. Параллельно или в начале полностью закончит с одним клиентом, а потом уже займётся другим?
А что это дает, ведь пока завершится асинхронный запрос, юзер уже пройдет стадию авторизации или можно как то принудить клиента ожидать принятия решения?Никак. Забудьте. Такие каллбеки требуют моментальной реакции.
Наиболее правильным решением будет уйти наOnClientAuthorized()
и использовать асинхронщину.
В форвардеможно как то принудить клиента ожидать принятия решения?
И как это реализовано в том же sourcebans?
OnClientPreAdminCheck()
вернуть Plugin_Stop
, вроде. В доку глянуть не могу (с телефона), но там написано, что вернуть для блокировки Post-форварда. NotifyPostAdminCheck()
. Название тоже не точное, в доке найдете. Когда есть некий хендл, который удалится по завершению выполнения функции, и клонировать не представляется возможным.1. В каком случае оправдано (либо не обойтись) без использования не поточных (синхронных) запросов?
Операция блокировки БД попросту не дает самому SM начать выполнение любых асинхронных запросов.2. Правильно ли я понимаю, операция блокировки бд приостанавливает работу основного потока, пока не будет завершена текущая (либо все?) поточные запросы к бд (в т.ч.) от контекста других плагинов?
Если в этом же потоке - можно.Кто владелец блокировки, т.е. в теории можно ли разблокировку выполнить из под контекста другого плагина?
Сам драйвер БД хранит, кажется.Где/кто хранит состояние блокировки?
Блокировка базы производится чисто логическая. Удаленная база не блокируется.Т.е. если в теории плагин будет внезапно выгружен/ нарвется на setfailstate до разблокировки бд, произойдет ли дедлок на физически другой машине, которая юзает эту бд через синхронные запросы?
Плагин, отправляя асинхронный запрос, на самом деле, ничего не отправляет. Запрос уходит в очередь. Один из воркеров СМ разбирает ее, постоянно вешая лок перед выполнением очередного запроса, и так же снимая по завершению.Leaving a lock on a database and then executing a threaded query results in a dead lock! Make sure to call SQL_UnlockDatabase()!
Не совсем понимаю логику написанного. ,Плагин отправивший асинхронный запрос ведь априори ничего дожидаться (приостанавливать поток) не будет. Дедлок должен произойти при очередном не поточном (синхронном) запросе.
Database g_hDatabase;
public void OnPluginStart()
{
Database.Connect(ConnectCallBack, "wsblock");
}
public void ConnectCallBack(Database hDatabase, const char[] sError, any data)
{
if (hDatabase == null)
{
SetFailState("Database failure: %s", sError);
return;
}
g_hDatabase = hDatabase;
SQL_LockDatabase(g_hDatabase);
g_hDatabase.Query(SQL_CheckError, "CREATE TABLE IF NOT EXISTS `ws_info` (\
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\
`auth` VARCHAR(32) NOT NULL,\
`name` VARCHAR default 'unknown',\
`time` INTEGER NOT NULL);");
SQL_UnlockDatabase(g_hDatabase);
g_hDatabase.SetCharset("utf8");
}
public void SQL_CheckError(Database hDatabase, DBResultSet results, const char[] szError, any data)
{
if(szError[0])
{
LogError("SQL_CheckError: %s", szError);
}
}
public void OnClientPostAdminCheck(iClient)
{
if(!IsFakeClient(iClient))
{
char szQuery[256], szAuth[32];
GetClientAuthId(iClient, AuthId_Engine, szAuth, sizeof(szAuth), true);
FormatEx(szQuery, sizeof(szQuery), "SELECT `id`, `time` FROM `ws_info` WHERE `auth` = '%s';", szAuth);
g_hDatabase.Query(SQL_ConnectClient, szQuery, GetClientUserId(iClient));
}
}
public void SQL_ConnectClient(Database hDatabase, DBResultSet hResults, const char[] sError, any iUserID)
{
if(sError[0])
{
LogError("SQL_ConnectClient: %s", sError);
return;
}
int iClient = GetClientOfUserId(iUserID);
if(iClient)
{
if(hResults.FetchRow())
{
g_iClientID[iClient] = hResults.FetchInt(0);
g_iClientTime[iClient] = hResults.FetchInt(1);
char szQuery[256], szName[MAX_NAME_LENGTH*2+1];
GetClientName(iClient, szQuery, MAX_NAME_LENGTH);
g_hDatabase.Escape(szQuery, szName, sizeof(szName));
FormatEx(szQuery, sizeof(szQuery), "UPDATE `ws_info` SET `name` = '%s' WHERE `id` = %i;", szName, g_iClientID[iClient]);
g_hDatabase.Query(SQL_CheckError, szQuery);
}
}
}
public void SQL_CreateClient(Database hDatabase, DBResultSet results, const char[] szError, any iUserID)
{
if(szError[0])
{
LogError("SQL_CreateClient: %s", szError);
return;
}
int iClient = GetClientOfUserId(iUserID);
if(iClient)
{
g_iClientID[iClient] = results.InsertId;
}
}
public void Dell_Client(char[] szAuth)
{
char szQuery[256];
FormatEx(szQuery, sizeof(szQuery), "DELETE FROM `ws_info` WHERE `auth` = '%s';", szAuth);
g_hDatabase.Query(SQL_CheckError, szQuery);
}
public void Create_Client(iClient)
{
char szQuery[256], szAuth[32];
GetClientAuthId(iClient, AuthId_Engine, szAuth, sizeof(szAuth), true);
FormatEx(szQuery, sizeof(szQuery), "INSERT INTO `ws_info` (`auth`, `time`) VALUES ( '%s', %i);", szAuth, g_fGiveTime[iClient]);
g_hDatabase.Query(SQL_CreateClient, szQuery, GetClientUserId(iClient));
}
public void OnClientDisconnect(iClient)
{
char szQuery[256];
FormatEx(szQuery, sizeof(szQuery), "UPDATE `ws_info` SET `time` = %i WHERE `id` = %i;", g_iClientTime[iClient] - g_iTime[iClient], g_iClientID[iClient]);
g_hDatabase.Query(SQL_CheckError, szQuery);
}
попробуй в интерте еще name передаватьПомогите пожалуйста!
Недавно столкнулся с ошибкой:
SQL_CreateClient: NOT NULL constraint failed: ws_info.time
Код где используется бд
C-подобный:Database g_hDatabase; public void OnPluginStart() { Database.Connect(ConnectCallBack, "wsblock"); } public void ConnectCallBack(Database hDatabase, const char[] sError, any data) { if (hDatabase == null) { SetFailState("Database failure: %s", sError); return; } g_hDatabase = hDatabase; SQL_LockDatabase(g_hDatabase); g_hDatabase.Query(SQL_CheckError, "CREATE TABLE IF NOT EXISTS `ws_info` (\ `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\ `auth` VARCHAR(32) NOT NULL,\ `name` VARCHAR default 'unknown',\ `time` INTEGER NOT NULL);"); SQL_UnlockDatabase(g_hDatabase); g_hDatabase.SetCharset("utf8"); } public void SQL_CheckError(Database hDatabase, DBResultSet results, const char[] szError, any data) { if(szError[0]) { LogError("SQL_CheckError: %s", szError); } } public void OnClientPostAdminCheck(iClient) { if(!IsFakeClient(iClient)) { char szQuery[256], szAuth[32]; GetClientAuthId(iClient, AuthId_Engine, szAuth, sizeof(szAuth), true); FormatEx(szQuery, sizeof(szQuery), "SELECT `id`, `time` FROM `ws_info` WHERE `auth` = '%s';", szAuth); g_hDatabase.Query(SQL_ConnectClient, szQuery, GetClientUserId(iClient)); } } public void SQL_ConnectClient(Database hDatabase, DBResultSet hResults, const char[] sError, any iUserID) { if(sError[0]) { LogError("SQL_ConnectClient: %s", sError); return; } int iClient = GetClientOfUserId(iUserID); if(iClient) { if(hResults.FetchRow()) { g_iClientID[iClient] = hResults.FetchInt(0); g_iClientTime[iClient] = hResults.FetchInt(1); char szQuery[256], szName[MAX_NAME_LENGTH*2+1]; GetClientName(iClient, szQuery, MAX_NAME_LENGTH); g_hDatabase.Escape(szQuery, szName, sizeof(szName)); FormatEx(szQuery, sizeof(szQuery), "UPDATE `ws_info` SET `name` = '%s' WHERE `id` = %i;", szName, g_iClientID[iClient]); g_hDatabase.Query(SQL_CheckError, szQuery); } } } public void SQL_CreateClient(Database hDatabase, DBResultSet results, const char[] szError, any iUserID) { if(szError[0]) { LogError("SQL_CreateClient: %s", szError); return; } int iClient = GetClientOfUserId(iUserID); if(iClient) { g_iClientID[iClient] = results.InsertId; } } public void Dell_Client(char[] szAuth) { char szQuery[256]; FormatEx(szQuery, sizeof(szQuery), "DELETE FROM `ws_info` WHERE `auth` = '%s';", szAuth); g_hDatabase.Query(SQL_CheckError, szQuery); } public void Create_Client(iClient) { char szQuery[256], szAuth[32]; GetClientAuthId(iClient, AuthId_Engine, szAuth, sizeof(szAuth), true); FormatEx(szQuery, sizeof(szQuery), "INSERT INTO `ws_info` (`auth`, `time`) VALUES ( '%s', %i);", szAuth, g_fGiveTime[iClient]); g_hDatabase.Query(SQL_CreateClient, szQuery, GetClientUserId(iClient)); } public void OnClientDisconnect(iClient) { char szQuery[256]; FormatEx(szQuery, sizeof(szQuery), "UPDATE `ws_info` SET `time` = %i WHERE `id` = %i;", g_iClientTime[iClient] - g_iTime[iClient], g_iClientID[iClient]); g_hDatabase.Query(SQL_CheckError, szQuery); }