Nekro
Терра инкогнита
- Сообщения
- 4,169
- Реакции
- 2,500
Здравствуйте, товарищи!
Есть следующий пример кода таймера
В данном варианте редко, но может возникать ошибка удаления уже невалидного handle, если в массиве осталась устаревшая ссылка на таймер.
Предположение такое, что иногда события выхода игрока, а так же форвард OnClientDisconnect - могут не успевать его поймать и игрок выходит раньше, чем форвард успевает сработать. В результате по userid уже нельзя получить валидный индекс игрока.
А callback таймера в таком случае получает уже невалидный index == 0
Следовательно обнулить переменную таймера мы не можем
hTimerPlayer[client] = null;
Так как отсутствует index вышедшего игрока.
Есть несколько вариантов решения:
1) А если пойти во все тяжкие и передавать не UserId игрока, а сразу его index?
Тогда мы даже при его выходе совершенно точно будем знать index и сможем обнулить переменную
Так же есть функция IsValidHandle · handles · SourceMod Scripting API Reference вопрос на сколько она рабочая..
В нашем чате (кстати заходим SourcePawn ) товарищи с сообщества Nebraska и Vit_ amin поделились двумя следующими идеями подходов к таймерам
2) Вот вариант на фреймах
3) Вот вариант с ДатаПаком
4) Так же можно модифицировать первый вариант и не выбирать index или UserId, а передавать через датапак сразу и то и то
Подведём итоги:
1) Стандартный пример
Классический вариант с CreateTimer и хранением Handle в массиве.
Рабочий вариант, но в редких случаях может приводить к ошибкам удаления невалидного handle,
если в массиве осталась устаревшая ссылка на таймер.
2) Передача client index вместо userid
Позволяет всегда знать индекс слота и корректно обнулить переменную таймера
даже если игрок уже вышел.
Однако не защищает от ситуации, когда слот игрока будет переиспользован
новым подключением.
3) Таймер через OnGameFrame
Реализуется без Handle и CreateTimer.
Таймер хранит только время окончания и проверяется каждый кадр.
Подходит для простых локальных таймеров на игроков и полностью исключает
проблемы с удалением handle.
4) Вариант через DataPack и поколения (generation)
Позволяет безопасно использовать CreateTimer, защищая:
- от смены игрока в том же слоте
- от устаревших таймеров того же игрока
Старые таймеры при этом не удаляются, а просто игнорируются в callback.
5) Комбинированный вариант
Можно передавать через DataPack сразу:
- client index
- userid
и дополнительно использовать проверку валидности handle через
IsValidHandle().
Такой подход даёт дополнительную защиту и более гибкий контроль над таймерами.
Есть следующий пример кода таймера
example_timer:
Handle hTimerPlayer[MAXPLAYERS+1];
public void OnPluginStart()
{
HookEvent("player_death", Event_PlayerDeath, EventHookMode_Post);
}
public void OnClientDisconnect(int client)
{
if(hTimerPlayer[client])
delete hTimerPlayer[client];
}
void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
int client = GetClientOfUserId(event.GetInt("userid"));
if(hTimerPlayer[client])
delete hTimerPlayer[client];
hTimerPlayer[client] = CreateTimer(1.5, Timer_Callback, GetClientUserId(client));
}
public Action Timer_Callback(Handle timer, int UserId)
{
int client = GetClientOfUserId(UserId);
if (IsValidClient(client))
{
hTimerPlayer[client] = null;
}
return Plugin_Stop;
}
bool IsValidClient(int client)
{
return 0 < client <= MaxClients && IsClientInGame(client);
}
C-подобный:
[SM] Exception reported: Handle 44a00880 is invalid (error 3)
[SM] Blaming: example_timer.smx
[SM] Call stack trace:
[SM] [0] CloseHandle
А callback таймера в таком случае получает уже невалидный index == 0
Следовательно обнулить переменную таймера мы не можем
hTimerPlayer[client] = null;
Так как отсутствует index вышедшего игрока.
int client = GetClientOfUserId(UserId);Есть несколько вариантов решения:
1) А если пойти во все тяжкие и передавать не UserId игрока, а сразу его index?
Тогда мы даже при его выходе совершенно точно будем знать index и сможем обнулить переменную
example_timer_2:
Handle hTimerPlayer[MAXPLAYERS+1];
public void OnPluginStart()
{
HookEvent("player_death", Event_PlayerDeath, EventHookMode_Post);
}
public void OnClientDisconnect(int client)
{
if(hTimerPlayer[client])
delete hTimerPlayer[client];
}
void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
int client = GetClientOfUserId(event.GetInt("userid"));
if(hTimerPlayer[client])
delete hTimerPlayer[client];
hTimerPlayer[client] = CreateTimer(1.5, Timer_Callback, client);
}
public Action Timer_Callback(Handle timer, int client)
{
if (IsValidIndex(client))
{
hTimerPlayer[client] = null;
if(IsValidClient(client))
{
// Шаманство над валидным игроком
}
}
return Plugin_Stop;
}
bool IsValidClient(int client)
{
return 0 < client <= MaxClients && IsClientInGame(client);
}
bool IsValidIndex(int client)
{
return 0 < client <= MaxClients;
}
Так же есть функция IsValidHandle · handles · SourceMod Scripting API Reference вопрос на сколько она рабочая..
В нашем чате (кстати заходим SourcePawn ) товарищи с сообщества Nebraska и Vit_ amin поделились двумя следующими идеями подходов к таймерам
2) Вот вариант на фреймах
example_timer_3:
/*
** Альтернативный вариант таймера #1
*/
enum struct PlayerTimer
{
bool active;
float endTime;
}
PlayerTimer g_TimerPlayer[MAXPLAYERS + 1];
// Старт таймера
void StartPlayerTimer(int client, float seconds)
{
g_TimerPlayer[client].active = true;
g_TimerPlayer[client].endTime = GetGameTime() + seconds;
}
// Сброс таймера
void ResetPlayerTimer(int client)
{
g_TimerPlayer[client].active = false;
g_TimerPlayer[client].endTime = 0.0;
}
public void OnPluginStart()
{
HookEvent("player_death", Event_PlayerDeath, EventHookMode_Post);
}
public void OnClientDisconnect(int client)
{
ResetPlayerTimer(client);
}
void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
int client = GetClientOfUserId(event.GetInt("userid"));
if (!IsValidClient(client))
return;
StartPlayerTimer(client, 1.5);
}
public void OnGameFrame()
{
float gameTime = GetGameTime();
for (int client = 1; client <= MaxClients; client++)
{
if (!g_TimerPlayer[client].active)
continue;
if (gameTime < g_TimerPlayer[client].endTime)
continue;
ResetPlayerTimer(client);
Timer_PlayerCallback(client);
}
}
void Timer_PlayerCallback(int client)
{
if (!IsValidClient(client))
return;
// Тут логика после истечения таймера
}
bool IsValidClient(int client)
{
return 0 < client <= MaxClients && IsClientInGame(client);
}
3) Вот вариант с ДатаПаком
example_timer_4:
/*
** Альтернативный вариант таймера #2
*/
int g_PlayerGen[MAXPLAYERS + 1];
int g_TimerGen[MAXPLAYERS + 1];
public void OnClientConnected(int client)
{
g_PlayerGen[client]++;
g_TimerGen[client] = 0;
}
public void OnClientDisconnect(int client)
{
g_PlayerGen[client]++;
g_TimerGen[client] = 0;
}
void StartPlayerTimer(int client, float seconds)
{
DataPack dp = new DataPack();
g_TimerGen[client]++;
dp.WriteCell(client);
dp.WriteCell(g_PlayerGen[client]);
dp.WriteCell(g_TimerGen[client]);
CreateTimer(seconds, Timer_PlayerCallback, dp);
}
public Action Timer_PlayerCallback(Handle timer, DataPack dp)
{
dp.Reset();
int client = dp.ReadCell();
int playerGen = dp.ReadCell();
int timerGen = dp.ReadCell();
delete dp;
if (!IsValidIndex(client))
return Plugin_Stop;
if (g_PlayerGen[client] != playerGen)
return Plugin_Stop;
if (g_TimerGen[client] != timerGen)
return Plugin_Stop;
if (!IsValidClient(client))
return Plugin_Stop;
// Актуальный таймер
PrintToServer("Таймер отработал для %N", client);
return Plugin_Stop;
}
bool IsValidClient(int client)
{
return 0 < client <= MaxClients && IsClientInGame(client);
}
bool IsValidIndex(int client)
{
return 0 < client <= MaxClients;
}
4) Так же можно модифицировать первый вариант и не выбирать index или UserId, а передавать через датапак сразу и то и то
Подведём итоги:
1) Стандартный пример
Классический вариант с CreateTimer и хранением Handle в массиве.
Рабочий вариант, но в редких случаях может приводить к ошибкам удаления невалидного handle,
если в массиве осталась устаревшая ссылка на таймер.
2) Передача client index вместо userid
Позволяет всегда знать индекс слота и корректно обнулить переменную таймера
даже если игрок уже вышел.
Однако не защищает от ситуации, когда слот игрока будет переиспользован
новым подключением.
3) Таймер через OnGameFrame
Реализуется без Handle и CreateTimer.
Таймер хранит только время окончания и проверяется каждый кадр.
Подходит для простых локальных таймеров на игроков и полностью исключает
проблемы с удалением handle.
4) Вариант через DataPack и поколения (generation)
Позволяет безопасно использовать CreateTimer, защищая:
- от смены игрока в том же слоте
- от устаревших таймеров того же игрока
Старые таймеры при этом не удаляются, а просто игнорируются в callback.
5) Комбинированный вариант
Можно передавать через DataPack сразу:
- client index
- userid
и дополнительно использовать проверку валидности handle через
IsValidHandle().
Такой подход даёт дополнительную защиту и более гибкий контроль над таймерами.