/* * Copyright (C) 2012-2017 CypherCore * * 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 3 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 . */ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; namespace Framework.Dynamic { public class TaskScheduler { public TaskScheduler() { _now = DateTime.Now; _task_holder = new TaskQueue(); _asyncHolder = new List(); _predicate = EmptyValidator; } public TaskScheduler(predicate_t predicate) { _now = DateTime.Now; _task_holder = new TaskQueue(); _asyncHolder = new List(); _predicate = predicate; } /// /// Clears the validator which is asked if tasks are allowed to be executed. /// /// TaskScheduler ClearValidator() { _predicate = EmptyValidator; return this; } /// /// Sets a validator which is asked if tasks are allowed to be executed. /// /// /// public TaskScheduler SetValidator(predicate_t predicate) { _predicate = predicate; return this; } /// /// Update the scheduler to the current time. /// Calls the optional callback on successfully finish. /// /// public TaskScheduler Update(success_t callback = null) { _now = DateTime.Now; Dispatch(callback); return this; } /// /// Update the scheduler with a difftime in ms. /// Calls the optional callback on successfully finish. /// /// /// public TaskScheduler Update(uint milliseconds, success_t callback = null) { return Update(TimeSpan.FromMilliseconds(milliseconds), callback); } /// /// Update the scheduler with a difftime. /// Calls the optional callback on successfully finish. /// /// /// TaskScheduler Update(TimeSpan difftime, success_t callback = null) { _now += difftime; Dispatch(callback); return this; } public TaskScheduler Async(Action callable) { _asyncHolder.Add(callable); return this; } /// /// Schedule an event with a fixed rate. /// Never call this from within a task context! Use TaskContext.Schedule instead! /// /// /// /// public TaskScheduler Schedule(TimeSpan time, Action 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! /// /// /// /// /// public TaskScheduler Schedule(TimeSpan time, uint group, Action 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! /// /// /// /// /// public TaskScheduler Schedule(TimeSpan min, TimeSpan max, Action 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! /// /// /// /// /// /// public TaskScheduler Schedule(TimeSpan min, TimeSpan max, uint group, Action task) { return Schedule(RandomDurationBetween(min, max), group, task); } public TaskScheduler CancelAll() { /// Clear the task holder _task_holder.Clear(); _asyncHolder.Clear(); return this; } public TaskScheduler CancelGroup(uint group) { _task_holder.RemoveIf(task => { return task.IsInGroup(group); }); return this; } public TaskScheduler CancelGroupsOf(List groups) { groups.ForEach(group => CancelGroup(group)); return this; } /// /// Delays all tasks with the given duration. /// /// /// public TaskScheduler DelayAll(TimeSpan duration) { _task_holder.ModifyIf(task => { task._end += duration; return true; }); return this; } /// /// Delays all tasks with a random duration between min and max. /// /// /// /// public TaskScheduler DelayAll(TimeSpan min, TimeSpan max) { return DelayAll(RandomDurationBetween(min, max)); } /// /// Delays all tasks of a group with the given duration. /// /// /// /// public TaskScheduler DelayGroup(uint group, TimeSpan duration) { _task_holder.ModifyIf(task => { 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. /// /// /// /// /// public TaskScheduler DelayGroup(uint group, TimeSpan min, TimeSpan max) { return DelayGroup(group, RandomDurationBetween(min, max)); } /// /// Reschedule all tasks with a given duration. /// /// /// public TaskScheduler RescheduleAll(TimeSpan duration) { var end = _now + duration; _task_holder.ModifyIf(task => { task._end = end; return true; }); return this; } /// /// Reschedule all tasks with a random duration between min and max. /// /// /// /// public TaskScheduler RescheduleAll(TimeSpan min, TimeSpan max) { return RescheduleAll(RandomDurationBetween(min, max)); } /// /// Reschedule all tasks of a group with the given duration. /// /// /// /// public TaskScheduler RescheduleGroup(uint group, TimeSpan duration) { var end = _now + duration; _task_holder.ModifyIf(task => { 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. /// /// /// /// /// public TaskScheduler RescheduleGroup(uint group, TimeSpan min, TimeSpan max) { return RescheduleGroup(group, RandomDurationBetween(min, max)); } internal TaskScheduler InsertTask(Task task) { _task_holder.Push(task); return this; } internal TaskScheduler ScheduleAt(DateTime end, TimeSpan time, Action task) { return InsertTask(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! /// /// /// /// /// /// internal TaskScheduler ScheduleAt(DateTime end, TimeSpan time, uint group, Action task) { return InsertTask(new Task(end + time, time, group, 0, task)); } /// /// Returns a random duration between min and max /// /// /// /// TimeSpan public static TimeSpan RandomDurationBetween(TimeSpan min, TimeSpan max) { var milli_min = min.TotalMilliseconds; var milli_max = max.TotalMilliseconds; // TC specific: use SFMT URandom return TimeSpan.FromMilliseconds(RandomHelper.URand(milli_min, milli_max)); } void Dispatch(success_t callback = null) { // If the validation failed abort the dispatching here. if (!_predicate()) return; // Process all asyncs while (!_asyncHolder.Empty()) { _asyncHolder.First().Invoke(); _asyncHolder.RemoveAt(0); // 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 = new TaskContext(_task_holder.Pop(), this); // Invoke the context context.Invoke(); // If the validation failed abort the dispatching here. if (!_predicate()) return; } callback?.Invoke(); } // The current time point (now) DateTime _now; // The Task Queue which contains all task objects. TaskQueue _task_holder; // Contains all asynchronous tasks which will be invoked at // the next update tick. List _asyncHolder; predicate_t _predicate; static bool EmptyValidator() { return true; } // Predicate type public delegate bool predicate_t(); // Success handle type public delegate void success_t(); } public class Task : IComparable { public Task(DateTime end, TimeSpan duration, uint group, uint repeated, Action task) { _end = end; _duration = duration; _group.Set(group); _repeated = repeated; _task = task; } public Task(DateTime end, TimeSpan duration, Action task) { _end = end; _duration = duration; _task = task; } public int CompareTo(Task other) { return _end.CompareTo(other._end); } /// /// Returns true if the task is in the given group /// /// /// public bool IsInGroup(uint group) { return _group.HasValue && _group.Value == group; } internal DateTime _end; internal TimeSpan _duration; internal Optional _group; internal uint _repeated; internal Action _task; } class TaskQueue { /// /// Pushes the task in the container /// /// public void Push(Task task) { if (!container.Add(task)) { } } /// /// Pops the task out of the container /// /// public Task Pop() { Task result = container.First(); container.Remove(result); return result; } public Task First() { return container.First(); } public void Clear() { container.Clear(); } public void RemoveIf(Predicate filter) { container.RemoveWhere(filter); } public void ModifyIf(Func filter) { List cache = new List(); container.Where(filter); foreach (var task in container.ToList()) { if (filter(task)) { cache.Add(task); container.Remove(task); } } foreach (var task in cache) container.Add(task); } public bool IsEmpty() { return container.Empty(); } SortedSet container = new SortedSet(); } public class TaskContext { public TaskContext(Task task, TaskScheduler owner) { _task = task; _owner = owner; _consumed = false; } /// /// Dispatches an action safe on the TaskScheduler /// /// /// TaskContext Dispatch(Action apply) { apply(); return this; } TaskContext Dispatch(Func apply) { apply(_owner); return this; } bool IsExpired() { return _owner == null; } /// /// Returns true if the event is in the given group /// /// /// bool IsInGroup(uint group) { return _task.IsInGroup(group); } /// /// Sets the event in the given group /// /// /// TaskContext SetGroup(uint group) { _task._group.Set(group); return this; } /// /// Removes the group from the event /// /// TaskContext ClearGroup() { _task._group.HasValue = false; return this; } /// /// Returns the repeat counter which increases every time the task is repeated. /// /// uint GetRepeatCounter() { return _task._repeated; } /// /// 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(Action callable) { return Dispatch(() => _owner.Async(callable)); } /// /// Cancels all tasks from within the context. /// /// public TaskContext CancelAll() { return Dispatch(() => _owner.CancelAll()); } /// /// Cancel all tasks of a single group from within the context. /// /// /// public TaskContext CancelGroup(uint group) { return Dispatch(() => CancelGroup(group)); } /// /// Cancels all groups in the given std.vector from within the context. /// /// /// public TaskContext CancelGroupsOf(List groups) { return Dispatch(() => CancelGroupsOf(groups)); } /// /// Asserts if the task was consumed already. /// void AssertOnConsumed() { // 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! Contract.Assert(!_consumed, "Bad task logic, task context was consumed already!"); } /// /// Invokes the associated hook of the task. /// public void Invoke() { _task._task(this); } /// /// Repeats the event and sets a new duration. /// This will consume the task context, its not possible to repeat the task again /// from the same task context! /// /// /// public TaskContext Repeat(TimeSpan 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(() => _owner.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! /// /// public TaskContext Repeat() { return Repeat(_task._duration); } /// /// Repeats the event and set a new duration that is randomized between min and max. /// This will consume the task context, its not possible to repeat the task again /// from the same task context! /// /// /// /// public TaskContext Repeat(TimeSpan min, TimeSpan max) { return Repeat(TaskScheduler.RandomDurationBetween(min, max)); } /// /// 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. /// /// /// /// public TaskContext Schedule(TimeSpan time, Action task) { var end = _task._end; return Dispatch(scheduler => { return scheduler.ScheduleAt(end, time, task); }); } public TaskContext Schedule(TimeSpan time, Action task) { return Schedule(time, delegate (TaskContext task1) { 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. /// /// /// /// /// public TaskContext Schedule(TimeSpan time, uint group, Action task) { var end = _task._end; return Dispatch(scheduler => { return scheduler.ScheduleAt(end, time, group, task); }); } public TaskContext Schedule(TimeSpan time, uint group, Action task) { return Schedule(time, group, delegate (TaskContext task1) { 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. /// /// /// /// /// public TaskContext Schedule(TimeSpan min, TimeSpan max, Action task) { return Schedule(TaskScheduler.RandomDurationBetween(min, max), task); } public TaskContext Schedule(TimeSpan min, TimeSpan max, Action task) { return Schedule(min, max, delegate (TaskContext task1) { 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. /// /// /// /// /// /// public TaskContext Schedule(TimeSpan min, TimeSpan max, uint group, Action task) { return Schedule(TaskScheduler.RandomDurationBetween(min, max), group, task); } public TaskContext Schedule(TimeSpan min, TimeSpan max, uint group, Action task) { return Schedule(min, max, group, delegate (TaskContext task1) { task(); }); } /// /// Delays all tasks with the given duration from within the context. /// /// /// public TaskContext DelayAll(TimeSpan duration) { return Dispatch(() => _owner.DelayAll(duration)); } /// /// Delays all tasks with a random duration between min and max from within the context. /// /// /// /// public TaskContext DelayAll(TimeSpan min, TimeSpan max) { return DelayAll(TaskScheduler.RandomDurationBetween(min, max)); } /// /// Delays all tasks of a group with the given duration from within the context. /// /// /// /// public TaskContext DelayGroup(uint group, TimeSpan duration) { return Dispatch(() => _owner.DelayGroup(group, duration)); } /// /// Delays all tasks of a group with a random duration between min and max from within the context. /// /// /// /// /// public TaskContext DelayGroup(uint group, TimeSpan min, TimeSpan max) { return DelayGroup(group, TaskScheduler.RandomDurationBetween(min, max)); } /// /// Reschedule all tasks with the given duration. /// /// /// public TaskContext RescheduleAll(TimeSpan duration) { return Dispatch(() => _owner.RescheduleAll(duration)); } /// /// Reschedule all tasks with a random duration between min and max. /// /// /// /// public TaskContext RescheduleAll(TimeSpan min, TimeSpan max) { return RescheduleAll(TaskScheduler.RandomDurationBetween(min, max)); } /// /// Reschedule all tasks of a group with the given duration. /// /// /// /// public TaskContext RescheduleGroup(uint group, TimeSpan duration) { return Dispatch(() => _owner.RescheduleGroup(group, duration)); } /// /// Reschedule all tasks of a group with a random duration between min and max. /// /// /// /// /// public TaskContext RescheduleGroup(uint group, TimeSpan min, TimeSpan max) { return RescheduleGroup(group, TaskScheduler.RandomDurationBetween(min, max)); } // Associated task Task _task; // Owner TaskScheduler _owner; // Marks the task as consumed bool _consumed = true; } }