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 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