Skip to content

Commit

Permalink
⚙ Added ability to schedule new events
Browse files Browse the repository at this point in the history
- Implemented ability to schedule new events from within the events
- Improved Event's BehaviourResult structure
- Further improved tests
  • Loading branch information
dolejska-daniel committed Dec 25, 2020
1 parent eedbebf commit c179b3c
Show file tree
Hide file tree
Showing 12 changed files with 469 additions and 33 deletions.
2 changes: 2 additions & 0 deletions Scripts/CoroutineMonoSimulationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
88 changes: 73 additions & 15 deletions Scripts/Events/BehaviourResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand All @@ -23,48 +34,95 @@ 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>
/// Event should be removed from the simulation.
/// </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()
{
Expand All @@ -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;
}
}
52 changes: 40 additions & 12 deletions Scripts/Events/EventBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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();
Expand All @@ -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();
}
}
6 changes: 3 additions & 3 deletions Scripts/Events/IEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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();
}
}
5 changes: 5 additions & 0 deletions Scripts/Events/SimulationTimeEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
17 changes: 14 additions & 3 deletions Scripts/ISimulationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions Scripts/SimulationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions Tests/Events.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c179b3c

Please sign in to comment.