using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace Coroutine { /// /// An object of this class can be used to start, tick and otherwise manage active s as well as their s. /// Note that a static implementation of this can be found in . /// public class CoroutineHandlerInstance { private readonly List tickingCoroutines = new List(); private readonly Dictionary> eventCoroutines = new Dictionary>(); private readonly HashSet<(Event, ActiveCoroutine)> eventCoroutinesToRemove = new HashSet<(Event, ActiveCoroutine)>(); private readonly HashSet outstandingEventCoroutines = new HashSet(); private readonly HashSet outstandingTickingCoroutines = new HashSet(); private readonly Stopwatch stopwatch = new Stopwatch(); private readonly object lockObject = new object(); /// /// The amount of instances that are currently waiting for a tick (waiting for time to pass) /// public int TickingCount { get { lock (this.lockObject) return this.tickingCoroutines.Count; } } /// /// The amount of instances that are currently waiting for an /// public int EventCount { get { lock (this.lockObject) return this.eventCoroutines.Sum(c => c.Value.Count); } } /// /// Starts the given coroutine, returning a object for management. /// Note that this calls to get the enumerator. /// /// The coroutine to start /// The that this coroutine should have. Defaults to an empty string. /// The that this coroutine should have. The higher the priority, the earlier it is advanced. Defaults to 0. /// An active coroutine object representing this coroutine public ActiveCoroutine Start(IEnumerable coroutine, string name = "", int priority = 0) { return this.Start(coroutine.GetEnumerator(), name, priority); } /// /// Starts the given coroutine, returning a object for management. /// /// The coroutine to start /// The that this coroutine should have. Defaults to an empty string. /// The that this coroutine should have. The higher the priority, the earlier it is advanced compared to other coroutines that advance around the same time. Defaults to 0. /// An active coroutine object representing this coroutine public ActiveCoroutine Start(IEnumerator coroutine, string name = "", int priority = 0) { var inst = new ActiveCoroutine(coroutine, name, priority, this.stopwatch); if (inst.MoveNext()) { lock (this.lockObject) this.GetOutstandingCoroutines(inst.IsWaitingForEvent).Add(inst); } return inst; } /// /// Causes the given action to be invoked after the given . /// This is equivalent to a coroutine that waits for the given wait and then executes the given . /// /// The wait to wait for /// The action to execute after waiting /// The that the underlying coroutine should have. Defaults to an empty string. /// The that the underlying coroutine should have. The higher the priority, the earlier it is advanced compared to other coroutines that advance around the same time. Defaults to 0. /// An active coroutine object representing this coroutine public ActiveCoroutine InvokeLater(Wait wait, Action action, string name = "", int priority = 0) { return this.Start(CoroutineHandlerInstance.InvokeLaterImpl(wait, action), name, priority); } /// /// Causes the given action to be invoked after the given . /// This is equivalent to a coroutine that waits for the given wait and then executes the given . /// /// The event to wait for /// The action to execute after waiting /// The that the underlying coroutine should have. Defaults to an empty string. /// The that the underlying coroutine should have. The higher the priority, the earlier it is advanced compared to other coroutines that advance around the same time. Defaults to 0. /// An active coroutine object representing this coroutine public ActiveCoroutine InvokeLater(Event evt, Action action, string name = "", int priority = 0) { return this.InvokeLater(new Wait(evt), action, name, priority); } /// /// Ticks this coroutine handler, causing all time-based s to be ticked. /// /// The amount of seconds that have passed since the last time this method was invoked public void Tick(double deltaSeconds) { lock (this.lockObject) { this.MoveOutstandingCoroutines(false); this.tickingCoroutines.RemoveAll(c => { if (c.Tick(deltaSeconds)) { return true; } else if (c.IsWaitingForEvent) { this.outstandingEventCoroutines.Add(c); return true; } return false; }); } } /// /// Ticks this coroutine handler, causing all time-based s to be ticked. /// This is a convenience method that calls , but accepts a instead of an amount of seconds. /// /// The time that has passed since the last time this method was invoked public void Tick(TimeSpan delta) { this.Tick(delta.TotalSeconds); } /// /// Raises the given event, causing all event-based s to be updated. /// /// The event to raise public void RaiseEvent(Event evt) { lock (this.lockObject) { this.MoveOutstandingCoroutines(true); var coroutines = this.GetEventCoroutines(evt, false); if (coroutines != null) { for (var i = 0; i < coroutines.Count; i++) { var c = coroutines[i]; var tup = (c.Event, c); if (this.eventCoroutinesToRemove.Contains(tup)) continue; if (c.OnEvent(evt)) { this.eventCoroutinesToRemove.Add(tup); } else if (!c.IsWaitingForEvent) { this.eventCoroutinesToRemove.Add(tup); this.outstandingTickingCoroutines.Add(c); } } } } } /// /// Returns a list of all currently active objects under this handler. /// /// All active coroutines public IEnumerable GetActiveCoroutines() { lock (this.lockObject) return this.tickingCoroutines.Concat(this.eventCoroutines.Values.SelectMany(c => c)); } private void MoveOutstandingCoroutines(bool evt) { // RemoveWhere is twice as fast as iterating and then clearing if (this.eventCoroutinesToRemove.Count > 0) { this.eventCoroutinesToRemove.RemoveWhere(c => { this.GetEventCoroutines(c.Item1, false).Remove(c.Item2); return true; }); } var coroutines = this.GetOutstandingCoroutines(evt); if (coroutines.Count > 0) { coroutines.RemoveWhere(c => { var list = evt ? this.GetEventCoroutines(c.Event, true) : this.tickingCoroutines; var position = list.BinarySearch(c); list.Insert(position < 0 ? ~position : position, c); return true; }); } } private HashSet GetOutstandingCoroutines(bool evt) { return evt ? this.outstandingEventCoroutines : this.outstandingTickingCoroutines; } private List GetEventCoroutines(Event evt, bool create) { if (!this.eventCoroutines.TryGetValue(evt, out var ret) && create) { ret = new List(); this.eventCoroutines.Add(evt, ret); } return ret; } private static IEnumerator InvokeLaterImpl(Wait wait, Action action) { yield return wait; action(); } } }