roby 0.7

Sign up to get free protection for your applications and to get access to all the features.
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
+