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 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; you do not need to use this class directly.
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 [Number]
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 [Number] time at which +action+ should run; must be >= {#now}
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 [nil]
49
+ # @return [Event]
50
50
  #
51
51
  def at time, &action
52
52
  raise "cannot schedule event in the past" if time < now
53
- @events.push(Event.new(time, action))
54
- nil
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 [Number] delay after which +action+ should run; non-negative
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 [nil]
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 [Number] interval non-negative
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 [Number, nil]
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.top
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 get special treatment: can avoid doing a push and a
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.replace_top(event)
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 TODO
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 [Number] now as {#now}
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 [nil]
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 [nil]
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
  #
@@ -16,7 +16,7 @@ module DiscreteEvent
16
16
  # class Foo
17
17
  # def do_stuff
18
18
  # # NB: FakeRand.for won't work if you write "Kernel::rand" instead of
19
- # # just "rand," here.
19
+ # # just "rand" here.
20
20
  # puts rand
21
21
  # end
22
22
  # end
@@ -1,6 +1,6 @@
1
1
  module DiscreteEvent
2
2
  VERSION_MAJOR = 1
3
- VERSION_MINOR = 0
3
+ VERSION_MINOR = 1
4
4
  VERSION_PATCH = 0
5
5
  VERSION = [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH].join('.')
6
6
  end
@@ -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
- # as currently implemented, the "next" event includes the current event
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.0.0
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-05-08 00:00:00.000000000 Z
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: &80923720 !ruby/object:Gem::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: 1.0.0
21
+ version: 2.0.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *80923720
24
+ version_requirements: *74031670
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: gemma
27
- requirement: &80923460 !ruby/object:Gem::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.0.0
32
+ version: 2.4.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *80923460
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.0.0 Documentation
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: -360047181
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: -360047181
79
+ hash: 326435089
80
80
  requirements: []
81
81
  rubyforge_project: discrete_event
82
82
  rubygems_version: 1.8.17