diff --git a/Coroutine/ActiveCoroutine.cs b/Coroutine/ActiveCoroutine.cs index 85cd45c..e6f8314 100644 --- a/Coroutine/ActiveCoroutine.cs +++ b/Coroutine/ActiveCoroutine.cs @@ -13,6 +13,9 @@ namespace Coroutine { private readonly Stopwatch stopwatch; private Wait current; + internal Event Event => this.current.Event; + internal bool IsWaitingForEvent => this.Event != null; + /// /// This property stores whether or not this active coroutine is finished. /// A coroutine is finished if all of its waits have passed, or if it . @@ -79,18 +82,14 @@ namespace Coroutine { } internal bool Tick(double deltaSeconds) { - if (!this.WasCanceled) { - if (this.current.Tick(deltaSeconds)) - this.MoveNext(); - } + if (!this.WasCanceled && this.current.Tick(deltaSeconds)) + this.MoveNext(); return this.IsFinished; } internal bool OnEvent(Event evt) { - if (!this.WasCanceled) { - if (this.current.OnEvent(evt)) - this.MoveNext(); - } + if (!this.WasCanceled && Equals(this.current.Event, evt)) + this.MoveNext(); return this.IsFinished; } @@ -112,10 +111,6 @@ namespace Coroutine { return true; } - internal bool IsWaitingForEvent() { - return this.current.IsWaitingForEvent(); - } - /// /// A delegate method used by . /// diff --git a/Coroutine/CoroutineHandlerInstance.cs b/Coroutine/CoroutineHandlerInstance.cs index a88e8c2..0458234 100644 --- a/Coroutine/CoroutineHandlerInstance.cs +++ b/Coroutine/CoroutineHandlerInstance.cs @@ -11,9 +11,9 @@ namespace Coroutine { public class CoroutineHandlerInstance { private readonly List tickingCoroutines = new List(); - private readonly List eventCoroutines = new List(); + private readonly Dictionary> eventCoroutines = new Dictionary>(); + private readonly Queue eventCoroutinesToRemove = new Queue(); private readonly Queue outstandingCoroutines = new Queue(); - private readonly ISet eventCoroutinesToRemove = new HashSet(); private readonly Stopwatch stopwatch = new Stopwatch(); /// @@ -23,7 +23,7 @@ namespace Coroutine { /// /// The amount of instances that are currently waiting for an /// - public int EventCount => this.eventCoroutines.Count; + public int EventCount => this.eventCoroutines.Sum(c => c.Value.Count); /// /// Starts the given coroutine, returning a object for management. @@ -66,7 +66,6 @@ namespace Coroutine { /// /// Ticks this coroutine handler, causing all time-based s to be ticked. - /// Note that this method needs to be called even if only event-based coroutines are used. /// /// The amount of seconds that have passed since the last time this method was invoked public void Tick(double deltaSeconds) { @@ -74,7 +73,7 @@ namespace Coroutine { this.tickingCoroutines.RemoveAll(c => { if (c.Tick(deltaSeconds)) { return true; - } else if (c.IsWaitingForEvent()) { + } else if (c.IsWaitingForEvent) { this.outstandingCoroutines.Enqueue(c); return true; } @@ -96,15 +95,19 @@ namespace Coroutine { /// /// The event to raise public void RaiseEvent(Event evt) { - for (var i = 0; i < this.eventCoroutines.Count; i++) { - if (this.eventCoroutinesToRemove.Contains(i)) - continue; - var c = this.eventCoroutines[i]; - if (c.OnEvent(evt)) { - this.eventCoroutinesToRemove.Add(i); - } else if (!c.IsWaitingForEvent()) { - this.outstandingCoroutines.Enqueue(c); - this.eventCoroutinesToRemove.Add(i); + this.MoveOutstandingCoroutines(); + var coroutines = this.GetEventCoroutines(evt, 0); + if (coroutines != null) { + for (var i = 0; i < coroutines.Count; i++) { + var c = coroutines[i]; + if (this.eventCoroutinesToRemove.Contains(c)) + continue; + if (c.OnEvent(evt)) { + this.eventCoroutinesToRemove.Enqueue(c); + } else if (!c.IsWaitingForEvent) { + this.outstandingCoroutines.Enqueue(c); + this.eventCoroutinesToRemove.Enqueue(c); + } } } } @@ -114,22 +117,32 @@ namespace Coroutine { /// /// All active coroutines public IEnumerable GetActiveCoroutines() { - return this.tickingCoroutines.Concat(this.eventCoroutines); + return this.tickingCoroutines.Concat(this.eventCoroutines.Values.SelectMany(c => c)); } private void MoveOutstandingCoroutines() { - var i = 0; - this.eventCoroutines.RemoveAll(c => this.eventCoroutinesToRemove.Contains(i++)); - this.eventCoroutinesToRemove.Clear(); - + while (this.eventCoroutinesToRemove.Count > 0) { + var c = this.eventCoroutinesToRemove.Peek(); + this.GetEventCoroutines(c.Event, 0).Remove(c); + this.eventCoroutinesToRemove.Dequeue(); + } while (this.outstandingCoroutines.Count > 0) { - var coroutine = this.outstandingCoroutines.Dequeue(); - var list = coroutine.IsWaitingForEvent() ? this.eventCoroutines : this.tickingCoroutines; + var coroutine = this.outstandingCoroutines.Peek(); + var list = coroutine.IsWaitingForEvent ? this.GetEventCoroutines(coroutine.Event, 1) : this.tickingCoroutines; var position = list.BinarySearch(coroutine); list.Insert(position < 0 ? ~position : position, coroutine); + this.outstandingCoroutines.Dequeue(); } } + private List GetEventCoroutines(Event evt, int capacity) { + if (!this.eventCoroutines.TryGetValue(evt, out var ret) && capacity > 0) { + ret = new List(capacity); + this.eventCoroutines.Add(evt, ret); + } + return ret; + } + private static IEnumerator InvokeLaterImpl(Wait wait, Action action) { yield return wait; action(); diff --git a/Coroutine/Wait.cs b/Coroutine/Wait.cs index 516fadb..b47ced5 100644 --- a/Coroutine/Wait.cs +++ b/Coroutine/Wait.cs @@ -2,19 +2,19 @@ using System; namespace Coroutine { /// - /// Represents either an amount of time, or an that is being waited for by an . + /// Represents either an amount of time, or an that is being waited for by an . /// public struct Wait { - private readonly Event evt; + internal readonly Event Event; private double seconds; /// - /// Creates a new wait that waits for the given . + /// Creates a new wait that waits for the given . /// /// The event to wait for public Wait(Event evt) { - this.evt = evt; + this.Event = evt; this.seconds = 0; } @@ -24,7 +24,7 @@ namespace Coroutine { /// The amount of seconds to wait for public Wait(double seconds) { this.seconds = seconds; - this.evt = null; + this.Event = null; } /// @@ -40,13 +40,5 @@ namespace Coroutine { return this.seconds <= 0; } - internal bool OnEvent(Event evt) { - return Equals(evt, this.evt); - } - - internal bool IsWaitingForEvent() { - return this.evt != null; - } - } } \ No newline at end of file diff --git a/Example/Example.cs b/Example/Example.cs index d1c86ba..88b899d 100644 --- a/Example/Example.cs +++ b/Example/Example.cs @@ -14,7 +14,7 @@ namespace Example { CoroutineHandler.Start(EmptyCoroutine()); - CoroutineHandler.InvokeLater(new Wait(10), () => { + CoroutineHandler.InvokeLater(new Wait(5), () => { Console.WriteLine("Raising test event"); CoroutineHandler.RaiseEvent(TestEvent); }); diff --git a/README.md b/README.md index d2b6d34..d8edcf9 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,5 @@ To actually cause the event to be raised, causing all currently waiting coroutin CoroutineHandler.RaiseEvent(TestEvent); ``` -Note that, since `Tick` is an important lifecycle method, it has to be [called continuously](#Setting-up-the-CoroutineHandler) even if only event-based coroutines are used. - ## Additional Examples For additional examples, take a look at the [Example class](https://github.com/Ellpeck/Coroutine/blob/master/Example/Example.cs). \ No newline at end of file diff --git a/Tests/TimeBasedCoroutineTests.cs b/Tests/TimeBasedCoroutineTests.cs index 9da3aa8..f97e462 100644 --- a/Tests/TimeBasedCoroutineTests.cs +++ b/Tests/TimeBasedCoroutineTests.cs @@ -21,7 +21,7 @@ namespace Tests { Assert.AreEqual(string.Empty, cr.Name, "Incorrect default name found"); Assert.AreEqual(0, cr.Priority, "Default priority is not minimum"); for (var i = 0; i < 5; i++) - SimulateTime(1); + CoroutineHandler.Tick(1); Assert.AreEqual(2, counter, "instruction after yield is not executed."); Assert.AreEqual(true, cr.IsFinished, "Incorrect IsFinished value."); Assert.AreEqual(false, cr.WasCanceled, "Incorrect IsCanceled value."); @@ -49,7 +49,7 @@ namespace Tests { cr[0] = CoroutineHandler.Start(OnTimeTickNeverReturnYield()); cr[1] = CoroutineHandler.Start(OnTimeTickYieldBreak()); for (var i = 0; i < 5; i++) - SimulateTime(1); + CoroutineHandler.Tick(1); Assert.AreEqual(3, counter, "Incorrect counter value."); for (var i = 0; i < cr.Length; i++) { @@ -71,7 +71,7 @@ namespace Tests { var cr = CoroutineHandler.Start(OnTimeTickYieldDefault()); for (var i = 0; i < 5; i++) - SimulateTime(1); + CoroutineHandler.Tick(1); Assert.AreEqual(2, counter, "Incorrect counter value."); Assert.AreEqual(true, cr.IsFinished, "Incorrect IsFinished value."); @@ -97,7 +97,7 @@ namespace Tests { var cr = CoroutineHandler.Start(OnTimerTickInfinite()); cr.OnFinished += SetCounterToUnreachableValue; for (var i = 0; i < 50; i++) - SimulateTime(1); + CoroutineHandler.Tick(1); Assert.AreEqual(51, counter, "Incorrect counter value."); Assert.AreEqual(false, cr.IsFinished, "Incorrect IsFinished value."); @@ -126,7 +126,7 @@ namespace Tests { var cr = CoroutineHandler.Start(OnTimeTick()); cr.OnFinished += SetCounterToUnreachableValue; - SimulateTime(50); + CoroutineHandler.Tick(50); Assert.AreEqual(-100, counter, "Incorrect counter value."); } @@ -173,22 +173,22 @@ namespace Tests { Assert.AreEqual(0, counterGrandParent, "Grand Parent counter is invalid at time 0."); Assert.AreEqual(0, counterParent, "Parent counter is invalid at time 0."); Assert.AreEqual(0, counterChild, "Child counter is invalid at time 0."); - SimulateTime(1); + CoroutineHandler.Tick(1); Assert.AreEqual(1, counterAlwaysRunning, "Always running counter is invalid at time 1."); Assert.AreEqual(1, counterGrandParent, "Grand Parent counter is invalid at time 1."); Assert.AreEqual(0, counterParent, "Parent counter is invalid at time 1."); Assert.AreEqual(0, counterChild, "Child counter is invalid at time 1."); - SimulateTime(1); + CoroutineHandler.Tick(1); Assert.AreEqual(2, counterAlwaysRunning, "Always running counter is invalid at time 2."); Assert.AreEqual(1, counterGrandParent, "Grand Parent counter is invalid at time 2."); Assert.AreEqual(1, counterParent, "Parent counter is invalid at time 2."); Assert.AreEqual(0, counterChild, "Child counter is invalid at time 2."); - SimulateTime(1); + CoroutineHandler.Tick(1); Assert.AreEqual(3, counterAlwaysRunning, "Always running counter is invalid at time 3."); Assert.AreEqual(1, counterGrandParent, "Grand Parent counter is invalid at time 3."); Assert.AreEqual(1, counterParent, "Parent counter is invalid at time 3."); Assert.AreEqual(1, counterChild, "Child counter is invalid at time 3."); - SimulateTime(1); + CoroutineHandler.Tick(1); Assert.AreEqual(4, counterAlwaysRunning, "Always running counter is invalid at time 4."); Assert.AreEqual(1, counterGrandParent, "Grand Parent counter is invalid at time 4."); Assert.AreEqual(1, counterParent, "Parent counter is invalid at time 4."); @@ -243,7 +243,7 @@ namespace Tests { CoroutineHandler.Start(ShouldExecuteAfter()); CoroutineHandler.Start(ShouldExecuteBefore0(), priority: highPriority); CoroutineHandler.Start(ShouldExecuteFinally(), priority: -1); - SimulateTime(10); + CoroutineHandler.Tick(10); Assert.AreEqual(1, counterShouldExecuteAfter, $"ShouldExecuteAfter counter {counterShouldExecuteAfter} is invalid."); Assert.AreEqual(1, counterShouldExecuteFinally, $"ShouldExecuteFinally counter {counterShouldExecuteFinally} is invalid."); } @@ -270,10 +270,10 @@ namespace Tests { CoroutineHandler.Start(IncrementCounter0Ever10Seconds()); CoroutineHandler.Start(IncrementCounter1Every5Seconds()); - SimulateTime(3); + CoroutineHandler.Tick(3); Assert.AreEqual(0, counter0, "Incorrect counter0 value after 3 seconds."); Assert.AreEqual(0, counter1, "Incorrect counter1 value after 3 seconds."); - SimulateTime(3); + CoroutineHandler.Tick(3); Assert.AreEqual(0, counter0, "Incorrect counter0 value after 6 seconds."); Assert.AreEqual(1, counter1, "Incorrect counter1 value after 6 seconds."); @@ -281,7 +281,7 @@ namespace Tests { // increments 5 seconds after last yield. not 5 seconds since start. // So the when we send 3 seconds in the last SimulateTime, // the 3rd second was technically ignored. - SimulateTime(5); + CoroutineHandler.Tick(5); Assert.AreEqual(1, counter0, "Incorrect counter0 value after 10 seconds."); Assert.AreEqual(2, counter1, "Incorrect counter1 value after next 5 seconds."); } @@ -293,9 +293,9 @@ namespace Tests { counter++; }, "Bird"); - SimulateTime(5); + CoroutineHandler.Tick(5); Assert.AreEqual(0, counter, "Incorrect counter value after 5 seconds."); - SimulateTime(5); + CoroutineHandler.Tick(5); Assert.AreEqual(1, counter, "Incorrect counter value after 10 seconds."); Assert.AreEqual(true, cr.IsFinished, "Incorrect IsFinished value."); Assert.AreEqual(false, cr.WasCanceled, "Incorrect IsCanceled value."); @@ -313,7 +313,7 @@ namespace Tests { var cr = CoroutineHandler.Start(CoroutineTakesMax500Ms()); for (var i = 0; i < 5; i++) - SimulateTime(50); + CoroutineHandler.Tick(50); const int expected1 = 350; const float errorbar1 = 5 / 100f * expected1; @@ -328,9 +328,5 @@ namespace Tests { Assert.IsTrue(gTc && lTd, $"Maximum Move Next Time {cr.MaxMoveNextTime.Milliseconds} is invalid."); } - private static void SimulateTime(double totalSeconds) { - CoroutineHandler.Tick(totalSeconds); - } - } } \ No newline at end of file