Ported .Net Core commits:

hondacrx:
- Initial commit: Switch to .Net Core 2.0
- Fix build and removed not needed files
Fabi:
- Updated solution platforms.
- Changed folder structure.
- Change library target framework to netstandard2.0.
- Updated solution platforms again...
- Removed windows specific kernel32 function usage (Ctrl-C handler).
This commit is contained in:
Fabian
2017-10-26 17:23:44 +02:00
parent 227702e19c
commit a3dc7b3f48
844 changed files with 26064 additions and 1824 deletions
+399
View File
@@ -0,0 +1,399 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Framework.Dynamic
{
public class EventMap
{
/// <summary>
/// Removes all scheduled events and resets time and phase.
/// </summary>
public void Reset()
{
_eventMap.Clear();
_time = 0;
_phase = 0;
}
/// <summary>
/// Updates the timer of the event map.
/// </summary>
/// <param name="time">Value in ms to be added to time.</param>
public void Update(uint time)
{
_time += time;
}
/// <summary>
///
/// </summary>
/// <returns>Current timer in ms value.</returns>
uint GetTimer()
{
return _time;
}
/// <summary>
///
/// </summary>
/// <returns>Active phases as mask.</returns>
byte GetPhaseMask()
{
return _phase;
}
/// <summary>
///
/// </summary>
/// <returns>True, if there are no events scheduled.</returns>
public bool Empty()
{
return _eventMap.Empty();
}
/// <summary>
/// Sets the phase of the map (absolute).
/// </summary>
/// <param name="phase">Phase which should be set. Values: 1 - 8. 0 resets phase.</param>
public void SetPhase(byte phase)
{
if (phase == 0)
_phase = 0;
else if (phase <= 8)
_phase = (byte)(1 << (phase - 1));
}
/// <summary>
/// Activates the given phase (bitwise).
/// </summary>
/// <param name="phase">Phase which should be activated. Values: 1 - 8</param>
void AddPhase(byte phase)
{
if (phase != 0 && phase <= 8)
_phase |= (byte)(1 << (phase - 1));
}
/// <summary>
/// Deactivates the given phase (bitwise).
/// </summary>
/// <param name="phase">Phase which should be deactivated. Values: 1 - 8.</param>
void RemovePhase(byte phase)
{
if (phase != 0 && phase <= 8)
_phase &= (byte)~(1 << (phase - 1));
}
/// <summary>
/// Creates new event entry in map.
/// </summary>
/// <param name="eventId">The id of the new event.</param>
/// <param name="time">The time in milliseconds as TimeSpan until the event occurs.</param>
/// <param name="group">The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group.</param>
/// <param name="phase">The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases.</param>
public void ScheduleEvent(uint eventId, TimeSpan time, uint group = 0, byte phase = 0)
{
ScheduleEvent(eventId, (uint)time.TotalMilliseconds, group, phase);
}
/// <summary>
/// Creates new event entry in map.
/// </summary>
/// <param name="eventId">The id of the new event.</param>
/// <param name="minTime">The minimum time until the event occurs as TimeSpan type.</param>
/// <param name="maxTime">The maximum time until the event occurs as TimeSpan type.</param>
/// <param name="group">The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group.</param>
/// <param name="phase">The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases.</param>
public void ScheduleEvent(uint eventId, TimeSpan minTime, TimeSpan maxTime, uint group = 0, byte phase = 0)
{
ScheduleEvent(eventId, RandomHelper.URand(minTime.TotalMilliseconds, maxTime.TotalMilliseconds), group, phase);
}
/// <summary>
/// Creates new event entry in map.
/// </summary>
/// <param name="eventId">The id of the new event.</param>
/// <param name="time">The time in milliseconds until the event occurs.</param>
/// <param name="group">The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group.</param>
/// <param name="phase">The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases.</param>
public void ScheduleEvent(uint eventId, uint time, uint group = 0, byte phase = 0)
{
if (group != 0 && group <= 8)
eventId |= (uint)(1 << ((int)group + 15));
if (phase != 0 && phase <= 8)
eventId |= (uint)(1 << (phase + 23));
_eventMap.Add(_time + time, eventId);
}
/// <summary>
/// Cancels the given event and reschedules it.
/// </summary>
/// <param name="eventId">The id of the event.</param>
/// <param name="time">The time in milliseconds as TimeSpan until the event occurs.</param>
/// <param name="group">The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group.</param>
/// <param name="phase">The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases.</param>
public void RescheduleEvent(uint eventId, TimeSpan time, uint group = 0, byte phase = 0)
{
RescheduleEvent(eventId, (uint)time.TotalMilliseconds, group, phase);
}
/// <summary>
/// Cancels the given event and reschedules it.
/// </summary>
/// <param name="eventId">The id of the event.</param>
/// <param name="minTime">The minimum time until the event occurs as TimeSpan type.</param>
/// <param name="maxTime">The maximum time until the event occurs as TimeSpan type.</param>
/// <param name="group">The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group.</param>
/// <param name="phase">The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases.</param>
void RescheduleEvent(uint eventId, TimeSpan minTime, TimeSpan maxTime, uint group = 0, byte phase = 0)
{
RescheduleEvent(eventId, RandomHelper.URand(minTime.TotalMilliseconds, maxTime.TotalMilliseconds), group, phase);
}
/// <summary>
/// Cancels the given event and reschedules it.
/// </summary>
/// <param name="eventId">The id of the event.</param>
/// <param name="time">The time in milliseconds until the event occurs.</param>
/// <param name="group">The group which the event is associated to. Has to be between 1 and 8. 0 means it has no group.</param>
/// <param name="phase">The phase in which the event can occur. Has to be between 1 and 8. 0 means it can occur in all phases.</param>
public void RescheduleEvent(uint eventId, uint time, uint group = 0, byte phase = 0)
{
CancelEvent(eventId);
ScheduleEvent(eventId, time, group, phase);
}
/// <summary>
/// Repeats the mostly recently executed event.
/// </summary>
/// <param name="time">Time until in ms as TimeSpan the event occurs</param>
public void Repeat(TimeSpan time)
{
Repeat((uint)time.TotalMilliseconds);
}
/// <summary>
/// Repeats the mostly recently executed event.
/// </summary>
/// <param name="time">Time until the event occurs</param>
public void Repeat(uint time)
{
_eventMap.Add(_time + time, _lastEvent);
}
/// <summary>
/// Repeats the mostly recently executed event. Equivalent to Repeat(urand(minTime, maxTime)
/// </summary>
/// <param name="minTime">Min Time as TimeSpan until the event occurs.</param>
/// <param name="maxTime">Max Time as TimeSpan until the event occurs.</param>
public void Repeat(TimeSpan minTime, TimeSpan maxTime)
{
Repeat((uint)minTime.TotalMilliseconds, (uint)maxTime.TotalMilliseconds);
}
/// <summary>
/// Repeats the mostly recently executed event. Equivalent to Repeat(urand(minTime, maxTime)
/// </summary>
/// <param name="minTime">Min Time until the event occurs.</param>
/// <param name="maxTime">Max Time until the event occurs.</param>
public void Repeat(uint minTime, uint maxTime)
{
Repeat(RandomHelper.URand(minTime, maxTime));
}
/// <summary>
/// Returns the next event to execute and removes it from map.
/// </summary>
/// <returns>Id of the event to execute.</returns>
///
public uint ExecuteEvent()
{
while (!Empty())
{
var pair = _eventMap.FirstOrDefault();
if (pair.Key > _time)
return 0;
else if (_phase != 0 && Convert.ToBoolean(pair.Value & 0xFF000000) && !Convert.ToBoolean((pair.Value >> 24) & _phase))
_eventMap.Remove(pair);
else
{
uint eventId = (pair.Value & 0x0000FFFF);
_lastEvent = pair.Value; // include phase/group
_eventMap.Remove(pair);
return eventId;
}
}
return 0;
}
public void ExecuteEvents(Action<uint> action)
{
uint id;
while ((id = ExecuteEvent()) != 0)
action(id);
}
/// <summary>
/// Delays all events in the map. If delay is greater than or equal internal timer, delay will be 0.
/// </summary>
/// <param name="delay">Amount of delay in ms as TimeSpan.</param>
public void DelayEvents(TimeSpan delay)
{
DelayEvents((uint)delay.TotalMilliseconds);
}
/// <summary>
/// Delays all events in the map. If delay is greater than or equal internal timer, delay will be 0.
/// </summary>
/// <param name="delay">Amount of delay.</param>
public void DelayEvents(uint delay)
{
_time = delay < _time ? _time - delay : 0;
}
/// <summary>
/// Delay all events of the same group.
/// </summary>
/// <param name="delay">Amount of delay.</param>
/// <param name="group">Group of the events.</param>
public void DelayEvents(uint delay, uint group)
{
if (group == 0 || group > 8 || Empty())
return;
MultiMap<uint, uint> delayed = new MultiMap<uint, uint>();
foreach (var pair in _eventMap.KeyValueList)
{
if (Convert.ToBoolean(pair.Value & (1 << (int)(group + 15))))
{
delayed.Add(pair.Key + delay, pair.Value);
_eventMap.Remove(pair.Key, pair.Value);
}
}
foreach (var del in delayed)
_eventMap.Add(del);
}
/// <summary>
/// Cancels all events of the specified id.
/// </summary>
/// <param name="eventId">Event id to cancel.</param>
public void CancelEvent(uint eventId)
{
if (Empty())
return;
foreach (var pair in _eventMap.KeyValueList)
{
if (eventId == (pair.Value & 0x0000FFFF))
_eventMap.Remove(pair.Key, pair.Value);
}
}
/// <summary>
/// Cancel events belonging to specified group.
/// </summary>
/// <param name="group">Group to cancel.</param>
void CancelEventGroup(uint group)
{
if (group == 0 || group > 8 || Empty())
return;
foreach (var pair in _eventMap.KeyValueList)
{
if (Convert.ToBoolean(pair.Value & (uint)(1 << ((int)group + 15))))
_eventMap.Remove(pair.Key, pair.Value);
}
}
/// <summary>
/// Returns closest occurence of specified event.
/// </summary>
/// <param name="eventId">Wanted event id.</param>
/// <returns>Time of found event.</returns>
uint GetNextEventTime(uint eventId)
{
if (Empty())
return 0;
foreach (var pair in _eventMap.KeyValueList)
if (eventId == (pair.Value & 0x0000FFFF))
return pair.Key;
return 0;
}
/// <summary>
///
/// </summary>
/// <returns>Time of next event.</returns>
uint GetNextEventTime()
{
return Empty() ? 0 : _eventMap[0][0];
}
/// <summary>
/// Returns time in milliseconds until next event.
/// </summary>
/// <param name="eventId">Id of the event.</param>
/// <returns>Time of next event.</returns>
public uint GetTimeUntilEvent(uint eventId)
{
foreach (var pair in _eventMap)
if (eventId == (pair.Value & 0x0000FFFF))
return pair.Key - _time;
return uint.MaxValue;
}
/// <summary>
/// Returns wether event map is in specified phase or not.
/// </summary>
/// <param name="phase">Wanted phase.</param>
/// <returns>True, if phase of event map contains specified phase.</returns>
public bool IsInPhase(byte phase)
{
return phase <= 8 && (phase == 0 || Convert.ToBoolean(_phase & (1 << (phase - 1))));
}
/// <summary>
/// Internal timer.
/// This does not represent the real date/time value.
/// It's more like a stopwatch: It can run, it can be stopped,
/// it can be resetted and so on. Events occur when this timer
/// has reached their time value. Its value is changed in the Update method.
/// </summary>
uint _time;
/// <summary>
/// Phase mask of the event map.
/// Contains the phases the event map is in. Multiple
/// phases from 1 to 8 can be set with SetPhase or
/// AddPhase. RemovePhase deactives a phase.
/// </summary>
byte _phase;
/// <summary>
/// Stores information on the most recently executed event
/// </summary>
uint _lastEvent;
/// <summary>
/// Key: Time as uint when the event should occur.
/// Value: The event data as uint.
///
/// Structure of event data:
/// - Bit 0 - 15: Event Id.
/// - Bit 16 - 23: Group
/// - Bit 24 - 31: Phase
/// - Pattern: 0xPPGGEEEE
/// </summary>
SortedMultiMap<uint, uint> _eventMap = new SortedMultiMap<uint, uint>();
}
}
+149
View File
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2012-2017 CypherCore <http://github.com/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 <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
namespace Framework.Dynamic
{
public class EventSystem
{
public EventSystem()
{
m_time = 0;
}
public void Update(uint p_time)
{
// update time
m_time += p_time;
// main event loop
KeyValuePair<ulong, BasicEvent> i;
while ((i = m_events.FirstOrDefault()).Value != null && i.Key <= m_time)
{
var Event = i.Value;
m_events.Remove(i);
if (Event.IsRunning())
{
Event.Execute(m_time, p_time);
continue;
}
if (Event.IsAbortScheduled())
{
Event.Abort(m_time);
// Mark the event as aborted
Event.SetAborted();
}
if (Event.IsDeletable())
continue;
// Reschedule non deletable events to be checked at
// the next update tick
AddEvent(Event, CalculateTime(1), false);
}
}
public void KillAllEvents(bool force)
{
foreach (var pair in m_events.KeyValueList)
{
// Abort events which weren't aborted already
if (!pair.Value.IsAborted())
{
pair.Value.SetAborted();
pair.Value.Abort(m_time);
}
// Skip non-deletable events when we are
// not forcing the event cancellation.
if (!force && !pair.Value.IsDeletable())
continue;
if (!force)
m_events.Remove(pair);
}
// fast clear event list (in force case)
if (force)
m_events.Clear();
}
public void AddEvent(BasicEvent Event, ulong e_time, bool set_addtime = true)
{
if (set_addtime)
Event.m_addTime = m_time;
Event.m_execTime = e_time;
m_events.Add(e_time, Event);
}
public ulong CalculateTime(ulong t_offset)
{
return (m_time + t_offset);
}
ulong m_time;
SortedMultiMap<ulong, BasicEvent> m_events = new SortedMultiMap<ulong, BasicEvent>();
}
public class BasicEvent
{
public BasicEvent() { m_abortState = AbortState.Running; }
public void ScheduleAbort()
{
Contract.Assert(IsRunning(), "Tried to scheduled the abortion of an event twice!");
m_abortState = AbortState.Scheduled;
}
public void SetAborted()
{
Contract.Assert(!IsAborted(), "Tried to abort an already aborted event!");
m_abortState = AbortState.Aborted;
}
// this method executes when the event is triggered
// return false if event does not want to be deleted
// e_time is execution time, p_time is update interval
public virtual bool Execute(ulong e_time, uint p_time) { return true; }
public virtual bool IsDeletable() { return true; } // this event can be safely deleted
public virtual void Abort(ulong e_time) { } // this method executes when the event is aborted
public bool IsRunning() { return m_abortState == AbortState.Running; }
public bool IsAbortScheduled() { return m_abortState == AbortState.Scheduled; }
public bool IsAborted() { return m_abortState == AbortState.Aborted; }
AbortState m_abortState; // set by externals when the event is aborted, aborted events don't execute
public ulong m_addTime; // time when the event was added to queue, filled by event handler
public ulong m_execTime; // planned time of next execution, filled by event handler
}
enum AbortState
{
Running,
Scheduled,
Aborted
}
}
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright (C) 2012-2017 CypherCore <http://github.com/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 <http://www.gnu.org/licenses/>.
*/
using System;
namespace Framework.Dynamic
{
public class FlagArray128
{
public FlagArray128(params uint[] parts)
{
_values = new uint[4];
for (var i = 0; i < parts.Length; ++i)
_values[i] = parts[i];
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public bool IsEqual(params uint[] parts)
{
for (var i = 0; i < _values.Length; ++i)
if (_values[i] == parts[i])
return false;
return true;
}
public void Set(params uint[] parts)
{
for (var i = 0; i < parts.Length; ++i)
_values[i] = parts[i];
}
public static bool operator <(FlagArray128 left, FlagArray128 right)
{
for (var i = left._values.Length; i > 0; --i)
{
if (left._values[i - 1] < right._values[i - 1])
return true;
else if (left._values[i - 1] > right._values[i - 1])
return false;
}
return false;
}
public static bool operator >(FlagArray128 left, FlagArray128 right)
{
for (var i = left._values.Length; i > 0; --i)
{
if (left._values[i - 1] > right._values[i - 1])
return true;
else if (left._values[i - 1] < right._values[i - 1])
return false;
}
return false;
}
public static FlagArray128 operator &(FlagArray128 left, FlagArray128 right)
{
FlagArray128 fl = new FlagArray128();
for (var i = 0; i < left._values.Length; ++i)
fl[i] = left._values[i] & right._values[i];
return fl;
}
public static FlagArray128 operator |(FlagArray128 left, FlagArray128 right)
{
FlagArray128 fl = new FlagArray128();
for (var i = 0; i < left._values.Length; ++i)
fl[i] = left._values[i] | right._values[i];
return fl;
}
public static FlagArray128 operator ^(FlagArray128 left, FlagArray128 right)
{
FlagArray128 fl = new FlagArray128();
for (var i = 0; i < left._values.Length; ++i)
fl[i] = left._values[i] ^ right._values[i];
return fl;
}
public static implicit operator bool (FlagArray128 left)
{
for (var i = 0; i < left._values.Length; ++i)
if (left._values[i] != 0)
return true;
return false;
}
public uint this[int i]
{
get
{
return _values[i];
}
set
{
_values[i] = value;
}
}
uint[] _values { get; set; }
}
public class FlaggedArray<T> where T : struct
{
int[] m_values;
uint m_flags;
public FlaggedArray(byte arraysize)
{
m_values = new int[4 * arraysize];
}
public uint GetFlags() { return m_flags; }
public bool HasFlag(T flag) { return Convert.ToBoolean(Convert.ToInt32(m_flags) & 1 << Convert.ToInt32(flag)); }
public void AddFlag(T flag) { m_flags |= (uint)(1 << Convert.ToInt32(flag)); }
public void DelFlag(T flag) { m_flags &= ~(uint)(1 << Convert.ToInt32(flag)); }
public int GetValue(T flag) { return m_values[Convert.ToInt32(flag)]; }
public void SetValue(T flag, object value) { m_values[Convert.ToInt32(flag)] = Convert.ToInt32(value); }
public void AddValue(T flag, object value) { m_values[Convert.ToInt32(flag)] += Convert.ToInt32(value); }
}
}
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2012-2017 CypherCore <http://github.com/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 <http://www.gnu.org/licenses/>.
*/
namespace Framework.Dynamic
{
public struct Optional<T> where T : new()
{
private bool _hasValue;
public T Value;
public bool HasValue
{
get { return _hasValue; }
set
{
_hasValue = value;
Value = _hasValue ? new T() : default(T);
}
}
public void Set(T v)
{
_hasValue = true;
Value = v;
}
public void Clear()
{
_hasValue = false;
Value = default(T);
}
}
}
+102
View File
@@ -0,0 +1,102 @@
/*
* Copyright (C) 2012-2017 CypherCore <http://github.com/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 <http://www.gnu.org/licenses/>.
*/
using Framework.Collections;
using System.Diagnostics.Contracts;
namespace Framework.Dynamic
{
public class Reference<TO, FROM> : LinkedListElement where TO : class where FROM : class
{
TO _RefTo;
FROM _RefFrom;
// Tell our refTo (target) object that we have a link
public virtual void targetObjectBuildLink() { }
// Tell our refTo (taget) object, that the link is cut
public virtual void targetObjectDestroyLink() { }
// Tell our refFrom (source) object, that the link is cut (Target destroyed)
public virtual void sourceObjectDestroyLink() { }
public Reference()
{
_RefTo = null; _RefFrom = null;
}
// Create new link
public void link(TO toObj, FROM fromObj)
{
Contract.Assert(fromObj != null); // fromObj MUST not be NULL
if (isValid())
unlink();
if (toObj != null)
{
_RefTo = toObj;
_RefFrom = fromObj;
targetObjectBuildLink();
}
}
// We don't need the reference anymore. Call comes from the refFrom object
// Tell our refTo object, that the link is cut
public void unlink()
{
targetObjectDestroyLink();
delink();
_RefTo = null;
_RefFrom = null;
}
// Link is invalid due to destruction of referenced target object. Call comes from the refTo object
// Tell our refFrom object, that the link is cut
public void invalidate() // the iRefFrom MUST remain!!
{
sourceObjectDestroyLink();
delink();
_RefTo = null;
}
public bool isValid() // Only check the iRefTo
{
return _RefTo != null;
}
public Reference<TO, FROM> next() { return ((Reference<TO, FROM>)GetNextElement()); }
public Reference<TO, FROM> prev() { return ((Reference<TO, FROM>)GetPrevElement()); }
public TO getTarget() { return _RefTo; }
public FROM GetSource() { return _RefFrom; }
}
public class RefManager<TO, FROM> : LinkedListHead where TO : class where FROM : class
{
~RefManager() { clearReferences(); }
public Reference<TO, FROM> getFirst() { return (Reference<TO, FROM>)base.GetFirstElement(); }
public Reference<TO, FROM> getLast() { return (Reference<TO, FROM>)base.GetLastElement(); }
public void clearReferences()
{
Reference<TO, FROM> refe;
while ((refe = getFirst()) != null)
refe.invalidate();
}
}
}
+846
View File
@@ -0,0 +1,846 @@
/*
* Copyright (C) 2012-2017 CypherCore <http://github.com/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 <http://www.gnu.org/licenses/>.
*/
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<Action>();
_predicate = EmptyValidator;
}
public TaskScheduler(predicate_t predicate)
{
_now = DateTime.Now;
_task_holder = new TaskQueue();
_asyncHolder = new List<Action>();
_predicate = predicate;
}
/// <summary>
/// Clears the validator which is asked if tasks are allowed to be executed.
/// </summary>
/// <returns></returns>
TaskScheduler ClearValidator()
{
_predicate = EmptyValidator;
return this;
}
/// <summary>
/// Sets a validator which is asked if tasks are allowed to be executed.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public TaskScheduler SetValidator(predicate_t predicate)
{
_predicate = predicate;
return this;
}
/// <summary>
/// Update the scheduler to the current time.
/// Calls the optional callback on successfully finish.
/// </summary>
/// <returns></returns>
public TaskScheduler Update(success_t callback = null)
{
_now = DateTime.Now;
Dispatch(callback);
return this;
}
/// <summary>
/// Update the scheduler with a difftime in ms.
/// Calls the optional callback on successfully finish.
/// </summary>
/// <param name="milliseconds"></param>
/// <returns></returns>
public TaskScheduler Update(uint milliseconds, success_t callback = null)
{
return Update(TimeSpan.FromMilliseconds(milliseconds), callback);
}
/// <summary>
/// Update the scheduler with a difftime.
/// Calls the optional callback on successfully finish.
/// </summary>
/// <param name="difftime"></param>
/// <returns></returns>
TaskScheduler Update(TimeSpan difftime, success_t callback = null)
{
_now += difftime;
Dispatch(callback);
return this;
}
public TaskScheduler Async(Action callable)
{
_asyncHolder.Add(callable);
return this;
}
/// <summary>
/// Schedule an event with a fixed rate.
/// Never call this from within a task context! Use TaskContext.Schedule instead!
/// </summary>
/// <param name="time"></param>
/// <param name="task"></param>
/// <returns></returns>
public TaskScheduler Schedule(TimeSpan time, Action<TaskContext> task)
{
return ScheduleAt(_now, time, task);
}
/// <summary>
/// Schedule an event with a fixed rate.
/// Never call this from within a task context! Use TaskContext.Schedule instead!
/// </summary>
/// <param name="time"></param>
/// <param name="group"></param>
/// <param name="task"></param>
/// <returns></returns>
public TaskScheduler Schedule(TimeSpan time, uint group, Action<TaskContext> task)
{
return ScheduleAt(_now, time, group, task);
}
/// <summary>
/// Schedule an event with a randomized rate between min and max rate.
/// Never call this from within a task context! Use TaskContext.Schedule instead!
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="task"></param>
/// <returns></returns>
public TaskScheduler Schedule(TimeSpan min, TimeSpan max, Action<TaskContext> task)
{
return Schedule(RandomDurationBetween(min, max), task);
}
/// <summary>
/// Schedule an event with a fixed rate.
/// Never call this from within a task context! Use TaskContext.Schedule instead!
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="group"></param>
/// <param name="task"></param>
/// <returns></returns>
public TaskScheduler Schedule(TimeSpan min, TimeSpan max, uint group, Action<TaskContext> 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<uint> groups)
{
groups.ForEach(group => CancelGroup(group));
return this;
}
/// <summary>
/// Delays all tasks with the given duration.
/// </summary>
/// <param name="duration"></param>
/// <returns></returns>
public TaskScheduler DelayAll(TimeSpan duration)
{
_task_holder.ModifyIf(task =>
{
task._end += duration;
return true;
});
return this;
}
/// <summary>
/// Delays all tasks with a random duration between min and max.
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
public TaskScheduler DelayAll(TimeSpan min, TimeSpan max)
{
return DelayAll(RandomDurationBetween(min, max));
}
/// <summary>
/// Delays all tasks of a group with the given duration.
/// </summary>
/// <param name="group"></param>
/// <param name="duration"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Delays all tasks of a group with a random duration between min and max.
/// </summary>
/// <param name="group"></param>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
public TaskScheduler DelayGroup(uint group, TimeSpan min, TimeSpan max)
{
return DelayGroup(group, RandomDurationBetween(min, max));
}
/// <summary>
/// Reschedule all tasks with a given duration.
/// </summary>
/// <param name="duration"></param>
/// <returns></returns>
public TaskScheduler RescheduleAll(TimeSpan duration)
{
var end = _now + duration;
_task_holder.ModifyIf(task =>
{
task._end = end;
return true;
});
return this;
}
/// <summary>
/// Reschedule all tasks with a random duration between min and max.
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
public TaskScheduler RescheduleAll(TimeSpan min, TimeSpan max)
{
return RescheduleAll(RandomDurationBetween(min, max));
}
/// <summary>
/// Reschedule all tasks of a group with the given duration.
/// </summary>
/// <param name="group"></param>
/// <param name="duration"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Reschedule all tasks of a group with a random duration between min and max.
/// </summary>
/// <param name="group"></param>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
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<TaskContext> task)
{
return InsertTask(new Task(end + time, time, task));
}
/// <summary>
/// Schedule an event with a fixed rate.
/// Never call this from within a task context! Use TaskContext.schedule instead!
/// </summary>
/// <param name="end"></param>
/// <param name="time"></param>
/// <param name="group"></param>
/// <param name="task"></param>
/// <returns></returns>
internal TaskScheduler ScheduleAt(DateTime end, TimeSpan time, uint group, Action<TaskContext> task)
{
return InsertTask(new Task(end + time, time, group, 0, task));
}
/// <summary>
/// Returns a random duration between min and max
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns>TimeSpan</returns>
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<Action> _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<Task>
{
public Task(DateTime end, TimeSpan duration, uint group, uint repeated, Action<TaskContext> task)
{
_end = end;
_duration = duration;
_group.Set(group);
_repeated = repeated;
_task = task;
}
public Task(DateTime end, TimeSpan duration, Action<TaskContext> task)
{
_end = end;
_duration = duration;
_task = task;
}
public int CompareTo(Task other)
{
return _end.CompareTo(other._end);
}
/// <summary>
/// Returns true if the task is in the given group
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
public bool IsInGroup(uint group)
{
return _group.HasValue && _group.Value == group;
}
internal DateTime _end;
internal TimeSpan _duration;
internal Optional<uint> _group;
internal uint _repeated;
internal Action<TaskContext> _task;
}
class TaskQueue
{
/// <summary>
/// Pushes the task in the container
/// </summary>
/// <param name="task"></param>
public void Push(Task task)
{
if (!container.Add(task))
{
}
}
/// <summary>
/// Pops the task out of the container
/// </summary>
/// <returns></returns>
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<Task> filter)
{
container.RemoveWhere(filter);
}
public void ModifyIf(Func<Task, bool> filter)
{
List<Task> cache = new List<Task>();
foreach (var task in container.Where(filter))
{
if (filter(task))
{
cache.Add(task);
container.Remove(task);
}
}
foreach (var task in cache)
container.Add(task);
}
public bool IsEmpty()
{
return container.Empty();
}
SortedSet<Task> container = new SortedSet<Task>();
}
public class TaskContext
{
public TaskContext(Task task, TaskScheduler owner)
{
_task = task;
_owner = owner;
_consumed = false;
}
/// <summary>
/// Dispatches an action safe on the TaskScheduler
/// </summary>
/// <param name="apply"></param>
/// <returns></returns>
TaskContext Dispatch(Action apply)
{
apply();
return this;
}
TaskContext Dispatch(Func<TaskScheduler, TaskScheduler> apply)
{
apply(_owner);
return this;
}
bool IsExpired()
{
return _owner == null;
}
/// <summary>
/// Returns true if the event is in the given group
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
bool IsInGroup(uint group)
{
return _task.IsInGroup(group);
}
/// <summary>
/// Sets the event in the given group
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
TaskContext SetGroup(uint group)
{
_task._group.Set(group);
return this;
}
/// <summary>
/// Removes the group from the event
/// </summary>
/// <returns></returns>
TaskContext ClearGroup()
{
_task._group.HasValue = false;
return this;
}
/// <summary>
/// Returns the repeat counter which increases every time the task is repeated.
/// </summary>
/// <returns></returns>
uint GetRepeatCounter()
{
return _task._repeated;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="callable"></param>
/// <returns></returns>
TaskContext Async(Action callable)
{
return Dispatch(() => _owner.Async(callable));
}
/// <summary>
/// Cancels all tasks from within the context.
/// </summary>
/// <returns></returns>
public TaskContext CancelAll()
{
return Dispatch(() => _owner.CancelAll());
}
/// <summary>
/// Cancel all tasks of a single group from within the context.
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
public TaskContext CancelGroup(uint group)
{
return Dispatch(() => CancelGroup(group));
}
/// <summary>
/// Cancels all groups in the given std.vector from within the context.
/// </summary>
/// <param name="groups"></param>
/// <returns></returns>
public TaskContext CancelGroupsOf(List<uint> groups)
{
return Dispatch(() => CancelGroupsOf(groups));
}
/// <summary>
/// Asserts if the task was consumed already.
/// </summary>
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!");
}
/// <summary>
/// Invokes the associated hook of the task.
/// </summary>
public void Invoke()
{
_task._task(this);
}
/// <summary>
/// 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!
/// </summary>
/// <param name="duration"></param>
/// <returns></returns>
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));
}
/// <summary>
/// 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!
/// </summary>
/// <returns></returns>
public TaskContext Repeat()
{
return Repeat(_task._duration);
}
/// <summary>
/// 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!
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
public TaskContext Repeat(TimeSpan min, TimeSpan max)
{
return Repeat(TaskScheduler.RandomDurationBetween(min, max));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="time"></param>
/// <param name="task"></param>
/// <returns></returns>
public TaskContext Schedule(TimeSpan time, Action<TaskContext> 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(); }); }
/// <summary>
/// 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.
/// </summary>
/// <param name="time"></param>
/// <param name="group"></param>
/// <param name="task"></param>
/// <returns></returns>
public TaskContext Schedule(TimeSpan time, uint group, Action<TaskContext> 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(); }); }
/// <summary>
/// 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.
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="task"></param>
/// <returns></returns>
public TaskContext Schedule(TimeSpan min, TimeSpan max, Action<TaskContext> 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(); }); }
/// <summary>
/// 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.
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="group"></param>
/// <param name="task"></param>
/// <returns></returns>
public TaskContext Schedule(TimeSpan min, TimeSpan max, uint group, Action<TaskContext> 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(); }); }
/// <summary>
/// Delays all tasks with the given duration from within the context.
/// </summary>
/// <param name="duration"></param>
/// <returns></returns>
public TaskContext DelayAll(TimeSpan duration)
{
return Dispatch(() => _owner.DelayAll(duration));
}
/// <summary>
/// Delays all tasks with a random duration between min and max from within the context.
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
public TaskContext DelayAll(TimeSpan min, TimeSpan max)
{
return DelayAll(TaskScheduler.RandomDurationBetween(min, max));
}
/// <summary>
/// Delays all tasks of a group with the given duration from within the context.
/// </summary>
/// <param name="group"></param>
/// <param name="duration"></param>
/// <returns></returns>
public TaskContext DelayGroup(uint group, TimeSpan duration)
{
return Dispatch(() => _owner.DelayGroup(group, duration));
}
/// <summary>
/// Delays all tasks of a group with a random duration between min and max from within the context.
/// </summary>
/// <param name="group"></param>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
public TaskContext DelayGroup(uint group, TimeSpan min, TimeSpan max)
{
return DelayGroup(group, TaskScheduler.RandomDurationBetween(min, max));
}
/// <summary>
/// Reschedule all tasks with the given duration.
/// </summary>
/// <param name="duration"></param>
/// <returns></returns>
public TaskContext RescheduleAll(TimeSpan duration)
{
return Dispatch(() => _owner.RescheduleAll(duration));
}
/// <summary>
/// Reschedule all tasks with a random duration between min and max.
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
public TaskContext RescheduleAll(TimeSpan min, TimeSpan max)
{
return RescheduleAll(TaskScheduler.RandomDurationBetween(min, max));
}
/// <summary>
/// Reschedule all tasks of a group with the given duration.
/// </summary>
/// <param name="group"></param>
/// <param name="duration"></param>
/// <returns></returns>
public TaskContext RescheduleGroup(uint group, TimeSpan duration)
{
return Dispatch(() => _owner.RescheduleGroup(group, duration));
}
/// <summary>
/// Reschedule all tasks of a group with a random duration between min and max.
/// </summary>
/// <param name="group"></param>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
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;
}
}