discrete_event 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|