roby 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +29 -0
- data/History.txt +4 -0
- data/License-fr.txt +519 -0
- data/License.txt +515 -0
- data/Manifest.txt +245 -0
- data/NOTES +4 -0
- data/README.txt +163 -0
- data/Rakefile +161 -0
- data/TODO.txt +146 -0
- data/app/README.txt +24 -0
- data/app/Rakefile +8 -0
- data/app/config/ROBOT.rb +5 -0
- data/app/config/app.yml +91 -0
- data/app/config/init.rb +7 -0
- data/app/config/roby.yml +3 -0
- data/app/controllers/.gitattributes +0 -0
- data/app/controllers/ROBOT.rb +2 -0
- data/app/data/.gitattributes +0 -0
- data/app/planners/ROBOT/main.rb +6 -0
- data/app/planners/main.rb +5 -0
- data/app/scripts/distributed +3 -0
- data/app/scripts/generate/bookmarks +3 -0
- data/app/scripts/replay +3 -0
- data/app/scripts/results +3 -0
- data/app/scripts/run +3 -0
- data/app/scripts/server +3 -0
- data/app/scripts/shell +3 -0
- data/app/scripts/test +3 -0
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby +210 -0
- data/bin/roby-log +168 -0
- data/bin/roby-shell +25 -0
- data/doc/images/event_generalization.png +0 -0
- data/doc/images/exception_propagation_1.png +0 -0
- data/doc/images/exception_propagation_2.png +0 -0
- data/doc/images/exception_propagation_3.png +0 -0
- data/doc/images/exception_propagation_4.png +0 -0
- data/doc/images/exception_propagation_5.png +0 -0
- data/doc/images/replay_handler_error.png +0 -0
- data/doc/images/replay_handler_error_0.png +0 -0
- data/doc/images/replay_handler_error_1.png +0 -0
- data/doc/images/roby_cycle_overview.png +0 -0
- data/doc/images/roby_replay_02.png +0 -0
- data/doc/images/roby_replay_03.png +0 -0
- data/doc/images/roby_replay_04.png +0 -0
- data/doc/images/roby_replay_event_representation.png +0 -0
- data/doc/images/roby_replay_first_state.png +0 -0
- data/doc/images/roby_replay_relations.png +0 -0
- data/doc/images/roby_replay_startup.png +0 -0
- data/doc/images/task_event_generalization.png +0 -0
- data/doc/papers.rdoc +11 -0
- data/doc/styles/allison.css +314 -0
- data/doc/styles/allison.js +316 -0
- data/doc/styles/allison.rb +276 -0
- data/doc/styles/jamis.rb +593 -0
- data/doc/tutorials/01-GettingStarted.rdoc +86 -0
- data/doc/tutorials/02-GoForward.rdoc +220 -0
- data/doc/tutorials/03-PlannedPath.rdoc +268 -0
- data/doc/tutorials/04-EventPropagation.rdoc +236 -0
- data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
- data/doc/tutorials/06-Overview.rdoc +40 -0
- data/doc/videos.rdoc +69 -0
- data/ext/droby/dump.cc +175 -0
- data/ext/droby/extconf.rb +3 -0
- data/ext/graph/algorithm.cc +746 -0
- data/ext/graph/extconf.rb +7 -0
- data/ext/graph/graph.cc +529 -0
- data/ext/graph/graph.hh +183 -0
- data/ext/graph/iterator_sequence.hh +102 -0
- data/ext/graph/undirected_dfs.hh +226 -0
- data/ext/graph/undirected_graph.hh +421 -0
- data/lib/roby.rb +41 -0
- data/lib/roby/app.rb +870 -0
- data/lib/roby/app/rake.rb +56 -0
- data/lib/roby/app/run.rb +14 -0
- data/lib/roby/app/scripts/distributed.rb +13 -0
- data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
- data/lib/roby/app/scripts/replay.rb +31 -0
- data/lib/roby/app/scripts/results.rb +15 -0
- data/lib/roby/app/scripts/run.rb +26 -0
- data/lib/roby/app/scripts/server.rb +18 -0
- data/lib/roby/app/scripts/shell.rb +88 -0
- data/lib/roby/app/scripts/test.rb +40 -0
- data/lib/roby/basic_object.rb +151 -0
- data/lib/roby/config.rb +5 -0
- data/lib/roby/control.rb +747 -0
- data/lib/roby/decision_control.rb +17 -0
- data/lib/roby/distributed.rb +32 -0
- data/lib/roby/distributed/base.rb +440 -0
- data/lib/roby/distributed/communication.rb +871 -0
- data/lib/roby/distributed/connection_space.rb +592 -0
- data/lib/roby/distributed/distributed_object.rb +206 -0
- data/lib/roby/distributed/drb.rb +62 -0
- data/lib/roby/distributed/notifications.rb +539 -0
- data/lib/roby/distributed/peer.rb +550 -0
- data/lib/roby/distributed/protocol.rb +529 -0
- data/lib/roby/distributed/proxy.rb +343 -0
- data/lib/roby/distributed/subscription.rb +311 -0
- data/lib/roby/distributed/transaction.rb +498 -0
- data/lib/roby/event.rb +897 -0
- data/lib/roby/exceptions.rb +234 -0
- data/lib/roby/executives/simple.rb +30 -0
- data/lib/roby/graph.rb +166 -0
- data/lib/roby/interface.rb +390 -0
- data/lib/roby/log.rb +3 -0
- data/lib/roby/log/chronicle.rb +303 -0
- data/lib/roby/log/console.rb +72 -0
- data/lib/roby/log/data_stream.rb +197 -0
- data/lib/roby/log/dot.rb +279 -0
- data/lib/roby/log/event_stream.rb +151 -0
- data/lib/roby/log/file.rb +340 -0
- data/lib/roby/log/gui/basic_display.ui +83 -0
- data/lib/roby/log/gui/chronicle.rb +26 -0
- data/lib/roby/log/gui/chronicle_view.rb +40 -0
- data/lib/roby/log/gui/chronicle_view.ui +70 -0
- data/lib/roby/log/gui/data_displays.rb +172 -0
- data/lib/roby/log/gui/data_displays.ui +155 -0
- data/lib/roby/log/gui/notifications.rb +26 -0
- data/lib/roby/log/gui/relations.rb +248 -0
- data/lib/roby/log/gui/relations.ui +123 -0
- data/lib/roby/log/gui/relations_view.rb +185 -0
- data/lib/roby/log/gui/relations_view.ui +149 -0
- data/lib/roby/log/gui/replay.rb +327 -0
- data/lib/roby/log/gui/replay_controls.rb +200 -0
- data/lib/roby/log/gui/replay_controls.ui +259 -0
- data/lib/roby/log/gui/runtime.rb +130 -0
- data/lib/roby/log/hooks.rb +185 -0
- data/lib/roby/log/logger.rb +202 -0
- data/lib/roby/log/notifications.rb +244 -0
- data/lib/roby/log/plan_rebuilder.rb +470 -0
- data/lib/roby/log/relations.rb +1056 -0
- data/lib/roby/log/server.rb +550 -0
- data/lib/roby/log/sqlite.rb +47 -0
- data/lib/roby/log/timings.rb +164 -0
- data/lib/roby/plan-object.rb +247 -0
- data/lib/roby/plan.rb +762 -0
- data/lib/roby/planning.rb +13 -0
- data/lib/roby/planning/loops.rb +302 -0
- data/lib/roby/planning/model.rb +906 -0
- data/lib/roby/planning/task.rb +151 -0
- data/lib/roby/propagation.rb +562 -0
- data/lib/roby/query.rb +619 -0
- data/lib/roby/relations.rb +583 -0
- data/lib/roby/relations/conflicts.rb +70 -0
- data/lib/roby/relations/ensured.rb +20 -0
- data/lib/roby/relations/error_handling.rb +23 -0
- data/lib/roby/relations/events.rb +9 -0
- data/lib/roby/relations/executed_by.rb +193 -0
- data/lib/roby/relations/hierarchy.rb +239 -0
- data/lib/roby/relations/influence.rb +10 -0
- data/lib/roby/relations/planned_by.rb +63 -0
- data/lib/roby/robot.rb +7 -0
- data/lib/roby/standard_errors.rb +218 -0
- data/lib/roby/state.rb +5 -0
- data/lib/roby/state/events.rb +221 -0
- data/lib/roby/state/information.rb +55 -0
- data/lib/roby/state/pos.rb +110 -0
- data/lib/roby/state/shapes.rb +32 -0
- data/lib/roby/state/state.rb +353 -0
- data/lib/roby/support.rb +92 -0
- data/lib/roby/task-operations.rb +182 -0
- data/lib/roby/task.rb +1618 -0
- data/lib/roby/test/common.rb +399 -0
- data/lib/roby/test/distributed.rb +214 -0
- data/lib/roby/test/tasks/empty_task.rb +9 -0
- data/lib/roby/test/tasks/goto.rb +36 -0
- data/lib/roby/test/tasks/simple_task.rb +23 -0
- data/lib/roby/test/testcase.rb +519 -0
- data/lib/roby/test/tools.rb +160 -0
- data/lib/roby/thread_task.rb +87 -0
- data/lib/roby/transactions.rb +462 -0
- data/lib/roby/transactions/proxy.rb +292 -0
- data/lib/roby/transactions/updates.rb +139 -0
- data/plugins/fault_injection/History.txt +4 -0
- data/plugins/fault_injection/README.txt +37 -0
- data/plugins/fault_injection/Rakefile +18 -0
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +52 -0
- data/plugins/fault_injection/fault_injection.rb +89 -0
- data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
- data/plugins/subsystems/README.txt +40 -0
- data/plugins/subsystems/Rakefile +18 -0
- data/plugins/subsystems/app.rb +171 -0
- data/plugins/subsystems/test/app/README +24 -0
- data/plugins/subsystems/test/app/Rakefile +8 -0
- data/plugins/subsystems/test/app/config/app.yml +71 -0
- data/plugins/subsystems/test/app/config/init.rb +9 -0
- data/plugins/subsystems/test/app/config/roby.yml +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +20 -0
- data/plugins/subsystems/test/app/scripts/distributed +3 -0
- data/plugins/subsystems/test/app/scripts/replay +3 -0
- data/plugins/subsystems/test/app/scripts/results +3 -0
- data/plugins/subsystems/test/app/scripts/run +3 -0
- data/plugins/subsystems/test/app/scripts/server +3 -0
- data/plugins/subsystems/test/app/scripts/shell +3 -0
- data/plugins/subsystems/test/app/scripts/test +3 -0
- data/plugins/subsystems/test/app/tasks/services.rb +15 -0
- data/plugins/subsystems/test/test_subsystems.rb +71 -0
- data/test/distributed/test_communication.rb +178 -0
- data/test/distributed/test_connection.rb +282 -0
- data/test/distributed/test_execution.rb +373 -0
- data/test/distributed/test_mixed_plan.rb +341 -0
- data/test/distributed/test_plan_notifications.rb +238 -0
- data/test/distributed/test_protocol.rb +516 -0
- data/test/distributed/test_query.rb +102 -0
- data/test/distributed/test_remote_plan.rb +491 -0
- data/test/distributed/test_transaction.rb +463 -0
- data/test/mockups/tasks.rb +27 -0
- data/test/planning/test_loops.rb +380 -0
- data/test/planning/test_model.rb +427 -0
- data/test/planning/test_task.rb +106 -0
- data/test/relations/test_conflicts.rb +42 -0
- data/test/relations/test_ensured.rb +38 -0
- data/test/relations/test_executed_by.rb +149 -0
- data/test/relations/test_hierarchy.rb +158 -0
- data/test/relations/test_planned_by.rb +54 -0
- data/test/suite_core.rb +24 -0
- data/test/suite_distributed.rb +9 -0
- data/test/suite_planning.rb +3 -0
- data/test/suite_relations.rb +8 -0
- data/test/test_bgl.rb +508 -0
- data/test/test_control.rb +399 -0
- data/test/test_event.rb +894 -0
- data/test/test_exceptions.rb +592 -0
- data/test/test_interface.rb +37 -0
- data/test/test_log.rb +114 -0
- data/test/test_log_server.rb +132 -0
- data/test/test_plan.rb +584 -0
- data/test/test_propagation.rb +210 -0
- data/test/test_query.rb +266 -0
- data/test/test_relations.rb +180 -0
- data/test/test_state.rb +414 -0
- data/test/test_support.rb +16 -0
- data/test/test_task.rb +938 -0
- data/test/test_testcase.rb +122 -0
- data/test/test_thread_task.rb +73 -0
- data/test/test_transactions.rb +569 -0
- data/test/test_transactions_proxy.rb +198 -0
- metadata +570 -0
data/lib/roby/event.rb
ADDED
@@ -0,0 +1,897 @@
|
|
1
|
+
require 'roby/plan-object'
|
2
|
+
require 'roby/exceptions'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Roby
|
6
|
+
# Event objects are the objects representing a particular emission in the
|
7
|
+
# event propagation process. They represent the common propagation
|
8
|
+
# information (time, generator, sources, ...) and provide some common
|
9
|
+
# functionalities related to propagation as well.
|
10
|
+
class Event
|
11
|
+
# The generator which emitted this event
|
12
|
+
attr_reader :generator
|
13
|
+
|
14
|
+
def initialize(generator, propagation_id, context, time = Time.now)
|
15
|
+
@generator, @propagation_id, @context, @time = generator, propagation_id, context.freeze, time
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :propagation_id, :context, :time
|
19
|
+
attr_accessor :sources
|
20
|
+
protected :propagation_id=, :context=, :time=
|
21
|
+
|
22
|
+
# To be used in the event generators ::new methods, when we need to reemit
|
23
|
+
# an event while changing its
|
24
|
+
def reemit(new_id, new_context = nil)
|
25
|
+
if propagation_id != new_id || (new_context && new_context != context)
|
26
|
+
new_event = self.dup
|
27
|
+
new_event.propagation_id = new_id
|
28
|
+
new_event.context = new_context
|
29
|
+
new_event.time = Time.now
|
30
|
+
new_event
|
31
|
+
else
|
32
|
+
self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def name; model.name end
|
37
|
+
def model; self.class end
|
38
|
+
def inspect; "#<#{model.to_s}:0x#{address.to_s(16)} generator=#{generator} model=#{model}" end
|
39
|
+
|
40
|
+
# Returns an event generator which will be emitted once +time+ seconds
|
41
|
+
# after this event has been emitted.
|
42
|
+
def after(time)
|
43
|
+
State.at :t => (self.time + time)
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
"[#{time.to_hms} @#{propagation_id}] #{self.class.to_s}: #{context}"
|
48
|
+
end
|
49
|
+
def pretty_print(pp)
|
50
|
+
pp.text "[#{time.to_hms} @#{propagation_id}] #{self.class}"
|
51
|
+
if context
|
52
|
+
pp.breakable
|
53
|
+
pp.nest(2) do
|
54
|
+
pp.text " "
|
55
|
+
pp.seplist(context) { |v| v.pretty_print(pp) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# EventGenerator objects are the objects which manage the event generation
|
62
|
+
# process (propagation, event creation, ...). They can be combined
|
63
|
+
# logically using & and |.
|
64
|
+
#
|
65
|
+
# === Standard relations
|
66
|
+
# - signals: calls the *command* of an event when this generator emits
|
67
|
+
# - forwardings: *emits* another event when this generator emits
|
68
|
+
#
|
69
|
+
# === Hooks
|
70
|
+
# The following hooks are defined:
|
71
|
+
# * #postponed
|
72
|
+
# * #calling
|
73
|
+
# * #called
|
74
|
+
# * #fired
|
75
|
+
# * #signalling
|
76
|
+
# * #forwarding
|
77
|
+
#
|
78
|
+
class EventGenerator < PlanObject
|
79
|
+
attr_writer :executable
|
80
|
+
|
81
|
+
# True if this event is executable. A non-executable event cannot be
|
82
|
+
# called even if it is controlable
|
83
|
+
def executable?; @executable end
|
84
|
+
|
85
|
+
# Creates a new Event generator which is emitted as soon as one of this
|
86
|
+
# object and +generator+ is emitted
|
87
|
+
def |(generator)
|
88
|
+
OrGenerator.new << self << generator
|
89
|
+
end
|
90
|
+
|
91
|
+
# Creates a AndGenerator object which is emitted when both this object
|
92
|
+
# and +generator+ are emitted
|
93
|
+
def &(generator)
|
94
|
+
AndGenerator.new << self << generator
|
95
|
+
end
|
96
|
+
|
97
|
+
attr_enumerable(:handler, :handlers) { Array.new }
|
98
|
+
|
99
|
+
def initialize_copy(old) # :nodoc:
|
100
|
+
super
|
101
|
+
|
102
|
+
@history = old.history.dup
|
103
|
+
end
|
104
|
+
|
105
|
+
def model; self.class end
|
106
|
+
# The model name
|
107
|
+
def name; model.name end
|
108
|
+
# The count of command calls that have not a corresponding emission
|
109
|
+
attr_reader :pending
|
110
|
+
# True if this event has been called but is not emitted yet
|
111
|
+
def pending?; pending end
|
112
|
+
|
113
|
+
# call-seq:
|
114
|
+
# EventGenerator.new
|
115
|
+
# EventGenerator.new(false)
|
116
|
+
# EventGenerator.new(true)
|
117
|
+
# EventGenerator.new { |event| ... }
|
118
|
+
#
|
119
|
+
# Create a new event generator. If a block is given, the event is
|
120
|
+
# controlable and the block is its command. If a +true+ argument is
|
121
|
+
# given, the event is controlable and is 'pass-through': it is emitted
|
122
|
+
# as soon as its command is called. If no argument is given (or a
|
123
|
+
# +false+ argument), then it is not controlable
|
124
|
+
def initialize(command_object = nil, &command_block)
|
125
|
+
@preconditions = []
|
126
|
+
@handlers = []
|
127
|
+
@pending = false
|
128
|
+
@unreachable = false
|
129
|
+
@unreachable_handlers = []
|
130
|
+
|
131
|
+
if command_object || command_block
|
132
|
+
self.command = if command_object.respond_to?(:call)
|
133
|
+
command_object
|
134
|
+
elsif command_block
|
135
|
+
command_block
|
136
|
+
else
|
137
|
+
method(:default_command)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
super() if defined? super
|
141
|
+
@executable = true
|
142
|
+
end
|
143
|
+
|
144
|
+
def default_command(context)
|
145
|
+
emit(*context)
|
146
|
+
end
|
147
|
+
|
148
|
+
# The current command block
|
149
|
+
attr_accessor :command
|
150
|
+
|
151
|
+
# True if this event is controlable
|
152
|
+
def controlable?; !!@command end
|
153
|
+
|
154
|
+
# Returns true if the command has been called and false otherwise
|
155
|
+
# The command won't be called if #postpone() is called within the
|
156
|
+
# #calling hook
|
157
|
+
#
|
158
|
+
# This is used by propagation code, and should never be called directly
|
159
|
+
def call_without_propagation(context) # :nodoc:
|
160
|
+
if !controlable?
|
161
|
+
raise EventNotControlable.new(self), "#call called on a non-controlable event"
|
162
|
+
end
|
163
|
+
|
164
|
+
postponed = catch :postponed do
|
165
|
+
calling(context)
|
166
|
+
@pending = true
|
167
|
+
|
168
|
+
Propagation.propagation_context([self]) do
|
169
|
+
command[context]
|
170
|
+
end
|
171
|
+
|
172
|
+
false
|
173
|
+
end
|
174
|
+
|
175
|
+
if postponed
|
176
|
+
@pending = false
|
177
|
+
postponed(context, *postponed)
|
178
|
+
false
|
179
|
+
else
|
180
|
+
called(context)
|
181
|
+
true
|
182
|
+
end
|
183
|
+
|
184
|
+
rescue Exception
|
185
|
+
@pending = false
|
186
|
+
raise
|
187
|
+
end
|
188
|
+
|
189
|
+
# Call the command associated with self. Note that an event might be
|
190
|
+
# non-controlable and respond to the :call message. Controlability must
|
191
|
+
# be checked using #controlable?
|
192
|
+
def call(*context)
|
193
|
+
if !self_owned?
|
194
|
+
raise OwnershipError, "not owner"
|
195
|
+
elsif !controlable?
|
196
|
+
raise EventNotControlable.new(self), "#call called on a non-controlable event"
|
197
|
+
elsif !executable?
|
198
|
+
raise EventNotExecutable.new(self), "#call called on #{self} which is non-executable event"
|
199
|
+
elsif !Roby.inside_control?
|
200
|
+
raise ThreadMismatch, "#call called while not in control thread"
|
201
|
+
end
|
202
|
+
|
203
|
+
context.compact!
|
204
|
+
if Propagation.gathering?
|
205
|
+
Propagation.add_event_propagation(false, Propagation.sources, self, (context unless context.empty?), nil)
|
206
|
+
else
|
207
|
+
errors = Propagation.propagate_events do |initial_set|
|
208
|
+
Propagation.add_event_propagation(false, nil, self, (context unless context.empty?), nil)
|
209
|
+
end
|
210
|
+
if errors.size == 1
|
211
|
+
e = errors.first.exception
|
212
|
+
raise e, e.message, e.backtrace
|
213
|
+
elsif !errors.empty?
|
214
|
+
for e in errors
|
215
|
+
STDERR.puts e.exception.full_message
|
216
|
+
end
|
217
|
+
raise "multiple exceptions"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Establishes signalling and/or event handlers from this event
|
223
|
+
# generator.
|
224
|
+
#
|
225
|
+
# If +time+ is given it is either a :delay => time association, or a
|
226
|
+
# :at => time association. In the first case, +time+ is a floating-point
|
227
|
+
# delay in seconds and in the second case it is a Time object which is
|
228
|
+
# the absolute point in time at which this propagation must happen.
|
229
|
+
def on(signal = nil, time = nil, &handler)
|
230
|
+
if signal
|
231
|
+
self.signal(signal, time)
|
232
|
+
end
|
233
|
+
|
234
|
+
if handler
|
235
|
+
check_arity(handler, 1)
|
236
|
+
self.handlers << handler
|
237
|
+
end
|
238
|
+
|
239
|
+
self
|
240
|
+
end
|
241
|
+
|
242
|
+
# Adds a signal from this event to +generator+. +generator+ must be
|
243
|
+
# controlable.
|
244
|
+
#
|
245
|
+
# If +time+ is given it is either a :delay => time association, or a
|
246
|
+
# :at => time association. In the first case, +time+ is a floating-point
|
247
|
+
# delay in seconds and in the second case it is a Time object which is
|
248
|
+
# the absolute point in time at which this propagation must happen.
|
249
|
+
def signal(generator, timespec = nil)
|
250
|
+
if !generator.controlable?
|
251
|
+
raise EventNotControlable.new(self), "trying to establish a signal between #{self} and #{generator}"
|
252
|
+
end
|
253
|
+
timespec = Propagation.validate_timespec(timespec)
|
254
|
+
|
255
|
+
add_signal generator, timespec
|
256
|
+
self
|
257
|
+
end
|
258
|
+
|
259
|
+
# A set of blocks called when this event cannot be emitted again
|
260
|
+
attr_reader :unreachable_handlers
|
261
|
+
|
262
|
+
# Calls +block+ if it is impossible that this event is ever emitted
|
263
|
+
def if_unreachable(cancel_at_emission = false, &block)
|
264
|
+
unreachable_handlers << [cancel_at_emission, block]
|
265
|
+
block.object_id
|
266
|
+
end
|
267
|
+
|
268
|
+
# Returns an event which will be emitted when this event becones
|
269
|
+
# unreachable
|
270
|
+
def when_unreachable
|
271
|
+
# NOTE: the unreachable event is not directly tied to this one from
|
272
|
+
# a GC point of view (being able to do this would be useful, but
|
273
|
+
# anyway). So, it is possible that it is GCed because the event
|
274
|
+
# user did not take care to use it.
|
275
|
+
if !@unreachable_event || !@unreachable_event.plan
|
276
|
+
result = EventGenerator.new(true)
|
277
|
+
if_unreachable(false) do
|
278
|
+
if result.plan
|
279
|
+
result.emit
|
280
|
+
end
|
281
|
+
end
|
282
|
+
add_causal_link result
|
283
|
+
@unreachable_event = result
|
284
|
+
end
|
285
|
+
@unreachable_event
|
286
|
+
end
|
287
|
+
|
288
|
+
# Emit +generator+ when +self+ is fired, without calling the command of
|
289
|
+
# +generator+, if any.
|
290
|
+
#
|
291
|
+
# If +timespec+ is given it is either a :delay => time association, or a
|
292
|
+
# :at => time association. In the first case, +time+ is a floating-point
|
293
|
+
# delay in seconds and in the second case it is a Time object which is
|
294
|
+
# the absolute point in time at which this propagation must happen.
|
295
|
+
def forward(generator, timespec = nil)
|
296
|
+
timespec = Propagation.validate_timespec(timespec)
|
297
|
+
add_forwarding generator, timespec
|
298
|
+
self
|
299
|
+
end
|
300
|
+
|
301
|
+
# Returns an event which is emitted +seconds+ seconds after this one
|
302
|
+
def delay(seconds)
|
303
|
+
if seconds == 0 then self
|
304
|
+
else
|
305
|
+
ev = EventGenerator.new
|
306
|
+
forward(ev, :delay => seconds)
|
307
|
+
ev
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Signal the +signal+ event the first time this event is emitted. If
|
312
|
+
# +time+ is non-nil, delay the signalling this many seconds.
|
313
|
+
def signal_once(signal, time = nil); once(signal, time) end
|
314
|
+
|
315
|
+
# Equivalent to #on, but call the handler and/or signal the target
|
316
|
+
# event only once.
|
317
|
+
def once(signal = nil, time = nil)
|
318
|
+
handler = nil
|
319
|
+
on(signal, time) do |context|
|
320
|
+
yield(context) if block_given?
|
321
|
+
self.handlers.delete(handler)
|
322
|
+
remove_signal(signal) if signal
|
323
|
+
end
|
324
|
+
handler = self.handlers.last
|
325
|
+
end
|
326
|
+
|
327
|
+
# Forwards to +ev+ only once
|
328
|
+
def forward_once(ev)
|
329
|
+
forward(ev)
|
330
|
+
once do
|
331
|
+
remove_forwarding ev
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def to_event; self end
|
336
|
+
|
337
|
+
# Returns the set of events directly related to this one
|
338
|
+
def related_events(result = nil); related_objects(nil, result) end
|
339
|
+
# Returns the set of tasks directly related to this event
|
340
|
+
def related_tasks(result = nil)
|
341
|
+
result ||= ValueSet.new
|
342
|
+
for ev in related_events
|
343
|
+
if ev.respond_to?(:task)
|
344
|
+
result << ev.task
|
345
|
+
end
|
346
|
+
end
|
347
|
+
result
|
348
|
+
end
|
349
|
+
|
350
|
+
# Create a new event object for +context+
|
351
|
+
def new(context); Event.new(self, Propagation.propagation_id, context, Time.now) end
|
352
|
+
|
353
|
+
# Adds a propagation originating from this event to event propagation
|
354
|
+
def add_propagation(only_forward, event, signalled, context, timespec) # :nodoc:
|
355
|
+
if self == signalled
|
356
|
+
raise PropagationError, "#{self} is trying to signal itself"
|
357
|
+
elsif !only_forward && !signalled.controlable?
|
358
|
+
raise PropagationError, "trying to signal #{signalled} from #{self}"
|
359
|
+
end
|
360
|
+
|
361
|
+
Propagation.add_event_propagation(only_forward, [event], signalled, context, timespec)
|
362
|
+
end
|
363
|
+
private :add_propagation
|
364
|
+
|
365
|
+
# Do fire this event. It gathers the list of signals that are to
|
366
|
+
# be propagated in the next step and calls fired()
|
367
|
+
#
|
368
|
+
# This method is always called in a propagation context
|
369
|
+
def fire(event)
|
370
|
+
Propagation.propagation_context([event]) do |result|
|
371
|
+
each_signal do |signalled|
|
372
|
+
add_propagation(false, event, signalled, event.context, self[signalled, EventStructure::Signal])
|
373
|
+
end
|
374
|
+
each_forwarding do |signalled|
|
375
|
+
add_propagation(true, event, signalled, event.context, self[signalled, EventStructure::Forwarding])
|
376
|
+
end
|
377
|
+
|
378
|
+
@happened = true
|
379
|
+
fired(event)
|
380
|
+
|
381
|
+
call_handlers(event)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
private :fire
|
386
|
+
|
387
|
+
# Call the event handlers defined for this event generator
|
388
|
+
def call_handlers(event)
|
389
|
+
# Since we are in a gathering context, call
|
390
|
+
# to other objects are not done, but gathered in the
|
391
|
+
# :propagation TLS
|
392
|
+
each_handler do |h|
|
393
|
+
begin
|
394
|
+
h.call(event)
|
395
|
+
rescue Exception => e
|
396
|
+
Propagation.add_error( EventHandlerError.new(e, event) )
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# Raises an exception object when an event whose command has been
|
402
|
+
# called won't be emitted (ever)
|
403
|
+
def emit_failed(*what)
|
404
|
+
what, message = *what
|
405
|
+
what ||= EmissionFailed
|
406
|
+
|
407
|
+
if !message && what.respond_to?(:to_str)
|
408
|
+
message = what.to_str
|
409
|
+
what = EmissionFailed
|
410
|
+
end
|
411
|
+
|
412
|
+
failure_message = "failed to emit #{self}: #{message}"
|
413
|
+
error = if Class === what then what.new(nil, self)
|
414
|
+
else what
|
415
|
+
end
|
416
|
+
error = error.exception failure_message
|
417
|
+
|
418
|
+
Propagation.add_error(error)
|
419
|
+
|
420
|
+
ensure
|
421
|
+
@pending = false
|
422
|
+
end
|
423
|
+
|
424
|
+
# Emits the event regardless of wether we are in a propagation context
|
425
|
+
# or not Returns true to match the behavior of
|
426
|
+
# #call_without_propagation
|
427
|
+
#
|
428
|
+
# This is used by event propagation. Do not call directly: use #call instead
|
429
|
+
def emit_without_propagation(context) # :nodoc:
|
430
|
+
if !executable?
|
431
|
+
raise EventNotExecutable.new(self), "#emit called on #{self} which is not executable"
|
432
|
+
end
|
433
|
+
|
434
|
+
emitting(context)
|
435
|
+
|
436
|
+
# Create the event object
|
437
|
+
event = new(context)
|
438
|
+
unless event.respond_to?(:context)
|
439
|
+
raise TypeError, "#{event} is not a valid event object in #{self}"
|
440
|
+
end
|
441
|
+
event.sources = Propagation.source_events
|
442
|
+
fire(event)
|
443
|
+
|
444
|
+
true
|
445
|
+
|
446
|
+
ensure
|
447
|
+
@pending = false
|
448
|
+
end
|
449
|
+
|
450
|
+
# Emit the event with +context+ as the event context
|
451
|
+
def emit(*context)
|
452
|
+
if !executable?
|
453
|
+
raise EventNotExecutable.new(self), "#emit called on #{self} which is not executable"
|
454
|
+
elsif !self_owned?
|
455
|
+
raise OwnershipError, "cannot emit an event we don't own. #{self} is owned by #{owners}"
|
456
|
+
elsif !Roby.inside_control?
|
457
|
+
raise ThreadMismatch, "#emit called while not in control thread"
|
458
|
+
end
|
459
|
+
|
460
|
+
context.compact!
|
461
|
+
if Propagation.gathering?
|
462
|
+
Propagation.add_event_propagation(true, Propagation.sources, self, (context unless context.empty?), nil)
|
463
|
+
else
|
464
|
+
errors = Propagation.propagate_events do |initial_set|
|
465
|
+
Propagation.add_event_propagation(true, Propagation.sources, self, (context unless context.empty?), nil)
|
466
|
+
end
|
467
|
+
if errors.size == 1
|
468
|
+
e = errors.first.exception
|
469
|
+
raise e, e.message, e.backtrace
|
470
|
+
elsif !errors.empty?
|
471
|
+
for e in errors
|
472
|
+
STDERR.puts e.full_message
|
473
|
+
end
|
474
|
+
raise "multiple exceptions"
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Deprecated. Instead of using
|
480
|
+
# dest.emit_on(source)
|
481
|
+
# now use
|
482
|
+
# source.forward(dest)
|
483
|
+
def emit_on(generator, timespec = nil)
|
484
|
+
generator.forward(self, timespec)
|
485
|
+
self
|
486
|
+
end
|
487
|
+
|
488
|
+
# Sets up +ev+ and +self+ to represent that the command of +self+ is to
|
489
|
+
# be achieved by the emission of +ev+. It is to be used in a command
|
490
|
+
# handler:
|
491
|
+
#
|
492
|
+
# event :start do |context|
|
493
|
+
# init = <create an initialization event>
|
494
|
+
# event(:start).achieve_with(init)
|
495
|
+
# end
|
496
|
+
#
|
497
|
+
# If +ev+ becomes unreachable, an EmissionFailed exception will be
|
498
|
+
# raised. If a block is given, it is supposed to return the context of
|
499
|
+
# the event emitted by +self+, given the context of the event emitted
|
500
|
+
# by +ev+.
|
501
|
+
#
|
502
|
+
# From an event propagation point of view, it looks like:
|
503
|
+
# TODO: add a figure
|
504
|
+
def achieve_with(ev)
|
505
|
+
stack = caller(1)
|
506
|
+
if block_given?
|
507
|
+
ev.add_causal_link self
|
508
|
+
ev.once do |context|
|
509
|
+
self.emit(yield(context))
|
510
|
+
end
|
511
|
+
else
|
512
|
+
ev.forward_once self
|
513
|
+
end
|
514
|
+
|
515
|
+
ev.if_unreachable(true) do |reason|
|
516
|
+
msg = "#{ev} is unreachable#{ " (#{reason})" if reason }, in #{stack.first}"
|
517
|
+
if ev.respond_to?(:task)
|
518
|
+
msg << "\n " << ev.task.history.map { |ev| "#{ev.time.to_hms} #{ev.symbol}: #{ev.context}" }.join("\n ")
|
519
|
+
end
|
520
|
+
emit_failed(UnreachableEvent.new(self, reason), msg)
|
521
|
+
end
|
522
|
+
end
|
523
|
+
# For backwards compatibility. Use #achieve_with.
|
524
|
+
def realize_with(task); achieve_with(task) end
|
525
|
+
|
526
|
+
# A [time, event] array of past event emitted by this object
|
527
|
+
attribute(:history) { Array.new }
|
528
|
+
# True if this event has been emitted once.
|
529
|
+
attr_predicate :happened
|
530
|
+
# Last event to have been emitted by this generator
|
531
|
+
def last; history.last end
|
532
|
+
|
533
|
+
# Defines a precondition handler for this event. Precondition handlers
|
534
|
+
# are blocks which are called just before the event command is called.
|
535
|
+
# If the handler returns false, the calling is aborted by a
|
536
|
+
# PreconditionFailed exception
|
537
|
+
def precondition(reason = nil, &block)
|
538
|
+
@preconditions << [reason, block]
|
539
|
+
end
|
540
|
+
|
541
|
+
# Yields all precondition handlers defined for this generator
|
542
|
+
def each_precondition # :yield:reason, block
|
543
|
+
@preconditions.each { |o| yield(o) }
|
544
|
+
end
|
545
|
+
|
546
|
+
# Call #postpone in #calling to announce that the event should not be
|
547
|
+
# called now, but should be called back when +generator+ is emitted
|
548
|
+
#
|
549
|
+
# A reason string can be provided for debugging purposes
|
550
|
+
def postpone(generator, reason = nil)
|
551
|
+
generator.on self
|
552
|
+
yield if block_given?
|
553
|
+
throw :postponed, [generator, reason]
|
554
|
+
end
|
555
|
+
|
556
|
+
# Hook called when the event has been postponed. See #postpone
|
557
|
+
def postponed(context, generator, reason); super if defined? super end
|
558
|
+
|
559
|
+
# Call this method in the #calling hook to cancel calling the event
|
560
|
+
# command. This raises an EventCanceled exception with +reason+ for
|
561
|
+
# message
|
562
|
+
def cancel(reason = nil)
|
563
|
+
raise EventCanceled.new(self), (reason || "event canceled")
|
564
|
+
end
|
565
|
+
|
566
|
+
# Hook called when this event generator is called (i.e. the associated
|
567
|
+
# command is), before the command is actually called. Think of it as a
|
568
|
+
# pre-call hook.
|
569
|
+
#
|
570
|
+
# The #postpone method can be called in this hook
|
571
|
+
def calling(context)
|
572
|
+
super if defined? super
|
573
|
+
each_precondition do |reason, block|
|
574
|
+
result = begin
|
575
|
+
block.call(self, context)
|
576
|
+
rescue EventPreconditionFailed => e
|
577
|
+
e.generator = self
|
578
|
+
raise
|
579
|
+
end
|
580
|
+
|
581
|
+
if !result
|
582
|
+
raise EventPreconditionFailed.new(self), "precondition #{reason} failed"
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
# Hook called just after the event command has been called
|
588
|
+
def called(context); super if defined? super end
|
589
|
+
|
590
|
+
# Hook called when this generator has been fired. +event+ is the Event object
|
591
|
+
# which has been created.
|
592
|
+
def fired(event)
|
593
|
+
unreachable_handlers.delete_if { |cancel, _| cancel }
|
594
|
+
|
595
|
+
history << event
|
596
|
+
if EventGenerator.event_gathering.has_key?(event.generator)
|
597
|
+
for c in EventGenerator.event_gathering[event.generator]
|
598
|
+
c << event
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
super if defined? super
|
603
|
+
end
|
604
|
+
|
605
|
+
# Hook called just before the +to+ generator is signalled by this
|
606
|
+
# generator. +event+ is the Event object which has been generated by
|
607
|
+
# this model
|
608
|
+
def signalling(event, to); super if defined? super end
|
609
|
+
|
610
|
+
# Hook called just before the propagation forwards +self+ to +to+.
|
611
|
+
# +event+ is the Event object which has been generated by this model
|
612
|
+
def forwarding(event, to); super if defined? super end
|
613
|
+
|
614
|
+
# Hook called when this event will be emitted
|
615
|
+
def emitting(context); super if defined? super end
|
616
|
+
|
617
|
+
# call-seq:
|
618
|
+
# filter(new_context) => filtering_event
|
619
|
+
# filter { |context| ... } => filtering_event
|
620
|
+
#
|
621
|
+
# Returns an event generator which forwards the events fired by this
|
622
|
+
# one, but by changing the context. In the first form, the new context
|
623
|
+
# is set to +new_context+. In the second form, to the value returned
|
624
|
+
# by the given block
|
625
|
+
def filter(*new_context, &block)
|
626
|
+
filter = FilterGenerator.new(new_context, &block)
|
627
|
+
self.on(filter)
|
628
|
+
filter
|
629
|
+
end
|
630
|
+
|
631
|
+
# Returns a new event generator which emits until the +limit+ event is
|
632
|
+
# sent
|
633
|
+
#
|
634
|
+
# source, ev, limit = (1..3).map { EventGenerator.new(true) }
|
635
|
+
# ev.until(limit).on { STDERR.puts "FIRED !!!" }
|
636
|
+
# source.on ev
|
637
|
+
#
|
638
|
+
# Will do
|
639
|
+
#
|
640
|
+
# source.call # => FIRED !!!
|
641
|
+
# limit.emit
|
642
|
+
# source.call # =>
|
643
|
+
#
|
644
|
+
# See also UntilGenerator
|
645
|
+
def until(limit); UntilGenerator.new(self, limit) end
|
646
|
+
|
647
|
+
# Checks that ownership allows to add the self => child relation
|
648
|
+
def add_child_object(child, type, info) # :nodoc:
|
649
|
+
unless child.read_write?
|
650
|
+
raise OwnershipError, "cannot add an event relation on a child we don't own. #{child} is owned by #{child.owners.to_a} (plan is owned by #{plan.owners.to_a if plan})"
|
651
|
+
end
|
652
|
+
|
653
|
+
super
|
654
|
+
end
|
655
|
+
|
656
|
+
@@event_gathering = Hash.new { |h, k| h[k] = ValueSet.new }
|
657
|
+
# If a generator in +events+ fires, add the fired event in +collection+
|
658
|
+
def self.gather_events(collection, events)
|
659
|
+
for ev in events
|
660
|
+
event_gathering[ev] << collection
|
661
|
+
end
|
662
|
+
end
|
663
|
+
# Remove the notifications that have been registered for +collection+
|
664
|
+
def self.remove_event_gathering(collection)
|
665
|
+
@@event_gathering.delete_if do |_, collections|
|
666
|
+
collections.delete(collection)
|
667
|
+
collections.empty?
|
668
|
+
end
|
669
|
+
end
|
670
|
+
# An array of [collection, events] elements, collection being the
|
671
|
+
# object in which we must add the fired events, and events the set of
|
672
|
+
# event generators +collection+ is listening for.
|
673
|
+
def self.event_gathering; @@event_gathering end
|
674
|
+
|
675
|
+
# This module is hooked in Roby::Plan to remove from the
|
676
|
+
# event_gathering sets the events that have been finalized
|
677
|
+
module FinalizedEventHook
|
678
|
+
def finalized_event(event)
|
679
|
+
super if defined? super
|
680
|
+
event.unreachable!
|
681
|
+
end
|
682
|
+
end
|
683
|
+
Roby::Plan.include FinalizedEventHook
|
684
|
+
|
685
|
+
attr_predicate :unreachable?
|
686
|
+
|
687
|
+
# Called internally when the event becomes unreachable
|
688
|
+
def unreachable!(reason = nil)
|
689
|
+
return if @unreachable
|
690
|
+
@unreachable = true
|
691
|
+
|
692
|
+
unreachable_handlers.each do |_, block|
|
693
|
+
begin
|
694
|
+
block.call(reason)
|
695
|
+
rescue Exception => e
|
696
|
+
Propagation.add_error(EventHandlerError.new(e, self))
|
697
|
+
end
|
698
|
+
end
|
699
|
+
unreachable_handlers.clear
|
700
|
+
end
|
701
|
+
|
702
|
+
def pretty_print(pp) # :nodoc:
|
703
|
+
pp.text to_s
|
704
|
+
pp.group(2, ' {', '}') do
|
705
|
+
pp.breakable
|
706
|
+
pp.text "owners: "
|
707
|
+
pp.seplist(owners) { |r| pp.text r.to_s }
|
708
|
+
|
709
|
+
pp.breakable
|
710
|
+
pp.text "relations: "
|
711
|
+
pp.seplist(relations) { |r| pp.text r.name }
|
712
|
+
end
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
716
|
+
|
717
|
+
# This generator reemits an event after having changed its context. See
|
718
|
+
# EventGenerator#filter for a more complete explanation
|
719
|
+
class FilterGenerator < EventGenerator
|
720
|
+
def initialize(user_context, &block)
|
721
|
+
if block && !user_context.empty?
|
722
|
+
raise ArgumentError, "you must set either the filter or the value, not both"
|
723
|
+
end
|
724
|
+
|
725
|
+
if block
|
726
|
+
super() do |context|
|
727
|
+
context = context.map do |val|
|
728
|
+
block.call(val)
|
729
|
+
end
|
730
|
+
emit(*context)
|
731
|
+
end
|
732
|
+
else
|
733
|
+
super() do
|
734
|
+
emit(*user_context)
|
735
|
+
end
|
736
|
+
end
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
# Event generator which fires when all its source events have fired
|
741
|
+
# See EventGenerator#& for a more complete description
|
742
|
+
class AndGenerator < EventGenerator
|
743
|
+
def initialize
|
744
|
+
super do |context|
|
745
|
+
emit_if_achieved(context)
|
746
|
+
end
|
747
|
+
|
748
|
+
# This hash is a event_generator => event mapping of the last
|
749
|
+
# events of each event generator. We compare the event stored in
|
750
|
+
# this hash with the last events of each source to know if the
|
751
|
+
# source fired since it has been added to this AndGenerator
|
752
|
+
@events = Hash.new
|
753
|
+
|
754
|
+
# This flag is true unless we are not waiting for the emission
|
755
|
+
# anymore.
|
756
|
+
@active = true
|
757
|
+
end
|
758
|
+
|
759
|
+
# Resets the waiting. If the event has already been emitted, it re-arms
|
760
|
+
# it.
|
761
|
+
def reset
|
762
|
+
@active = true
|
763
|
+
each_parent_object(EventStructure::Signal) do |source|
|
764
|
+
@events[source] = source.last
|
765
|
+
if source.respond_to?(:reset)
|
766
|
+
source.reset
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
def emit_if_achieved(context) # :nodoc:
|
772
|
+
return unless @active
|
773
|
+
each_parent_object(EventStructure::Signal) do |source|
|
774
|
+
return if @events[source] == source.last
|
775
|
+
end
|
776
|
+
@active = false
|
777
|
+
emit(nil)
|
778
|
+
end
|
779
|
+
|
780
|
+
def empty?; events.empty? end
|
781
|
+
|
782
|
+
# Adds a new source to +events+ when a source event is added
|
783
|
+
def added_parent_object(parent, relations, info) # :nodoc:
|
784
|
+
super if defined? super
|
785
|
+
return unless relations.include?(EventStructure::Signal)
|
786
|
+
@events[parent] = parent.last
|
787
|
+
|
788
|
+
# If the parent is unreachable, check that it has neither been
|
789
|
+
# removed, nor it has been emitted
|
790
|
+
parent.if_unreachable(true) do |reason|
|
791
|
+
if @events[parent] == parent.last
|
792
|
+
unreachable!(reason || parent)
|
793
|
+
end
|
794
|
+
end
|
795
|
+
end
|
796
|
+
|
797
|
+
# Removes a source from +events+ when the source is removed
|
798
|
+
def removed_parent_object(parent, relations) # :nodoc:
|
799
|
+
super if defined? super
|
800
|
+
return unless relations.include?(EventStructure::Signal)
|
801
|
+
@events.delete(parent)
|
802
|
+
end
|
803
|
+
|
804
|
+
# The set of source events
|
805
|
+
def events; parent_objects(EventStructure::Signal) end
|
806
|
+
# The set of events which we are waiting for
|
807
|
+
def waiting; parent_objects(EventStructure::Signal).find_all { |ev| @events[ev] == ev.last } end
|
808
|
+
|
809
|
+
# Add a new source to this generator
|
810
|
+
def << (generator)
|
811
|
+
generator.add_signal self
|
812
|
+
self
|
813
|
+
end
|
814
|
+
end
|
815
|
+
|
816
|
+
# Event generator which fires when the first of its source events fires.
|
817
|
+
# All event generators which signal this one are considered as sources.
|
818
|
+
#
|
819
|
+
# See also EventGenerator#| and #<<
|
820
|
+
class OrGenerator < EventGenerator
|
821
|
+
# Creates a new OrGenerator without any sources.
|
822
|
+
def initialize
|
823
|
+
super do |context|
|
824
|
+
emit_if_first(context)
|
825
|
+
end
|
826
|
+
@active = true
|
827
|
+
end
|
828
|
+
|
829
|
+
# True if there is no source event for this combinator.
|
830
|
+
def empty?; parent_objects(EventStructure::Signal).empty? end
|
831
|
+
|
832
|
+
# Reset its state, so as to behave as if no source has ever
|
833
|
+
# been emitted.
|
834
|
+
def reset
|
835
|
+
@active = true
|
836
|
+
each_parent_object(EventStructure::Signal) do |source|
|
837
|
+
if source.respond_to?(:reset)
|
838
|
+
source.reset
|
839
|
+
end
|
840
|
+
end
|
841
|
+
end
|
842
|
+
|
843
|
+
def emit_if_first(context) # :nodoc:
|
844
|
+
return unless @active
|
845
|
+
@active = false
|
846
|
+
emit(context)
|
847
|
+
end
|
848
|
+
|
849
|
+
def added_parent_object(parent, relations, info) # :nodoc:
|
850
|
+
super if defined? super
|
851
|
+
return unless relations.include?(EventStructure::Signal)
|
852
|
+
|
853
|
+
parent.if_unreachable(true) do |reason|
|
854
|
+
if !happened? && parent_objects(EventStructure::Signal).all? { |ev| ev.unreachable? }
|
855
|
+
unreachable!(reason || parent)
|
856
|
+
end
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
# Adds +generator+ to the sources of this event
|
861
|
+
def << (generator)
|
862
|
+
generator.add_signal self
|
863
|
+
self
|
864
|
+
end
|
865
|
+
end
|
866
|
+
|
867
|
+
# This event generator combines a source and a limit in a temporal pattern.
|
868
|
+
# The generator acts as a pass-through for the source, until the limit is
|
869
|
+
# itself emitted. It means that:
|
870
|
+
#
|
871
|
+
# * before the limit is emitted, the generator will emit each time its
|
872
|
+
# source emits
|
873
|
+
# * since the point where the limit is emitted, the generator
|
874
|
+
# does not emit anymore
|
875
|
+
#
|
876
|
+
# See also EventGenerator#until
|
877
|
+
class UntilGenerator < Roby::EventGenerator
|
878
|
+
# Creates a until generator for the given source and limit event
|
879
|
+
# generators
|
880
|
+
def initialize(source = nil, limit = nil)
|
881
|
+
super() do |context|
|
882
|
+
plan.remove_object(self) if plan
|
883
|
+
clear_relations
|
884
|
+
end
|
885
|
+
|
886
|
+
if source && limit
|
887
|
+
source.forward(self)
|
888
|
+
limit.signal(self)
|
889
|
+
end
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
unless defined? EventStructure
|
894
|
+
EventStructure = RelationSpace(EventGenerator)
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|