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();
///
/// The amount of instances that are currently waiting for a tick (waiting for time to pass)
///
public int TickingCount => this.tickingCoroutines.Count;
///
/// The amount of instances that are currently waiting for an
///
public int EventCount => 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())
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(InvokeLaterImpl(wait, 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) {
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) {
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() {
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();
}
}
}