Skip to content

Commit

Permalink
⚙ Implemented Facility control object
Browse files Browse the repository at this point in the history
  • Loading branch information
dolejska-daniel committed Dec 25, 2020
1 parent c179b3c commit 29acfac
Show file tree
Hide file tree
Showing 6 changed files with 540 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Scripts/Controls.meta

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

206 changes: 206 additions & 0 deletions Scripts/Controls/Facility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using UnityDES.Events;
using UnityDES.Utils;

namespace UnityDES.Controls
{
public class Facility<TEvent, TKey>
where TEvent : class, IEvent<TEvent, TKey>
{
/// <summary>
/// Internal facility entry structure.
/// </summary>
public class FacilityEntry : IQueueItem<int>
{
/// <summary>
/// Priority of the event in the queue.
/// </summary>
public int QueueKey { get; set; }

/// <summary>
/// Event in the queue.
/// </summary>
public readonly TEvent Event;

public FacilityEntry(int priority, TEvent @event)
{
QueueKey = priority;
Event = @event;
}

public static Comparer<FacilityEntry> Comparer = Comparer<FacilityEntry>.Create(
(a, b) => a.QueueKey - b.QueueKey);
}

/// <summary>
/// Default claim priority of new events.
/// </summary>
const int DEFAULT_PRIORITY = 100;

/// <summary>
/// Complete concurrent capacity of the facility.
/// </summary>
public int Capacity { get; protected set; }

/// <summary>
/// Number of events with currently active claims.
/// </summary>
public int CurrentlyUsing { get => Inside.Count; }

/// <summary>
/// Priority queue with events waiting for facility being freed.
/// </summary>
protected PriorityQueue<FacilityEntry, int> WaitingQueue { get; }

/// <summary>
/// Priority queue with events interrupted during their active claims.
/// </summary>
protected PriorityQueue<FacilityEntry, int> InterruptedQueue { get; }

/// <summary>
/// Set of events with currently active claims.
/// </summary>
protected SortedSet<FacilityEntry> Inside { get; }

/// <summary>
/// Mapping of event instances to facility's internal entries.
/// </summary>
protected Dictionary<TEvent, FacilityEntry> EventEntryMapping { get; }

/// <summary>
/// Simulation controller.
/// </summary>
protected ISimulationController<TEvent, TKey> Controller { get; }

/// <summary>
/// Initializes the facility control object for provided simulation (<paramref name="controller"/>) with given <paramref name="capacity"/>.
/// </summary>
///
/// <param name="controller">Controller of corresponding simulation</param>
/// <param name="capacity">Concurrent event claim capacity of the facility</param>
public Facility(ISimulationController<TEvent, TKey> controller, int capacity = 1)
{
if (capacity < 1)
throw new ArgumentException("Capacity of the facility cannot be less than 1.");

Controller = controller;
Capacity = capacity;

WaitingQueue = new PriorityQueue<FacilityEntry, int>(FacilityEntry.Comparer);
InterruptedQueue = new PriorityQueue<FacilityEntry, int>(FacilityEntry.Comparer);
Inside = new SortedSet<FacilityEntry>(FacilityEntry.Comparer);
EventEntryMapping = new Dictionary<TEvent, FacilityEntry>();
}

/// <summary>
/// Claims a signle capacity slot for provied <paramref name="event"/>.
/// The event may skip the queue or even interrupt active claims having enough <paramref name="priority"/>.
/// </summary>
///
/// <param name="event">Event claiming the facility capacity slot</param>
/// <param name="priority">Priority of the event's claim</param>
/// <returns>
/// <c>Continue</c> behaviour type if claim has been successful, <c>Unschedule</c> otherwise.
/// The event will be rescheduled by the facility when it frees up.
/// </returns>
public BehaviourResult<TEvent, TKey> Claim(TEvent @event, int priority = DEFAULT_PRIORITY)
{
if (EventEntryMapping.ContainsKey(@event))
throw new ArgumentException("Provided event is already within the facility.");

// the event is not within this facility, create corresponding entry
var entry = new FacilityEntry(priority, @event);
// create the mapping of the event to the created internal entry
EventEntryMapping.Add(entry.Event, entry);

if (CurrentlyUsing < Capacity)
{
// there is enough room in the facility
// register active claim of the event
Inside.Add(entry);

// claim has been registered, continue with the event's behaviour
return BehaviourResult<TEvent, TKey>.Continue();
}

// there is not enough room in the facility
var existingEntry = Inside.Min;
if (FacilityEntry.Comparer.Compare(existingEntry, entry) >= 0)
{
// the smallest existing entry inside the facility is still larger than the new entry
// add this new entry to the waiting queue
WaitingQueue.Enqueue(entry);

// the event is waiting, it is necessary to remove it from the simulation
// until the claim is ready
return BehaviourResult<TEvent, TKey>.Unschedule();
}

// the existing entry has lower priority than the new entry
// unschedule the existing entry - it has just lost control over the facility
Controller.Unschedule(existingEntry.Event);

// remove the entry from inside the facility
Inside.Remove(existingEntry);
// add it to the queue of interrupted entries
InterruptedQueue.Enqueue(existingEntry);

// add new entry inside the facility
Inside.Add(entry);

// continue processing the event's behaviour
return BehaviourResult<TEvent, TKey>.Continue();
}

/// <summary>
/// Gives up active claim of the provied <paramref name="event"/>.
/// </summary>
///
/// <param name="event">Event giving up the active claim</param>
/// <returns><c>Continue</c> behaviour type</returns>
public BehaviourResult<TEvent, TKey> Free(TEvent @event)
{
if (!EventEntryMapping.ContainsKey(@event))
throw new ArgumentException("Provided event is not within the facility.");

var entry = EventEntryMapping[@event];

// remove the entry from inside the facility
Inside.Remove(entry);
// remove the event mapping
EventEntryMapping.Remove(entry.Event);

// the event may free before actually acquiring the facility
// make sure the internal queues are clear
InterruptedQueue.Dequeue(entry);
WaitingQueue.Dequeue(entry);

// activate waiting events
while (InterruptedQueue.Count > 0 && CurrentlyUsing < Capacity)
{
// there is both a previously interrupted event and a free slot in the facility
var interruptedEntry = InterruptedQueue.Dequeue();

// activate the events claim of the facility
Inside.Add(interruptedEntry);
// schedule the event to be run this tick
Controller.Schedule(interruptedEntry.Event, 0);
}

while (WaitingQueue.Count > 0 && CurrentlyUsing < Capacity)
{
// there is both a waiting event and a free slot in the facility
var waitingEntry = WaitingQueue.Dequeue();

// activate the events claim of the facility
Inside.Add(waitingEntry);
// schedule the event to be run this simulation tick
Controller.Schedule(waitingEntry.Event, 0);
}

// continue processing the event's behaviour
return BehaviourResult<TEvent, TKey>.Continue();
}
}
}
11 changes: 11 additions & 0 deletions Scripts/Controls/Facility.cs.meta

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

8 changes: 8 additions & 0 deletions Tests/Controls.meta

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

Loading

0 comments on commit 29acfac

Please sign in to comment.