/* * Copyright (C) 2008-2016 TrinityCore * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #ifndef _TASK_SCHEDULER_H_ #define _TASK_SCHEDULER_H_ #include #include #include #include #include #include #include #include #include "Util.h" #include "Duration.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 task_handler_t; // Predicate type typedef std::function predicate_t; // Success handle type typedef std::function success_t; class Task { friend class TaskContext; friend class TaskScheduler; timepoint_t _end; duration_t _duration; boost::optional _group; repeated_t _repeated; task_handler_t _task; public: // All Argument construct Task(timepoint_t const& end, duration_t const& duration, boost::optional 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(boost::none), _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 TaskContainer; /// Container which provides Task order, insert and reschedule operations. struct Compare { bool operator() (TaskContainer const& left, TaskContainer const& right) { return (*left.get()) < (*right.get()); }; }; class TaskQueue { std::multiset 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 const& filter); void ModifyIf(std::function const& filter); bool IsEmpty() const; }; /// Contains a self reference to track if this object was deleted or not. std::shared_ptr self_reference; /// The current time point (now) timepoint_t _now; /// The Task Queue which contains all task objects. TaskQueue _task_holder; typedef std::queue> 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 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; /// 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 = 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 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 const& 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<_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 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 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 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 const& groups); /// Delays all tasks with the given duration. template 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 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 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 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 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 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 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 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 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 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 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(min); auto const milli_max = std::chrono::duration_cast(max); // TC specific: use SFMT URandom return std::chrono::milliseconds(urand(milli_min.count(), 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 _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(task), _owner(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) : _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 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 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 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<_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 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 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 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 const& groups); /// Delays all tasks with the given duration from within the context. template 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 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 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 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 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 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 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 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 /// _TASK_SCHEDULER_H_