diff --git a/lib/priority_queue.rb b/lib/priority_queue.rb index 0d77512d5061c6ec4b620d23a0e9fd266cc6bd1f..a8d023952c511f82ad6a4a6e20ea201db8427893 100644 --- a/lib/priority_queue.rb +++ b/lib/priority_queue.rb @@ -4,6 +4,7 @@ class PriorityQueue clear end + # Push +element+ onto the priority queue. def <<(element) @elements << element # bubble up the element that we just added @@ -12,11 +13,13 @@ class PriorityQueue alias push << + # Inspect the element at the head of the queue. def peek # the first element will always be the min, because of the heap constraint @elements[1] end + # Remove and return the next element from the queue, determined by priority. def pop # remove the last element of the list min = @elements[1] @@ -26,10 +29,12 @@ class PriorityQueue min end + # Reset the priority queue to empty. def clear @elements = [nil] end + # Return a boolean indicating whether the queue is empty or not def empty? @elements.length < 2 end diff --git a/lib/simplekit.rb b/lib/simplekit.rb index a50f7885134dc82fac4fd8c9bf91ce682f6a6b58..2af35510a2373025696cdbc550914ab7c8e3458a 100644 --- a/lib/simplekit.rb +++ b/lib/simplekit.rb @@ -4,9 +4,9 @@ require_relative 'priority_queue' # The +SimpleKit+ module provides basic event scheduling capabilities. # # Including +SimpleKit+ in your simulation model gives you methods +:run+, -# +:model_time+, +:schedule+, and +:halt+ as mixins. You <b>MUST NOT</b> -# provide your own implementations of methods with these names in your model. -# All but +:run+ are delegated to the +EventScheduler+ class. +# +:model_time+, +:schedule+, +:cancel+, +:cancel_all+, and +:halt+ as mixins. +# <b>DO NOT</b> create your own implementations of methods with these names +# in your model. All but +:run+ are delegated to the +EventScheduler+ class. module SimpleKit # The set of module methods to be passed to the EventScheduler # if not found in the model class. @@ -52,17 +52,25 @@ module SimpleKit # - +delay+ -> the amount of time which should elapse before # the event executes. # - +args+ -> zero or more named arguments to pass to the event - # at invocation time. + # at invocation time. These should be specified with labels, and + # consequently they can be placed in any order. def schedule(event, delay, **args) raise 'Model scheduled event with negative delay.' if delay < 0 @event_list.push EventNotice.new(event, @model_time, delay, args) end + # Cancel an individual occurrence of event type +event+. + # If no +args+ are provided, the next scheduled occurrence of +event+ + # is targeted. If a subset of the event's +args+ is provided, they must + # all be a match with the corresponding +args+ of the scheduled event + # in order for the cancellation to apply, but +args+ which are not + # specified do not affect the target event matching. def cancel(event, **args) @cancel_set[event] ||= Set.new @cancel_set[event].add(args.empty? ? nil : args) end + # Cancel all currently scheduled events of type +event+. def cancel_all(event) if event PriorityQueue.new.tap do |pq| @@ -76,9 +84,9 @@ module SimpleKit # Start execution of a model. The simulation +model_time+ is initialized # to zero and the model is initialized via the mandatory +init+ method. - # Then loop while events are pending on the +event_list+. The event with - # the smallest time is popped, +model_time+ is updated to the event time, - # and the event method is invoked. + # Then loop while events are pending on the +event_list+. The event with the + # smallest time is popped, +model_time+ is updated to the event time, and + # the event method is invoked with the +args+, if any, set by +schedule+. def run @model_time = 0.0 @user_model.init @@ -101,6 +109,8 @@ module SimpleKit private + # Private method that returns a boolean to determine if +event_notice+ + # represents an event subject to cancellation. def should_cancel?(event_notice) e = event_notice.event if @cancel_set.key? e @@ -134,6 +144,8 @@ module SimpleKit end include Comparable + # Compare EventNotice objects for ordering, first by time, + # breaking ties using priority. def <=>(other) (time <=> other.time).tap do |outcome| return priority <=> other.priority if outcome == 0