mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-22 13:16:23 +00:00
feat(Core/Utilities): Introduce a task scheduler which schedules task (#4932)
This commit is contained in:
218
src/common/Utilities/TaskScheduler.cpp
Normal file
218
src/common/Utilities/TaskScheduler.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
|
||||
* Copyright (C) 2008+ TrinityCore <http://www.trinitycore.org/>
|
||||
*/
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Errors.h"
|
||||
|
||||
TaskScheduler& TaskScheduler::ClearValidator()
|
||||
{
|
||||
_predicate = EmptyValidator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskScheduler& TaskScheduler::Update(success_t const& callback)
|
||||
{
|
||||
_now = clock_t::now();
|
||||
Dispatch(callback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskScheduler& TaskScheduler::Update(size_t const milliseconds, success_t const& callback)
|
||||
{
|
||||
return Update(std::chrono::milliseconds(milliseconds), callback);
|
||||
}
|
||||
|
||||
TaskScheduler& TaskScheduler::Async(std::function<void()> const& callable)
|
||||
{
|
||||
_asyncHolder.push(callable);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskScheduler& TaskScheduler::CancelAll()
|
||||
{
|
||||
/// Clear the task holder
|
||||
_task_holder.Clear();
|
||||
_asyncHolder = AsyncHolder();
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskScheduler& TaskScheduler::CancelGroup(group_t const group)
|
||||
{
|
||||
_task_holder.RemoveIf([group](TaskContainer const & task) -> bool
|
||||
{
|
||||
return task->IsInGroup(group);
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskScheduler& TaskScheduler::CancelGroupsOf(std::vector<group_t> const& groups)
|
||||
{
|
||||
std::for_each(groups.begin(), groups.end(),
|
||||
std::bind(&TaskScheduler::CancelGroup, this, std::placeholders::_1));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskScheduler& TaskScheduler::InsertTask(TaskContainer task)
|
||||
{
|
||||
_task_holder.Push(std::move(task));
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TaskScheduler::Dispatch(success_t const& callback)
|
||||
{
|
||||
// If the validation failed abort the dispatching here.
|
||||
if (!_predicate())
|
||||
return;
|
||||
|
||||
// Process all asyncs
|
||||
while (!_asyncHolder.empty())
|
||||
{
|
||||
_asyncHolder.front()();
|
||||
_asyncHolder.pop();
|
||||
|
||||
// If the validation failed abort the dispatching here.
|
||||
if (!_predicate())
|
||||
return;
|
||||
}
|
||||
|
||||
while (!_task_holder.IsEmpty())
|
||||
{
|
||||
if (_task_holder.First()->_end > _now)
|
||||
break;
|
||||
|
||||
// Perfect forward the context to the handler
|
||||
// Use weak references to catch destruction before callbacks.
|
||||
TaskContext context(_task_holder.Pop(), std::weak_ptr<TaskScheduler>(self_reference));
|
||||
|
||||
// Invoke the context
|
||||
context.Invoke();
|
||||
|
||||
// If the validation failed abort the dispatching here.
|
||||
if (!_predicate())
|
||||
return;
|
||||
}
|
||||
|
||||
// On finish call the final callback
|
||||
callback();
|
||||
}
|
||||
|
||||
void TaskScheduler::TaskQueue::Push(TaskContainer&& task)
|
||||
{
|
||||
container.insert(task);
|
||||
}
|
||||
|
||||
auto TaskScheduler::TaskQueue::Pop() -> TaskContainer
|
||||
{
|
||||
TaskContainer result = *container.begin();
|
||||
container.erase(container.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
auto TaskScheduler::TaskQueue::First() const -> TaskContainer const&
|
||||
{
|
||||
return *container.begin();
|
||||
}
|
||||
|
||||
void TaskScheduler::TaskQueue::Clear()
|
||||
{
|
||||
container.clear();
|
||||
}
|
||||
|
||||
void TaskScheduler::TaskQueue::RemoveIf(std::function<bool(TaskContainer const&)> const& filter)
|
||||
{
|
||||
for (auto itr = container.begin(); itr != container.end();)
|
||||
if (filter(*itr))
|
||||
itr = container.erase(itr);
|
||||
else
|
||||
++itr;
|
||||
}
|
||||
|
||||
void TaskScheduler::TaskQueue::ModifyIf(std::function<bool(TaskContainer const&)> const& filter)
|
||||
{
|
||||
std::vector<TaskContainer> cache;
|
||||
for (auto itr = container.begin(); itr != container.end();)
|
||||
if (filter(*itr))
|
||||
{
|
||||
cache.push_back(*itr);
|
||||
itr = container.erase(itr);
|
||||
}
|
||||
else
|
||||
++itr;
|
||||
|
||||
container.insert(cache.begin(), cache.end());
|
||||
}
|
||||
|
||||
bool TaskScheduler::TaskQueue::IsEmpty() const
|
||||
{
|
||||
return container.empty();
|
||||
}
|
||||
|
||||
TaskContext& TaskContext::Dispatch(std::function<TaskScheduler&(TaskScheduler&)> const& apply)
|
||||
{
|
||||
if (auto const owner = _owner.lock())
|
||||
apply(*owner);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool TaskContext::IsExpired() const
|
||||
{
|
||||
return _owner.expired();
|
||||
}
|
||||
|
||||
bool TaskContext::IsInGroup(TaskScheduler::group_t const group) const
|
||||
{
|
||||
return _task->IsInGroup(group);
|
||||
}
|
||||
|
||||
TaskContext& TaskContext::SetGroup(TaskScheduler::group_t const group)
|
||||
{
|
||||
_task->_group = group;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskContext& TaskContext::ClearGroup()
|
||||
{
|
||||
_task->_group = std::nullopt;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskScheduler::repeated_t TaskContext::GetRepeatCounter() const
|
||||
{
|
||||
return _task->_repeated;
|
||||
}
|
||||
|
||||
TaskContext& TaskContext::Async(std::function<void()> const& callable)
|
||||
{
|
||||
return Dispatch(std::bind(&TaskScheduler::Async, std::placeholders::_1, callable));
|
||||
}
|
||||
|
||||
TaskContext& TaskContext::CancelAll()
|
||||
{
|
||||
return Dispatch(std::mem_fn(&TaskScheduler::CancelAll));
|
||||
}
|
||||
|
||||
TaskContext& TaskContext::CancelGroup(TaskScheduler::group_t const group)
|
||||
{
|
||||
return Dispatch(std::bind(&TaskScheduler::CancelGroup, std::placeholders::_1, group));
|
||||
}
|
||||
|
||||
TaskContext& TaskContext::CancelGroupsOf(std::vector<TaskScheduler::group_t> const& groups)
|
||||
{
|
||||
return Dispatch(std::bind(&TaskScheduler::CancelGroupsOf, std::placeholders::_1, std::cref(groups)));
|
||||
}
|
||||
|
||||
void TaskContext::AssertOnConsumed() const
|
||||
{
|
||||
// This was adapted to TC to prevent static analysis tools from complaining.
|
||||
// If you encounter this assertion check if you repeat a TaskContext more then 1 time!
|
||||
ASSERT(!(*_consumed) && "Bad task logic, task context was consumed already!");
|
||||
}
|
||||
|
||||
void TaskContext::Invoke()
|
||||
{
|
||||
_task->_task(*this);
|
||||
}
|
||||
635
src/common/Utilities/TaskScheduler.h
Normal file
635
src/common/Utilities/TaskScheduler.h
Normal file
@@ -0,0 +1,635 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
|
||||
* Copyright (C) 2008+ TrinityCore <http://www.trinitycore.org/>
|
||||
*/
|
||||
|
||||
#ifndef _TASK_SCHEDULER_H_
|
||||
#define _TASK_SCHEDULER_H_
|
||||
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include "Util.h"
|
||||
|
||||
class TaskContext;
|
||||
|
||||
/// The TaskScheduler class provides the ability to schedule std::function's in the near future.
|
||||
/// Use TaskScheduler::Update to update the scheduler.
|
||||
/// Popular methods are:
|
||||
/// * Schedule (Schedules a std::function which will be executed in the near future).
|
||||
/// * Schedules an asynchronous function which will be executed at the next update tick.
|
||||
/// * Cancel, Delay & Reschedule (Methods to manipulate already scheduled tasks).
|
||||
/// Tasks are organized in groups (uint), multiple tasks can have the same group id,
|
||||
/// you can provide a group or not, but keep in mind that you can only manipulate specific tasks through its group id!
|
||||
/// Tasks callbacks use the function signature void(TaskContext) where TaskContext provides
|
||||
/// access to the function schedule plan which makes it possible to repeat the task
|
||||
/// with the same duration or a new one.
|
||||
/// It also provides access to the repeat counter which is useful for task that repeat itself often
|
||||
/// but behave different every time (spoken event dialogs for example).
|
||||
class TaskScheduler
|
||||
{
|
||||
friend class TaskContext;
|
||||
|
||||
// Time definitions (use steady clock)
|
||||
typedef std::chrono::steady_clock clock_t;
|
||||
typedef clock_t::time_point timepoint_t;
|
||||
typedef clock_t::duration duration_t;
|
||||
|
||||
// Task group type
|
||||
typedef uint32 group_t;
|
||||
// Task repeated type
|
||||
typedef uint32 repeated_t;
|
||||
// Task handle type
|
||||
typedef std::function<void(TaskContext)> task_handler_t;
|
||||
// Predicate type
|
||||
typedef std::function<bool()> predicate_t;
|
||||
// Success handle type
|
||||
typedef std::function<void()> success_t;
|
||||
|
||||
class Task
|
||||
{
|
||||
friend class TaskContext;
|
||||
friend class TaskScheduler;
|
||||
|
||||
timepoint_t _end;
|
||||
duration_t _duration;
|
||||
std::optional<group_t> _group;
|
||||
repeated_t _repeated;
|
||||
task_handler_t _task;
|
||||
|
||||
public:
|
||||
// All Argument construct
|
||||
Task(timepoint_t const& end, duration_t const& duration, std::optional<group_t> const& group,
|
||||
repeated_t const repeated, task_handler_t const& task)
|
||||
: _end(end), _duration(duration), _group(group), _repeated(repeated), _task(task) { }
|
||||
|
||||
// Minimal Argument construct
|
||||
Task(timepoint_t const& end, duration_t const& duration, task_handler_t const& task)
|
||||
: _end(end), _duration(duration), _group(std::nullopt), _repeated(0), _task(task) { }
|
||||
|
||||
// Copy construct
|
||||
Task(Task const&) = delete;
|
||||
// Move construct
|
||||
Task(Task&&) = delete;
|
||||
// Copy Assign
|
||||
Task& operator= (Task const&) = default;
|
||||
// Move Assign
|
||||
Task& operator= (Task&& right) = delete;
|
||||
|
||||
// Order tasks by its end
|
||||
inline bool operator< (Task const& other) const
|
||||
{
|
||||
return _end < other._end;
|
||||
}
|
||||
|
||||
inline bool operator> (Task const& other) const
|
||||
{
|
||||
return _end > other._end;
|
||||
}
|
||||
|
||||
// Compare tasks with its end
|
||||
inline bool operator== (Task const& other)
|
||||
{
|
||||
return _end == other._end;
|
||||
}
|
||||
|
||||
// Returns true if the task is in the given group
|
||||
inline bool IsInGroup(group_t const group) const
|
||||
{
|
||||
return _group == group;
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Task> TaskContainer;
|
||||
|
||||
/// Container which provides Task order, insert and reschedule operations.
|
||||
struct Compare
|
||||
{
|
||||
bool operator() (TaskContainer const& left, TaskContainer const& right) const
|
||||
{
|
||||
return (*left.get()) < (*right.get());
|
||||
};
|
||||
};
|
||||
|
||||
class TaskQueue
|
||||
{
|
||||
std::multiset<TaskContainer, Compare> container;
|
||||
|
||||
public:
|
||||
// Pushes the task in the container
|
||||
void Push(TaskContainer&& task);
|
||||
|
||||
/// Pops the task out of the container
|
||||
TaskContainer Pop();
|
||||
|
||||
TaskContainer const& First() const;
|
||||
|
||||
void Clear();
|
||||
|
||||
void RemoveIf(std::function<bool(TaskContainer const&)> const& filter);
|
||||
|
||||
void ModifyIf(std::function<bool(TaskContainer const&)> const& filter);
|
||||
|
||||
bool IsEmpty() const;
|
||||
};
|
||||
|
||||
/// Contains a self reference to track if this object was deleted or not.
|
||||
std::shared_ptr<TaskScheduler> self_reference;
|
||||
|
||||
/// The current time point (now)
|
||||
timepoint_t _now;
|
||||
|
||||
/// The Task Queue which contains all task objects.
|
||||
TaskQueue _task_holder;
|
||||
|
||||
typedef std::queue<std::function<void()>> AsyncHolder;
|
||||
|
||||
/// Contains all asynchronous tasks which will be invoked at
|
||||
/// the next update tick.
|
||||
AsyncHolder _asyncHolder;
|
||||
|
||||
predicate_t _predicate;
|
||||
|
||||
static bool EmptyValidator()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void EmptyCallback()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
TaskScheduler()
|
||||
: self_reference(this, [](TaskScheduler const*) { }), _now(clock_t::now()), _predicate(EmptyValidator) { }
|
||||
|
||||
template<typename P> TaskScheduler(P&& predicate)
|
||||
: self_reference(this, [](TaskScheduler const*) { }), _now(clock_t::now()), _predicate(std::forward<P>(predicate)) { }
|
||||
|
||||
TaskScheduler(TaskScheduler const&) = delete;
|
||||
TaskScheduler(TaskScheduler&&) = delete;
|
||||
TaskScheduler& operator= (TaskScheduler const&) = delete;
|
||||
TaskScheduler& operator= (TaskScheduler&&) = delete;
|
||||
|
||||
/// Sets a validator which is asked if tasks are allowed to be executed.
|
||||
template<typename P>
|
||||
TaskScheduler& SetValidator(P&& predicate)
|
||||
{
|
||||
_predicate = std::forward<P>(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 = EmptyCallback);
|
||||
|
||||
/// Update the scheduler with a difftime in ms.
|
||||
/// Calls the optional callback on successfully finish.
|
||||
TaskScheduler& Update(size_t const milliseconds, success_t const& callback = EmptyCallback);
|
||||
|
||||
/// Update the scheduler with a difftime.
|
||||
/// Calls the optional callback on successfully finish.
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& Update(std::chrono::duration<_Rep, _Period> const& difftime,
|
||||
success_t const& callback = EmptyCallback)
|
||||
{
|
||||
_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<void()> const& callable);
|
||||
|
||||
/// Schedule an event with a fixed rate.
|
||||
/// Never call this from within a task context! Use TaskContext::Schedule instead!
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& Schedule(std::chrono::duration<_Rep, _Period> const& time,
|
||||
task_handler_t const& task)
|
||||
{
|
||||
return ScheduleAt(_now, time, task);
|
||||
}
|
||||
|
||||
/// Schedule an event with a fixed rate.
|
||||
/// Never call this from within a task context! Use TaskContext::Schedule instead!
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& Schedule(std::chrono::duration<_Rep, _Period> const& time,
|
||||
group_t const group, task_handler_t const& task)
|
||||
{
|
||||
return ScheduleAt(_now, time, group, 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<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskScheduler& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max, task_handler_t const& task)
|
||||
{
|
||||
return Schedule(RandomDurationBetween(min, max), task);
|
||||
}
|
||||
|
||||
/// Schedule an event with a fixed rate.
|
||||
/// Never call this from within a task context! Use TaskContext::Schedule instead!
|
||||
template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskScheduler& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max, group_t const group,
|
||||
task_handler_t const& task)
|
||||
{
|
||||
return Schedule(RandomDurationBetween(min, max), group, 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 const group);
|
||||
|
||||
/// Cancels all groups in the given std::vector.
|
||||
/// Hint: Use std::initializer_list for this: "{1, 2, 3, 4}"
|
||||
TaskScheduler& CancelGroupsOf(std::vector<group_t> const& groups);
|
||||
|
||||
/// Delays all tasks with the given duration.
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& DelayAll(std::chrono::duration<_Rep, _Period> const& 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<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskScheduler& DelayAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return DelayAll(RandomDurationBetween(min, max));
|
||||
}
|
||||
|
||||
/// Delays all tasks of a group with the given duration.
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& DelayGroup(group_t const group, std::chrono::duration<_Rep, _Period> const& 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<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskScheduler& DelayGroup(group_t const group,
|
||||
std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return DelayGroup(group, RandomDurationBetween(min, max));
|
||||
}
|
||||
|
||||
/// Reschedule all tasks with a given duration.
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& RescheduleAll(std::chrono::duration<_Rep, _Period> const& 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<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskScheduler& RescheduleAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return RescheduleAll(RandomDurationBetween(min, max));
|
||||
}
|
||||
|
||||
/// Reschedule all tasks of a group with the given duration.
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& RescheduleGroup(group_t const group, std::chrono::duration<_Rep, _Period> const& 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<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskScheduler& RescheduleGroup(group_t const group,
|
||||
std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return RescheduleGroup(group, RandomDurationBetween(min, max));
|
||||
}
|
||||
|
||||
private:
|
||||
/// Insert a new task to the enqueued tasks.
|
||||
TaskScheduler& InsertTask(TaskContainer task);
|
||||
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& ScheduleAt(timepoint_t const& end,
|
||||
std::chrono::duration<_Rep, _Period> const& time, task_handler_t const& task)
|
||||
{
|
||||
return InsertTask(TaskContainer(new Task(end + time, time, task)));
|
||||
}
|
||||
|
||||
/// Schedule an event with a fixed rate.
|
||||
/// Never call this from within a task context! Use TaskContext::schedule instead!
|
||||
template<class _Rep, class _Period>
|
||||
TaskScheduler& ScheduleAt(timepoint_t const& end,
|
||||
std::chrono::duration<_Rep, _Period> const& time,
|
||||
group_t const group, task_handler_t const& task)
|
||||
{
|
||||
static repeated_t const DEFAULT_REPEATED = 0;
|
||||
return InsertTask(TaskContainer(new Task(end + time, time, group, DEFAULT_REPEATED, task)));
|
||||
}
|
||||
|
||||
// Returns a random duration between min and max
|
||||
template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
static std::chrono::milliseconds RandomDurationBetween(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
auto const milli_min = std::chrono::duration_cast<std::chrono::milliseconds>(min);
|
||||
auto const milli_max = std::chrono::duration_cast<std::chrono::milliseconds>(max);
|
||||
|
||||
// TC specific: use SFMT URandom
|
||||
return std::chrono::milliseconds(urand(uint32(milli_min.count()), uint32(milli_max.count())));
|
||||
}
|
||||
|
||||
/// Dispatch remaining tasks
|
||||
void Dispatch(success_t const& callback);
|
||||
};
|
||||
|
||||
class TaskContext
|
||||
{
|
||||
friend class TaskScheduler;
|
||||
|
||||
/// Associated task
|
||||
TaskScheduler::TaskContainer _task;
|
||||
|
||||
/// Owner
|
||||
std::weak_ptr<TaskScheduler> _owner;
|
||||
|
||||
/// Marks the task as consumed
|
||||
std::shared_ptr<bool> _consumed;
|
||||
|
||||
/// Dispatches an action safe on the TaskScheduler
|
||||
TaskContext& Dispatch(std::function<TaskScheduler&(TaskScheduler&)> const& apply);
|
||||
|
||||
public:
|
||||
// Empty constructor
|
||||
TaskContext()
|
||||
: _task(), _owner(), _consumed(std::make_shared<bool>(true)) { }
|
||||
|
||||
// Construct from task and owner
|
||||
explicit TaskContext(TaskScheduler::TaskContainer&& task, std::weak_ptr<TaskScheduler>&& owner)
|
||||
: _task(task), _owner(owner), _consumed(std::make_shared<bool>(false)) { }
|
||||
|
||||
// Copy construct
|
||||
TaskContext(TaskContext const& right)
|
||||
: _task(right._task), _owner(right._owner), _consumed(right._consumed) { }
|
||||
|
||||
// Move construct
|
||||
TaskContext(TaskContext&& right)
|
||||
: _task(std::move(right._task)), _owner(std::move(right._owner)), _consumed(std::move(right._consumed)) { }
|
||||
|
||||
// Copy assign
|
||||
TaskContext& operator= (TaskContext const& right)
|
||||
{
|
||||
_task = right._task;
|
||||
_owner = right._owner;
|
||||
_consumed = right._consumed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Move assign
|
||||
TaskContext& operator= (TaskContext&& right)
|
||||
{
|
||||
_task = std::move(right._task);
|
||||
_owner = std::move(right._owner);
|
||||
_consumed = std::move(right._consumed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// 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<class _Rep, class _Period>
|
||||
TaskContext& Repeat(std::chrono::duration<_Rep, _Period> const& duration)
|
||||
{
|
||||
AssertOnConsumed();
|
||||
|
||||
// Set new duration, in-context timing and increment repeat counter
|
||||
_task->_duration = duration;
|
||||
_task->_end += duration;
|
||||
_task->_repeated += 1;
|
||||
(*_consumed) = true;
|
||||
return Dispatch(std::bind(&TaskScheduler::InsertTask, std::placeholders::_1, _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<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskContext& Repeat(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return Repeat(TaskScheduler::RandomDurationBetween(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<void()> 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<class _Rep, class _Period>
|
||||
TaskContext& Schedule(std::chrono::duration<_Rep, _Period> const& time,
|
||||
TaskScheduler::task_handler_t const& task)
|
||||
{
|
||||
auto const end = _task->_end;
|
||||
return Dispatch([end, time, task](TaskScheduler & scheduler) -> TaskScheduler &
|
||||
{
|
||||
return scheduler.ScheduleAt<_Rep, _Period>(end, time, 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<class _Rep, class _Period>
|
||||
TaskContext& Schedule(std::chrono::duration<_Rep, _Period> const& time,
|
||||
TaskScheduler::group_t const group, TaskScheduler::task_handler_t const& task)
|
||||
{
|
||||
auto const end = _task->_end;
|
||||
return Dispatch([end, time, group, task](TaskScheduler & scheduler) -> TaskScheduler &
|
||||
{
|
||||
return scheduler.ScheduleAt<_Rep, _Period>(end, time, group, 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<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskContext& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max, TaskScheduler::task_handler_t const& task)
|
||||
{
|
||||
return Schedule(TaskScheduler::RandomDurationBetween(min, max), 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<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskContext& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max, TaskScheduler::group_t const group,
|
||||
TaskScheduler::task_handler_t const& task)
|
||||
{
|
||||
return Schedule(TaskScheduler::RandomDurationBetween(min, max), group, 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<TaskScheduler::group_t> const& groups);
|
||||
|
||||
/// Delays all tasks with the given duration from within the context.
|
||||
template<class _Rep, class _Period>
|
||||
TaskContext& DelayAll(std::chrono::duration<_Rep, _Period> const& duration)
|
||||
{
|
||||
return Dispatch(std::bind(&TaskScheduler::DelayAll<_Rep, _Period>, std::placeholders::_1, duration));
|
||||
}
|
||||
|
||||
/// Delays all tasks with a random duration between min and max from within the context.
|
||||
template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskContext& DelayAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return DelayAll(TaskScheduler::RandomDurationBetween(min, max));
|
||||
}
|
||||
|
||||
/// Delays all tasks of a group with the given duration from within the context.
|
||||
template<class _Rep, class _Period>
|
||||
TaskContext& DelayGroup(TaskScheduler::group_t const group, std::chrono::duration<_Rep, _Period> const& duration)
|
||||
{
|
||||
return Dispatch(std::bind(&TaskScheduler::DelayGroup<_Rep, _Period>, std::placeholders::_1, group, duration));
|
||||
}
|
||||
|
||||
/// Delays all tasks of a group with a random duration between min and max from within the context.
|
||||
template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskContext& DelayGroup(TaskScheduler::group_t const group,
|
||||
std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return DelayGroup(group, TaskScheduler::RandomDurationBetween(min, max));
|
||||
}
|
||||
|
||||
/// Reschedule all tasks with the given duration.
|
||||
template<class _Rep, class _Period>
|
||||
TaskContext& RescheduleAll(std::chrono::duration<_Rep, _Period> const& duration)
|
||||
{
|
||||
return Dispatch(std::bind(&TaskScheduler::RescheduleAll, std::placeholders::_1, duration));
|
||||
}
|
||||
|
||||
/// Reschedule all tasks with a random duration between min and max.
|
||||
template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskContext& RescheduleAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return RescheduleAll(TaskScheduler::RandomDurationBetween(min, max));
|
||||
}
|
||||
|
||||
/// Reschedule all tasks of a group with the given duration.
|
||||
template<class _Rep, class _Period>
|
||||
TaskContext& RescheduleGroup(TaskScheduler::group_t const group, std::chrono::duration<_Rep, _Period> const& duration)
|
||||
{
|
||||
return Dispatch(std::bind(&TaskScheduler::RescheduleGroup<_Rep, _Period>, std::placeholders::_1, group, duration));
|
||||
}
|
||||
|
||||
/// Reschedule all tasks of a group with a random duration between min and max.
|
||||
template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
|
||||
TaskContext& RescheduleGroup(TaskScheduler::group_t const group,
|
||||
std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
|
||||
std::chrono::duration<_RepRight, _PeriodRight> const& max)
|
||||
{
|
||||
return RescheduleGroup(group, TaskScheduler::RandomDurationBetween(min, max));
|
||||
}
|
||||
|
||||
private:
|
||||
/// Asserts if the task was consumed already.
|
||||
void AssertOnConsumed() const;
|
||||
|
||||
/// Invokes the associated hook of the task.
|
||||
void Invoke();
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user