From c179b3c625467a9e80742c412445a9f1fee7507c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Dolej=C5=A1ka?= <dolejska-daniel@users.noreply.github.com> Date: Fri, 25 Dec 2020 13:29:23 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9A=99=20Added=20ability=20to=20schedule=20n?= =?UTF-8?q?ew=20events=20-=20Implemented=20ability=20to=20schedule=20new?= =?UTF-8?q?=20events=20from=20within=20the=20events=20-=20Improved=20Event?= =?UTF-8?q?'s=20BehaviourResult=20structure=20-=20Further=20improved=20tes?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Scripts/CoroutineMonoSimulationController.cs | 2 + Scripts/Events/BehaviourResult.cs | 88 ++++++++-- Scripts/Events/EventBase.cs | 52 ++++-- Scripts/Events/IEvent.cs | 6 +- Scripts/Events/SimulationTimeEvent.cs | 5 + Scripts/ISimulationController.cs | 17 +- Scripts/SimulationController.cs | 3 + Tests/Events.meta | 8 + Tests/Events/BehaviourResultTests.cs | 131 +++++++++++++++ Tests/Events/BehaviourResultTests.cs.meta | 11 ++ Tests/Events/EventTests.cs | 168 +++++++++++++++++++ Tests/Events/EventTests.cs.meta | 11 ++ 12 files changed, 469 insertions(+), 33 deletions(-) create mode 100755 Tests/Events.meta create mode 100755 Tests/Events/BehaviourResultTests.cs create mode 100755 Tests/Events/BehaviourResultTests.cs.meta create mode 100755 Tests/Events/EventTests.cs create mode 100755 Tests/Events/EventTests.cs.meta diff --git a/Scripts/CoroutineMonoSimulationController.cs b/Scripts/CoroutineMonoSimulationController.cs index 335ddbd..48a9808 100755 --- a/Scripts/CoroutineMonoSimulationController.cs +++ b/Scripts/CoroutineMonoSimulationController.cs @@ -38,6 +38,8 @@ protected virtual void Start() public IEnumerator RunAvailableTicksCoroutine() => Controller.RunAvailableTicksCoroutine(); + public void Schedule(EventBase<SimulationTime> @event, float scheduleTime) => Controller.Schedule(@event, scheduleTime); + public void Schedule(EventBase<SimulationTime> @event, int tickCount = 1) => Controller.Schedule(@event, tickCount); public bool Unschedule(EventBase<SimulationTime> @event) => Controller.Unschedule(@event); diff --git a/Scripts/Events/BehaviourResult.cs b/Scripts/Events/BehaviourResult.cs index 43b4f39..21dba64 100755 --- a/Scripts/Events/BehaviourResult.cs +++ b/Scripts/Events/BehaviourResult.cs @@ -5,13 +5,24 @@ namespace UnityDES.Events /// <summary> /// Structure describing result of the event's behaviour. /// </summary> - public struct BehaviourResult + public struct BehaviourResult<TEvent, TKey> + where TEvent : class, IEvent<TEvent, TKey> { + /// <summary> + /// Reserved time constant - the behaviour should continue being processed. + /// </summary> + const float TIME_CONTINUE = -1f; + + /// <summary> + /// Reserved time constant - the event should be removed from the simulation. + /// </summary> + const float TIME_UNSCHEDULE = -2f; + /// <summary> /// Minimum number of time to pass before running the event's behaviour again. /// </summary> /// <remarks> - /// Values <c><=0</c> are reserved for other result states. + /// Values <c><0</c> are reserved for other result states. /// </remarks> public float RescheduleTime { get; internal set; } @@ -23,32 +34,50 @@ public struct BehaviourResult /// <summary> /// Simulation should continue processing the event's behaviour. /// </summary> - public bool ContinueBehaviour { get => RescheduleTime == 0; } + public bool ContinueBehaviour { get => RescheduleTime == TIME_CONTINUE; } /// <summary> /// Simulation should now remove the event from the simulation. /// </summary> - public bool UnscheduleEvent{ get => RescheduleTime < 0; } + public bool UnscheduleEvent { get => RescheduleTime == TIME_UNSCHEDULE; } - BehaviourResult(float rescheduleTime, bool reset) + /// <summary> + /// Simulation should add a new event to the simulation. + /// </summary> + public bool ScheduleNewEvent { get => NewEvent != null; } + + /// <summary> + /// Event to be added to the simulation. + /// </summary> + public TEvent NewEvent { get; internal set; } + + /// <summary> + /// . + /// </summary> + public float NewEventScheduleTime { get; internal set; } + + BehaviourResult(float rescheduleTime, bool reset, TEvent @event = null, float scheduleTime = 0f) { RescheduleTime = rescheduleTime; ResetBehaviour = reset; + NewEvent = @event; + NewEventScheduleTime = scheduleTime; } /// <summary> - /// Event should be rescheduled and its behaviour run after minimum of <paramref name="time"/> seconds. + /// Event should be rescheduled and its behaviour run after minimum of <paramref name="rescheduleTime"/> seconds + /// or be reset and rescheduled (if <paramref name="reset"/> is <c>True</c>). /// </summary> /// - /// <param name="time">Minimum amount of time to skip</param> + /// <param name="rescheduleTime">Minimum amount of time to skip</param> /// <param name="reset">Should behaviour iterator be reset?</param> /// <returns>Behaviour result instance reflecting rescheduling</returns> - public static BehaviourResult Reschedule(float time, bool reset = false) + public static BehaviourResult<TEvent, TKey> Reschedule(float rescheduleTime, bool reset = true) { - if (time <= 0) - throw new ArgumentException("Event's reschedule time cannot be less or equal to 0."); + if (rescheduleTime < 0f) + throw new ArgumentException("Event's reschedule time cannot be less than 0."); - return new BehaviourResult(time, reset); + return new BehaviourResult<TEvent, TKey>(rescheduleTime, reset); } /// <summary> @@ -56,15 +85,44 @@ public static BehaviourResult Reschedule(float time, bool reset = false) /// </summary> /// /// <returns>Behaviour result instance reflecting unscheduling</returns> - public static BehaviourResult Unschedule() => new BehaviourResult(-1, false); + public static BehaviourResult<TEvent, TKey> Unschedule() + => new BehaviourResult<TEvent, TKey>(TIME_UNSCHEDULE, false); /// <summary> - /// Processing of the event's behaviour should continue immediately. + /// Processing of the event's behaviour should continue immediately + /// or be reset and continue immediately (if <paramref name="reset"/> is <c>True</c>). /// </summary> /// /// <param name="reset">Should behaviour iterator be reset?</param> /// <returns>Behaviour result instance reflecting continuing</returns> - public static BehaviourResult Continue(bool reset = false) => new BehaviourResult(0, reset); + public static BehaviourResult<TEvent, TKey> Continue(bool reset = false) + => new BehaviourResult<TEvent, TKey>(TIME_CONTINUE, reset); + + /// <summary> + /// Processing of the event's behaviour should continue after scheduling a new <paramref name="event"/> + /// or be reset after scheduling the event (if <paramref name="reset"/> is <c>True</c>). + /// </summary> + /// + /// <param name="event">Event to be scheduled</param> + /// <param name="scheduleTime">Event to be scheduled</param> + /// <param name="reset">Should behaviour iterator be reset?</param> + /// <returns>Behaviour result instance reflecting new event scheduling</returns> + public static BehaviourResult<TEvent, TKey> ScheduleNewAndContinue(TEvent @event, float scheduleTime = 0, bool reset = false) + => ScheduleNew(TIME_CONTINUE, @event, scheduleTime, reset); + + /// <summary> + /// Event should be rescheduled and its behaviour run after minimum of <paramref name="rescheduleTime"/> seconds + /// or be reset and rescheduled (if <paramref name="reset"/> is <c>True</c>). + /// All that after scheduling a new <paramref name="event"/> (with that event waiting for a minimum of <paramref name="scheduleTime"/> seconds). + /// </summary> + /// + /// <param name="rescheduleTime">Minimum amount of time to skip</param> + /// <param name="event">Event to be scheduled</param> + /// <param name="scheduleTime">Event to be scheduled</param> + /// <param name="reset">Should behaviour iterator be reset?</param> + /// <returns>Behaviour result instance reflecting new event scheduling</returns> + public static BehaviourResult<TEvent, TKey> ScheduleNew(float rescheduleTime, TEvent @event, float scheduleTime = 0, bool reset = false) + => new BehaviourResult<TEvent, TKey>(rescheduleTime, reset, @event, scheduleTime); public override string ToString() { @@ -77,6 +135,6 @@ public override string ToString() return "EventBehaviour: Reschedule in " + RescheduleTime + (ResetBehaviour ? " and Reset" : ""); } - public static implicit operator float(BehaviourResult result) => result.RescheduleTime; + public static implicit operator float(BehaviourResult<TEvent, TKey> result) => result.RescheduleTime; } } diff --git a/Scripts/Events/EventBase.cs b/Scripts/Events/EventBase.cs index e25aac2..4ec7112 100755 --- a/Scripts/Events/EventBase.cs +++ b/Scripts/Events/EventBase.cs @@ -14,7 +14,7 @@ public abstract class EventBase<TKey> : IEvent<EventBase<TKey>, TKey> public TKey SimulationTime { get => QueueKey; } - public IEnumerator<BehaviourResult> BehaviourCycle { get; protected set; } + public IEnumerator<BehaviourResult<EventBase<TKey>, TKey>> BehaviourCycle { get; protected set; } /// <summary> /// Initializes BehaviourCycle property. @@ -24,28 +24,43 @@ public EventBase() BehaviourCycle = Behaviour(); } - public abstract IEnumerator<BehaviourResult> Behaviour(); + /// <summary> + /// Updates the key so the event's execution happens in the future based on value of <paramref name="time"/>. + /// </summary> + /// + /// <param name="time">Time offset into the future</param> + protected abstract void IncreaseKey(float time); + + public abstract IEnumerator<BehaviourResult<EventBase<TKey>, TKey>> Behaviour(); public void Run(ISimulationController<EventBase<TKey>, TKey> simulationController) { bool unfinished; + BehaviourResult<EventBase<TKey>, TKey> behaviourResult; + do { // continue processing the behaviour until its end or voluntary rescheduling unfinished = BehaviourCycle.MoveNext(); + behaviourResult = BehaviourCycle.Current; + + if (behaviourResult.ScheduleNewEvent) + { + simulationController.Schedule(behaviourResult.NewEvent, behaviourResult.NewEventScheduleTime); + } } - while (unfinished && BehaviourCycle.Current.ContinueBehaviour); + while (unfinished && behaviourResult.ContinueBehaviour); - if (unfinished && !BehaviourCycle.Current.UnscheduleEvent) + if (unfinished && !behaviourResult.UnscheduleEvent) { // update the event's key (must be done before rescheduling!) - IncreaseKey(BehaviourCycle.Current.RescheduleTime); + IncreaseKey(behaviourResult.RescheduleTime); // reschedule the event if (!simulationController.Reschedule(this)) throw new ApplicationException("Rescheduling of existing event has failed!"); - if (BehaviourCycle.Current.ResetBehaviour) + if (behaviourResult.ResetBehaviour) { // create new behaviour iterator instance BehaviourCycle = Behaviour(); @@ -59,11 +74,24 @@ public void Run(ISimulationController<EventBase<TKey>, TKey> simulationControlle } } - /// <summary> - /// Updates the key so the event's execution happens in the future based on value of <paramref name="time"/>. - /// </summary> - /// - /// <param name="time">Time offset into the future</param> - protected abstract void IncreaseKey(float time); + /// <inheritdoc cref="BehaviourResult{TEvent, TKey}.ScheduleNew(float, TEvent, float, bool)"/> + protected BehaviourResult<EventBase<TKey>, TKey> ScheduleNew(float rescheduleTime, EventBase<TKey> @event, float scheduleTime, bool reset = false) + => BehaviourResult<EventBase<TKey>, TKey>.ScheduleNew(rescheduleTime, @event, scheduleTime, reset); + + /// <inheritdoc cref="BehaviourResult{TEvent, TKey}.ScheduleNewAndContinue(TEvent, float, bool)"/> + protected BehaviourResult<EventBase<TKey>, TKey> ScheduleNewAndContinue(EventBase<TKey> @event, float scheduleTime, bool reset = false) + => BehaviourResult<EventBase<TKey>, TKey>.ScheduleNewAndContinue(@event, scheduleTime, reset); + + /// <inheritdoc cref="BehaviourResult{TEvent, TKey}.Continue(bool)"/> + protected BehaviourResult<EventBase<TKey>, TKey> Continue(bool reset = false) + => BehaviourResult<EventBase<TKey>, TKey>.Continue(reset); + + /// <inheritdoc cref="BehaviourResult{TEvent, TKey}.Reschedule(float, bool)"/> + protected BehaviourResult<EventBase<TKey>, TKey> Reschedule(float rescheduleTime, bool reset = true) + => BehaviourResult<EventBase<TKey>, TKey>.Reschedule(rescheduleTime, reset); + + /// <inheritdoc cref="BehaviourResult{TEvent, TKey}.Unschedule"/> + protected BehaviourResult<EventBase<TKey>, TKey> Unschedule() + => BehaviourResult<EventBase<TKey>, TKey>.Unschedule(); } } diff --git a/Scripts/Events/IEvent.cs b/Scripts/Events/IEvent.cs index d749831..400f7bf 100755 --- a/Scripts/Events/IEvent.cs +++ b/Scripts/Events/IEvent.cs @@ -9,12 +9,12 @@ namespace UnityDES.Events /// /// <typeparam name="TKey">Type of the queue key</typeparam> public interface IEvent<TEvent, TKey> : IQueueItem<TKey> - where TEvent : IEvent<TEvent, TKey> + where TEvent : class, IEvent<TEvent, TKey> { /// <summary> /// Current behaviour enumerator instance. /// </summary> - public IEnumerator<BehaviourResult> BehaviourCycle { get; } + public IEnumerator<BehaviourResult<TEvent, TKey>> BehaviourCycle { get; } /// <summary> /// Runs the behaviour of the event and manages immediate rescheduling through the simulation instance. @@ -31,6 +31,6 @@ public interface IEvent<TEvent, TKey> : IQueueItem<TKey> /// <c>0</c> value will skip the yield and continue processing the behaviour. /// Value <c>>0</c> will reschedule the event accordingly always of minimum 1 tick ahead. /// </returns> - public abstract IEnumerator<BehaviourResult> Behaviour(); + public abstract IEnumerator<BehaviourResult<TEvent, TKey>> Behaviour(); } } diff --git a/Scripts/Events/SimulationTimeEvent.cs b/Scripts/Events/SimulationTimeEvent.cs index bf126e4..ff5bae6 100755 --- a/Scripts/Events/SimulationTimeEvent.cs +++ b/Scripts/Events/SimulationTimeEvent.cs @@ -6,6 +6,11 @@ namespace UnityDES.Events /// </summary> public abstract class SimulationTimeEvent : EventBase<SimulationTime> { + protected SimulationTimeEvent(SimulationTime simulationTime) : base() + { + QueueKey = new SimulationTime(simulationTime); + } + protected SimulationTimeEvent(int ticksPerFrame) : base() { QueueKey = new SimulationTime(ticksPerFrame); diff --git a/Scripts/ISimulationController.cs b/Scripts/ISimulationController.cs index 84e2b4b..47ca931 100755 --- a/Scripts/ISimulationController.cs +++ b/Scripts/ISimulationController.cs @@ -10,7 +10,7 @@ namespace UnityDES /// <typeparam name="TEvent">Type of the event</typeparam> /// <typeparam name="TKey">Type of the simulation time</typeparam> public interface ISimulationController<TEvent, TKey> - where TEvent : IEvent<TEvent, TKey> + where TEvent : class, IEvent<TEvent, TKey> { /// <summary> /// Current simulation time. @@ -36,10 +36,21 @@ public interface ISimulationController<TEvent, TKey> void RunAvailableTicks(float deltaTime); /// <summary> - /// Adds the event (<paramref name="event"/>) to the simulation. + /// Schedules the <paramref name="event"/> to run within the simulation after minimum of <paramref name="scheduleTime"/> have passed. /// </summary> /// <remarks> - /// The event's time will be set to the current simulation time and then offset by provided number of ticks (<paramref name="tickCount"/>). + /// The event's time will be set to the current simulation time and then offset by calculated number of ticks based on <paramref name="scheduleTime"/> offset. + /// </remarks> + /// + /// <param name="event">Event to be scheduled</param> + /// <param name="scheduleTime">Minimum amount of time to be skipped</param> + void Schedule(TEvent @event, float scheduleTime); + + /// <summary> + /// Schedules the <paramref name="event"/> to run within the simulation after <paramref name="tickCount"/> of skipped ticks. + /// </summary> + /// <remarks> + /// The event's time will be set to the current simulation time and then offset by provided <paramref name="tickCount"/>. /// </remarks> /// /// <param name="event">Event to be scheduled</param> diff --git a/Scripts/SimulationController.cs b/Scripts/SimulationController.cs index 2a77ff5..50fcc7b 100755 --- a/Scripts/SimulationController.cs +++ b/Scripts/SimulationController.cs @@ -90,6 +90,9 @@ public void RunTick() SimulationTime.DoTick(); } + public void Schedule(EventBase<SimulationTime> @event, float scheduleTime) + => Schedule(@event, (int)Math.Ceiling(@event.QueueKey.TicksPerFrame * scheduleTime)); + public void Schedule(EventBase<SimulationTime> @event, int tickCount = 1) { // set current simulation time diff --git a/Tests/Events.meta b/Tests/Events.meta new file mode 100755 index 0000000..7c40656 --- /dev/null +++ b/Tests/Events.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fc68708862e67f747bcd1fcbccd3a349 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Events/BehaviourResultTests.cs b/Tests/Events/BehaviourResultTests.cs new file mode 100755 index 0000000..7e2d2fc --- /dev/null +++ b/Tests/Events/BehaviourResultTests.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using NUnit.Framework; +using UnityDES; +using UnityDES.Events; + + +namespace Events +{ + public class BehaviourResultTests + { + public class PublicTestEvent : SimulationTimeEvent + { + public PublicTestEvent(int ticksPerFrame = 10) : base(ticksPerFrame) + { + } + + public override IEnumerator<BehaviourResult<EventBase<SimulationTime>, SimulationTime>> Behaviour() + { + yield return Reschedule(1f); + } + } + + [Test] + public void Continue() + { + BehaviourResult<EventBase<SimulationTime>, SimulationTime> result; + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.Continue(); + Assert.IsTrue(result.ContinueBehaviour); + Assert.IsFalse(result.ScheduleNewEvent); + Assert.IsFalse(result.UnscheduleEvent); + Assert.IsFalse(result.ResetBehaviour); + Assert.IsNull(result.NewEvent); + Assert.IsTrue(result.RescheduleTime < 0); + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.Continue(true); + Assert.IsTrue(result.ContinueBehaviour); + Assert.IsFalse(result.ScheduleNewEvent); + Assert.IsFalse(result.UnscheduleEvent); + Assert.IsTrue(result.ResetBehaviour); + Assert.IsNull(result.NewEvent); + Assert.IsTrue(result.RescheduleTime < 0); + } + + [Test] + public void Reschedule() + { + BehaviourResult<EventBase<SimulationTime>, SimulationTime> result; + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.Reschedule(1f, false); + Assert.IsFalse(result.ContinueBehaviour); + Assert.IsFalse(result.ScheduleNewEvent); + Assert.IsFalse(result.UnscheduleEvent); + Assert.IsFalse(result.ResetBehaviour); + Assert.IsNull(result.NewEvent); + Assert.AreEqual(1f, result.RescheduleTime); + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.Reschedule(1f); + Assert.IsFalse(result.ContinueBehaviour); + Assert.IsFalse(result.ScheduleNewEvent); + Assert.IsFalse(result.UnscheduleEvent); + Assert.IsTrue(result.ResetBehaviour); + Assert.IsNull(result.NewEvent); + Assert.AreEqual(1f, result.RescheduleTime); + } + + [Test] + public void Unschedule() + { + BehaviourResult<EventBase<SimulationTime>, SimulationTime> result; + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.Unschedule(); + Assert.IsFalse(result.ContinueBehaviour); + Assert.IsFalse(result.ScheduleNewEvent); + Assert.IsTrue(result.UnscheduleEvent); + Assert.IsFalse(result.ResetBehaviour); + Assert.IsNull(result.NewEvent); + Assert.IsTrue(result.RescheduleTime < 0); + } + + [Test] + public void ScheduleNew() + { + BehaviourResult<EventBase<SimulationTime>, SimulationTime> result; + var newEvent = new PublicTestEvent(); + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.ScheduleNew(1f, newEvent, 2f); + Assert.IsFalse(result.ContinueBehaviour); + Assert.IsTrue(result.ScheduleNewEvent); + Assert.IsFalse(result.UnscheduleEvent); + Assert.IsFalse(result.ResetBehaviour); + Assert.AreSame(newEvent, result.NewEvent); + Assert.AreEqual(2f, result.NewEventScheduleTime); + Assert.AreEqual(1f, result.RescheduleTime); + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.ScheduleNew(1f, newEvent, 2f, true); + Assert.IsFalse(result.ContinueBehaviour); + Assert.IsTrue(result.ScheduleNewEvent); + Assert.IsFalse(result.UnscheduleEvent); + Assert.IsTrue(result.ResetBehaviour); + Assert.AreSame(newEvent, result.NewEvent); + Assert.AreEqual(2f, result.NewEventScheduleTime); + Assert.AreEqual(1f, result.RescheduleTime); + } + + [Test] + public void ScheduleNewAndContinue() + { + BehaviourResult<EventBase<SimulationTime>, SimulationTime> result; + var newEvent = new PublicTestEvent(); + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.ScheduleNewAndContinue(newEvent, 2f); + Assert.IsTrue(result.ContinueBehaviour); + Assert.IsTrue(result.ScheduleNewEvent); + Assert.IsFalse(result.UnscheduleEvent); + Assert.IsFalse(result.ResetBehaviour); + Assert.AreSame(newEvent, result.NewEvent); + Assert.AreEqual(2f, result.NewEventScheduleTime); + Assert.IsTrue(result.RescheduleTime < 0); + + result = BehaviourResult<EventBase<SimulationTime>, SimulationTime>.ScheduleNewAndContinue(newEvent, 2f, true); + Assert.IsTrue(result.ContinueBehaviour); + Assert.IsTrue(result.ScheduleNewEvent); + Assert.IsFalse(result.UnscheduleEvent); + Assert.IsTrue(result.ResetBehaviour); + Assert.AreSame(newEvent, result.NewEvent); + Assert.AreEqual(2f, result.NewEventScheduleTime); + Assert.IsTrue(result.RescheduleTime < 0); + } + } +} \ No newline at end of file diff --git a/Tests/Events/BehaviourResultTests.cs.meta b/Tests/Events/BehaviourResultTests.cs.meta new file mode 100755 index 0000000..78136d7 --- /dev/null +++ b/Tests/Events/BehaviourResultTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4e1e4eba583688f479206052a9fdffa5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Events/EventTests.cs b/Tests/Events/EventTests.cs new file mode 100755 index 0000000..a29ae9f --- /dev/null +++ b/Tests/Events/EventTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using NUnit.Framework; +using UnityDES; +using UnityDES.Events; +using UnityDES.Utils; + + +namespace Events +{ + public class EventTests + { + public class PublicTestEvent : SimulationTimeEvent + { + public PublicTestEvent(int ticksPerFrame = 10) : base(ticksPerFrame) + { + } + + public int State { get; protected set; } = 0; + + public PublicTestEvent ScheduledEvent { get; protected set; } + + public override IEnumerator<BehaviourResult<EventBase<SimulationTime>, SimulationTime>> Behaviour() + { + State = 1; + // reschedule to next tick + yield return Reschedule(QueueKey.TickLength, false); + + State = 2; + // continue processing the behaviour + yield return Continue(); + + State = 3; + ScheduledEvent = new PublicTestEvent(QueueKey.TicksPerFrame); + // reschedule to tick after the next one + yield return ScheduleNewAndContinue(ScheduledEvent, QueueKey.TickLength * 4); + + State = 4; + // remove the event from the queue + yield return Unschedule(); + } + + public new void IncreaseKey(float time) => base.IncreaseKey(time); + } + + public class PublicSimulationController : SimulationController + { + public PriorityQueue<EventBase<SimulationTime>, SimulationTime> QueuedEvents { get => Events; } + + public PublicSimulationController(int ticksPerFrame) : base(ticksPerFrame) + { + } + } + + [Test] + public void IncreaseKey() + { + var simTime = new SimulationTime(4); + var @event = new PublicTestEvent(); + @event.QueueKey = new SimulationTime(simTime); + + Assert.AreEqual(simTime, @event.QueueKey); + simTime.DoTick(); + Assert.AreNotEqual(simTime, @event.QueueKey); + @event.IncreaseKey(simTime.TickLength); + Assert.AreEqual(simTime, @event.QueueKey); + + simTime.DoTick(2); + Assert.AreNotEqual(simTime, @event.QueueKey); + @event.IncreaseKey(simTime.TickLength * 2); + Assert.AreEqual(simTime, @event.QueueKey); + + simTime.DoTick(14); + Assert.AreNotEqual(simTime, @event.QueueKey); + @event.IncreaseKey(simTime.TickLength * 14); + Assert.AreEqual(simTime, @event.QueueKey); + } + + [Test] + public void Behaviour() + { + var @event = new PublicTestEvent(); + @event.QueueKey = new SimulationTime(4); + + var iterator = @event.Behaviour(); + Assert.AreEqual(0, @event.State); + Assert.IsTrue(iterator.Current.RescheduleTime == 0); + Assert.IsFalse(iterator.Current.ContinueBehaviour); + Assert.IsFalse(iterator.Current.ScheduleNewEvent); + Assert.IsFalse(iterator.Current.UnscheduleEvent); + Assert.IsFalse(iterator.Current.ResetBehaviour); + + Assert.IsTrue(iterator.MoveNext()); + Assert.AreEqual(1, @event.State); + Assert.AreEqual(.25f, iterator.Current.RescheduleTime); + Assert.IsFalse(iterator.Current.ContinueBehaviour); + Assert.IsFalse(iterator.Current.ScheduleNewEvent); + Assert.IsFalse(iterator.Current.UnscheduleEvent); + Assert.IsFalse(iterator.Current.ResetBehaviour); + + Assert.IsTrue(iterator.MoveNext()); + Assert.AreEqual(2, @event.State); + Assert.IsTrue(iterator.Current.RescheduleTime <= 0); + Assert.IsTrue(iterator.Current.ContinueBehaviour); + Assert.IsFalse(iterator.Current.ScheduleNewEvent); + Assert.IsFalse(iterator.Current.UnscheduleEvent); + Assert.IsFalse(iterator.Current.ResetBehaviour); + + Assert.IsTrue(iterator.MoveNext()); + Assert.AreEqual(3, @event.State); + Assert.IsTrue(iterator.Current.RescheduleTime <= 0); + Assert.IsTrue(iterator.Current.ContinueBehaviour); + Assert.IsTrue(iterator.Current.ScheduleNewEvent); + Assert.IsFalse(iterator.Current.UnscheduleEvent); + Assert.IsFalse(iterator.Current.ResetBehaviour); + + Assert.IsTrue(iterator.MoveNext()); + Assert.AreEqual(4, @event.State); + Assert.IsTrue(iterator.Current.RescheduleTime <= 0); + Assert.IsFalse(iterator.Current.ContinueBehaviour); + Assert.IsFalse(iterator.Current.ScheduleNewEvent); + Assert.IsTrue(iterator.Current.UnscheduleEvent); + Assert.IsFalse(iterator.Current.ResetBehaviour); + + Assert.IsFalse(iterator.MoveNext()); + Assert.AreEqual(4, @event.State); + } + + [Test] + public void Run_Complex() + { + var event1 = new PublicTestEvent(4); + var event2 = new PublicTestEvent(4); + + var sim = new PublicSimulationController(event1.QueueKey.TicksPerFrame); + sim.QueuedEvents.Enqueue(event1); + sim.QueuedEvents.Enqueue(event2); + + Assert.AreSame(event1, sim.QueuedEvents.Peek()); + Assert.AreEqual(2, sim.QueuedEvents.Count); + + event1.Run(sim); + Assert.AreEqual(1, event1.State); + Assert.AreNotSame(event1, sim.QueuedEvents.Peek()); + Assert.AreSame(event2, sim.QueuedEvents.Peek()); + Assert.AreEqual(2, sim.QueuedEvents.Count); + + event2.QueueKey.Tick = 2; + sim.QueuedEvents.Update(event2); + + Assert.AreSame(event1, sim.QueuedEvents.Peek()); + Assert.AreEqual(1, event1.State); + + event1.Run(sim); + Assert.AreEqual(4, event1.State); + Assert.AreNotSame(event1, sim.QueuedEvents.Peek()); + Assert.AreSame(event2, sim.QueuedEvents.Peek()); + Assert.AreEqual(2, sim.QueuedEvents.Count); + + Assert.AreSame(event2, sim.QueuedEvents.Dequeue()); + Assert.AreEqual(1, sim.QueuedEvents.Count); + + Assert.AreSame(event1.ScheduledEvent, sim.QueuedEvents.Dequeue()); + Assert.AreEqual(0, sim.QueuedEvents.Count); + } + } +} \ No newline at end of file diff --git a/Tests/Events/EventTests.cs.meta b/Tests/Events/EventTests.cs.meta new file mode 100755 index 0000000..de78cbd --- /dev/null +++ b/Tests/Events/EventTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7224cd757d1732f49bed34f1f233d526 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: