discrete_event 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +7 -0
- data/lib/discrete_event/event_queue.rb +58 -24
- data/lib/discrete_event/events.rb +11 -2
- data/lib/discrete_event/fake_rand.rb +1 -1
- data/lib/discrete_event/version.rb +1 -1
- data/test/discrete_event/discrete_event_test.rb +178 -2
- metadata +11 -11
data/README.rdoc
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
http://github.com/jdleesmiller/discrete_event
|
4
4
|
|
5
|
+
{<img src="https://secure.travis-ci.org/jdleesmiller/discrete_event.png"/>}[http://travis-ci.org/jdleesmiller/discrete_event]
|
6
|
+
|
5
7
|
== SYNOPSIS
|
6
8
|
|
7
9
|
This gem provides some tools for discrete event simulation (DES) in ruby. The
|
@@ -130,6 +132,11 @@ You may also be interested in the Ruby bindings of the GNU Science Library, whic
|
|
130
132
|
|
131
133
|
== HISTORY
|
132
134
|
|
135
|
+
<em>1.1.0:</em>
|
136
|
+
* compatibility with PQueue 2.x; fix for event cancellation (thanks: joshcarter)
|
137
|
+
* added {DiscreteEvent::EventQueue#run_to}
|
138
|
+
* updated dependency versions
|
139
|
+
|
133
140
|
<em>1.0.0:</em>
|
134
141
|
* split {DiscreteEvent::EventQueue} out of DiscreteEvent::Simulation for
|
135
142
|
easier sharing between objects
|
@@ -13,7 +13,7 @@ module DiscreteEvent
|
|
13
13
|
#
|
14
14
|
class EventQueue
|
15
15
|
#
|
16
|
-
# Event queue entry for events
|
16
|
+
# Event queue entry for events.
|
17
17
|
#
|
18
18
|
Event = Struct.new(:time, :action)
|
19
19
|
|
@@ -21,7 +21,7 @@ module DiscreteEvent
|
|
21
21
|
# Current time (taken from the currently executing event, if any). You can
|
22
22
|
# use floating point or integer time.
|
23
23
|
#
|
24
|
-
# @return [
|
24
|
+
# @return [Numeric]
|
25
25
|
#
|
26
26
|
attr_reader :now
|
27
27
|
|
@@ -42,32 +42,54 @@ module DiscreteEvent
|
|
42
42
|
# Schedule +action+ (a block) to run at the given +time+; +time+ must not be
|
43
43
|
# in the past.
|
44
44
|
#
|
45
|
-
# @param [
|
45
|
+
# @param [Numeric] time at which +action+ should run; must be >= {#now}
|
46
46
|
#
|
47
47
|
# @yield [] action to be run at +time+
|
48
48
|
#
|
49
|
-
# @return [
|
49
|
+
# @return [Event]
|
50
50
|
#
|
51
51
|
def at time, &action
|
52
52
|
raise "cannot schedule event in the past" if time < now
|
53
|
-
|
54
|
-
|
53
|
+
event = Event.new(time, action)
|
54
|
+
@events.push(event)
|
55
|
+
event
|
55
56
|
end
|
56
57
|
|
57
58
|
#
|
58
59
|
# Schedule +action+ (a block) to run after the given +delay+ (with respect
|
59
60
|
# to {#now}).
|
60
61
|
#
|
61
|
-
# @param [
|
62
|
+
# @param [Numeric] delay after which +action+ should run; non-negative
|
62
63
|
#
|
63
64
|
# @yield [] action to be run after +delay+
|
64
65
|
#
|
65
|
-
# @return [
|
66
|
+
# @return [Event]
|
66
67
|
#
|
67
68
|
def after delay, &action
|
68
69
|
at(@now + delay, &action)
|
69
70
|
end
|
70
71
|
|
72
|
+
#
|
73
|
+
# Cancel an event previously created with {#at} or {#after}.
|
74
|
+
#
|
75
|
+
# @param [Event] event the event to cancel
|
76
|
+
#
|
77
|
+
# @return [nil]
|
78
|
+
#
|
79
|
+
def cancel event
|
80
|
+
# not very efficient but hopefully not used very often
|
81
|
+
temp = []
|
82
|
+
until @events.empty? || @events.top.time > event.time
|
83
|
+
e = @events.pop
|
84
|
+
break if e.equal?(event)
|
85
|
+
temp << e
|
86
|
+
end
|
87
|
+
temp.each do |e|
|
88
|
+
@events.push(e)
|
89
|
+
end
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
71
93
|
#
|
72
94
|
# Schedule +action+ (a block) to run for each element in the given list
|
73
95
|
# (possibly at different times).
|
@@ -152,7 +174,7 @@ module DiscreteEvent
|
|
152
174
|
# but it is somewhat more efficient to call +recur_after+, and, if you do,
|
153
175
|
# the named method is not necessary.
|
154
176
|
#
|
155
|
-
# @param [
|
177
|
+
# @param [Numeric] interval non-negative
|
156
178
|
#
|
157
179
|
# @return [nil]
|
158
180
|
#
|
@@ -199,7 +221,7 @@ module DiscreteEvent
|
|
199
221
|
# (that is, the current event hasn't finished yet, so it's still in some
|
200
222
|
# sense the next event).
|
201
223
|
#
|
202
|
-
# @return [
|
224
|
+
# @return [Numeric, nil]
|
203
225
|
#
|
204
226
|
def next_event_time
|
205
227
|
event = @events.top
|
@@ -216,25 +238,17 @@ module DiscreteEvent
|
|
216
238
|
# @return [Boolean] false if there are no more events.
|
217
239
|
#
|
218
240
|
def run_next
|
219
|
-
event = @events.
|
241
|
+
event = @events.pop
|
220
242
|
if event
|
221
243
|
# run the action
|
222
244
|
@now = event.time
|
223
245
|
event.action.call
|
224
246
|
|
225
|
-
# recurring events
|
226
|
-
# pop by reusing the Event at the top of the heap, but with a new time
|
227
|
-
#
|
228
|
-
# NB: this assumes that the top element in the heap can't change due to
|
229
|
-
# the event that we just ran, which is the case here, because we don't
|
230
|
-
# allow events to be created in the past, and because of the internals
|
231
|
-
# of the PQueue datastructure
|
247
|
+
# Handle recurring events.
|
232
248
|
if @recur_interval
|
233
249
|
event.time = @now + @recur_interval
|
234
|
-
@events.
|
250
|
+
@events.push(event)
|
235
251
|
@recur_interval = nil
|
236
|
-
else
|
237
|
-
@events.pop
|
238
252
|
end
|
239
253
|
|
240
254
|
true
|
@@ -243,11 +257,31 @@ module DiscreteEvent
|
|
243
257
|
end
|
244
258
|
end
|
245
259
|
|
260
|
+
#
|
261
|
+
# Run events until the given time (inclusive). When this method returns,
|
262
|
+
# {#now} is +time+, and all events scheduled to run at times up to and
|
263
|
+
# including +time+ have been run.
|
264
|
+
#
|
265
|
+
# @param [Numeric] time to run to (inclusive)
|
266
|
+
#
|
267
|
+
# @return [nil]
|
268
|
+
#
|
269
|
+
def run_to time
|
270
|
+
# add an event to ensure that we actually stop at the given time, even if
|
271
|
+
# there isn't an event in the queue
|
272
|
+
at time do
|
273
|
+
# nothing
|
274
|
+
end
|
275
|
+
run_next until @events.empty? || next_event_time > time
|
276
|
+
nil
|
277
|
+
end
|
278
|
+
|
246
279
|
#
|
247
280
|
# Allow for the creation of a ruby +Enumerator+ for the simulation. This
|
248
|
-
# yields for each event.
|
281
|
+
# yields for each event. Note that this enumerator may return infinitely
|
282
|
+
# many events, if there are repeating events ({#every}).
|
249
283
|
#
|
250
|
-
# @example
|
284
|
+
# @example
|
251
285
|
# eq = EventQueue.new
|
252
286
|
# eq.at 13 do
|
253
287
|
# puts "hi"
|
@@ -261,7 +295,7 @@ module DiscreteEvent
|
|
261
295
|
#
|
262
296
|
# @yield [now] called immediately after each event runs
|
263
297
|
#
|
264
|
-
# @yieldparam [
|
298
|
+
# @yieldparam [Numeric] now as {#now}
|
265
299
|
#
|
266
300
|
# @return [self]
|
267
301
|
#
|
@@ -14,7 +14,7 @@ module DiscreteEvent
|
|
14
14
|
#
|
15
15
|
# @yield [] action to be run at +time+
|
16
16
|
#
|
17
|
-
# @return [
|
17
|
+
# @return [Event]
|
18
18
|
#
|
19
19
|
def at time, &action
|
20
20
|
event_queue.at(time, &action)
|
@@ -27,12 +27,21 @@ module DiscreteEvent
|
|
27
27
|
#
|
28
28
|
# @yield [] action to be run after +delay+
|
29
29
|
#
|
30
|
-
# @return [
|
30
|
+
# @return [Event]
|
31
31
|
#
|
32
32
|
def after delay, &action
|
33
33
|
event_queue.after(delay, &action)
|
34
34
|
end
|
35
35
|
|
36
|
+
#
|
37
|
+
# See {EventQueue#cancel}.
|
38
|
+
#
|
39
|
+
# @return [nil]
|
40
|
+
#
|
41
|
+
def cancel event
|
42
|
+
event_queue.cancel(event)
|
43
|
+
end
|
44
|
+
|
36
45
|
#
|
37
46
|
# See {EventQueue#at_each}.
|
38
47
|
#
|
@@ -202,8 +202,7 @@ class TestDiscreteEvent < Test::Unit::TestCase
|
|
202
202
|
assert s.run_next
|
203
203
|
assert_nil s.next_event_time
|
204
204
|
|
205
|
-
|
206
|
-
assert_equal [0, 5], output
|
205
|
+
assert_equal [5, nil], output
|
207
206
|
end
|
208
207
|
|
209
208
|
def test_enumerator
|
@@ -253,5 +252,182 @@ class TestDiscreteEvent < Test::Unit::TestCase
|
|
253
252
|
[5, ["a", "b", "c"]], # third and fourth objects consumed
|
254
253
|
[6, ["a", "b", "c", "d"]]], output
|
255
254
|
end
|
255
|
+
|
256
|
+
def test_cancel_single
|
257
|
+
#
|
258
|
+
# cancel the only event in the queue
|
259
|
+
#
|
260
|
+
out = []
|
261
|
+
q = EventQueue.new(0)
|
262
|
+
e_a = q.at( 5) { out << :a }
|
263
|
+
q.cancel e_a
|
264
|
+
assert !q.run_next
|
265
|
+
assert_equal [], out
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_cancel_empty
|
269
|
+
#
|
270
|
+
# cancel an event in the past; this should have no effect
|
271
|
+
#
|
272
|
+
out = []
|
273
|
+
q = EventQueue.new(0)
|
274
|
+
e_a = q.at( 5) { out << :a }
|
275
|
+
assert q.run_next
|
276
|
+
assert_equal [:a], out
|
277
|
+
q.cancel e_a
|
278
|
+
assert !q.run_next
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_cancel_first
|
282
|
+
#
|
283
|
+
# should be able to cancel the first (next) event in the queue
|
284
|
+
#
|
285
|
+
out = []
|
286
|
+
q = EventQueue.new(0)
|
287
|
+
e_a = q.at( 5) { out << :a }
|
288
|
+
e_b = q.at(10) { out << :b }
|
289
|
+
q.cancel e_a
|
290
|
+
nil while q.run_next
|
291
|
+
assert_equal [:b], out
|
292
|
+
end
|
293
|
+
|
294
|
+
def test_cancel_first2
|
295
|
+
out = []
|
296
|
+
q = EventQueue.new(0)
|
297
|
+
|
298
|
+
e_a = q.at( 5) do
|
299
|
+
out << :a
|
300
|
+
|
301
|
+
e_c = q.at(6) { out << :c }
|
302
|
+
|
303
|
+
q.cancel e_a # This should have no effect
|
304
|
+
end
|
305
|
+
|
306
|
+
e_b = q.at(10) do
|
307
|
+
out << :b
|
308
|
+
end
|
309
|
+
|
310
|
+
nil while q.run_next
|
311
|
+
assert_equal [:a, :c, :b], out
|
312
|
+
end
|
313
|
+
|
314
|
+
def test_cancel_last
|
315
|
+
#
|
316
|
+
# should be able to cancel the last event in the queue
|
317
|
+
#
|
318
|
+
out = []
|
319
|
+
q = EventQueue.new(0)
|
320
|
+
e_a = q.at( 5) { out << :a }
|
321
|
+
e_b = q.at(10) { out << :b }
|
322
|
+
q.cancel e_b
|
323
|
+
nil while q.run_next
|
324
|
+
assert_equal [:a], out
|
325
|
+
end
|
326
|
+
|
327
|
+
def test_cancel_middle
|
328
|
+
#
|
329
|
+
# should be able to cancel an event in the middle of the queue
|
330
|
+
#
|
331
|
+
out = []
|
332
|
+
q = EventQueue.new(0)
|
333
|
+
e_a = q.at( 5) { out << :a }
|
334
|
+
e_b = q.at(10) { out << :b }
|
335
|
+
e_c = q.at(13) { out << :c }
|
336
|
+
q.cancel e_b
|
337
|
+
nil while q.run_next
|
338
|
+
assert_equal [:a, :c], out
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_cancel_at_same_time_1
|
342
|
+
#
|
343
|
+
# if there are two events at the same time, and we cancel one of them, the
|
344
|
+
# correct event should be cancelled (can't just compare on times)
|
345
|
+
#
|
346
|
+
out = []
|
347
|
+
q = EventQueue.new(0)
|
348
|
+
e_a = q.at 10 do
|
349
|
+
out << :a
|
350
|
+
end
|
351
|
+
e_b = q.at 10 do
|
352
|
+
out << :b
|
353
|
+
end
|
354
|
+
assert !e_a.equal?(e_b)
|
355
|
+
q.cancel e_a
|
356
|
+
nil while q.run_next
|
357
|
+
assert_equal [:b], out
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_cancel_at_same_time_2
|
361
|
+
#
|
362
|
+
# see test_cancel_at_same_time_1
|
363
|
+
#
|
364
|
+
out = []
|
365
|
+
q = EventQueue.new(0)
|
366
|
+
e_a = q.at 10 do
|
367
|
+
out << :a
|
368
|
+
end
|
369
|
+
e_b = q.at 10 do
|
370
|
+
out << :b
|
371
|
+
end
|
372
|
+
q.cancel e_b
|
373
|
+
nil while q.run_next
|
374
|
+
assert_equal [:a], out
|
375
|
+
end
|
376
|
+
|
377
|
+
def test_cancel_self
|
378
|
+
#
|
379
|
+
# not sure that there is any reason to do this, but it should work
|
380
|
+
#
|
381
|
+
q = EventQueue.new(0)
|
382
|
+
out = []
|
383
|
+
@e_a = nil
|
384
|
+
@e_a = q.at 3 do
|
385
|
+
out << :a
|
386
|
+
q.cancel @e_a
|
387
|
+
end
|
388
|
+
nil while q.run_next
|
389
|
+
assert_equal [:a], out
|
390
|
+
end
|
391
|
+
|
392
|
+
def test_cancel_from_every_block
|
393
|
+
#
|
394
|
+
# check that we don't change the top event when we cancel
|
395
|
+
#
|
396
|
+
q = EventQueue.new(0)
|
397
|
+
out = []
|
398
|
+
e_a = q.at 10 do # will cancel this one
|
399
|
+
out << :a
|
400
|
+
end
|
401
|
+
q.every 9, 9 do
|
402
|
+
out << q.now
|
403
|
+
q.cancel e_a
|
404
|
+
end
|
405
|
+
q.at 9 do # same time as first instance of repeating event
|
406
|
+
out << :b
|
407
|
+
end
|
408
|
+
3.times { assert q.run_next }
|
409
|
+
assert_equal [9, :b, 18], out
|
410
|
+
end
|
411
|
+
|
412
|
+
def test_run_to
|
413
|
+
q = EventQueue.new(0)
|
414
|
+
out = []
|
415
|
+
q.at 10 do
|
416
|
+
out << :a
|
417
|
+
end
|
418
|
+
|
419
|
+
# run to just before the first event; it should not fire
|
420
|
+
q.run_to 9
|
421
|
+
assert_equal [], out
|
422
|
+
assert_equal 9, q.now
|
423
|
+
|
424
|
+
q.run_to 10
|
425
|
+
assert_equal [:a], out
|
426
|
+
assert_equal 10, q.now
|
427
|
+
|
428
|
+
# running again should have no effect
|
429
|
+
q.run_to 10
|
430
|
+
assert_equal [:a], out
|
431
|
+
end
|
256
432
|
end
|
257
433
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: discrete_event
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,30 +9,30 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pqueue
|
16
|
-
requirement: &
|
16
|
+
requirement: &74031670 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 2.0.2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *74031670
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: gemma
|
27
|
-
requirement: &
|
27
|
+
requirement: &74031240 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 2.
|
32
|
+
version: 2.4.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *74031240
|
36
36
|
description: Some simple primitives for event-based discrete event simulation.
|
37
37
|
email:
|
38
38
|
- jdleesmiller@gmail.com
|
@@ -56,7 +56,7 @@ rdoc_options:
|
|
56
56
|
- --main
|
57
57
|
- README.rdoc
|
58
58
|
- --title
|
59
|
-
- discrete_event-1.
|
59
|
+
- discrete_event-1.1.0 Documentation
|
60
60
|
require_paths:
|
61
61
|
- lib
|
62
62
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -67,7 +67,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
67
|
version: '0'
|
68
68
|
segments:
|
69
69
|
- 0
|
70
|
-
hash:
|
70
|
+
hash: 326435089
|
71
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
76
|
version: '0'
|
77
77
|
segments:
|
78
78
|
- 0
|
79
|
-
hash:
|
79
|
+
hash: 326435089
|
80
80
|
requirements: []
|
81
81
|
rubyforge_project: discrete_event
|
82
82
|
rubygems_version: 1.8.17
|