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.
Files changed (240) hide show
  1. data/.gitignore +29 -0
  2. data/History.txt +4 -0
  3. data/License-fr.txt +519 -0
  4. data/License.txt +515 -0
  5. data/Manifest.txt +245 -0
  6. data/NOTES +4 -0
  7. data/README.txt +163 -0
  8. data/Rakefile +161 -0
  9. data/TODO.txt +146 -0
  10. data/app/README.txt +24 -0
  11. data/app/Rakefile +8 -0
  12. data/app/config/ROBOT.rb +5 -0
  13. data/app/config/app.yml +91 -0
  14. data/app/config/init.rb +7 -0
  15. data/app/config/roby.yml +3 -0
  16. data/app/controllers/.gitattributes +0 -0
  17. data/app/controllers/ROBOT.rb +2 -0
  18. data/app/data/.gitattributes +0 -0
  19. data/app/planners/ROBOT/main.rb +6 -0
  20. data/app/planners/main.rb +5 -0
  21. data/app/scripts/distributed +3 -0
  22. data/app/scripts/generate/bookmarks +3 -0
  23. data/app/scripts/replay +3 -0
  24. data/app/scripts/results +3 -0
  25. data/app/scripts/run +3 -0
  26. data/app/scripts/server +3 -0
  27. data/app/scripts/shell +3 -0
  28. data/app/scripts/test +3 -0
  29. data/app/tasks/.gitattributes +0 -0
  30. data/app/tasks/ROBOT/.gitattributes +0 -0
  31. data/bin/roby +210 -0
  32. data/bin/roby-log +168 -0
  33. data/bin/roby-shell +25 -0
  34. data/doc/images/event_generalization.png +0 -0
  35. data/doc/images/exception_propagation_1.png +0 -0
  36. data/doc/images/exception_propagation_2.png +0 -0
  37. data/doc/images/exception_propagation_3.png +0 -0
  38. data/doc/images/exception_propagation_4.png +0 -0
  39. data/doc/images/exception_propagation_5.png +0 -0
  40. data/doc/images/replay_handler_error.png +0 -0
  41. data/doc/images/replay_handler_error_0.png +0 -0
  42. data/doc/images/replay_handler_error_1.png +0 -0
  43. data/doc/images/roby_cycle_overview.png +0 -0
  44. data/doc/images/roby_replay_02.png +0 -0
  45. data/doc/images/roby_replay_03.png +0 -0
  46. data/doc/images/roby_replay_04.png +0 -0
  47. data/doc/images/roby_replay_event_representation.png +0 -0
  48. data/doc/images/roby_replay_first_state.png +0 -0
  49. data/doc/images/roby_replay_relations.png +0 -0
  50. data/doc/images/roby_replay_startup.png +0 -0
  51. data/doc/images/task_event_generalization.png +0 -0
  52. data/doc/papers.rdoc +11 -0
  53. data/doc/styles/allison.css +314 -0
  54. data/doc/styles/allison.js +316 -0
  55. data/doc/styles/allison.rb +276 -0
  56. data/doc/styles/jamis.rb +593 -0
  57. data/doc/tutorials/01-GettingStarted.rdoc +86 -0
  58. data/doc/tutorials/02-GoForward.rdoc +220 -0
  59. data/doc/tutorials/03-PlannedPath.rdoc +268 -0
  60. data/doc/tutorials/04-EventPropagation.rdoc +236 -0
  61. data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
  62. data/doc/tutorials/06-Overview.rdoc +40 -0
  63. data/doc/videos.rdoc +69 -0
  64. data/ext/droby/dump.cc +175 -0
  65. data/ext/droby/extconf.rb +3 -0
  66. data/ext/graph/algorithm.cc +746 -0
  67. data/ext/graph/extconf.rb +7 -0
  68. data/ext/graph/graph.cc +529 -0
  69. data/ext/graph/graph.hh +183 -0
  70. data/ext/graph/iterator_sequence.hh +102 -0
  71. data/ext/graph/undirected_dfs.hh +226 -0
  72. data/ext/graph/undirected_graph.hh +421 -0
  73. data/lib/roby.rb +41 -0
  74. data/lib/roby/app.rb +870 -0
  75. data/lib/roby/app/rake.rb +56 -0
  76. data/lib/roby/app/run.rb +14 -0
  77. data/lib/roby/app/scripts/distributed.rb +13 -0
  78. data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
  79. data/lib/roby/app/scripts/replay.rb +31 -0
  80. data/lib/roby/app/scripts/results.rb +15 -0
  81. data/lib/roby/app/scripts/run.rb +26 -0
  82. data/lib/roby/app/scripts/server.rb +18 -0
  83. data/lib/roby/app/scripts/shell.rb +88 -0
  84. data/lib/roby/app/scripts/test.rb +40 -0
  85. data/lib/roby/basic_object.rb +151 -0
  86. data/lib/roby/config.rb +5 -0
  87. data/lib/roby/control.rb +747 -0
  88. data/lib/roby/decision_control.rb +17 -0
  89. data/lib/roby/distributed.rb +32 -0
  90. data/lib/roby/distributed/base.rb +440 -0
  91. data/lib/roby/distributed/communication.rb +871 -0
  92. data/lib/roby/distributed/connection_space.rb +592 -0
  93. data/lib/roby/distributed/distributed_object.rb +206 -0
  94. data/lib/roby/distributed/drb.rb +62 -0
  95. data/lib/roby/distributed/notifications.rb +539 -0
  96. data/lib/roby/distributed/peer.rb +550 -0
  97. data/lib/roby/distributed/protocol.rb +529 -0
  98. data/lib/roby/distributed/proxy.rb +343 -0
  99. data/lib/roby/distributed/subscription.rb +311 -0
  100. data/lib/roby/distributed/transaction.rb +498 -0
  101. data/lib/roby/event.rb +897 -0
  102. data/lib/roby/exceptions.rb +234 -0
  103. data/lib/roby/executives/simple.rb +30 -0
  104. data/lib/roby/graph.rb +166 -0
  105. data/lib/roby/interface.rb +390 -0
  106. data/lib/roby/log.rb +3 -0
  107. data/lib/roby/log/chronicle.rb +303 -0
  108. data/lib/roby/log/console.rb +72 -0
  109. data/lib/roby/log/data_stream.rb +197 -0
  110. data/lib/roby/log/dot.rb +279 -0
  111. data/lib/roby/log/event_stream.rb +151 -0
  112. data/lib/roby/log/file.rb +340 -0
  113. data/lib/roby/log/gui/basic_display.ui +83 -0
  114. data/lib/roby/log/gui/chronicle.rb +26 -0
  115. data/lib/roby/log/gui/chronicle_view.rb +40 -0
  116. data/lib/roby/log/gui/chronicle_view.ui +70 -0
  117. data/lib/roby/log/gui/data_displays.rb +172 -0
  118. data/lib/roby/log/gui/data_displays.ui +155 -0
  119. data/lib/roby/log/gui/notifications.rb +26 -0
  120. data/lib/roby/log/gui/relations.rb +248 -0
  121. data/lib/roby/log/gui/relations.ui +123 -0
  122. data/lib/roby/log/gui/relations_view.rb +185 -0
  123. data/lib/roby/log/gui/relations_view.ui +149 -0
  124. data/lib/roby/log/gui/replay.rb +327 -0
  125. data/lib/roby/log/gui/replay_controls.rb +200 -0
  126. data/lib/roby/log/gui/replay_controls.ui +259 -0
  127. data/lib/roby/log/gui/runtime.rb +130 -0
  128. data/lib/roby/log/hooks.rb +185 -0
  129. data/lib/roby/log/logger.rb +202 -0
  130. data/lib/roby/log/notifications.rb +244 -0
  131. data/lib/roby/log/plan_rebuilder.rb +470 -0
  132. data/lib/roby/log/relations.rb +1056 -0
  133. data/lib/roby/log/server.rb +550 -0
  134. data/lib/roby/log/sqlite.rb +47 -0
  135. data/lib/roby/log/timings.rb +164 -0
  136. data/lib/roby/plan-object.rb +247 -0
  137. data/lib/roby/plan.rb +762 -0
  138. data/lib/roby/planning.rb +13 -0
  139. data/lib/roby/planning/loops.rb +302 -0
  140. data/lib/roby/planning/model.rb +906 -0
  141. data/lib/roby/planning/task.rb +151 -0
  142. data/lib/roby/propagation.rb +562 -0
  143. data/lib/roby/query.rb +619 -0
  144. data/lib/roby/relations.rb +583 -0
  145. data/lib/roby/relations/conflicts.rb +70 -0
  146. data/lib/roby/relations/ensured.rb +20 -0
  147. data/lib/roby/relations/error_handling.rb +23 -0
  148. data/lib/roby/relations/events.rb +9 -0
  149. data/lib/roby/relations/executed_by.rb +193 -0
  150. data/lib/roby/relations/hierarchy.rb +239 -0
  151. data/lib/roby/relations/influence.rb +10 -0
  152. data/lib/roby/relations/planned_by.rb +63 -0
  153. data/lib/roby/robot.rb +7 -0
  154. data/lib/roby/standard_errors.rb +218 -0
  155. data/lib/roby/state.rb +5 -0
  156. data/lib/roby/state/events.rb +221 -0
  157. data/lib/roby/state/information.rb +55 -0
  158. data/lib/roby/state/pos.rb +110 -0
  159. data/lib/roby/state/shapes.rb +32 -0
  160. data/lib/roby/state/state.rb +353 -0
  161. data/lib/roby/support.rb +92 -0
  162. data/lib/roby/task-operations.rb +182 -0
  163. data/lib/roby/task.rb +1618 -0
  164. data/lib/roby/test/common.rb +399 -0
  165. data/lib/roby/test/distributed.rb +214 -0
  166. data/lib/roby/test/tasks/empty_task.rb +9 -0
  167. data/lib/roby/test/tasks/goto.rb +36 -0
  168. data/lib/roby/test/tasks/simple_task.rb +23 -0
  169. data/lib/roby/test/testcase.rb +519 -0
  170. data/lib/roby/test/tools.rb +160 -0
  171. data/lib/roby/thread_task.rb +87 -0
  172. data/lib/roby/transactions.rb +462 -0
  173. data/lib/roby/transactions/proxy.rb +292 -0
  174. data/lib/roby/transactions/updates.rb +139 -0
  175. data/plugins/fault_injection/History.txt +4 -0
  176. data/plugins/fault_injection/README.txt +37 -0
  177. data/plugins/fault_injection/Rakefile +18 -0
  178. data/plugins/fault_injection/TODO.txt +0 -0
  179. data/plugins/fault_injection/app.rb +52 -0
  180. data/plugins/fault_injection/fault_injection.rb +89 -0
  181. data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
  182. data/plugins/subsystems/README.txt +40 -0
  183. data/plugins/subsystems/Rakefile +18 -0
  184. data/plugins/subsystems/app.rb +171 -0
  185. data/plugins/subsystems/test/app/README +24 -0
  186. data/plugins/subsystems/test/app/Rakefile +8 -0
  187. data/plugins/subsystems/test/app/config/app.yml +71 -0
  188. data/plugins/subsystems/test/app/config/init.rb +9 -0
  189. data/plugins/subsystems/test/app/config/roby.yml +3 -0
  190. data/plugins/subsystems/test/app/planners/main.rb +20 -0
  191. data/plugins/subsystems/test/app/scripts/distributed +3 -0
  192. data/plugins/subsystems/test/app/scripts/replay +3 -0
  193. data/plugins/subsystems/test/app/scripts/results +3 -0
  194. data/plugins/subsystems/test/app/scripts/run +3 -0
  195. data/plugins/subsystems/test/app/scripts/server +3 -0
  196. data/plugins/subsystems/test/app/scripts/shell +3 -0
  197. data/plugins/subsystems/test/app/scripts/test +3 -0
  198. data/plugins/subsystems/test/app/tasks/services.rb +15 -0
  199. data/plugins/subsystems/test/test_subsystems.rb +71 -0
  200. data/test/distributed/test_communication.rb +178 -0
  201. data/test/distributed/test_connection.rb +282 -0
  202. data/test/distributed/test_execution.rb +373 -0
  203. data/test/distributed/test_mixed_plan.rb +341 -0
  204. data/test/distributed/test_plan_notifications.rb +238 -0
  205. data/test/distributed/test_protocol.rb +516 -0
  206. data/test/distributed/test_query.rb +102 -0
  207. data/test/distributed/test_remote_plan.rb +491 -0
  208. data/test/distributed/test_transaction.rb +463 -0
  209. data/test/mockups/tasks.rb +27 -0
  210. data/test/planning/test_loops.rb +380 -0
  211. data/test/planning/test_model.rb +427 -0
  212. data/test/planning/test_task.rb +106 -0
  213. data/test/relations/test_conflicts.rb +42 -0
  214. data/test/relations/test_ensured.rb +38 -0
  215. data/test/relations/test_executed_by.rb +149 -0
  216. data/test/relations/test_hierarchy.rb +158 -0
  217. data/test/relations/test_planned_by.rb +54 -0
  218. data/test/suite_core.rb +24 -0
  219. data/test/suite_distributed.rb +9 -0
  220. data/test/suite_planning.rb +3 -0
  221. data/test/suite_relations.rb +8 -0
  222. data/test/test_bgl.rb +508 -0
  223. data/test/test_control.rb +399 -0
  224. data/test/test_event.rb +894 -0
  225. data/test/test_exceptions.rb +592 -0
  226. data/test/test_interface.rb +37 -0
  227. data/test/test_log.rb +114 -0
  228. data/test/test_log_server.rb +132 -0
  229. data/test/test_plan.rb +584 -0
  230. data/test/test_propagation.rb +210 -0
  231. data/test/test_query.rb +266 -0
  232. data/test/test_relations.rb +180 -0
  233. data/test/test_state.rb +414 -0
  234. data/test/test_support.rb +16 -0
  235. data/test/test_task.rb +938 -0
  236. data/test/test_testcase.rb +122 -0
  237. data/test/test_thread_task.rb +73 -0
  238. data/test/test_transactions.rb +569 -0
  239. data/test/test_transactions_proxy.rb +198 -0
  240. 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
+