roby 0.7
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/.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
|
+
|