diff --git a/Scripts/Controls.meta b/Scripts/Controls.meta new file mode 100755 index 0000000..358ea35 --- /dev/null +++ b/Scripts/Controls.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 60596c5676071e048b1d961c8b8820cd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Controls/Facility.cs b/Scripts/Controls/Facility.cs new file mode 100755 index 0000000..7e0d2b0 --- /dev/null +++ b/Scripts/Controls/Facility.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using UnityDES.Events; +using UnityDES.Utils; + +namespace UnityDES.Controls +{ + public class Facility + where TEvent : class, IEvent + { + /// + /// Internal facility entry structure. + /// + public class FacilityEntry : IQueueItem + { + /// + /// Priority of the event in the queue. + /// + public int QueueKey { get; set; } + + /// + /// Event in the queue. + /// + public readonly TEvent Event; + + public FacilityEntry(int priority, TEvent @event) + { + QueueKey = priority; + Event = @event; + } + + public static Comparer Comparer = Comparer.Create( + (a, b) => a.QueueKey - b.QueueKey); + } + + /// + /// Default claim priority of new events. + /// + const int DEFAULT_PRIORITY = 100; + + /// + /// Complete concurrent capacity of the facility. + /// + public int Capacity { get; protected set; } + + /// + /// Number of events with currently active claims. + /// + public int CurrentlyUsing { get => Inside.Count; } + + /// + /// Priority queue with events waiting for facility being freed. + /// + protected PriorityQueue WaitingQueue { get; } + + /// + /// Priority queue with events interrupted during their active claims. + /// + protected PriorityQueue InterruptedQueue { get; } + + /// + /// Set of events with currently active claims. + /// + protected SortedSet Inside { get; } + + /// + /// Mapping of event instances to facility's internal entries. + /// + protected Dictionary EventEntryMapping { get; } + + /// + /// Simulation controller. + /// + protected ISimulationController Controller { get; } + + /// + /// Initializes the facility control object for provided simulation () with given . + /// + /// + /// Controller of corresponding simulation + /// Concurrent event claim capacity of the facility + public Facility(ISimulationController 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.Comparer); + InterruptedQueue = new PriorityQueue(FacilityEntry.Comparer); + Inside = new SortedSet(FacilityEntry.Comparer); + EventEntryMapping = new Dictionary(); + } + + /// + /// Claims a signle capacity slot for provied . + /// The event may skip the queue or even interrupt active claims having enough . + /// + /// + /// Event claiming the facility capacity slot + /// Priority of the event's claim + /// + /// Continue behaviour type if claim has been successful, Unschedule otherwise. + /// The event will be rescheduled by the facility when it frees up. + /// + public BehaviourResult 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.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.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.Continue(); + } + + /// + /// Gives up active claim of the provied . + /// + /// + /// Event giving up the active claim + /// Continue behaviour type + public BehaviourResult 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.Continue(); + } + } +} diff --git a/Scripts/Controls/Facility.cs.meta b/Scripts/Controls/Facility.cs.meta new file mode 100755 index 0000000..d47da97 --- /dev/null +++ b/Scripts/Controls/Facility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1313f1246897ca444a5aa8ce4dbae9af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Controls.meta b/Tests/Controls.meta new file mode 100755 index 0000000..0485881 --- /dev/null +++ b/Tests/Controls.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: caa4dbaeed08fe149890a6cfe56c1419 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Controls/FacilityTests.cs b/Tests/Controls/FacilityTests.cs new file mode 100755 index 0000000..c0ff06d --- /dev/null +++ b/Tests/Controls/FacilityTests.cs @@ -0,0 +1,296 @@ +using System.Collections.Generic; +using NUnit.Framework; +using UnityDES; +using UnityDES.Events; +using UnityDES.Controls; +using UnityDES.Utils; + +namespace Controls +{ + public class FacilityTests + { + public class PublicEvent : SimulationTimeEvent + { + public PublicEvent(int ticksPerFrame = 10) : base(ticksPerFrame) + { + } + + public override IEnumerator, SimulationTime>> Behaviour() + { + // reschedule to next tick + yield return Reschedule(QueueKey.TickLength, false); + + // remove the event from the queue + yield return Unschedule(); + } + + public new void IncreaseKey(float time) => base.IncreaseKey(time); + } + + public class PublicFacility : Facility, SimulationTime> + { + public new PriorityQueue WaitingQueue { get => base.WaitingQueue; } + + public new PriorityQueue InterruptedQueue { get => base.InterruptedQueue; } + + public new SortedSet Inside { get => base.Inside; } + + public new Dictionary, FacilityEntry> EventEntryMapping { get => base.EventEntryMapping; } + + public PublicFacility(ISimulationController, SimulationTime> controller, int capacity = 1) : base(controller, capacity) + { + } + } + + [Test] + public void Claim_Free() + { + var controller = new SimulationController(4); + var facility = new PublicFacility(controller); + + var event1 = new PublicEvent(4); + + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(0, facility.CurrentlyUsing); + Assert.AreEqual(0, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + var behaviour = facility.Claim(event1); + + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(1, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsTrue(behaviour.ContinueBehaviour); + Assert.IsFalse(behaviour.UnscheduleEvent); + } + + [Test] + public void Claim_Full() + { + var controller = new SimulationController(4); + var facility = new PublicFacility(controller); + + var event1 = new PublicEvent(4); + var event2 = new PublicEvent(4); + + facility.Claim(event1); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(1, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + var behaviour = facility.Claim(event2); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(2, facility.EventEntryMapping.Count); + Assert.AreEqual(1, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsFalse(behaviour.ContinueBehaviour); + Assert.IsTrue(behaviour.UnscheduleEvent); + } + + [Test] + public void Claim_Full_Priority() + { + var controller = new SimulationController(4); + var facility = new PublicFacility(controller); + + var event1 = new PublicEvent(4); + var event2 = new PublicEvent(4); + + facility.Claim(event1); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(1, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + var behaviour = facility.Claim(event2, 200); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(2, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(1, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.InterruptedQueue.Queued(facility.EventEntryMapping[event1])); + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event2])); + + Assert.IsTrue(behaviour.ContinueBehaviour); + Assert.IsFalse(behaviour.UnscheduleEvent); + } + + [Test] + public void Claim_Full_PrioritySelction() + { + var controller = new SimulationController(4); + var facility = new PublicFacility(controller); + + var event1 = new PublicEvent(4); + var event2 = new PublicEvent(4); + var event3 = new PublicEvent(4); + + facility.Claim(event1, 75); + facility.Claim(event2, 50); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(2, facility.EventEntryMapping.Count); + Assert.AreEqual(1, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.WaitingQueue.Queued(facility.EventEntryMapping[event2])); + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event1])); + + var behaviour = facility.Claim(event3); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(3, facility.EventEntryMapping.Count); + Assert.AreEqual(1, facility.WaitingQueue.Count); + Assert.AreEqual(1, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.WaitingQueue.Queued(facility.EventEntryMapping[event2])); + Assert.IsTrue(facility.InterruptedQueue.Queued(facility.EventEntryMapping[event1])); + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event3])); + + Assert.IsTrue(behaviour.ContinueBehaviour); + Assert.IsFalse(behaviour.UnscheduleEvent); + } + + [Test] + public void Free() + { + var controller = new SimulationController(4); + var facility = new PublicFacility(controller); + + var event1 = new PublicEvent(4); + + facility.Claim(event1); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(1, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + var behaviour = facility.Free(event1); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(0, facility.CurrentlyUsing); + Assert.AreEqual(0, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsTrue(behaviour.ContinueBehaviour); + Assert.IsFalse(behaviour.UnscheduleEvent); + } + + [Test] + public void Free_WithWaiting() + { + var controller = new SimulationController(4); + var facility = new PublicFacility(controller); + + var event1 = new PublicEvent(4); + var event2 = new PublicEvent(4); + facility.Claim(event1); + + var behaviour = facility.Claim(event2); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(2, facility.EventEntryMapping.Count); + Assert.AreEqual(1, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsFalse(behaviour.ContinueBehaviour); + Assert.IsTrue(behaviour.UnscheduleEvent); + + facility.Free(event1); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(1, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event2])); + } + + [Test] + public void Free_WithInterrupted() + { + var controller = new SimulationController(4); + var facility = new PublicFacility(controller); + + var event1 = new PublicEvent(4); + var event2 = new PublicEvent(4); + facility.Claim(event1); + + var behaviour = facility.Claim(event2, 200); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(2, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(1, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.InterruptedQueue.Queued(facility.EventEntryMapping[event1])); + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event2])); + + Assert.IsTrue(behaviour.ContinueBehaviour); + Assert.IsFalse(behaviour.UnscheduleEvent); + + facility.Free(event2); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(1, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event1])); + } + + [Test] + public void Free_WithWaiting_WithInterrupted() + { + var controller = new SimulationController(4); + var facility = new PublicFacility(controller); + + var event1 = new PublicEvent(4); + var event2 = new PublicEvent(4); + var event3 = new PublicEvent(4); + facility.Claim(event1); + facility.Claim(event2, 200); + facility.Claim(event3); + + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(3, facility.EventEntryMapping.Count); + Assert.AreEqual(1, facility.WaitingQueue.Count); + Assert.AreEqual(1, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.InterruptedQueue.Queued(facility.EventEntryMapping[event1])); + Assert.IsTrue(facility.WaitingQueue.Queued(facility.EventEntryMapping[event3])); + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event2])); + + facility.Free(event2); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(2, facility.EventEntryMapping.Count); + Assert.AreEqual(1, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.WaitingQueue.Queued(facility.EventEntryMapping[event3])); + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event1])); + + facility.Free(event1); + Assert.AreEqual(1, facility.Capacity); + Assert.AreEqual(1, facility.CurrentlyUsing); + Assert.AreEqual(1, facility.EventEntryMapping.Count); + Assert.AreEqual(0, facility.WaitingQueue.Count); + Assert.AreEqual(0, facility.InterruptedQueue.Count); + + Assert.IsTrue(facility.Inside.Contains(facility.EventEntryMapping[event3])); + } + } +} \ No newline at end of file diff --git a/Tests/Controls/FacilityTests.cs.meta b/Tests/Controls/FacilityTests.cs.meta new file mode 100755 index 0000000..161ea55 --- /dev/null +++ b/Tests/Controls/FacilityTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 009c0fca3d7fe5a48af8a5694f588286 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: