对于一个复杂的大型系统来说,将逻辑划分成更小的流程环节一般来说是更好的实践,如此一来,就涉及到如何对这些 流程 进行调度和协作的问题
流程 Process
首先我们要定义 流程, entt::process 的用法是 CRTP 模版的典型用法
它支持覆盖以下方法
void update(Delta, void *);
该方法会每个tick调用一次,直到process明确中止或成功执行, 其中void *表示用户数据指针void init();
当 流程 加入 调度器 的运行队列时,本方法会被调用一次, 这通常 发生在 流程 被附加到调度器并且 流程 是 顶层流程 时, 如果 它是 子流程, 则将在其取代 父流程 时发生调用void succeeded();
此方法会在 流程 成功执行后被调用void failed();
此方法会在 流程 执行失败后被调用void aborted();
此方法在 流程 被显式中止时被调用, 它可以被 调度器 的abort()触发
流程 本身也可以通过直接调用上述方法来改变自身状态, 除了上面的
方法之外, 还有 pause 和 unpause 方法
struct my_process: entt::process<my_process, std::uint32_t> {
using delta_type = std::uint32_t;
my_process(delta_type delay)
: remaining{delay}
{}
void update(delta_type delta, void *) {
remaining -= std::min(remaining, delta);
// ...
if(!remaining) {
succeed();
}
}
private:
delta_type remaining;
};也可以使用 lambda 和 函子来作为 流程, 它们本身不被支持, 但是 它们被注册到 调度器 时, 会自动使用适配器, 这要求它们必须有以下签名
void(Delta delta, void *data, auto succeed, auto fail);delta是经过的时间data是自定义数据指针succeed是进程执行成功之后进行的回调fail是进程执行失败之后进行的回调
注意 succeed 和 fail 都不接受任何参数
调度器 Scheduler
调度器会在每个 tick 调用所有注册的 流程, 如果 流程 被终结
则会从调度器中移除, 并不会在以后的 tick 中进行调用
entt::basic_scheduler<std::uint64_t> scheduler;
// 默认 delta type 是 std::uint32_t
entt::scheduler scheduler;流程 可以拥有子流程, 在这种情况下只有当 父流程 执行成功以后, 子流程 才会进入调度器被执行, 如果 父流程 执行失败, 那么它和 它的 子流程 都会被丢弃, 这可以用于构造流程执行链
调度器拥有常见容器类似的方法
// checks if there are processes still running
const auto empty = scheduler.empty();
// gets the number of processes still running
entt::scheduler::size_type size = scheduler.size();
// resets the scheduler to its initial state and discards all the processes
scheduler.clear();就像上面介绍 流程 所说的, 有两种附加方式
// 继承方式, 提供自定义数据
scheduler.attach<my_process>(1000u);
// lambda 方式, 会在内部自动使用适配器
scheduler.attach([](auto...){ /* ... */ });不管使用哪种方式进行流程注册, 调度器都会返回自身, 这让我们可以使用
then 方法进行链式任务的注册
// schedules a task in the form of a lambda function
scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
// ...
})
// appends a child in the form of another lambda function
.then([](auto delta, void *, auto succeed, auto fail) {
// ...
})
// appends a child in the form of a process class
.then<my_process>(1000u);使用 update 方法对所有在 调度器 中注册的 流程 进行执行
// updates all the processes, no user data are provided
scheduler.update(delta);
// updates all the processes and provides them with custom data
scheduler.update(delta, &data);使用 abort 方法立即或在下一个 tick 丢弃所有正在运行的 流程
// aborts all the processes abruptly ...
scheduler.abort(true);
// ... or gracefully during the next tick
scheduler.abort();