Class: DiscreteEvent::EventQueue
- Inherits:
-
Object
- Object
- DiscreteEvent::EventQueue
- Defined in:
- lib/discrete_event/event_queue.rb
Overview
Queue of pending events; also keeps track of the clock (the current time).
There are two key terms:
-
action: any Ruby block
-
event: an action to be executed at some specified time in the future
Events are usually created using the #at and #after methods. The methods #at_each, #every and #recur_after make some important special cases more efficient.
See the README for an example.
Direct Known Subclasses
Defined Under Namespace
Classes: Event
Instance Attribute Summary collapse
-
#events ⇒ PQueue
readonly
Event queue.
-
#now ⇒ Numeric
readonly
Current time (taken from the currently executing event, if any).
Instance Method Summary collapse
-
#after(delay) { ... } ⇒ Event
Schedule
action
(a block) to run after the givendelay
(with respect to #now). -
#at(time) { ... } ⇒ Event
Schedule
action
(a block) to run at the giventime
;time
must not be in the past. -
#at_each(elements, time = nil) {|element| ... } ⇒ nil
Schedule
action
(a block) to run for each element in the given list (possibly at different times). - #cancel(event) ⇒ nil
-
#each {|now| ... } ⇒ self
Allow for the creation of a ruby
Enumerator
for the simulation. -
#every(interval, start = 0) ⇒ nil
Schedule
action
(a block) to run periodically. -
#initialize(now = 0.0) ⇒ EventQueue
constructor
A new instance of EventQueue.
-
#next_event_time ⇒ Numeric?
The time of the next queued event, or
nil
if there are no queued events. -
#recur_after(interval) ⇒ nil
When called from within an action block, repeats the action block after the specified
interval
has elapsed. -
#reset(now = 0.0) ⇒ self
Clear any pending events in the event queue and reset #now.
-
#run_next ⇒ Boolean
Run the action for the next event in the queue.
-
#run_to(time) ⇒ nil
Run events until the given time (inclusive).
Constructor Details
#initialize(now = 0.0) ⇒ EventQueue
Returns a new instance of EventQueue.
37 38 39 40 41 |
# File 'lib/discrete_event/event_queue.rb', line 37 def initialize(now = 0.0) @now = now @events = FastContainers::PriorityQueue.new(:min) @recur_interval = nil end |
Instance Attribute Details
#events ⇒ PQueue (readonly)
Event queue.
35 36 37 |
# File 'lib/discrete_event/event_queue.rb', line 35 def events @events end |
#now ⇒ Numeric (readonly)
Current time (taken from the currently executing event, if any). You can use floating point or integer time.
28 29 30 |
# File 'lib/discrete_event/event_queue.rb', line 28 def now @now end |
Instance Method Details
#after(delay) { ... } ⇒ Event
Schedule action
(a block) to run after the given delay
(with respect to #now).
70 71 72 |
# File 'lib/discrete_event/event_queue.rb', line 70 def after(delay, &action) at(@now + delay, &action) end |
#at(time) { ... } ⇒ Event
Schedule action
(a block) to run at the given time
; time
must not be in the past.
53 54 55 56 57 58 |
# File 'lib/discrete_event/event_queue.rb', line 53 def at(time, &action) raise 'cannot schedule event in the past' if time < now event = Event.new(time, action) @events.push(event, time) event end |
#at_each(elements, time = nil) {|element| ... } ⇒ nil
Schedule action
(a block) to run for each element in the given list (possibly at different times).
This method may be of interest if you have a large number of events that occur at known times. You could use #at to add each one to the event queue at the start of the simulation, but this will make adding other events more expensive. Instead, this method adds them one at a time, so only the next event is stored in the event queue.
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/discrete_event/event_queue.rb', line 131 def at_each(elements, time = nil, &action) raise ArgumentError, 'no action given' unless block_given? unless elements.empty? element = elements.shift if time.nil? element_time = element.time elsif time.is_a? Proc element_time = time.call(element) elsif time.is_a? Symbol element_time = element.send(time) else raise ArgumentError, 'bad time' end at element_time do yield element at_each elements, time, &action end end nil end |
#cancel(event) ⇒ nil
81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/discrete_event/event_queue.rb', line 81 def cancel(event) # not very efficient but hopefully not used very often temp = [] until @events.empty? || @events.top_key > event.time e = @events.top @events.pop break if e.equal?(event) temp << e end temp.each do |temp_event| @events.push(temp_event, temp_event.time) end nil end |
#each {|now| ... } ⇒ self
Allow for the creation of a ruby Enumerator
for the simulation. This yields for each event. Note that this enumerator may return infinitely many events, if there are repeating events (#every).
300 301 302 303 |
# File 'lib/discrete_event/event_queue.rb', line 300 def each yield now while run_next self end |
#every(interval, start = 0) ⇒ nil
Schedule action
(a block) to run periodically.
This is useful for statistics collection.
Note that if you specify one or more events of this kind, the simulation will never run out of events.
212 213 214 215 216 217 218 |
# File 'lib/discrete_event/event_queue.rb', line 212 def every(interval, start = 0) at start do yield recur_after interval end nil end |
#next_event_time ⇒ Numeric?
The time of the next queued event, or nil
if there are no queued events.
If this method is called from within an action block, it returns #now (that is, the current event hasn’t finished yet, so it’s still in some sense the next event).
229 230 231 |
# File 'lib/discrete_event/event_queue.rb', line 229 def next_event_time @events.top_key unless @events.empty? end |
#recur_after(interval) ⇒ nil
When called from within an action block, repeats the action block after the specified interval
has elapsed.
Calling this method from outside an action block has no effect. You may call this method at most once in an action block.
Note that you can achieve the same effect using #at and #after and a named method, as in
def demo
at 5 do
puts "now: #{now}"
after 10*rand do
demo
end
end
end
but it is somewhat more efficient to call recur_after
, and, if you do, the named method is not necessary.
184 185 186 187 188 |
# File 'lib/discrete_event/event_queue.rb', line 184 def recur_after(interval) raise 'cannot recur twice' if @recur_interval @recur_interval = interval nil end |
#reset(now = 0.0) ⇒ self
Clear any pending events in the event queue and reset #now.
310 311 312 313 314 |
# File 'lib/discrete_event/event_queue.rb', line 310 def reset(now = 0.0) @now = now @events = FastContainers::PriorityQueue.new(:min) self end |
#run_next ⇒ Boolean
Run the action for the next event in the queue.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/discrete_event/event_queue.rb', line 238 def run_next return false if @events.empty? event = @events.top @events.pop # run the action @now = event.time event.action.call # Handle recurring events. if @recur_interval event.time = @now + @recur_interval @events.push(event, event.time) @recur_interval = nil end true end |
#run_to(time) ⇒ nil
Run events until the given time (inclusive). When this method returns, #now is time
, and all events scheduled to run at times up to and including time
have been run.
267 268 269 270 271 272 273 274 275 |
# File 'lib/discrete_event/event_queue.rb', line 267 def run_to(time) # add an event to ensure that we actually stop at the given time, even if # there isn't an event in the queue at time do # nothing end run_next until @events.empty? || next_event_time > time nil end |