Skip to content

Commit

Permalink
🧹 BehaviourResult refactor and improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
dolejska-daniel committed Dec 27, 2020
1 parent b103998 commit af3a99a
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 137 deletions.
150 changes: 86 additions & 64 deletions Scripts/Events/BehaviourResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,106 @@ public struct BehaviourResult<TEvent, TKey>
where TEvent : class, IEvent<TEvent, TKey>
{
/// <summary>
/// Reserved time constant - the behaviour should continue being processed.
/// Behaviour state option.
/// The names should be self explanatory.
/// </summary>
const float TIME_CONTINUE = -1f;
public enum State
{
IGNORE = 0x00,
CONTINUE = 0x01,
SCHEDULE = 0x02,
RESCHEDULE = 0x04,
UNSCHEDULE = 0x08,
RESET = 0x10,
}

/// <summary>
/// States allowed for event returning this result.
/// </summary>
public const State ALLOWED_SELF_STATES = State.CONTINUE | State.RESCHEDULE | State.UNSCHEDULE | State.RESET;

/// <summary>
/// States allowed for events referenced by this result.
/// </summary>
public const State ALLOWED_REFERENCED_STATES = State.SCHEDULE | State.UNSCHEDULE;

/// <summary>
/// Reserved time constant - the event should be removed from the simulation.
/// Behaviour state of the event returning this result.
/// </summary>
const float TIME_UNSCHEDULE = -2f;
public State SelfState { get; internal set; }

/// <summary>
/// Minimum amount of time to pass before running the event's behaviour again.
/// </summary>
/// <remarks>
/// Values <c>&lt;0</c> are reserved for other result states.
/// </remarks>
public float RescheduleTime { get; internal set; }
public float SelfTime { get; internal set; }

/// <summary>
/// Behaviour iterator should be reset before its next run.
/// Behaviour state of the event referenced by this result.
/// </summary>
public bool ResetBehaviour { get; internal set; }
public State ReferencedState { get; internal set; }

/// <summary>
/// Simulation should continue processing the event's behaviour.
/// Event to be added to the simulation.
/// </summary>
public bool ContinueBehaviour { get => RescheduleTime == TIME_CONTINUE; }
public TEvent ReferencedEvent { get; internal set; }

/// <summary>
/// Simulation should now remove the event from the simulation.
/// Minimum amount of time to pass before running the event.
/// </summary>
public bool UnscheduleEvent { get => RescheduleTime == TIME_UNSCHEDULE; }
public float ReferencedTime { get; internal set; }

/// <summary>
/// Simulation should add a new event to the simulation.
/// Event retuning this result wants to continue processing its behaviour.
/// </summary>
public bool ScheduleNewEvent { get => NewEvent != null; }
public bool ContinueBehaviour { get => (SelfState & State.CONTINUE) != State.IGNORE; }

/// <summary>
/// Event to be added to the simulation.
/// Event retuning this result wants to reset its behaviour.
/// </summary>
public TEvent NewEvent { get; internal set; }
public bool ResetBehaviour { get => (SelfState & State.RESET) != State.IGNORE; }

/// <summary>
/// Minimum amount of time to pass before running the event.
/// Event retuning this result wants to reschedule itself.
/// </summary>
public bool RescheduleEvent { get => (SelfState & State.RESCHEDULE) != State.IGNORE; }

/// <summary>
/// Event retuning this result wants to unschedule itself.
/// </summary>
public bool UnscheduleEvent { get => (SelfState & State.UNSCHEDULE) != State.IGNORE; }

/// <summary>
/// Event retuning this result wants to schedule event it is referencing.
/// </summary>
public bool ScheduleReferenced { get => (ReferencedState & State.SCHEDULE) != State.IGNORE; }

/// <summary>
/// Event retuning this result wants to unschedule event it is referencing.
/// </summary>
public float NewEventScheduleTime { get; internal set; }
public bool UnscheduleReferenced { get => (ReferencedState & State.UNSCHEDULE) != State.IGNORE; }

BehaviourResult(float rescheduleTime, bool reset, TEvent @event = null, float scheduleTime = 0f)
BehaviourResult(State selfState, float selfTime = 0f, State referencedState = State.IGNORE, TEvent referencedEvent = null, float referencedTime = 0f)
{
RescheduleTime = rescheduleTime;
ResetBehaviour = reset;
NewEvent = @event;
NewEventScheduleTime = scheduleTime;
selfState &= ALLOWED_SELF_STATES;
referencedState &= ALLOWED_REFERENCED_STATES;

SelfState = selfState;
SelfTime = selfTime;
ReferencedState = referencedState;
ReferencedEvent = referencedEvent;
ReferencedTime = referencedTime;
}

/// <summary>
/// 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<TEvent, TKey> Continue(bool reset = false)
=> new BehaviourResult<TEvent, TKey>(State.CONTINUE | (reset ? State.RESET : State.IGNORE));

/// <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>).
Expand All @@ -74,10 +119,10 @@ public struct BehaviourResult<TEvent, TKey>
/// <returns>Behaviour result instance reflecting rescheduling</returns>
public static BehaviourResult<TEvent, TKey> Reschedule(float rescheduleTime, bool reset = true)
{
if (rescheduleTime < 0f)
throw new ArgumentException("Event's reschedule time cannot be less than 0.");
if (rescheduleTime <= 0f)
throw new ArgumentException("Event's reschedule time cannot be less than or equal to 0.");

return new BehaviourResult<TEvent, TKey>(rescheduleTime, reset);
return new BehaviourResult<TEvent, TKey>(State.RESCHEDULE | (reset ? State.RESET : State.IGNORE), rescheduleTime);
}

/// <summary>
Expand All @@ -86,55 +131,32 @@ public static BehaviourResult<TEvent, TKey> Reschedule(float rescheduleTime, boo
///
/// <returns>Behaviour result instance reflecting unscheduling</returns>
public static BehaviourResult<TEvent, TKey> Unschedule()
=> new BehaviourResult<TEvent, TKey>(TIME_UNSCHEDULE, false);

/// <summary>
/// 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<TEvent, TKey> Continue(bool reset = false)
=> new BehaviourResult<TEvent, TKey>(TIME_CONTINUE, reset);
=> new BehaviourResult<TEvent, TKey>(State.UNSCHEDULE);

/// <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>).
/// Event should be rescheduled and its behaviour run after minimum of <paramref name="rescheduleTime"/> seconds
/// or be reset and rescheduled (if <paramref name="selfReset"/> 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>
/// <param name="selfReset">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);
public static BehaviourResult<TEvent, TKey> ScheduleNew(float rescheduleTime, TEvent @event, float scheduleTime = 0f, bool selfReset = false)
=> new BehaviourResult<TEvent, TKey>(State.RESCHEDULE | (selfReset ? State.RESET : State.IGNORE), rescheduleTime, State.SCHEDULE, @event, scheduleTime);

/// <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).
/// 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="selfReset"/> is <c>True</c>).
/// </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>
/// <param name="selfReset">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()
{
if (ContinueBehaviour)
return "EventBehaviour: Continue" + (ResetBehaviour ? " and Reset" : "");

if (UnscheduleEvent)
return "EventBehaviour: Unschedule";

return "EventBehaviour: Reschedule in " + RescheduleTime + (ResetBehaviour ? " and Reset" : "");
}

public static implicit operator float(BehaviourResult<TEvent, TKey> result) => result.RescheduleTime;
public static BehaviourResult<TEvent, TKey> ScheduleNewAndContinue(TEvent @event, float scheduleTime = 0f, bool selfReset = false)
=> new BehaviourResult<TEvent, TKey>(State.CONTINUE | (selfReset ? State.RESET : State.IGNORE), 0f, State.SCHEDULE, @event, scheduleTime);
}
}
55 changes: 35 additions & 20 deletions Scripts/Events/EventBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ namespace UnityDES.Events
/// Base class for any simulation event.
/// </summary>
///
/// <typeparam name="TEvent">Type of the derived event</typeparam>
/// <typeparam name="TKey">Type of the queue key</typeparam>
public abstract class EventBase<TEvent, TKey> : IEvent<TEvent, TKey>
where TEvent : class, IEvent<TEvent, TKey>
{
public TKey QueueKey { get; set; }

/// <summary>
/// Should always be current simulation time during processing the event's behaviour.
/// </summary>
public TKey SimulationTime { get => QueueKey; }

public IEnumerator<BehaviourResult<TEvent, TKey>> BehaviourCycle { get; protected set; }
Expand Down Expand Up @@ -53,44 +57,47 @@ public void Run(ISimulationController<TEvent, TKey> simulationController)
unfinished = BehaviourCycle.MoveNext();
behaviourResult = BehaviourCycle.Current;

if (behaviourResult.ScheduleNewEvent)
if (behaviourResult.ScheduleReferenced)
{
simulationController.Schedule(behaviourResult.NewEvent, behaviourResult.NewEventScheduleTime);
// the currently processed event wants to schedule new referenced event
simulationController.Schedule(behaviourResult.ReferencedEvent, behaviourResult.ReferencedTime);
}
else if (behaviourResult.UnscheduleReferenced)
{
// the currently processed event wants to unschedule referenced event
simulationController.Unschedule(behaviourResult.ReferencedEvent);
}

if (behaviourResult.ResetBehaviour)
{
// the currently processed event wants its to be reset
BehaviourCycle = Behaviour();
}
}
while (unfinished && behaviourResult.ContinueBehaviour);

if (unfinished && !behaviourResult.UnscheduleEvent)
if (unfinished && behaviourResult.RescheduleEvent)
{
// update the event's key (must be done before rescheduling!)
IncreaseKey(behaviourResult.RescheduleTime);
IncreaseKey(behaviourResult.SelfTime);

// reschedule the event
if (!simulationController.Reschedule(This()))
throw new ApplicationException("Rescheduling of existing event has failed!");

if (behaviourResult.ResetBehaviour)
{
// create new behaviour iterator instance
BehaviourCycle = Behaviour();
}
}
else
else if (!unfinished || behaviourResult.UnscheduleEvent)
{
// the event's behaviour either completely finished or it voluntarily wants to be unscheduled
// remove the event from the simulation
simulationController.Unschedule(This());
if (!simulationController.Unschedule(This()))
throw new ApplicationException("Unscheduling of existing event has failed!");
}
else
{
throw new ApplicationException("This should never happen.");
}
}

/// <inheritdoc cref="BehaviourResult{TEvent, TKey}.ScheduleNew(float, TEvent, float, bool)"/>
protected BehaviourResult<TEvent, TKey> ScheduleNew(float rescheduleTime, TEvent @event, float scheduleTime, bool reset = false)
=> BehaviourResult<TEvent, TKey>.ScheduleNew(rescheduleTime, @event, scheduleTime, reset);

/// <inheritdoc cref="BehaviourResult{TEvent, TKey}.ScheduleNewAndContinue(TEvent, float, bool)"/>
protected BehaviourResult<TEvent, TKey> ScheduleNewAndContinue(TEvent @event, float scheduleTime, bool reset = false)
=> BehaviourResult<TEvent, TKey>.ScheduleNewAndContinue(@event, scheduleTime, reset);

/// <inheritdoc cref="BehaviourResult{TEvent, TKey}.Continue(bool)"/>
protected BehaviourResult<TEvent, TKey> Continue(bool reset = false)
=> BehaviourResult<TEvent, TKey>.Continue(reset);
Expand All @@ -102,5 +109,13 @@ protected BehaviourResult<TEvent, TKey> Reschedule(float rescheduleTime, bool re
/// <inheritdoc cref="BehaviourResult{TEvent, TKey}.Unschedule"/>
protected BehaviourResult<TEvent, TKey> Unschedule()
=> BehaviourResult<TEvent, TKey>.Unschedule();

/// <inheritdoc cref="BehaviourResult{TEvent, TKey}.ScheduleNew(float, TEvent, float, bool)"/>
protected BehaviourResult<TEvent, TKey> ScheduleNew(float rescheduleTime, TEvent @event, float scheduleTime, bool reset = false)
=> BehaviourResult<TEvent, TKey>.ScheduleNew(rescheduleTime, @event, scheduleTime, reset);

/// <inheritdoc cref="BehaviourResult{TEvent, TKey}.ScheduleNewAndContinue(TEvent, float, bool)"/>
protected BehaviourResult<TEvent, TKey> ScheduleNewAndContinue(TEvent @event, float scheduleTime, bool reset = false)
=> BehaviourResult<TEvent, TKey>.ScheduleNewAndContinue(@event, scheduleTime, reset);
}
}
9 changes: 3 additions & 6 deletions Scripts/Events/IEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public interface IEvent<TEvent, TKey> : IQueueItem<TKey>
public IEnumerator<BehaviourResult<TEvent, TKey>> BehaviourCycle { get; }

/// <summary>
/// Runs the behaviour of the event and manages immediate rescheduling through the simulation instance.
/// Runs the behaviour of the event and manages its immediate rescheduling through the simulation instance.
/// It also processes any simulation requests the event's behaviour could have - schedule/unschedule referenced event, etc.
/// </summary>
void Run(ISimulationController<TEvent, TKey> simulationController);

Expand All @@ -26,11 +27,7 @@ public interface IEvent<TEvent, TKey> : IQueueItem<TKey>
/// Its logic can be split into multiple stages using yield.
/// </summary>
///
/// <returns>
/// <c>Null</c> value will remove the event from the queue.
/// <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>
/// <returns><see cref="BehaviourResult{TEvent, TKey}"/> describing what the event wants to do</returns>
public abstract IEnumerator<BehaviourResult<TEvent, TKey>> Behaviour();
}
}
Loading

0 comments on commit af3a99a

Please sign in to comment.