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,220 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
# This will slow down with time -- the more
|
4
|
+
# times we yield, the harder will be to hit
|
5
|
+
# an unused value. So use standerd shuffle
|
6
|
+
# if you need significant amount of values
|
7
|
+
# shuffled. But if you need just a few, this
|
8
|
+
# one is faster ;-)
|
9
|
+
def lazy_shuffle
|
10
|
+
return enum_for(:lazy_shuffle) unless block_given?
|
11
|
+
was = {}
|
12
|
+
self.size.times do
|
13
|
+
i = 0
|
14
|
+
begin
|
15
|
+
i = rand self.size
|
16
|
+
end while was.has_key?(i)
|
17
|
+
was[i] = true
|
18
|
+
yield self[i]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module FastTCPN
|
24
|
+
|
25
|
+
class HashMarking
|
26
|
+
|
27
|
+
include Clone
|
28
|
+
|
29
|
+
|
30
|
+
InvalidToken = Class.new RuntimeError
|
31
|
+
InvalidKey = Class.new RuntimeError
|
32
|
+
CannotAddKeys = Class.new RuntimeError
|
33
|
+
|
34
|
+
include Enumerable
|
35
|
+
|
36
|
+
attr_reader :keys
|
37
|
+
|
38
|
+
# Creates new HashMarking with specified keys. At least
|
39
|
+
# one key must be specified. The keys are used to
|
40
|
+
# store tokens in Hashes -- one hash for each key. Thus
|
41
|
+
# finding tokens by the keys is fast.
|
42
|
+
#
|
43
|
+
# +keys+ is a hash of the form: { key_name => method }, where +key_name+ is a name that
|
44
|
+
# will be used to access tokens indexed by this key and +method+ is a method that should
|
45
|
+
# be called on token's value to get value that should be used group tokens indexed by this key.
|
46
|
+
def initialize(keys = {})
|
47
|
+
@keys = keys
|
48
|
+
@lists = {}
|
49
|
+
@global_list = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Allows to iterate over all values in marking or over all values for which
|
53
|
+
# specified +key+ has specified +value+. If no block is given, returns adequate
|
54
|
+
# Enumerator. Yielded values are deep-cloned so you can use them without fear of
|
55
|
+
# interfering with TCPN simulation.
|
56
|
+
#
|
57
|
+
# Values are yielded in random order -- each time each is called with block
|
58
|
+
# or a new Enumerator is created, the order is changed. Thus tokens are selection
|
59
|
+
# is `in some sense fair`. Current implementation assumes, that in most cases iteration
|
60
|
+
# will finish quickly, without yielding large number of tokens. In such cases the
|
61
|
+
# shuffling algorithm is efficient. But if for some reason most tokens from marking
|
62
|
+
# should be yielded, it will be less and less efficient, with every nxt token. In this
|
63
|
+
# case one should consider using standard +shuffle+ method here instead of +lazy_shuffle+.
|
64
|
+
def each(key = nil, value = nil)
|
65
|
+
return enum_for(:each, key, value) unless block_given?
|
66
|
+
return if empty?
|
67
|
+
list_for(key, value).lazy_shuffle do |token|
|
68
|
+
yield clone token
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Add new keys to this marking. This list will be merged with exisiting keys.
|
73
|
+
# Adding keys is possible only if marking is empty, otherwise CannotAddKeys
|
74
|
+
# exception will be raised.
|
75
|
+
def add_keys(keys)
|
76
|
+
unless empty?
|
77
|
+
raise CannotAddKeys.new("marking not empty!");
|
78
|
+
end
|
79
|
+
@keys.merge!(keys)
|
80
|
+
end
|
81
|
+
|
82
|
+
# True if the marking is empty.
|
83
|
+
def empty?
|
84
|
+
size == 0
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates new token of the +object+ and adds it to the marking.
|
88
|
+
# Objects added to the marking are deep-cloned, so you can use them
|
89
|
+
# without fear to interfere with TCPN simulation. But have it in mind!
|
90
|
+
# If you put a large object with a lot of references in the marking,
|
91
|
+
# it will significanntly slow down simulation and increase memory usage.
|
92
|
+
def add(objects)
|
93
|
+
unless objects.kind_of? Array
|
94
|
+
objects = [ objects ]
|
95
|
+
end
|
96
|
+
objects.each do |object|
|
97
|
+
value = object
|
98
|
+
if object.instance_of? Hash
|
99
|
+
value = object[:val]
|
100
|
+
end
|
101
|
+
add_token prepare_token(value)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
alias << add
|
106
|
+
|
107
|
+
# Deletes the +token+ from the marking.
|
108
|
+
# To do it you must first find the token in
|
109
|
+
# the marking.
|
110
|
+
def delete(tokens)
|
111
|
+
unless tokens.instance_of? Array
|
112
|
+
tokens = [ tokens ]
|
113
|
+
end
|
114
|
+
removed = tokens.map do |token|
|
115
|
+
validate_token!(token)
|
116
|
+
delete_token(token)
|
117
|
+
end
|
118
|
+
if removed.size == 1
|
119
|
+
removed.first
|
120
|
+
else
|
121
|
+
removed
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns number of tokens in this marking
|
126
|
+
def size
|
127
|
+
@global_list.size
|
128
|
+
end
|
129
|
+
|
130
|
+
# :nodoc:
|
131
|
+
# Return fresh, unaltered clone of given token
|
132
|
+
# The given token's value could have been changed when it was
|
133
|
+
# passed to a user-defined code. Use this method to refresh this value.
|
134
|
+
# Will work as long as user interfered with token value, but not with
|
135
|
+
# the token object iself.
|
136
|
+
#
|
137
|
+
# For internal use, while firing transition
|
138
|
+
def get(token)
|
139
|
+
clone @global_list[token]
|
140
|
+
end
|
141
|
+
|
142
|
+
# reimplement if inherited classes need different one
|
143
|
+
def token_type
|
144
|
+
Token
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def tokens_by_key(key_name, value)
|
150
|
+
unless @keys.has_key? key_name
|
151
|
+
raise InvalidKey.new(key_name)
|
152
|
+
end
|
153
|
+
@lists[key_name][value]
|
154
|
+
end
|
155
|
+
|
156
|
+
def each_key_with(token)
|
157
|
+
@keys.each do |name, method_with_params|
|
158
|
+
params = nil
|
159
|
+
method = method_with_params
|
160
|
+
if method_with_params.instance_of? Array
|
161
|
+
method = method_with_params.first
|
162
|
+
params = method_with_params[1..-1]
|
163
|
+
end
|
164
|
+
unless token.value.respond_to? method
|
165
|
+
raise InvalidToken.new("#{token.inspect} does not respond to #{method.inspect}")
|
166
|
+
end
|
167
|
+
value = token.value.send(method, *params)
|
168
|
+
yield name, value
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def prepare_token(object)
|
173
|
+
if object.instance_of? token_type
|
174
|
+
clone object
|
175
|
+
elsif object.kind_of? token_type
|
176
|
+
token_type.new clone(object.value)
|
177
|
+
else
|
178
|
+
token_type.new clone(object)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def add_token(token)
|
183
|
+
@global_list[token] = token
|
184
|
+
each_key_with(token) do |key_name, value|
|
185
|
+
@lists[key_name] ||= {}
|
186
|
+
@lists[key_name][value] ||= []
|
187
|
+
@lists[key_name][value] << token
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def validate_token!(token)
|
192
|
+
unless token.instance_of? token_type
|
193
|
+
raise InvalidToken.new "#{token} is not a #{token_type} object!"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def delete_token(token)
|
198
|
+
return nil unless @global_list.delete token
|
199
|
+
each_key_with(token) do |key_name, value|
|
200
|
+
next unless @lists.has_key? key_name
|
201
|
+
next unless @lists[key_name].has_key? value
|
202
|
+
@lists[key_name][value].delete token
|
203
|
+
end
|
204
|
+
token
|
205
|
+
end
|
206
|
+
|
207
|
+
def list_for(key, value)
|
208
|
+
list = if !key.nil? && !value.nil?
|
209
|
+
tokens_by_key key, value
|
210
|
+
else
|
211
|
+
@global_list.keys
|
212
|
+
end
|
213
|
+
if list.nil?
|
214
|
+
list = []
|
215
|
+
end
|
216
|
+
list
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module FastTCPN
|
2
|
+
|
3
|
+
# This class imlements standard place of CPN model.
|
4
|
+
# Marking reprezented as HashMarking object with
|
5
|
+
# specified keys (see #new and #add_keys)
|
6
|
+
#
|
7
|
+
# It supports neither time nor timed tokens!
|
8
|
+
class Place
|
9
|
+
|
10
|
+
# Class passed to callback fired when tokens are added and/or removed from places.
|
11
|
+
# Describes details of the event that caused the callback to be fired.
|
12
|
+
Event = Struct.new(:place, :tokens, :clock, :tcpn) do
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
unless self.tokens.instance_of? Array
|
16
|
+
self.tokens = [ self.tokens ]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :name
|
22
|
+
|
23
|
+
# Create new Place object.
|
24
|
+
# +name+ is unique identifier of this place
|
25
|
+
# +keys+ are token keys used to quickly find tokens in marking (see HashMarking)
|
26
|
+
# +net+ is reference to TCPN object, required to support callbacks
|
27
|
+
def initialize(name, keys = {}, net = nil)
|
28
|
+
@name = name
|
29
|
+
@marking = HashMarking.new keys
|
30
|
+
@net = net
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return reference this place's marking.
|
34
|
+
# If you modify marking using this reference, no
|
35
|
+
# callbacks defined for places will be fired!
|
36
|
+
def marking
|
37
|
+
@marking
|
38
|
+
end
|
39
|
+
|
40
|
+
# Removes +token+ from this place
|
41
|
+
# Callbacks defined for places will be fired
|
42
|
+
def delete(token)
|
43
|
+
@net.call_callbacks(:place, :remove, Event.new(@name, token, @clock, @net)) unless @net.nil?
|
44
|
+
@marking.delete token
|
45
|
+
end
|
46
|
+
|
47
|
+
# Add +token+ to this place
|
48
|
+
# Callbacks defined for places will be fired
|
49
|
+
def add(token)
|
50
|
+
@net.call_callbacks(:place, :add, Event.new(@name, token, @clock, @net)) unless @net.nil?
|
51
|
+
@marking.add token
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add keys that will be used to store and quickly find tokens in this place's marking.
|
55
|
+
# see HashMarking for more info.
|
56
|
+
def add_keys(keys)
|
57
|
+
@marking.add_keys keys
|
58
|
+
end
|
59
|
+
|
60
|
+
# returns keys used to store and access tokens in this place.
|
61
|
+
def keys
|
62
|
+
@marking.keys
|
63
|
+
end
|
64
|
+
|
65
|
+
def inspect
|
66
|
+
"<#{self.class} name: #{name.inspect}>"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
module FastTCPN
|
2
|
+
|
3
|
+
# This class represents TCPN model: places, transitions, arcs
|
4
|
+
class TCPN
|
5
|
+
|
6
|
+
PlaceTypeDoesNotMach = Class.new RuntimeError
|
7
|
+
InvalidCallback = Class.new RuntimeError
|
8
|
+
StopSimulation = Class.new RuntimeError
|
9
|
+
|
10
|
+
class SimulationError < RuntimeError
|
11
|
+
def initialize(cause)
|
12
|
+
@cause = cause
|
13
|
+
set_backtrace @cause.backtrace
|
14
|
+
set_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
alias full_backtrace backtrace
|
18
|
+
|
19
|
+
def backtrace
|
20
|
+
return full_backtrace if @dir.nil?
|
21
|
+
return full_backtrace if FastTCPN.debug
|
22
|
+
full_backtrace.select { |b| b !~ /#{@dir}/ }
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"<#{self.class} #{@cause.inspect}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
"#{self.class}: #{@cause.message}"
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def set_dir
|
35
|
+
@dir = __FILE__
|
36
|
+
@dir = File.dirname(@dir) while(File.basename(@dir) != 'lib' && !@dir.empty? && @dir != '/')
|
37
|
+
@dir = nil if @dir.empty? || @dir == '/'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
ClockEvent = Struct.new(:clock, :previous_clock, :tcpn)
|
42
|
+
|
43
|
+
class Clock
|
44
|
+
# :nodoc:
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@value = 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def set(value)
|
51
|
+
return false if value <= @value
|
52
|
+
@value = value
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def get
|
57
|
+
@value
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
@places = {}
|
64
|
+
@timed_places = {}
|
65
|
+
@transitions = []
|
66
|
+
@clock = Clock.new
|
67
|
+
@callbacks = {
|
68
|
+
transition: { before: [], after: [] },
|
69
|
+
place: { add: [], remove: [] },
|
70
|
+
clock: { before: [], after: [] }
|
71
|
+
}
|
72
|
+
@stopped = false
|
73
|
+
end
|
74
|
+
|
75
|
+
# Create and return new not timed place for this model.
|
76
|
+
#
|
77
|
+
# If a place with this
|
78
|
+
# name exists somewhere in the model (e.g. on other pages), and object
|
79
|
+
# representing exisiting place will be returned. Keys of both keys will
|
80
|
+
# be merged. For description of keys see doc for HashMarking.
|
81
|
+
def place(name, keys = {})
|
82
|
+
create_or_find_place(name, keys, Place)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Create and return a new timed place for this model.
|
86
|
+
#
|
87
|
+
# If a place with this
|
88
|
+
# name exists somewhere in the model (e.g. on other pages), and object
|
89
|
+
# representing exisiting place will be returned. Keys of both keys will
|
90
|
+
# be merged. For description of keys see doc for HashMarking.
|
91
|
+
def timed_place(name, keys = {})
|
92
|
+
place = create_or_find_place(name, keys, TimedPlace)
|
93
|
+
@timed_places[place] = true
|
94
|
+
place
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create and return new transition for this model.
|
98
|
+
# +name+ identifies transition in the net.
|
99
|
+
def transition(name)
|
100
|
+
t = find_transition name
|
101
|
+
if t.nil?
|
102
|
+
t = Transition.new name, self
|
103
|
+
@transitions << t
|
104
|
+
end
|
105
|
+
t
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns place with given name for this net.
|
109
|
+
def find_place(name)
|
110
|
+
@places[name]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns transition with given name for this net.
|
114
|
+
def find_transition(name)
|
115
|
+
@transitions.select { |t| t.name == name }.first
|
116
|
+
end
|
117
|
+
|
118
|
+
# Number of places in this net.
|
119
|
+
def places_count
|
120
|
+
@places.size
|
121
|
+
end
|
122
|
+
|
123
|
+
# Number of transitions in this net.
|
124
|
+
def transitions_count
|
125
|
+
@transitions.size
|
126
|
+
end
|
127
|
+
|
128
|
+
# Starts simulation of this net.
|
129
|
+
def sim
|
130
|
+
@stopped = catch :stop_simulation do
|
131
|
+
begin
|
132
|
+
fired = fire_transitions
|
133
|
+
advanced = move_clock_to find_next_time
|
134
|
+
end while fired || advanced
|
135
|
+
end
|
136
|
+
@stopped = false if @stopped == nil
|
137
|
+
rescue StandardError => e
|
138
|
+
raise SimulationError.new(e)
|
139
|
+
end
|
140
|
+
|
141
|
+
alias run sim
|
142
|
+
|
143
|
+
# Returns current value of global simulation clock for this net.
|
144
|
+
def clock
|
145
|
+
@clock.get
|
146
|
+
end
|
147
|
+
|
148
|
+
# Defines new callback for this net.
|
149
|
+
# +what+ can be +:transition+, +:place+ or +:clock+.
|
150
|
+
# Transition callbacks are fired when transitions are fired, place
|
151
|
+
# callbacks when place marking changes, clock callbacks when clock is moved.
|
152
|
+
# +tag+ for transition and clock callback can be +:before+ or +:after+, for
|
153
|
+
# place, can be +:add+ or +:remove+. It defines when the callbacks fill be
|
154
|
+
# fired. If omitted, it will be called for both cases.
|
155
|
+
#
|
156
|
+
# Callback block for transition gets value of event +tag+ (:before or :after)
|
157
|
+
# FastTCPN::Transition::Event object.
|
158
|
+
#
|
159
|
+
# Callback block for place gets value of event +tag+ (:add or :remove)
|
160
|
+
# and FastTCPN::Place::Event object.
|
161
|
+
#
|
162
|
+
# Callback block for clock gets value of event +tag+ (:before or :after)
|
163
|
+
# and FastTCPN::TCPN::ClockEvent object.
|
164
|
+
def cb_for(what, tag = nil, &block)
|
165
|
+
if what == :transition
|
166
|
+
cb_for_transition tag, &block
|
167
|
+
elsif what == :place
|
168
|
+
cb_for_place tag, &block
|
169
|
+
elsif what == :clock
|
170
|
+
cb_for_clock tag, &block
|
171
|
+
else
|
172
|
+
raise InvalidCallback.new "Don't know how to add callback for #{what}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# :nodoc:
|
177
|
+
# Calls callbacks, for internal use.
|
178
|
+
def call_callbacks(what, tag, *params)
|
179
|
+
@callbacks[what][tag].each do |block|
|
180
|
+
block.call tag, *params
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
# OLD API, derived from tcpn gem
|
186
|
+
|
187
|
+
# Return marking for specified place in a form of Hash:
|
188
|
+
# { val: token_value, ts: token_timestamp }
|
189
|
+
def marking_for(name)
|
190
|
+
find_place(name).marking.map { |t| t.to_hash }
|
191
|
+
end
|
192
|
+
|
193
|
+
def add_marking_for(name, m)
|
194
|
+
token = m
|
195
|
+
find_place(name).add token
|
196
|
+
end
|
197
|
+
|
198
|
+
# stop simulation now, no matter if there are any enabled transitions
|
199
|
+
def stop
|
200
|
+
# raise StopSimulation.new
|
201
|
+
throw :stop_simulation, true
|
202
|
+
end
|
203
|
+
|
204
|
+
# True if simulation was stopped using #stop method
|
205
|
+
def stopped?
|
206
|
+
@stopped
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def cb_for_transition(tag, &block)
|
212
|
+
if tag == :before || tag.nil?
|
213
|
+
@callbacks[:transition][:before] << block
|
214
|
+
end
|
215
|
+
if tag == :after || tag.nil?
|
216
|
+
@callbacks[:transition][:after] << block
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def cb_for_place(tag, &block)
|
221
|
+
if tag == :add || tag.nil?
|
222
|
+
@callbacks[:place][:add] << block
|
223
|
+
end
|
224
|
+
if tag == :remove || tag.nil?
|
225
|
+
@callbacks[:place][:remove] << block
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def cb_for_clock(tag, &block)
|
230
|
+
if tag == :before || tag.nil?
|
231
|
+
@callbacks[:clock][:before] << block
|
232
|
+
end
|
233
|
+
if tag == :after || tag.nil?
|
234
|
+
@callbacks[:clock][:after] << block
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def create_or_find_place(name, keys, type)
|
239
|
+
place = @places[name]
|
240
|
+
if place.nil?
|
241
|
+
place = type.new name, keys, self
|
242
|
+
else
|
243
|
+
unless type == place.class
|
244
|
+
raise PlaceTypeDoesNotMatch.new "You tried to create place #{name} of type #{type}, but it already exsists and has type #{place.class}"
|
245
|
+
end
|
246
|
+
place.add_keys keys
|
247
|
+
end
|
248
|
+
@places[name] = place
|
249
|
+
end
|
250
|
+
|
251
|
+
def move_clock_to(val)
|
252
|
+
previous_clock = @clock.get
|
253
|
+
call_callbacks(:clock, :before, ClockEvent.new(@clock.get, nil, @tcpn))
|
254
|
+
return false unless @clock.set val
|
255
|
+
@timed_places.each_key do |place|
|
256
|
+
place.time = val
|
257
|
+
end
|
258
|
+
call_callbacks(:clock, :after, ClockEvent.new(@clock.get, previous_clock, @tcpn))
|
259
|
+
true
|
260
|
+
end
|
261
|
+
|
262
|
+
def fire_transitions
|
263
|
+
fired_count = 0
|
264
|
+
begin
|
265
|
+
fired = false
|
266
|
+
@transitions.shuffle.each do |transition|
|
267
|
+
if transition.fire clock
|
268
|
+
fired_count += 1
|
269
|
+
fired = true
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end while fired
|
273
|
+
fired_count > 0
|
274
|
+
end
|
275
|
+
|
276
|
+
def find_next_time
|
277
|
+
time = 0
|
278
|
+
@timed_places.each_key do |place|
|
279
|
+
next_time = place.next_time
|
280
|
+
if next_time > clock && (time == 0 || next_time < time)
|
281
|
+
time = next_time
|
282
|
+
end
|
283
|
+
end
|
284
|
+
time
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
end
|