TaskScheduler(P&& predicate)
: self_reference(this, [](TaskScheduler const*) { }), _now(clock_t::now()), _predicate(std::forward(predicate)) { }
TaskScheduler(TaskScheduler const&) = delete;
TaskScheduler(TaskScheduler&&) = delete;
TaskScheduler& operator= (TaskScheduler const&) = delete;
TaskScheduler& operator= (TaskScheduler&&) = delete;
~TaskScheduler() = default;
/// Sets a validator which is asked if tasks are allowed to be executed.
template
TaskScheduler& SetValidator(P&& predicate)
{
_predicate = std::forward(predicate);
return *this;
}
/// Clears the validator which is asked if tasks are allowed to be executed.
TaskScheduler& ClearValidator();
/// Update the scheduler to the current time.
/// Calls the optional callback on successfully finish.
TaskScheduler& Update(success_t const& callback = nullptr);
/// Update the scheduler with a difftime in ms.
/// Calls the optional callback on successfully finish.
TaskScheduler& Update(size_t milliseconds, success_t const& callback = nullptr);
/// Update the scheduler with a difftime.
/// Calls the optional callback on successfully finish.
template
TaskScheduler& Update(std::chrono::duration difftime,
success_t const& callback = nullptr)
{
_now += difftime;
Dispatch(callback);
return *this;
}
/// Schedule an callable function that is executed at the next update tick.
/// Its safe to modify the TaskScheduler from within the callable.
TaskScheduler& Async(std::function callable);
/// Schedule an event with a fixed rate.
/// Never call this from within a task context! Use TaskContext::Schedule instead!
template
TaskScheduler& Schedule(std::chrono::duration time,
task_handler_t task)
{
return this->ScheduleAt(_now, time, std::move(task));
}
/// Schedule an event with a fixed rate.
/// Never call this from within a task context! Use TaskContext::Schedule instead!
template
TaskScheduler& Schedule(std::chrono::duration time,
group_t const group, task_handler_t task)
{
return this->ScheduleAt(_now, time, group, std::move(task));
}
/// Schedule an event with a randomized rate between min and max rate.
/// Never call this from within a task context! Use TaskContext::Schedule instead!
template
TaskScheduler& Schedule(std::chrono::duration min,
std::chrono::duration max, task_handler_t task)
{
return this->Schedule(::randtime(min, max), std::move(task));
}
/// Schedule an event with a fixed rate.
/// Never call this from within a task context! Use TaskContext::Schedule instead!
template
TaskScheduler& Schedule(std::chrono::duration min,
std::chrono::duration max, group_t const group,
task_handler_t task)
{
return this->Schedule(::randtime(min, max), group, std::move(task));
}
/// Cancels all tasks.
/// Never call this from within a task context! Use TaskContext::CancelAll instead!
TaskScheduler& CancelAll();
/// Cancel all tasks of a single group.
/// Never call this from within a task context! Use TaskContext::CancelGroup instead!
TaskScheduler& CancelGroup(group_t group);
/// Cancels all groups in the given std::vector.
/// Hint: Use std::initializer_list for this: "{1, 2, 3, 4}"
TaskScheduler& CancelGroupsOf(std::vector const& groups);
/// Delays all tasks with the given duration.
template
TaskScheduler& DelayAll(std::chrono::duration duration)
{
_task_holder.ModifyIf([&duration](TaskContainer const& task) -> bool
{
task->_end += duration;
return true;
});
return *this;
}
/// Delays all tasks with a random duration between min and max.
template
TaskScheduler& DelayAll(std::chrono::duration min,
std::chrono::duration max)
{
return this->DelayAll(::randtime(min, max));
}
/// Delays all tasks of a group with the given duration.
template
TaskScheduler& DelayGroup(group_t const group, std::chrono::duration duration)
{
_task_holder.ModifyIf([&duration, group](TaskContainer const& task) -> bool
{
if (task->IsInGroup(group))
{
task->_end += duration;
return true;
}
else
return false;
});
return *this;
}
/// Delays all tasks of a group with a random duration between min and max.
template
TaskScheduler& DelayGroup(group_t const group,
std::chrono::duration min,
std::chrono::duration max)
{
return this->DelayGroup(group, ::randtime(min, max));
}
/// Reschedule all tasks with a given duration.
template
TaskScheduler& RescheduleAll(std::chrono::duration duration)
{
auto const end = _now + duration;
_task_holder.ModifyIf([end](TaskContainer const& task) -> bool
{
task->_end = end;
return true;
});
return *this;
}
/// Reschedule all tasks with a random duration between min and max.
template
TaskScheduler& RescheduleAll(std::chrono::duration min,
std::chrono::duration max)
{
return this->RescheduleAll(::randtime(min, max));
}
/// Reschedule all tasks of a group with the given duration.
template
TaskScheduler& RescheduleGroup(group_t const group, std::chrono::duration duration)
{
auto const end = _now + duration;
_task_holder.ModifyIf([end, group](TaskContainer const& task) -> bool
{
if (task->IsInGroup(group))
{
task->_end = end;
return true;
}
else
return false;
});
return *this;
}
/// Reschedule all tasks of a group with a random duration between min and max.
template
TaskScheduler& RescheduleGroup(group_t const group,
std::chrono::duration min,
std::chrono::duration max)
{
return this->RescheduleGroup(group, ::randtime(min, max));
}
private:
/// Insert a new task to the enqueued tasks.
TaskScheduler& InsertTask(TaskContainer task);
template
TaskScheduler& ScheduleAt(timepoint_t end,
std::chrono::duration time, task_handler_t task)
{
return InsertTask(TaskContainer(new Task(end + time, time, std::move(task))));
}
/// Schedule an event with a fixed rate.
/// Never call this from within a task context! Use TaskContext::schedule instead!
template
TaskScheduler& ScheduleAt(timepoint_t end,
std::chrono::duration time,
group_t const group, task_handler_t task)
{
static constexpr repeated_t DEFAULT_REPEATED = 0;
return InsertTask(TaskContainer(new Task(end + time, time, group, DEFAULT_REPEATED, std::move(task))));
}
/// Dispatch remaining tasks
void Dispatch(success_t const& callback);
};
class TC_COMMON_API TaskContext
{
friend class TaskScheduler;
/// Associated task
TaskScheduler::TaskContainer _task;
/// Owner
std::weak_ptr _owner;
/// Marks the task as consumed
std::shared_ptr _consumed;
/// Dispatches an action safe on the TaskScheduler
TaskContext& Dispatch(std::function const& apply);
public:
// Empty constructor
TaskContext()
: _task(), _owner(), _consumed(std::make_shared(true)) { }
// Construct from task and owner
explicit TaskContext(TaskScheduler::TaskContainer&& task, std::weak_ptr&& owner)
: _task(std::move(task)), _owner(std::move(owner)), _consumed(std::make_shared(false)) { }
// Copy construct
TaskContext(TaskContext const& right)
: _task(right._task), _owner(right._owner), _consumed(right._consumed) { }
// Move construct
TaskContext(TaskContext&& right) noexcept
: _task(std::move(right._task)), _owner(std::move(right._owner)), _consumed(std::move(right._consumed)) { }
// Copy assign
TaskContext& operator=(TaskContext const& right)
{
if (this != &right)
{
_task = right._task;
_owner = right._owner;
_consumed = right._consumed;
}
return *this;
}
// Move assign
TaskContext& operator=(TaskContext&& right) noexcept
{
if (this != &right)
{
_task = std::move(right._task);
_owner = std::move(right._owner);
_consumed = std::move(right._consumed);
}
return *this;
}
~TaskContext() = default;
/// Returns true if the owner was deallocated and this context has expired.
bool IsExpired() const;
/// Returns true if the event is in the given group
bool IsInGroup(TaskScheduler::group_t const group) const;
/// Sets the event in the given group
TaskContext& SetGroup(TaskScheduler::group_t const group);
/// Removes the group from the event
TaskContext& ClearGroup();
/// Returns the repeat counter which increases every time the task is repeated.
TaskScheduler::repeated_t GetRepeatCounter() const;
/// Repeats the event and sets a new duration.
/// std::chrono::seconds(5) for example.
/// This will consume the task context, its not possible to repeat the task again
/// from the same task context!
template
TaskContext& Repeat(std::chrono::duration duration)
{
AssertOnConsumed();
// Set new duration, in-context timing and increment repeat counter
_task->_duration = duration;
_task->_end += duration;
_task->_repeated += 1;
(*_consumed) = true;
return this->Dispatch([this](TaskScheduler& scheduler) -> TaskScheduler&
{
return scheduler.InsertTask(_task);
});
}
/// Repeats the event with the same duration.
/// This will consume the task context, its not possible to repeat the task again
/// from the same task context!
TaskContext& Repeat()
{
return Repeat(_task->_duration);
}
/// Repeats the event and set a new duration that is randomized between min and max.
/// std::chrono::seconds(5) for example.
/// This will consume the task context, its not possible to repeat the task again
/// from the same task context!
template
TaskContext& Repeat(std::chrono::duration min,
std::chrono::duration max)
{
return this->Repeat(::randtime(min, max));
}
/// Schedule a callable function that is executed at the next update tick from within the context.
/// Its safe to modify the TaskScheduler from within the callable.
TaskContext& Async(std::function const& callable);
/// Schedule an event with a fixed rate from within the context.
/// Its possible that the new event is executed immediately!
/// Use TaskScheduler::Async to create a task
/// which will be called at the next update tick.
template
TaskContext& Schedule(std::chrono::duration time,
TaskScheduler::task_handler_t task)
{
auto const end = _task->_end;
return this->Dispatch([end, time, task = std::move(task)](TaskScheduler& scheduler) -> TaskScheduler&
{
return scheduler.ScheduleAt(end, time, std::move(task));
});
}
/// Schedule an event with a fixed rate from within the context.
/// Its possible that the new event is executed immediately!
/// Use TaskScheduler::Async to create a task
/// which will be called at the next update tick.
template
TaskContext& Schedule(std::chrono::duration time,
TaskScheduler::group_t const group, TaskScheduler::task_handler_t task)
{
auto const end = _task->_end;
return this->Dispatch([end, time, group, task = std::move(task)](TaskScheduler& scheduler) -> TaskScheduler&
{
return scheduler.ScheduleAt(end, time, group, std::move(task));
});
}
/// Schedule an event with a randomized rate between min and max rate from within the context.
/// Its possible that the new event is executed immediately!
/// Use TaskScheduler::Async to create a task
/// which will be called at the next update tick.
template
TaskContext& Schedule(std::chrono::duration min,
std::chrono::duration max, TaskScheduler::task_handler_t task)
{
return this->Schedule(::randtime(min, max), std::move(task));
}
/// Schedule an event with a randomized rate between min and max rate from within the context.
/// Its possible that the new event is executed immediately!
/// Use TaskScheduler::Async to create a task
/// which will be called at the next update tick.
template
TaskContext& Schedule(std::chrono::duration min,
std::chrono::duration max, TaskScheduler::group_t const group,
TaskScheduler::task_handler_t task)
{
return this->Schedule(::randtime(min, max), group, std::move(task));
}
/// Cancels all tasks from within the context.
TaskContext& CancelAll();
/// Cancel all tasks of a single group from within the context.
TaskContext& CancelGroup(TaskScheduler::group_t const group);
/// Cancels all groups in the given std::vector from within the context.
/// Hint: Use std::initializer_list for this: "{1, 2, 3, 4}"
TaskContext& CancelGroupsOf(std::vector const& groups);
/// Delays all tasks with the given duration from within the context.
template
TaskContext& DelayAll(std::chrono::duration duration)
{
return this->Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
{
return scheduler.DelayAll(duration);
});
}
/// Delays all tasks with a random duration between min and max from within the context.
template
TaskContext& DelayAll(std::chrono::duration min,
std::chrono::duration max)
{
return this->DelayAll(::randtime(min, max));
}
/// Delays all tasks of a group with the given duration from within the context.
template
TaskContext& DelayGroup(TaskScheduler::group_t const group, std::chrono::duration duration)
{
return this->Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
{
return scheduler.DelayGroup(group, duration);
});
}
/// Delays all tasks of a group with a random duration between min and max from within the context.
template
TaskContext& DelayGroup(TaskScheduler::group_t const group,
std::chrono::duration min,
std::chrono::duration max)
{
return this->DelayGroup(group, ::randtime(min, max));
}
/// Reschedule all tasks with the given duration.
template
TaskContext& RescheduleAll(std::chrono::duration duration)
{
return this->Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
{
return scheduler.RescheduleAll(duration);
});
}
/// Reschedule all tasks with a random duration between min and max.
template
TaskContext& RescheduleAll(std::chrono::duration min,
std::chrono::duration max)
{
return this->RescheduleAll(::randtime(min, max));
}
/// Reschedule all tasks of a group with the given duration.
template
TaskContext& RescheduleGroup(TaskScheduler::group_t const group, std::chrono::duration duration)
{
return this->Dispatch([=](TaskScheduler& scheduler) -> TaskScheduler&
{
return scheduler.RescheduleGroup(group, duration);
});
}
/// Reschedule all tasks of a group with a random duration between min and max.
template
TaskContext& RescheduleGroup(TaskScheduler::group_t const group,
std::chrono::duration min,
std::chrono::duration max)
{
return this->RescheduleGroup(group, ::randtime(min, max));
}
private:
/// Asserts if the task was consumed already.
void AssertOnConsumed() const;
/// Invokes the associated hook of the task.
void Invoke();
};
#endif /// TRINITYCORE_TASK_SCHEDULER_H