fast-tcpn 0.0.5
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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/LICENSE.txt +674 -0
- data/README.md +453 -0
- data/fast-tcpn.gemspec +40 -0
- data/fast-tcpn.rb +192 -0
- data/lib/fast-tcpn.rb +25 -0
- data/lib/fast-tcpn/clone.rb +7 -0
- data/lib/fast-tcpn/clone/using_code_from_stack.rb +50 -0
- data/lib/fast-tcpn/clone/using_deep_clone.rb +10 -0
- data/lib/fast-tcpn/clone/using_deep_dive.rb +25 -0
- data/lib/fast-tcpn/clone/using_marshal.rb +8 -0
- data/lib/fast-tcpn/dsl.rb +177 -0
- data/lib/fast-tcpn/hash_marking.rb +220 -0
- data/lib/fast-tcpn/place.rb +70 -0
- data/lib/fast-tcpn/tcpn.rb +288 -0
- data/lib/fast-tcpn/timed_hash_marking.rb +87 -0
- data/lib/fast-tcpn/timed_place.rb +44 -0
- data/lib/fast-tcpn/timed_token.rb +27 -0
- data/lib/fast-tcpn/token.rb +30 -0
- data/lib/fast-tcpn/transition.rb +224 -0
- data/lib/fast-tcpn/version.rb +3 -0
- data/spec/callbacks_spec.rb +164 -0
- data/spec/dsl/page_spec.rb +195 -0
- data/spec/dsl/transition_spec.rb +41 -0
- data/spec/hash_marking_spec.rb +9 -0
- data/spec/place_spec.rb +10 -0
- data/spec/spec_helper.rb +101 -0
- data/spec/support/hash_marking_shared.rb +274 -0
- data/spec/support/place_shared.rb +66 -0
- data/spec/support/token_shared.rb +27 -0
- data/spec/support/uses_temp_files.rb +31 -0
- data/spec/tcpn_binding_spec.rb +54 -0
- data/spec/tcpn_sim_spec.rb +323 -0
- data/spec/tcpn_spec.rb +150 -0
- data/spec/timed_hash_marking_spec.rb +132 -0
- data/spec/timed_place_spec.rb +38 -0
- data/spec/timed_token_spec.rb +50 -0
- data/spec/token_spec.rb +13 -0
- data/spec/transition_spec.rb +236 -0
- metadata +156 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
module FastTCPN
|
2
|
+
|
3
|
+
# This class extends HashMarking to support timed tokens.
|
4
|
+
# It is however slower than not-timed version, so use it
|
5
|
+
# if you really need it.
|
6
|
+
class TimedHashMarking < HashMarking
|
7
|
+
InvalidTime = Class.new RuntimeError
|
8
|
+
|
9
|
+
attr_reader :time, :next_time
|
10
|
+
|
11
|
+
# Create a new TimedHashMarking
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
@time = 0
|
15
|
+
@waiting = {}
|
16
|
+
# Next time when more tokens will be available from this marking
|
17
|
+
@next_time = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates token with +object+ as its value and adds it to the marking.
|
21
|
+
# if no timestamp is given, current time will be used.
|
22
|
+
def add(objects, timestamp = @time)
|
23
|
+
unless objects.kind_of? Array
|
24
|
+
objects = [ objects ]
|
25
|
+
end
|
26
|
+
objects.each do |object|
|
27
|
+
if object.instance_of? Hash
|
28
|
+
timestamp = object[:ts] || 0
|
29
|
+
object = object[:val]
|
30
|
+
end
|
31
|
+
token = prepare_token(object, timestamp)
|
32
|
+
timestamp = token.timestamp
|
33
|
+
if timestamp > @time
|
34
|
+
add_to_waiting token
|
35
|
+
else
|
36
|
+
add_token token
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set current time for the marking.
|
42
|
+
# This will cause moving tokens from waiting to active list.
|
43
|
+
# Putting clock back will cause error.
|
44
|
+
def time=(time)
|
45
|
+
if time < @time
|
46
|
+
raise InvalidTime.new("You are trying to put back clock from #{@time} back to #{time}")
|
47
|
+
end
|
48
|
+
@time = time
|
49
|
+
@waiting.keys.sort.each do |timestamp|
|
50
|
+
if timestamp > @time
|
51
|
+
@next_time = timestamp
|
52
|
+
break
|
53
|
+
end
|
54
|
+
@waiting[timestamp].each { |token| add_token token }
|
55
|
+
@waiting.delete timestamp
|
56
|
+
end
|
57
|
+
@next_time = 0 if @waiting.empty?
|
58
|
+
@time
|
59
|
+
end
|
60
|
+
|
61
|
+
def token_type
|
62
|
+
TimedToken
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def prepare_token(object, timestamp = 0)
|
68
|
+
if object.instance_of? token_type
|
69
|
+
clone object
|
70
|
+
elsif object.kind_of? token_type
|
71
|
+
token_type.new clone(object.value), object.timestamp
|
72
|
+
else
|
73
|
+
token_type.new clone(object), timestamp
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_to_waiting(token)
|
78
|
+
@waiting[token.timestamp] ||= []
|
79
|
+
@waiting[token.timestamp] << token
|
80
|
+
if @next_time == 0 || token.timestamp < @next_time
|
81
|
+
@next_time = token.timestamp
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module FastTCPN
|
2
|
+
# This class extends Place class to support time and timed tokens.
|
3
|
+
# TimedHashMarking class is used to store tokens.
|
4
|
+
#
|
5
|
+
# It is however slower in simulation than not-timed version, so
|
6
|
+
# use with care when you really need it.
|
7
|
+
class TimedPlace < Place
|
8
|
+
# Create new timed place.
|
9
|
+
# +name+ is unique identifier of the place
|
10
|
+
# +keys+ are used to efficiently access marking in this place
|
11
|
+
# +net+ is TCPN class object, used to enable callbacks
|
12
|
+
def initialize(name, keys = {}, net = nil)
|
13
|
+
super
|
14
|
+
@marking = TimedHashMarking.new keys
|
15
|
+
@clock = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
# returns next timestamp that will cause more tokens
|
19
|
+
# to be available in this place
|
20
|
+
def next_time
|
21
|
+
@marking.next_time
|
22
|
+
end
|
23
|
+
|
24
|
+
# set current time for this place
|
25
|
+
# (will move tokens from waiting to active state).
|
26
|
+
# Putting clock back will cause error.
|
27
|
+
def time=(val)
|
28
|
+
@clock = val
|
29
|
+
@marking.time = val
|
30
|
+
end
|
31
|
+
|
32
|
+
# Adds token with specified timestamp to the place.
|
33
|
+
# Any callbacks defined for places will be fired.
|
34
|
+
def add(token, timestamp = nil)
|
35
|
+
@net.call_callbacks(:place, :add, Event.new(@name, [token], @net)) unless @net.nil?
|
36
|
+
if timestamp.nil?
|
37
|
+
@marking.add token
|
38
|
+
else
|
39
|
+
@marking.add token, timestamp
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module FastTCPN
|
2
|
+
|
3
|
+
# Extends Token class to support time.
|
4
|
+
# Stores also timestamp.
|
5
|
+
class TimedToken < Token
|
6
|
+
|
7
|
+
attr_accessor :timestamp
|
8
|
+
|
9
|
+
# Create new token with specified +value+ and +timestamp+.
|
10
|
+
def initialize(value, timestamp = 0)
|
11
|
+
super value
|
12
|
+
@timestamp = timestamp
|
13
|
+
end
|
14
|
+
|
15
|
+
# Create new token with the same value but new timestamp
|
16
|
+
# Use it to quickly delay tokens while firing a transition
|
17
|
+
# if token value change is not required.
|
18
|
+
def with_timestamp(timestamp)
|
19
|
+
self.class.new(value, timestamp)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
{ val: value, ts: timestamp }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module FastTCPN
|
2
|
+
|
3
|
+
# Represents tokens in CPN.
|
4
|
+
# Stores values, ensures correct handling of
|
5
|
+
# equality and hash values.
|
6
|
+
class Token
|
7
|
+
alias orig_hash hash
|
8
|
+
attr_reader :value, :hash, :token_id
|
9
|
+
|
10
|
+
def initialize(value)
|
11
|
+
@value = value
|
12
|
+
@token_id = object_id
|
13
|
+
@hash = orig_hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def eql?(o)
|
17
|
+
@token_id == o.token_id
|
18
|
+
rescue # faster then checking for instance_of?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
{ val: value }
|
24
|
+
end
|
25
|
+
|
26
|
+
alias == eql?
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
module FastTCPN
|
2
|
+
|
3
|
+
class OutputArc
|
4
|
+
attr_reader :place, :block
|
5
|
+
# the +block+ will be given actual binding and
|
6
|
+
# must return token that should be put in the
|
7
|
+
# +palce+
|
8
|
+
def initialize(place, block)
|
9
|
+
@place, @block = place, block
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class TCPNBinding
|
14
|
+
TokenNotFound = Class.new RuntimeError
|
15
|
+
|
16
|
+
def initialize(mapping, marking_for)
|
17
|
+
@mapping, @marking_for = mapping, marking_for
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](place_name)
|
21
|
+
token = @mapping[place_name]
|
22
|
+
if token.nil?
|
23
|
+
raise TokenNotFound.new("No mapping for place `#{place_name}`")
|
24
|
+
end
|
25
|
+
marking = @marking_for[place_name]
|
26
|
+
if marking.nil?
|
27
|
+
raise TokenNotFound.new("No marking for place `#{place_name}`")
|
28
|
+
end
|
29
|
+
if token.instance_of? Array
|
30
|
+
token.map { |t| get_new_token marking, t }
|
31
|
+
else
|
32
|
+
get_new_token marking, token
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def get_new_token(marking, token)
|
39
|
+
new_token = marking.get token
|
40
|
+
if new_token.nil?
|
41
|
+
raise TokenNotFound.new("There was no `#{token.inspect}` in `#{marking.inspect}`!")
|
42
|
+
end
|
43
|
+
new_token
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# This is implementation of TCPN transition.
|
48
|
+
# It has input and output places and it can be fired.
|
49
|
+
class Transition
|
50
|
+
InvalidToken = Class.new RuntimeError
|
51
|
+
|
52
|
+
class FiringError < RuntimeError
|
53
|
+
attr_reader :cause, :transition
|
54
|
+
|
55
|
+
def initialize(transition, cause)
|
56
|
+
@transition, @cause = transition, cause
|
57
|
+
set_backtrace @cause.backtrace
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
"<#{self.class} #{@cause.inspect} in transition `#{@transition.name}`>"
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
inspect
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_reader :name
|
70
|
+
|
71
|
+
# Class passed to callback fired when a transition is fired.
|
72
|
+
# Describes details of the event that caused the callback to be fired.
|
73
|
+
Event = Struct.new(:transition, :binding, :clock, :tcpn)
|
74
|
+
|
75
|
+
# Create new Transition.
|
76
|
+
# +name+ identifies this transition
|
77
|
+
# +net+ is reference to TCPN object, required to enable callback handling.
|
78
|
+
def initialize(name, net = nil)
|
79
|
+
@name = name
|
80
|
+
@inputs = []
|
81
|
+
@outputs = []
|
82
|
+
@sentry = nil
|
83
|
+
@net = net
|
84
|
+
end
|
85
|
+
|
86
|
+
# Add input arc from the +place+.
|
87
|
+
# No inscription currently possible,
|
88
|
+
# one token will be taken from the +place+
|
89
|
+
# each time the transition is fired.
|
90
|
+
def input(place)
|
91
|
+
raise "This is not a Place object!" unless place.kind_of? Place
|
92
|
+
@inputs << place
|
93
|
+
end
|
94
|
+
|
95
|
+
# Add output arc to the +place+.
|
96
|
+
# +block+ is the arc's expresstion, it will be called while firing
|
97
|
+
# transition. Value returned from the block will be put in output
|
98
|
+
# place. The block gets +binding+, and +clock+ values. +binding+ is
|
99
|
+
# a hash with names of input places as keys nad tokens as values.
|
100
|
+
def output(place, &block)
|
101
|
+
raise "This is not a Place object!" unless place.kind_of? Place
|
102
|
+
raise "Tried to define output arc without expression! Block is required!" unless block_given?
|
103
|
+
@outputs << OutputArc.new(place, block)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Define sentry for this transition as a block.
|
107
|
+
# The block gets three parameters: +marking_for+, +clock+ and +result+.
|
108
|
+
# +marking_for+ is a hash with input place names as keys and marking objects
|
109
|
+
# as values. Thus one can iterate over tokens from specified input places.
|
110
|
+
# This block is supposed to push a hash of valid binding to the result like this:
|
111
|
+
# result << { output_place_name1 => token, output_place_name2 => token2 }
|
112
|
+
def sentry(&block)
|
113
|
+
@sentry = block
|
114
|
+
end
|
115
|
+
|
116
|
+
# fire this transition if possible
|
117
|
+
# returns true if fired false otherwise
|
118
|
+
def fire(clock = 0)
|
119
|
+
|
120
|
+
# Marking is shuffled each time before it is
|
121
|
+
# used so here we can take first found binding
|
122
|
+
mapping = Enumerator.new do |y|
|
123
|
+
get_sentry.call(input_markings, clock, y)
|
124
|
+
end.first
|
125
|
+
|
126
|
+
return false if mapping.nil?
|
127
|
+
|
128
|
+
tcpn_binding = TCPNBinding.new mapping, input_markings
|
129
|
+
|
130
|
+
call_callbacks :before, Event.new(@name, tcpn_binding, clock, @net)
|
131
|
+
|
132
|
+
tokens_for_outputs = @outputs.map do |o|
|
133
|
+
o.block.call(tcpn_binding, clock)
|
134
|
+
end
|
135
|
+
|
136
|
+
mapping.each do |place_name, token|
|
137
|
+
unless token.kind_of? Token
|
138
|
+
t = if token.instance_of? Array
|
139
|
+
token
|
140
|
+
else
|
141
|
+
[ token ]
|
142
|
+
end
|
143
|
+
t.each do |t|
|
144
|
+
unless t.kind_of? Token
|
145
|
+
raise InvalidToken.new("#{t.inspect} put by sentry for transition `#{name}` in binding for `#{place_name}`")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
deleted = find_input(place_name).delete(token)
|
150
|
+
if deleted.nil?
|
151
|
+
raise InvalidToken.new("#{token.inspect} put by sentry for transition `#{name}` does not exists in `#{place_name}`")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
@outputs.each do |o|
|
156
|
+
token = tokens_for_outputs.shift
|
157
|
+
o.place.add token unless token.nil?
|
158
|
+
end
|
159
|
+
|
160
|
+
call_callbacks :after, Event.new(@name, mapping, clock, @net)
|
161
|
+
|
162
|
+
true
|
163
|
+
rescue InvalidToken
|
164
|
+
raise
|
165
|
+
rescue RuntimeError => e
|
166
|
+
raise FiringError.new(self, e)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns true if no custom sentry was defined for this transition.
|
170
|
+
def default_sentry?
|
171
|
+
@sentry.nil?
|
172
|
+
end
|
173
|
+
|
174
|
+
# Number of input places
|
175
|
+
def inputs_size
|
176
|
+
@inputs.size
|
177
|
+
end
|
178
|
+
|
179
|
+
# Number of output places
|
180
|
+
def outputs_size
|
181
|
+
@outputs.size
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def get_sentry
|
187
|
+
@sentry || default_sentry
|
188
|
+
end
|
189
|
+
|
190
|
+
def call_callbacks(event, event_object)
|
191
|
+
return if @net.nil?
|
192
|
+
@net.call_callbacks(:transition, event, event_object)
|
193
|
+
end
|
194
|
+
|
195
|
+
def input_markings
|
196
|
+
bnd = {}
|
197
|
+
@inputs.each do |place|
|
198
|
+
bnd[place.name] = place.marking
|
199
|
+
end
|
200
|
+
bnd
|
201
|
+
end
|
202
|
+
|
203
|
+
def find_input(name)
|
204
|
+
@inputs.each do |place|
|
205
|
+
return place if place.name == name
|
206
|
+
end
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
|
210
|
+
def default_sentry
|
211
|
+
proc do |marking_for, clock, result|
|
212
|
+
mapping = marking_for.map do |place, marking|
|
213
|
+
break nil if marking.first.nil?
|
214
|
+
{ place => marking.first }
|
215
|
+
end
|
216
|
+
unless mapping.nil?
|
217
|
+
result << mapping.compact.reduce(:merge)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FastTCPN::TCPN do
|
4
|
+
describe "callback" do
|
5
|
+
let :tcpn do
|
6
|
+
m = FastTCPN::TCPN.new
|
7
|
+
p1 = m.timed_place 'input'
|
8
|
+
p2 = m.place 'output'
|
9
|
+
t = m.transition 'send'
|
10
|
+
t.input p1
|
11
|
+
t.output p2 do |binding, clock|
|
12
|
+
binding['input']
|
13
|
+
end
|
14
|
+
p1.add :data_package, 100
|
15
|
+
m
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "for transition" do
|
19
|
+
shared_examples "calls callbacks" do
|
20
|
+
it "is called when transition is fired" do
|
21
|
+
count = 0
|
22
|
+
tcpn.cb_for :transition, tag do |tag, event|
|
23
|
+
expect(event.binding['input'].value).to eq :data_package
|
24
|
+
expect(event.transition).to eq 'send'
|
25
|
+
expect(event.clock).to eq 100
|
26
|
+
count += 1
|
27
|
+
end
|
28
|
+
tcpn.sim
|
29
|
+
expect(count).to eq expected_count
|
30
|
+
end
|
31
|
+
end
|
32
|
+
describe "without tag" do
|
33
|
+
let(:expected_count) { 2 }
|
34
|
+
let(:tag) { }
|
35
|
+
include_examples "calls callbacks"
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "with :before tag" do
|
39
|
+
let(:expected_count) { 1 }
|
40
|
+
let(:tag) { :before }
|
41
|
+
include_examples "calls callbacks"
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "with :after tag" do
|
45
|
+
let(:expected_count) { 1 }
|
46
|
+
let(:tag) { :after }
|
47
|
+
include_examples "calls callbacks"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "for place" do
|
53
|
+
it "is called when token is removed" do
|
54
|
+
called = 0
|
55
|
+
tcpn.cb_for :place, :remove do |tag, event|
|
56
|
+
expect(tag).to eq :remove
|
57
|
+
expect(event.place).to eq 'input'
|
58
|
+
expect(event.clock).to eq 100
|
59
|
+
expect(event.tokens.first.value).to eq :data_package
|
60
|
+
called += 1
|
61
|
+
end
|
62
|
+
tcpn.sim
|
63
|
+
expect(called).to eq 1
|
64
|
+
end
|
65
|
+
|
66
|
+
it "is called when token is added" do
|
67
|
+
called = 0
|
68
|
+
tcpn.cb_for :place, :add do |tag, event|
|
69
|
+
expect(tag).to eq :add
|
70
|
+
expect(event.place).to eq 'output'
|
71
|
+
expect(event.clock).to eq nil # this place is not timed!
|
72
|
+
called += 1
|
73
|
+
end
|
74
|
+
tcpn.sim
|
75
|
+
expect(called).to eq 1
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when token Array is added or removed" do
|
79
|
+
let :tcpn do
|
80
|
+
m = FastTCPN::TCPN.new
|
81
|
+
p1 = m.timed_place 'input'
|
82
|
+
p2 = m.place 'output'
|
83
|
+
t = m.transition 'send'
|
84
|
+
t.input p1
|
85
|
+
t.output p2 do |binding, clock|
|
86
|
+
binding['input']
|
87
|
+
end
|
88
|
+
t.sentry do |marking_for, clock, result|
|
89
|
+
unless marking_for[p1.name].empty?
|
90
|
+
tokens = marking_for[p1.name].to_a
|
91
|
+
result << { p1.name => tokens }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
p1.add :data_package1, 100
|
95
|
+
p1.add :data_package2, 100
|
96
|
+
m
|
97
|
+
end
|
98
|
+
|
99
|
+
it "passes token array when it is added" do
|
100
|
+
called = 0
|
101
|
+
tcpn.cb_for :place, :add do |tag, event|
|
102
|
+
expect(tag).to eq :add
|
103
|
+
expect(event.place).to eq 'output'
|
104
|
+
expect(event.tokens.map{ |t| t.value }).to match_array [ :data_package1, :data_package2 ]
|
105
|
+
called += 1
|
106
|
+
end
|
107
|
+
tcpn.sim
|
108
|
+
expect(called).to eq 1
|
109
|
+
end
|
110
|
+
it "passes token array when it is removed" do
|
111
|
+
called = 0
|
112
|
+
tcpn.cb_for :place, :remove do |tag, event|
|
113
|
+
expect(tag).to eq :remove
|
114
|
+
expect(event.place).to eq 'input'
|
115
|
+
expect(event.clock).to eq 100
|
116
|
+
expect(event.tokens.map{ |t| t.value }).to match_array [ :data_package1, :data_package2 ]
|
117
|
+
called += 1
|
118
|
+
end
|
119
|
+
tcpn.sim
|
120
|
+
expect(called).to eq 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "for clock" do
|
126
|
+
|
127
|
+
let :tcpn do
|
128
|
+
m = FastTCPN::TCPN.new
|
129
|
+
p1 = m.timed_place 'input'
|
130
|
+
p2 = m.place 'output'
|
131
|
+
t = m.transition 'send'
|
132
|
+
t.input p1
|
133
|
+
t.output p2 do |binding, clock|
|
134
|
+
binding['input']
|
135
|
+
end
|
136
|
+
p1.add :data_package, 100
|
137
|
+
m
|
138
|
+
end
|
139
|
+
|
140
|
+
it "is called before clock is changed" do
|
141
|
+
called = 0
|
142
|
+
tcpn.cb_for :clock, :before do |tag, event|
|
143
|
+
expect(tag).to eq :before
|
144
|
+
expect(event.clock).to eq(0) if called == 0
|
145
|
+
called += 1
|
146
|
+
end
|
147
|
+
tcpn.sim
|
148
|
+
expect(called).to be > 1
|
149
|
+
end
|
150
|
+
|
151
|
+
it "is called after clock is changed" do
|
152
|
+
called = 0
|
153
|
+
tcpn.cb_for :clock, :after do |tag, event|
|
154
|
+
expect(tag).to eq :after
|
155
|
+
expect(event.clock).to eq 100
|
156
|
+
expect(event.previous_clock).to eq 0
|
157
|
+
called += 1
|
158
|
+
end
|
159
|
+
tcpn.sim
|
160
|
+
expect(called).to eq 1
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|