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>&lt;=0</c> are reserved for other result states.
+        /// Values <c>&lt;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>&gt;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: