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 <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:
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:
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:
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 <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.