Дилемма таймера

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
Здравствуйте форумчане, подскажите пожалуйста на счёт удаления таймера.

delete hTimer[client] - по идеи проверяет хендл на 0, приравнивает к null и удаляет. Но следующих тестовый код:

C++:
Handle
    hTimer[MAXPLAYERS];
    
public void OnClientConnected(int client)
{
    if(IsFakeClient(client))
        return;
        
    //hTimer[client] = null;
    delete hTimer[client];
    hTimer[client] = CreateTimer(2.0, CheckTimer, GetClientUserId(client));
}

public Action CheckTimer(Handle timer, any UserID)
{
    int client = GetClientOfUserId(UserID);
    CheckClient(client);
    
    //hTimer[client] = null;
    delete hTimer[client];
}

stock void CheckClient(int client)
{
    PrintToChatAll("Приветствуем Вас [%N] !", client);
}
Выдаёт следующие ошибки, если не убрать комментарии, если же раскомментировать приравнение к null, то ошибок не будет и код будет отрабатывать как надо.

C++:
L 02/12/2022 - 18:16:28: [SM] Plugin "testTimer.smx" encountered error 23: Native detected error
L 02/12/2022 - 18:16:28: [SM] Invalid timer handle f7b00e9 (error 3) during timer end, displayed function is timer callback, not the stack trace
L 02/12/2022 - 18:16:28: [SM] Unable to call function "CheckTimer" due to above error(s).

Так как правильно удалить таймер?
 

CTe6eJIeK_vRn

Участник
Сообщения
2,114
Реакции
1,867
Возможно покажется полезным:

 

xstage

🏹
Сообщения
726
Реакции
754
C-подобный:
Handle
    hTimer[MAXPLAYERS];
    
public void OnClientConnected(int client)
{
    if(IsFakeClient(client))
        return;
        
    delete hTimer[client];
    hTimer[client] = CreateTimer(2.0, CheckTimer, GetClientUserId(client));
}

public Action CheckTimer(Handle timer, any UserID)
{
    int client = GetClientOfUserId(UserID);
    CheckClient(client);
    
    hTimer[client] = null;
}

stock void CheckClient(int client)
{
    PrintToChatAll("Приветствуем Вас [%N] !", client);
}
 

DENFER

Пишу плагины за два биг тести и картошку фри..
Сообщения
260
Реакции
289
C-подобный:
Handle
    hTimer[MAXPLAYERS];
 
public void OnClientConnected(int client)
{
    if(IsFakeClient(client))
        return;
     
    delete hTimer[client];
    hTimer[client] = CreateTimer(2.0, CheckTimer, GetClientUserId(client));
}

public Action CheckTimer(Handle timer, any UserID)
{
    int client = GetClientOfUserId(UserID);
    CheckClient(client);
 
    hTimer[client] = null;
}

stock void CheckClient(int client)
{
    PrintToChatAll("Приветствуем Вас [%N] !", client);
}
Не лучший вариант, так как почему-то не учитываете тот случай, когда игрок подключится и сразу же отсоединится от сервера, а в данном случае - это ничто иное, как Client index ... is invalid.

Что касается конкретного вопроса, попробую ответить по-своему.
Пожалуй, начну с того, что оператор delete, действительно, обнуляет (приравнивает к null) и удаляет (освобождает выделенную память), но не проверяет переменную.
Во-вторых, если объяснять на пальцах, то данная ошибка с таймером возникает при завершение самого таймера, следовательно нужно копать в том направление, а где же "заканчивается" таймер? Думаю, очевидно в вызове его колбека, но в ошибке отчетливо написано, что колбек не был вызван, тогда вопрос, почему?
Начнем с истоков, в 11 строчке ты создаешь таймер, который записывает в переменную hTimer[client] - хендл (ссылку на сам таймер), таймер в свою очередь знает, что ты его запустил и знает свое место (ссылку на свое происхождение), после чего, через определенный промежуток времени, в данном случае 2 секунды, происходит вызов хендлера (обработчика aka колбека) таймера и дальше все просто, когда оператор delete используется непосредственно в теле колбека таймера и указывает на его хендл, помимо того, что он обнуляет переменную хендла, так еще и освобождает память от работы самого таймера, что и приводе к его невыполнению.
Отвечая на главный вопрос, а как правильно удалять таймер?
- Использовать оператор delete, тогда он и удалит таймер и обнулит переменную.
- Использовать функцию CloseHandle() и в дальнейшем приравнять к null - это тоже самое, что и KillTimer(), только убийство таймера имеет еще один дополнительный параметр и не забыть приравнять к null.

Зачем приравнивать к null?
- Затем, чтобы показать, что данная переменная уже не содержит адрес таймера, это некий фокус для проверки - если переменная содержит адрес - значит таймер еще работает, именно поэтому переменную таймера приравнивает к null, когда завершается колбек.

* Удаление таймера должно происходить непосредственно в момент до вызова его колбека, следовательно мы должны удалить таймер перед созданием самого таймера - такое бывает, когда мы хотим создать новый таймер, прикрыв предыдущий.

P.S. А вообще есть чудесная тема-статья от @Dragokas, советую почитать.
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
@xstage, кстати, должно быть hTimer[MAXPLAYERS+1] (ага, последний игрок на полном сервере нам без надодобности), delete в твоём коде не требуется (лучше всего его прописать в OnClientDisconnect(), которого у тебя нет).
Ну и в таймере неплохо бы проверять валидность индекса игрока перед тем, как пытаться что-то писать ему в чат (у тебя в каллбэке таймера индекс может оказаться равен нулю).
Сообщения автоматически склеены:

То есть вот так лучше всего, имхо:
C-подобный:
Handle
    hTimer[MAXPLAYERS+1];

public void OnClientConnected(int client)
{
    if(!IsFakeClient(client)) hTimer[client] = CreateTimer(2.0, CheckTimer, GetClientUserId(client));
}

public void OnClientDisconnect(int client)
{
    if(hTimer[client]) delete hTimer[client];
}

public Action CheckTimer(Handle timer, int client)
{
    if((client = GetClientOfUserId(client)))
    {
        CheckClient(client);
        hTimer[client] = null;
    }
}

stock void CheckClient(int client)
{
    PrintToChatAll("Приветствуем Вас, [%N]!", client);
}
Сообщения автоматически склеены:

Еще можно убивать таймеры при смене карты, но игроки в это время переподключаются и всё равно сработает OnClientDisconnect(), емнип.
 
Последнее редактирование:

xstage

🏹
Сообщения
726
Реакции
754
@xstage, кстати, должно быть hTimer[MAXPLAYERS+1] (ага, последний игрок на полном сервере нам без надодобности), delete в твоём коде не требуется (лучше всего его прописать в OnClientDisconnect(), которого у тебя нет).
Ну и в таймере неплохо бы проверять валидность индекса игрока перед тем, как пытаться что-то писать ему в чат (у тебя в каллбэке таймера индекс может оказаться равен нулю).
Сообщения автоматически склеены:

То есть вот так лучше всего, имхо:
C-подобный:
Handle
    hTimer[MAXPLAYERS+1];

public void OnClientConnected(int client)
{
    if(!IsFakeClient(client)) hTimer[client] = CreateTimer(2.0, CheckTimer, GetClientUserId(client));
}

public void OnClientDisconnect(int client)
{
    if(hTimer[client]) delete hTimer[client];
}

public Action CheckTimer(Handle timer, int client)
{
    if((client = GetClientOfUserId(client)))
    {
        CheckClient(client);
        hTimer[client] = null;
    }
}

stock void CheckClient(int client)
{
    PrintToChatAll("Приветствуем Вас, [%N]!", client);
}
Сообщения автоматически склеены:

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