ruck 0.2.0 → 0.3.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/LICENSE +21 -0
- data/README.markdown +146 -0
- data/Rakefile +11 -0
- data/VERSION +1 -1
- data/examples/ex01.rb +27 -0
- data/examples/ex02.rb +31 -0
- data/examples/ex03.rb +75 -0
- data/examples/ex04.rb +33 -0
- data/examples/ex05.rb +33 -0
- data/examples/ex06.rb +110 -0
- data/examples/space/media/Beep.wav +0 -0
- data/examples/space/media/Space.png +0 -0
- data/examples/space/media/Star.png +0 -0
- data/examples/space/media/Starfighter.bmp +0 -0
- data/examples/space/space.rb +307 -0
- data/lib/ruck.rb +4 -4
- data/lib/ruck/clock.rb +137 -0
- data/lib/ruck/event_clock.rb +39 -0
- data/lib/ruck/shred.rb +135 -0
- data/lib/ruck/shreduler.rb +149 -0
- data/ruck.gemspec +45 -8
- data/spec/clock_spec.rb +218 -0
- data/spec/shred_spec.rb +116 -0
- data/spec/shreduler_spec.rb +200 -0
- metadata +58 -12
- data/README +0 -48
- data/lib/ruck/shreduling.rb +0 -135
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
module Ruck
|
3
|
+
class EventClock
|
4
|
+
attr_reader :now
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@now = 0
|
8
|
+
@waiting = Hash.new { |hash, event| hash[event] = [] }
|
9
|
+
@raised = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# fast-forward this clock by the given time delta
|
13
|
+
def fast_forward(dt)
|
14
|
+
@now += dt
|
15
|
+
end
|
16
|
+
|
17
|
+
def schedule(obj, event = nil)
|
18
|
+
@waiting[event] << obj
|
19
|
+
end
|
20
|
+
|
21
|
+
def unschedule(obj)
|
22
|
+
@waiting.each { |event, objs| objs.delete(obj) }
|
23
|
+
@raised.delete(obj)
|
24
|
+
end
|
25
|
+
|
26
|
+
def next
|
27
|
+
[@raised.first, 0] if @raised.length > 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def unschedule_next
|
31
|
+
[@raised.shift, 0] if @raised.length > 0
|
32
|
+
end
|
33
|
+
|
34
|
+
def raise_all(event)
|
35
|
+
@raised += @waiting[event]
|
36
|
+
@waiting[event].clear
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/ruck/shred.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
|
2
|
+
module Ruck
|
3
|
+
|
4
|
+
# A resumable Proc implemented using continuation. If the given
|
5
|
+
# block calls #pause during its execution, its execution is paused
|
6
|
+
# and the caller resumed. The second time the Shred is called, it
|
7
|
+
# resumes where it left off.
|
8
|
+
#
|
9
|
+
# If #pause is called anywhere but inside the given block, I can
|
10
|
+
# almost guarantee that strange things will happen.
|
11
|
+
|
12
|
+
class CallccShred
|
13
|
+
# I don't mean to actually expose @proc. I noticed that Ruby 1.8's
|
14
|
+
# garbage collection cycles become much longer when @proc (a
|
15
|
+
# Continuation) is returned from a custom method, but not
|
16
|
+
# if returned from an attr_reader. I use attr_reader and alias it
|
17
|
+
# to running? to avoid this cost.
|
18
|
+
attr_reader :proc
|
19
|
+
alias running? proc
|
20
|
+
|
21
|
+
@@current_shreds = []
|
22
|
+
|
23
|
+
# the currently executing shred
|
24
|
+
def self.current
|
25
|
+
@@current_shreds.last
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(&block)
|
29
|
+
@proc = block || Proc.new{}
|
30
|
+
end
|
31
|
+
|
32
|
+
# pause execution by saving this execution point and returning
|
33
|
+
# to the point where go was called
|
34
|
+
def pause
|
35
|
+
return unless Shred.current == self
|
36
|
+
|
37
|
+
@@current_shreds.pop
|
38
|
+
|
39
|
+
callcc do |cont|
|
40
|
+
@proc = cont
|
41
|
+
@caller.call
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# begin or resume execution
|
46
|
+
def call(*args)
|
47
|
+
return unless @proc
|
48
|
+
|
49
|
+
callcc do |cont|
|
50
|
+
@caller = cont
|
51
|
+
|
52
|
+
@@current_shreds << self
|
53
|
+
@proc.call
|
54
|
+
|
55
|
+
# if we made it here, we're done
|
56
|
+
@@current_shreds.pop
|
57
|
+
@proc = nil
|
58
|
+
@caller.call
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# alias for call. It takes arguments, but ignores them.
|
63
|
+
def [](*args)
|
64
|
+
call(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
# returns true if calling this Shred again will have no effect
|
68
|
+
def finished?
|
69
|
+
!running?
|
70
|
+
end
|
71
|
+
|
72
|
+
# makes it so calling this Shred in the future will have no effect
|
73
|
+
def kill
|
74
|
+
@proc = nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# See the documentation for CallccShred
|
79
|
+
class FiberShred
|
80
|
+
@@current_shreds = []
|
81
|
+
|
82
|
+
def self.current
|
83
|
+
@@current_shreds.last
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize(&block)
|
87
|
+
@fiber = Fiber.new(&block)
|
88
|
+
end
|
89
|
+
|
90
|
+
def pause
|
91
|
+
return unless Shred.current == self
|
92
|
+
|
93
|
+
@@current_shreds.pop
|
94
|
+
|
95
|
+
Fiber.yield
|
96
|
+
end
|
97
|
+
|
98
|
+
def call(*args)
|
99
|
+
return unless @fiber
|
100
|
+
@@current_shreds << self
|
101
|
+
@fiber.resume
|
102
|
+
rescue FiberError
|
103
|
+
@fiber = nil
|
104
|
+
ensure
|
105
|
+
@@current_shreds.pop
|
106
|
+
end
|
107
|
+
|
108
|
+
def [](*args)
|
109
|
+
call(*args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def finished?
|
113
|
+
@fiber.nil?
|
114
|
+
end
|
115
|
+
|
116
|
+
def running?
|
117
|
+
!finished?
|
118
|
+
end
|
119
|
+
|
120
|
+
def kill
|
121
|
+
@fiber = nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Fiber was introduced in Ruby 1.9 and supports a cleaner implementation
|
126
|
+
# of Shred than the callcc-based version, but I would like to support
|
127
|
+
# Ruby 1.8 as well.
|
128
|
+
if defined? Fiber
|
129
|
+
class Shred < FiberShred
|
130
|
+
end
|
131
|
+
else
|
132
|
+
class Shred < CallccShred
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
|
2
|
+
module Ruck
|
3
|
+
class Shreduler
|
4
|
+
attr_reader :clock
|
5
|
+
attr_reader :event_clock
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@clock = Clock.new
|
9
|
+
@event_clock = EventClock.new
|
10
|
+
@clock.add_child_clock(@event_clock)
|
11
|
+
end
|
12
|
+
|
13
|
+
# this Shreduler's idea of the current time
|
14
|
+
def now
|
15
|
+
@clock.now
|
16
|
+
end
|
17
|
+
|
18
|
+
# schedules the given Shred at the given time, on the given Clock.
|
19
|
+
# if no time is given, it is scheduled for immediate execution.
|
20
|
+
# if no Clock is given, it is scheduled on the default Clock.
|
21
|
+
def shredule(shred, time = nil, clock = nil)
|
22
|
+
(clock || @clock).schedule(shred, time)
|
23
|
+
shred
|
24
|
+
end
|
25
|
+
|
26
|
+
# unschedules the provided Shred
|
27
|
+
def unshredule(shred)
|
28
|
+
@clock.unschedule(shred)
|
29
|
+
end
|
30
|
+
|
31
|
+
# wakes up all Shreds waiting on the given event
|
32
|
+
def raise_all(event)
|
33
|
+
event_clock.raise_all(event)
|
34
|
+
end
|
35
|
+
|
36
|
+
# runs the next scheduled Shred, if one exists, returning that Shred
|
37
|
+
def run_one
|
38
|
+
shred, relative_time = @clock.unschedule_next
|
39
|
+
return nil unless shred
|
40
|
+
|
41
|
+
fast_forward(relative_time) if relative_time > 0
|
42
|
+
invoke_shred(shred)
|
43
|
+
end
|
44
|
+
|
45
|
+
# runs until all Shreds have died, or are all waiting on events
|
46
|
+
def run
|
47
|
+
loop { return unless run_one }
|
48
|
+
end
|
49
|
+
|
50
|
+
# runs shreds until the given target time, then fast-forwards to
|
51
|
+
# that time
|
52
|
+
def run_until(target_time)
|
53
|
+
return if target_time < now
|
54
|
+
|
55
|
+
loop do
|
56
|
+
shred, relative_time = next_shred
|
57
|
+
break unless shred
|
58
|
+
break unless now + relative_time <= target_time
|
59
|
+
run_one
|
60
|
+
end
|
61
|
+
|
62
|
+
# I hope rounding errors are okay
|
63
|
+
fast_forward(target_time - now)
|
64
|
+
end
|
65
|
+
|
66
|
+
# makes this the global shreduler, adding convenience methods to
|
67
|
+
# Object and Shred to make it easier to use
|
68
|
+
def make_convenient
|
69
|
+
$shreduler = self
|
70
|
+
|
71
|
+
Shred.module_eval do
|
72
|
+
class << self
|
73
|
+
include ShredConvenienceMethods
|
74
|
+
end
|
75
|
+
end
|
76
|
+
Object.module_eval { include ObjectConvenienceMethods }
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def invoke_shred(shred)
|
82
|
+
begin
|
83
|
+
shred.call
|
84
|
+
rescue Exception => e
|
85
|
+
puts e.inspect
|
86
|
+
end
|
87
|
+
shred
|
88
|
+
end
|
89
|
+
|
90
|
+
# if you override this method, you should probably call super
|
91
|
+
def fast_forward(dt)
|
92
|
+
@clock.fast_forward(dt)
|
93
|
+
end
|
94
|
+
|
95
|
+
def next_shred
|
96
|
+
clock.next
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module ShredConvenienceMethods
|
101
|
+
# yields the given amount of time on the global Shreduler, using the
|
102
|
+
# provided Clock if given
|
103
|
+
def yield(dt, clock = nil)
|
104
|
+
clock ||= $shreduler.clock
|
105
|
+
$shreduler.shredule(Shred.current, clock.now + dt, clock)
|
106
|
+
Shred.current.pause
|
107
|
+
end
|
108
|
+
|
109
|
+
# sleeps, waiting on the given event on the default EventClock of
|
110
|
+
# the global Shreduler
|
111
|
+
def wait_on(event)
|
112
|
+
$shreduler.shredule(Shred.current, event, $shreduler.event_clock)
|
113
|
+
Shred.current.pause
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
module ObjectConvenienceMethods
|
118
|
+
# creates a new Shred with the given block on the global Shreduler
|
119
|
+
def spork(&block)
|
120
|
+
$shreduler.shredule(Shred.new(&block))
|
121
|
+
end
|
122
|
+
|
123
|
+
# creates a new Shred with the given block on the global Shreduler,
|
124
|
+
# automatically surrounded by loop { }. If the delay_or_event parameter
|
125
|
+
# is given, a Shred.yield(delay) or Shred.wait_on(event) is inserted
|
126
|
+
# before the call to your block.
|
127
|
+
def spork_loop(delay_or_event = nil, clock = nil, &block)
|
128
|
+
shred = Shred.new do
|
129
|
+
while Shred.current.running?
|
130
|
+
if delay_or_event
|
131
|
+
if delay_or_event.is_a?(Numeric)
|
132
|
+
Shred.yield(delay_or_event, clock)
|
133
|
+
else
|
134
|
+
Shred.wait_on(delay_or_event)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
block.call
|
139
|
+
end
|
140
|
+
end
|
141
|
+
$shreduler.shredule(shred)
|
142
|
+
end
|
143
|
+
|
144
|
+
# raises an event on the default EventClock of the global Shreduler.
|
145
|
+
def raise_event(event)
|
146
|
+
$shreduler.event_clock.raise_all(event)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/ruck.gemspec
CHANGED
@@ -5,44 +5,81 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ruck}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Tom Lieber"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-08-15}
|
13
13
|
s.description = %q{ Ruck uses continuations and a simple scheduler to ensure "shreds"
|
14
14
|
(Ruck threads) are woken at precisely the right time according
|
15
15
|
to its virtual clock. Schedulers can map virtual time to samples
|
16
16
|
in a WAV file, real time, time in a MIDI file, or anything else
|
17
17
|
by overriding "sim_to" in the Shreduler class.
|
18
|
+
|
19
|
+
A small library of useful unit generators and plenty of examples
|
20
|
+
are provided. See the README or the web page for details.
|
18
21
|
}
|
19
22
|
s.email = %q{tom@alltom.com}
|
20
23
|
s.extra_rdoc_files = [
|
21
|
-
"
|
24
|
+
"LICENSE",
|
25
|
+
"README.markdown"
|
22
26
|
]
|
23
27
|
s.files = [
|
24
28
|
".gitignore",
|
25
|
-
"
|
29
|
+
"LICENSE",
|
30
|
+
"README.markdown",
|
26
31
|
"Rakefile",
|
27
32
|
"VERSION",
|
33
|
+
"examples/ex01.rb",
|
34
|
+
"examples/ex02.rb",
|
35
|
+
"examples/ex03.rb",
|
36
|
+
"examples/ex04.rb",
|
37
|
+
"examples/ex05.rb",
|
38
|
+
"examples/ex06.rb",
|
39
|
+
"examples/space/media/Beep.wav",
|
40
|
+
"examples/space/media/Space.png",
|
41
|
+
"examples/space/media/Star.png",
|
42
|
+
"examples/space/media/Starfighter.bmp",
|
43
|
+
"examples/space/space.rb",
|
28
44
|
"lib/ruck.rb",
|
29
|
-
"lib/ruck/
|
30
|
-
"ruck.
|
45
|
+
"lib/ruck/clock.rb",
|
46
|
+
"lib/ruck/event_clock.rb",
|
47
|
+
"lib/ruck/shred.rb",
|
48
|
+
"lib/ruck/shreduler.rb",
|
49
|
+
"ruck.gemspec",
|
50
|
+
"spec/clock_spec.rb",
|
51
|
+
"spec/shred_spec.rb",
|
52
|
+
"spec/shreduler_spec.rb"
|
31
53
|
]
|
32
54
|
s.homepage = %q{http://github.com/alltom/ruck}
|
33
55
|
s.rdoc_options = ["--charset=UTF-8"]
|
34
56
|
s.require_paths = ["lib"]
|
35
|
-
s.rubygems_version = %q{1.3.
|
57
|
+
s.rubygems_version = %q{1.3.7}
|
36
58
|
s.summary = %q{strong timing for Ruby: cooperative threads on a virtual clock}
|
59
|
+
s.test_files = [
|
60
|
+
"spec/clock_spec.rb",
|
61
|
+
"spec/shred_spec.rb",
|
62
|
+
"spec/shreduler_spec.rb",
|
63
|
+
"examples/ex01.rb",
|
64
|
+
"examples/ex02.rb",
|
65
|
+
"examples/ex03.rb",
|
66
|
+
"examples/ex04.rb",
|
67
|
+
"examples/ex05.rb",
|
68
|
+
"examples/ex06.rb",
|
69
|
+
"examples/space/space.rb"
|
70
|
+
]
|
37
71
|
|
38
72
|
if s.respond_to? :specification_version then
|
39
73
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
40
74
|
s.specification_version = 3
|
41
75
|
|
42
|
-
if Gem::Version.new(Gem::
|
76
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
77
|
+
s.add_runtime_dependency(%q<PriorityQueue>, [">= 0"])
|
43
78
|
else
|
79
|
+
s.add_dependency(%q<PriorityQueue>, [">= 0"])
|
44
80
|
end
|
45
81
|
else
|
82
|
+
s.add_dependency(%q<PriorityQueue>, [">= 0"])
|
46
83
|
end
|
47
84
|
end
|
48
85
|
|
data/spec/clock_spec.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
|
2
|
+
require "ruck"
|
3
|
+
|
4
|
+
include Ruck
|
5
|
+
|
6
|
+
class MockOccurrenceObj
|
7
|
+
def self.next_name
|
8
|
+
@@next_name ||= "a"
|
9
|
+
name = @@next_name
|
10
|
+
@@next_name = @@next_name.succ
|
11
|
+
name
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@name = MockOccurrenceObj.next_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
"MockOccurrenceObj<#{@name}>"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Clock do
|
24
|
+
before(:each) do
|
25
|
+
@clock = Clock.new
|
26
|
+
@clocks = [@clock]
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when creating" do
|
30
|
+
it "starts with now = 0" do
|
31
|
+
Clock.new.now.should == 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when fast-forwarding" do
|
36
|
+
it "works" do
|
37
|
+
@clock.fast_forward(1)
|
38
|
+
@clock.now.should == 1
|
39
|
+
@clock.fast_forward(1)
|
40
|
+
@clock.now.should == 2
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when scheduling" do
|
45
|
+
it "should default to using the current time" do
|
46
|
+
@clock.fast_forward(3)
|
47
|
+
@occurrence = MockOccurrenceObj.new
|
48
|
+
@clock.schedule(@occurrence)
|
49
|
+
@clock.next.should == [@occurrence, 0]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should use the given time if provided" do
|
53
|
+
@clock.fast_forward(3)
|
54
|
+
@occurrence = MockOccurrenceObj.new
|
55
|
+
@clock.schedule(@occurrence, 5)
|
56
|
+
@clock.next.should == [@occurrence, 2]
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with no occurrences" do
|
60
|
+
it "next should be nil" do
|
61
|
+
@clock.next.should == nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "with multiple occurrences" do
|
66
|
+
before(:each) do
|
67
|
+
@next_occurrence = MockOccurrenceObj.new
|
68
|
+
@occurrence_after = MockOccurrenceObj.new
|
69
|
+
@clock.schedule(@occurrence_after, 2)
|
70
|
+
@clock.schedule(@next_occurrence, 1)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "knows the next scheduled occurrence" do
|
74
|
+
@clock.next.should == [@next_occurrence, 1]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "can dequeue the next scheduled occurrence" do
|
78
|
+
@clock.unschedule_next.should == [@next_occurrence, 1]
|
79
|
+
@clock.next.should == [@occurrence_after, 2]
|
80
|
+
end
|
81
|
+
|
82
|
+
it "can enqueue and dequeue a new occurrence" do
|
83
|
+
@last_occurrence = MockOccurrenceObj.new
|
84
|
+
@clock.schedule(@last_occurrence, 3)
|
85
|
+
@clock.unschedule_next.should == [@next_occurrence, 1]
|
86
|
+
@clock.unschedule_next.should == [@occurrence_after, 2]
|
87
|
+
@clock.next.should == [@last_occurrence, 3]
|
88
|
+
end
|
89
|
+
|
90
|
+
it "can interleavedly enqueue and dequeue a new occurrence" do
|
91
|
+
@last_occurrence = MockOccurrenceObj.new
|
92
|
+
@clock.unschedule_next.should == [@next_occurrence, 1]
|
93
|
+
@clock.schedule(@last_occurrence, 1)
|
94
|
+
@clock.unschedule_next.should == [@last_occurrence, 1]
|
95
|
+
@clock.next.should == [@occurrence_after, 2]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "with sub-clocks" do
|
101
|
+
before do
|
102
|
+
# clock/clocks[0]
|
103
|
+
# - clocks[1] x1
|
104
|
+
# - clocks[2] x2
|
105
|
+
# - clocks[3] x2
|
106
|
+
@clocks << @clock.add_child_clock(Clock.new(1))
|
107
|
+
@clocks << @clock.add_child_clock(Clock.new(2))
|
108
|
+
@clocks << @clocks[2].add_child_clock(Clock.new(2))
|
109
|
+
end
|
110
|
+
|
111
|
+
context "when fast-forwarding" do
|
112
|
+
it "fast-forwards children clocks" do
|
113
|
+
@clock.fast_forward(1)
|
114
|
+
@clocks[1].now.should == 1
|
115
|
+
@clocks[2].now.should == 2
|
116
|
+
@clocks[3].now.should == 4
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when finding the next occurrence" do
|
121
|
+
it "should return the correct time offset" do
|
122
|
+
@occurrence = MockOccurrenceObj.new
|
123
|
+
@clocks[2].schedule(@occurrence, 4)
|
124
|
+
@clock.next.should == [@occurrence, 2]
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return the correct time offset in a sub-clock 2 levels deep" do
|
128
|
+
@occurrence = MockOccurrenceObj.new
|
129
|
+
@clocks[3].schedule(@occurrence, 8)
|
130
|
+
@clock.next.should == [@occurrence, 2]
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should return the correct time offset after a fast-forward" do
|
134
|
+
@occurrence = MockOccurrenceObj.new
|
135
|
+
@clocks[2].schedule(@occurrence, 4)
|
136
|
+
@clock.fast_forward(1)
|
137
|
+
@clock.next.should == [@occurrence, 1]
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should return the correct time offset in a sub-clock 2 levels deep after a fast-forward" do
|
141
|
+
@occurrence = MockOccurrenceObj.new
|
142
|
+
@clocks[3].schedule(@occurrence, 8)
|
143
|
+
@clock.fast_forward(1)
|
144
|
+
@clock.next.should == [@occurrence, 1]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context "when dequeuing the next occurrence" do
|
149
|
+
it "should work when the occurrence is on the parent clock" do
|
150
|
+
@occurrence = MockOccurrenceObj.new
|
151
|
+
@clocks[0].schedule(@occurrence, 4)
|
152
|
+
@clock.unschedule_next.should == [@occurrence, 4]
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should work when the occurrence is one clock deep" do
|
156
|
+
@occurrence = MockOccurrenceObj.new
|
157
|
+
@clocks[1].schedule(@occurrence, 4)
|
158
|
+
@clock.unschedule_next.should == [@occurrence, 4]
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should work when the occurrence is one clock deep and account for rate" do
|
162
|
+
@occurrence = MockOccurrenceObj.new
|
163
|
+
@clocks[2].schedule(@occurrence, 4)
|
164
|
+
@clock.unschedule_next.should == [@occurrence, 2]
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should work when the occurrence is two clocks deep and account for rate" do
|
168
|
+
@occurrence = MockOccurrenceObj.new
|
169
|
+
@clocks[3].schedule(@occurrence, 4)
|
170
|
+
@clock.unschedule_next.should == [@occurrence, 1]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "when dequeuing occurrences" do
|
176
|
+
it "should work" do
|
177
|
+
@occurrence = MockOccurrenceObj.new
|
178
|
+
@clock.schedule(@occurrence, 2)
|
179
|
+
@clock.unschedule(@occurrence).should == 2
|
180
|
+
end
|
181
|
+
|
182
|
+
context "with sub-clocks" do
|
183
|
+
before(:each) do
|
184
|
+
# clock/clocks[0]
|
185
|
+
# - clocks[1] x1
|
186
|
+
# - clocks[2] x2
|
187
|
+
# - clocks[3] x2
|
188
|
+
@clocks << @clock.add_child_clock(Clock.new(1))
|
189
|
+
@clocks << @clock.add_child_clock(Clock.new(2))
|
190
|
+
@clocks << @clocks[2].add_child_clock(Clock.new(2))
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should work with the parent clock" do
|
194
|
+
@occurrence = MockOccurrenceObj.new
|
195
|
+
@clocks[0].schedule(@occurrence, 2)
|
196
|
+
@clocks[0].unschedule(@occurrence).should == 2
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should work one clock deep" do
|
200
|
+
@occurrence = MockOccurrenceObj.new
|
201
|
+
@clocks[1].schedule(@occurrence, 2)
|
202
|
+
@clocks[0].unschedule(@occurrence).should == 2
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should work one clock deep and adjust for rate" do
|
206
|
+
@occurrence = MockOccurrenceObj.new
|
207
|
+
@clocks[2].schedule(@occurrence, 2)
|
208
|
+
@clocks[0].unschedule(@occurrence).should == 1
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should work two clocks deep and adjust for rate" do
|
212
|
+
@occurrence = MockOccurrenceObj.new
|
213
|
+
@clocks[3].schedule(@occurrence, 4)
|
214
|
+
@clocks[0].unschedule(@occurrence).should == 1
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|