Scheduling tasks

Run delayed, repeating, and asynchronous tasks with the scheduler.

The scheduler runs work later, or on a repeating interval. Time is measured in ticks - 20 ticks is roughly one second. Reach it through getServer().getScheduler(), and pass your plugin (*this) as the first argument so the task is owned by it.

Run something later

runTaskLater runs a task once, after a delay:

include/my_plugin.h
#include <endstone/endstone.hpp>

namespace es = endstone;

class MyPlugin : public es::Plugin {
public:
    void onEnable() override
    {
        getServer().getScheduler().runTaskLater(*this, [this]() { announce(); }, 100);
    }

    void announce()
    {
        getServer().broadcastMessage("The event starts now!");
    }
};

A delay of 100 ticks waits about five seconds before the single run.

Repeat on an interval

runTaskTimer adds a period, running the task again and again with that gap between runs:

include/my_plugin.h
    void onEnable() override
    {
        getServer().getScheduler().runTaskTimer(*this, [this]() { sayHi(); }, 0, 20);
    }

    void sayHi()
    {
        for (auto &player : getServer().getOnlinePlayers()) {
            player->sendPopup("Hi");
        }
    }

The last two arguments are the initial delay and the repeat period, in ticks. 0, 20 runs the task at once and then once a second.

Stop a task

Every runTask* method returns a std::shared_ptr<Task>. Keep it, and cancel it when you're done:

include/my_plugin.h
class MyPlugin : public es::Plugin {
public:
    void onEnable() override
    {
        task_ = getServer().getScheduler().runTaskTimer(*this, [this]() { sayHi(); }, 0, 20);
    }

    void onDisable() override
    {
        task_->cancel();
    }

private:
    std::shared_ptr<es::Task> task_;
};

You can also cancel by id with getServer().getScheduler().cancelTask(task_->getTaskId()), or drop everything your plugin scheduled at once with cancelTasks(*this).

A self-cancelling countdown

For a task that should stop on its own, hold onto its Task and cancel it from inside the callback once the work is finished:

include/my_plugin.h
class MyPlugin : public es::Plugin {
public:
    void onEnable() override
    {
        countdown_ = 5;
        task_ = getServer().getScheduler().runTaskTimer(*this, [this]() { tick(); }, 0, 20);
    }

    void tick()
    {
        if (countdown_ > 0) {
            getServer().broadcastMessage("Starting in " + std::to_string(countdown_) + "...");
            countdown_--;
        } else {
            getServer().broadcastMessage("Go!");
            task_->cancel();
        }
    }

private:
    int countdown_ = 0;
    std::shared_ptr<es::Task> task_;
};

Asynchronous tasks

Every runTask* method has an *Async twin - runTaskAsync, runTaskLaterAsync, and runTaskTimerAsync - that runs the task on a background thread instead of the main thread. Reach for them when work would otherwise stall a tick: file or network I/O, or heavy computation.

The Endstone API is not thread-safe. An async task must never touch players, worlds, or the server. Do the off-thread work, then schedule a sync task with runTask to apply the result on the main thread - scheduling is the one cross-thread call that's safe.

include/my_plugin.h
#include <endstone/endstone.hpp>

namespace es = endstone;

class MyPlugin : public es::Plugin {
public:
    void onEnable() override
    {
        getServer().getScheduler().runTaskAsync(*this, [this]() {
            int total = countSaves();  // slow disk work - off the main thread, no API here

            // hop back to the main thread before touching the API
            getServer().getScheduler().runTask(*this, [this, total]() {
                getServer().broadcastMessage("Loaded " + std::to_string(total) + " saves.");
            });
        });
    }
};

runTaskLaterAsync and runTaskTimerAsync take the same delay and period arguments as their sync counterparts, so you can, for example, poll a remote endpoint on an interval without ever blocking a tick.

On this page