Система унифицированного планировщика задач в Pawn

Активный
Статус
Сообщения
516
Лайки
32

8

месяц на сайте

Когда пригодится
Администраторам приходится запускать десятки периодических процессов: автосейв игроков, напоминания об ивентах, обновление текстдравов. Когда каждый процесс живёт на собственном SetTimer, обслуживать их тяжело. Планировщик объединяет задачи в единый список и даёт ручной контроль.

Структура расписания
Код:
enum TaskInfo
{
    TaskName[32],
    TaskInterval,      // миллисекунды
    TaskElapsed,       // накопитель
    TaskEnabled,
    TaskRepeat,
    TaskHandler[32]
}

new Scheduler[64][TaskInfo];
new SchedulerCount;

Регистрация
Код:
AddTask(const name[], interval, repeat, const handler[])
{
    if(SchedulerCount >= sizeof(Scheduler))
        return -1;

    new idx = SchedulerCount++;
    format(Scheduler[idx][TaskName], 32, "%s", name);
    Scheduler[idx][TaskInterval] = interval;
    Scheduler[idx][TaskRepeat] = repeat;
    Scheduler[idx][TaskHandler] = handler;
    Scheduler[idx][TaskElapsed] = 0;
    Scheduler[idx][TaskEnabled] = 1;
    return idx;
}

Сердце планировщика
Глобальный тикер раз в секунду проверяет накопившееся время и вызывает обработчики.
Код:
forward SchedulerTick();
public SchedulerTick()
{
    for(new i = 0; i < SchedulerCount; i++)
    {
        if(!Scheduler[i][TaskEnabled])
            continue;

        Scheduler[i][TaskElapsed] += 1000;
        if(Scheduler[i][TaskElapsed] >= Scheduler[i][TaskInterval])
        {
            Scheduler[i][TaskElapsed] = 0;
            CallLocalFunction(Scheduler[i][TaskHandler], "i", i);
            if(!Scheduler[i][TaskRepeat])
            {
                Scheduler[i][TaskEnabled] = 0;
            }
        }
    }
    return 1;
}

Инициализация
Код:
public OnGameModeInit()
{
    AddTask("Autosave", 300000, 1, "Task_Autosave");
    AddTask("EventHint", 60000, 1, "Task_EventHint");
    SetTimer("SchedulerTick", 1000, true);
    return 1;
}

Примеры обработчиков
Код:
forward Task_Autosave(taskid);
public Task_Autosave(taskid)
{
    foreach(new i : Player)
    {
        SavePlayerData(i);
    }
    printf("[TASK] %s выполнена", Scheduler[taskid][TaskName]);
    return 1;
}

forward Task_EventHint(taskid);
public Task_EventHint(taskid)
{
    SendClientMessageToAll(-1, "[EVENT] До старта гонки 10 минут!");
    return 1;
}

Команды для админов
* `/tasks` — вывести список задач с интервалами.
* `/taskoff Autosave` — отключить задачу по имени (TaskEnabled = 0).
* `/taskrun EventHint` — принудительно выполнить обработчик.

Логирование и защита
* Добавьте лог в SchedulerTick, чтобы видеть длительность вызовов.
* Для тяжёлых задач ведите флаг Busy, чтобы не запускать их повторно, пока предыдущий запуск не завершён.
* Старайтесь держать обработчики короткими; сложную работу выносите в отдельные функции и запускайте через CallRemoteFunction/Timers.

Расширение
1. Храните задачи в `tasks.json`, чтобы админы могли редактировать расписание без перекомпиляции.
2. Добавьте поле `TaskArgs`, где держите JSON-строку с параметрами (например, ID события).
3. Реализуйте приоритеты: сначала выполняйте задачи с коротким интервалом, затем остальные.

Проверка
* Создайте тестовую задачу, которая каждые 10 секунд пишет текущую дату в чат.
* Отключите её через `/taskoff`, убедитесь, что сообщения пропали.
* Сделайте одноразовую задачу (repeat = 0), чтобы проверить, что она исчезает после выполнения.
Система унифицированного планировщика задач в Pawn

Когда пригодится
Администраторам приходится запускать десятки периодических процессов: автосейв игроков, выдачу напоминаний о событиях, обновление текстдравов. Если каждый процесс живёт своим SetTimer, разобраться трудно. Планировщик объединяет все таймеры, даёт ручное управление и журнал.

Базовая структура
Код:
enum TaskInfo
{
    TaskName[32],
    TaskInterval,      // миллисекунды
    TaskElapsed,       // накопитель
    TaskEnabled,
    TaskRepeat,
    TaskHandler[32]
}

new Scheduler[64][TaskInfo];
new SchedulerCount;

Регистрация задач
Код:
AddTask(const name[], interval, repeat, const handler[])
{
    if(SchedulerCount >= sizeof(Scheduler))
        return -1;

    new idx = SchedulerCount++;
    format(Scheduler[idx][TaskName], 32, "%s", name);
    Scheduler[idx][TaskInterval] = interval;
    Scheduler[idx][TaskRepeat] = repeat;
    Scheduler[idx][TaskHandler] = handler;
    Scheduler[idx][TaskElapsed] = 0;
    Scheduler[idx][TaskEnabled] = 1;
    return idx;
}

Сердце планировщика
Глобальный таймер раз в секунду проверяет накопившееся время и вызывает нужные обработчики.
Код:
forward SchedulerTick();
public SchedulerTick()
{
    for(new i = 0; i < SchedulerCount; i++)
    {
        if(!Scheduler[i][TaskEnabled])
            continue;

        Scheduler[i][TaskElapsed] += 1000;
        if(Scheduler[i][TaskElapsed] >= Scheduler[i][TaskInterval])
        {
            Scheduler[i][TaskElapsed] = 0;
            CallLocalFunction(Scheduler[i][TaskHandler], "i", i);
            if(!Scheduler[i][TaskRepeat])
            {
                Scheduler[i][TaskEnabled] = 0;
            }
        }
    }
    return 1;
}

Инициализация
Код:
public OnGameModeInit()
{
    AddTask("Autosave", 300000, 1, "Task_Autosave");
    AddTask("EventHint", 60000, 1, "Task_EventHint");
    SetTimer("SchedulerTick", 1000, true);
    return 1;
}

Пример обработчиков
Код:
forward Task_Autosave(taskid);
public Task_Autosave(taskid)
{
    foreach(new i : Player)
    {
        SavePlayerData(i);
    }
    printf("[TASK] %s завершена", Scheduler[taskid][TaskName]);
    return 1;
}

forward Task_EventHint(taskid);
public Task_EventHint(taskid)
{
    SendClientMessageToAll(-1, "[EVENT] До старта гонки 10 минут!");
    return 1;
}

Команды для админов
* `/tasks` — выводит список активных задач, интервал и время до следующего запуска.
* `/taskoff Autosave` — отключить задачу по имени (просто ставим `TaskEnabled = 0`).
* `/taskrun EventHint` — принудительно выполнить обработчик и сбросить таймер.

Журналирование
Добавьте лог в `SchedulerTick`, чтобы видеть длительность вызовов. Если обработчик выполняется дольше интервала, сделайте флаг “Busy” и пропускайте следующие циклы, пока задача не завершается.

Расширение
1. Загружайте список задач из `tasks.json`, чтобы админы могли править расписание без перекомпиляции.
2. Добавьте тип `TaskArgs`, где храните параметры (например, ID события), и передавайте их в обработчик через `CallLocalFunction`.
3. Введите приоритеты: сначала выполняем срочные задачи (интервал < 1 мин), потом все остальные.

Почему это работает
Единый планировщик делает сервер прозрачным: из лога видно, какие процессы живут, а какие отключены. Если нужно временно заморозить кусок логики, достаточно отключить одну запись, а не охотиться за сотней SetTimer.
 

1 человек читают эту тему (Всего: 1, Пользователей: 0, Гостей: 1)

Сверху