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
@@ -0,0 +1,92 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ class String # :nodoc: all
3
+ include ActiveSupport::CoreExtensions::String::Inflections
4
+ end
5
+
6
+ require 'roby/config'
7
+ require 'facets/kernel/constant'
8
+ require 'utilrb/enumerable'
9
+ require 'utilrb/time/to_hms'
10
+ require 'utilrb/module/cached_enum'
11
+ require 'utilrb/logger'
12
+ require 'utilrb/gc/force'
13
+ require 'utilrb/hash/to_sym_keys'
14
+ require 'utilrb/array/to_s'
15
+ require 'utilrb/hash/to_s'
16
+ require 'utilrb/set/to_s'
17
+
18
+ class IO
19
+ def ask(question, default, output_io = STDOUT)
20
+ output_io.print question
21
+ output_io.flush
22
+ loop do
23
+ answer = readline.chomp.downcase
24
+ if answer.empty?
25
+ return default
26
+ elsif answer == 'y'
27
+ return true
28
+ elsif answer == 'n'
29
+ return false
30
+ else
31
+ output_io.print "\nInvalid answer, try again: "
32
+ output_io.flush
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ module Enumerable
39
+ def empty?
40
+ for i in self
41
+ return false
42
+ end
43
+ true
44
+ end
45
+ end
46
+
47
+ class Module
48
+ # Defines a new constant under a given module
49
+ # :call-seq
50
+ # define_under(name, value) -> value
51
+ # define_under(name) { ... } -> value
52
+ #
53
+ # In the first form, the method gets its value from its argument.
54
+ # In the second case, it calls the provided block
55
+ def define_under(name, value = nil)
56
+ if old = constants.find { |cn| cn == name.to_s }
57
+ return const_get(old)
58
+ else
59
+ const_set(name, (value || yield))
60
+ end
61
+ end
62
+ end
63
+
64
+ class Thread
65
+ def send_to(object, name, *args, &prc)
66
+ if Thread.current == self
67
+ object.send(name, *args, &prc)
68
+ else
69
+ @msg_queue ||= Queue.new
70
+ @msg_queue << [ object, name, args, prc ]
71
+ end
72
+ end
73
+ def process_events
74
+ @msg_queue ||= Queue.new
75
+ loop do
76
+ object, name, args, block = *@msg_queue.deq(true)
77
+ object.send(name, *args, &block)
78
+ end
79
+ rescue ThreadError
80
+ end
81
+ end
82
+
83
+ module Roby
84
+ @logger = Logger.new(STDERR)
85
+ @logger.level = Logger::WARN
86
+ @logger.progname = "Roby"
87
+ @logger.formatter = lambda { |severity, time, progname, msg| "#{time.to_hms} (#{progname}) #{msg}\n" }
88
+
89
+ extend Logger::Hierarchy
90
+ extend Logger::Forward
91
+ end
92
+
@@ -0,0 +1,182 @@
1
+ require 'roby/task'
2
+
3
+ module Roby
4
+ module TaskOperations
5
+ def +(task)
6
+ # !!!! + is NOT commutative
7
+ if task.null?
8
+ self
9
+ elsif self.null?
10
+ task
11
+ else
12
+ Sequence.new << self << task
13
+ end
14
+ end
15
+ def |(task)
16
+ if self.null?
17
+ task
18
+ elsif task.null?
19
+ self
20
+ else
21
+ Parallel.new << self << task
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ class Task
28
+ include TaskOperations
29
+ end
30
+
31
+ class TaskAggregator < Roby::Task
32
+ terminates
33
+ event(:start, :command => true)
34
+
35
+ attr_reader :tasks
36
+ def initialize(arguments = {}); @tasks = Array.new; super end
37
+ def each_task(&iterator)
38
+ yield(self)
39
+ tasks.each(&iterator)
40
+ end
41
+ def empty?; tasks.empty? end
42
+
43
+ def delete
44
+ @name = self.name
45
+ @tasks = nil
46
+ if plan
47
+ plan.remove_object(self)
48
+ else
49
+ clear_relations
50
+ freeze
51
+ end
52
+ end
53
+ end
54
+
55
+ class Sequence < TaskAggregator
56
+ def name
57
+ @name || @tasks.map { |t| t.name }.join("+")
58
+ end
59
+
60
+ def to_task(task = nil)
61
+ return super() unless task
62
+ task = task.new unless task.kind_of?(Roby::Task)
63
+ @tasks.each { |t| task.realized_by t }
64
+
65
+ task.on(:start, @tasks.first, :start)
66
+ @tasks.last.forward(:success, task, :success)
67
+
68
+ delete
69
+
70
+ task
71
+ end
72
+
73
+ def connect_start(task)
74
+ if old = @tasks.first
75
+ event(:start).remove_signal old.event(:start)
76
+ task.on(:success, old, :start)
77
+ end
78
+
79
+ event(:start).on task.event(:start)
80
+ end
81
+
82
+ def connect_stop(task)
83
+ if old = @tasks.last
84
+ old.on(:success, task, :start)
85
+ old.event(:success).remove_forwarding event(:success)
86
+ end
87
+ task.forward(:success, self)
88
+ end
89
+ private :connect_stop, :connect_start
90
+
91
+ def unshift(task)
92
+ raise "trying to do Sequence#unshift on a running or finished sequence" if (running? || finished?)
93
+ connect_start(task)
94
+ connect_stop(task) if @tasks.empty?
95
+
96
+ @tasks.unshift(task)
97
+ realized_by task
98
+ self
99
+ end
100
+
101
+ def <<(task)
102
+ raise "trying to do Sequence#<< on a finished sequence" if finished?
103
+ connect_start(task) if @tasks.empty?
104
+ connect_stop(task)
105
+
106
+ @tasks << task
107
+ realized_by task
108
+ self
109
+ end
110
+
111
+ def to_sequence; self end
112
+ end
113
+
114
+ class Parallel < TaskAggregator
115
+ def name
116
+ @name || @tasks.map { |t| t.name }.join("|")
117
+ end
118
+
119
+ attr_reader :children_success
120
+ def initialize(arguments = {})
121
+ super
122
+
123
+ @children_success = Roby::AndGenerator.new
124
+ @children_success.forward event(:success)
125
+ end
126
+
127
+ def to_task(task = nil)
128
+ return super() unless task
129
+
130
+ task = task.new unless task.kind_of?(Roby::Task)
131
+ @tasks.each do |t|
132
+ task.realized_by t
133
+ task.on(:start, t, :start)
134
+ end
135
+ task.event(:success).emit_on children_success
136
+
137
+ delete
138
+
139
+ task
140
+ end
141
+
142
+ def <<(task)
143
+ raise "trying to change a running parallel task" if running?
144
+ @tasks << task
145
+
146
+ on(:start, task, :start)
147
+ realized_by task
148
+ children_success << task.event(:success)
149
+
150
+ self
151
+ end
152
+
153
+ def to_parallel; self end
154
+ end
155
+
156
+ class Group < Roby::Task
157
+ def initialize(*tasks)
158
+ super()
159
+ if tasks.empty? || tasks.first.kind_of?(Hash)
160
+ return
161
+ end
162
+
163
+ success = AndGenerator.new
164
+ tasks.each do |task|
165
+ realized_by task
166
+ task.event(:success).on success
167
+ end
168
+ success.forward event(:success)
169
+ end
170
+
171
+ event :start do
172
+ children.each do |child|
173
+ if child.pending? && child.event(:start).root?
174
+ child.start!
175
+ end
176
+ end
177
+ emit :start
178
+ end
179
+ terminates
180
+ end
181
+ end
182
+
data/lib/roby/task.rb ADDED
@@ -0,0 +1,1618 @@
1
+ require 'roby/plan-object'
2
+ require 'roby/exceptions'
3
+ require 'roby/event'
4
+ require 'utilrb/module/attr_predicate'
5
+
6
+ module Roby
7
+ class TaskModelTag < Module
8
+ module ClassExtension
9
+ # Returns the list of static arguments required by this task model
10
+ def arguments(*new_arguments)
11
+ new_arguments.each do |arg_name|
12
+ argument_set << arg_name.to_sym
13
+ unless method_defined?(arg_name)
14
+ define_method(arg_name) { arguments[arg_name] }
15
+ end
16
+ end
17
+
18
+ @argument_enumerator ||= enum_for(:each_argument_set)
19
+ end
20
+ # Declares a set of arguments required by this task model
21
+ def argument(*args); arguments(*args) end
22
+ end
23
+ include TaskModelTag::ClassExtension
24
+
25
+ def initialize(&block)
26
+ super do
27
+ inherited_enumerable("argument_set", "argument_set") { ValueSet.new }
28
+ unless const_defined? :ClassExtension
29
+ const_set(:ClassExtension, Module.new)
30
+ end
31
+
32
+ self::ClassExtension.include TaskModelTag::ClassExtension
33
+ end
34
+ class_eval(&block) if block_given?
35
+ end
36
+
37
+ def clear_model
38
+ @argument_set.clear if @argument_set
39
+ end
40
+ end
41
+
42
+ # Base class for task events
43
+ # When events are emitted, then the created object is
44
+ # an instance of a class derived from this one
45
+ class TaskEvent < Event
46
+ # The task which fired this event
47
+ attr_reader :task
48
+
49
+ def initialize(task, generator, propagation_id, context, time = Time.now)
50
+ @task = task
51
+ @terminal_flag = generator.terminal_flag
52
+ super(generator, propagation_id, context, time)
53
+ end
54
+
55
+ # Returns the set of events from the task that are the cause of this
56
+ # event
57
+ def task_sources
58
+ result = ValueSet.new
59
+ if sources
60
+ for ev in sources
61
+ gen = ev.generator
62
+ if gen.respond_to?(:task) && gen.task == task
63
+ result.merge ev.task_sources
64
+ end
65
+ end
66
+ end
67
+ if result.empty?
68
+ result << self
69
+ end
70
+
71
+ result
72
+ end
73
+
74
+ def to_s
75
+ "#{generator.to_s}@#{propagation_id} [#{time.to_hms}]: #{context}"
76
+ end
77
+
78
+ def pretty_print(pp)
79
+ pp.text "at [#{time.to_hms}/#{propagation_id}] in the "
80
+ generator.pretty_print(pp)
81
+ pp.breakable
82
+ pp.group(2) do
83
+ pp.seplist(context || []) { |v| v.pretty_print pp }
84
+ end
85
+ end
86
+
87
+ # If the event model defines a controlable event
88
+ # By default, an event is controlable if the model
89
+ # responds to #call
90
+ def self.controlable?; respond_to?(:call) end
91
+ # If the event is controlable
92
+ def controlable?; self.class.controlable? end
93
+ class << self
94
+ # Called by Task.update_terminal_flag to update the flag
95
+ attr_writer :terminal
96
+ end
97
+ # If the event model defines a terminal event
98
+ def self.terminal?; @terminal end
99
+ # If this event is terminal
100
+ def success?; @terminal_flag == :success end
101
+ # If this event is terminal
102
+ def failure?; @terminal_flag == :failure end
103
+ # If this event is terminal
104
+ def terminal?; @terminal_flag end
105
+ # The event symbol
106
+ def self.symbol; @symbol end
107
+ # The event symbol
108
+ def symbol; self.class.symbol end
109
+ end
110
+
111
+ # A task event model bound to a particular task instance
112
+ # The Task/TaskEvent/TaskEventGenerator relationship is
113
+ # comparable to the Class/UnboundMethod/Method one:
114
+ # * a Task object is a model for a task, a Class in a model for an object
115
+ # * a TaskEvent object is a model for an event instance (the instance being unspecified),
116
+ # an UnboundMethod is a model for an instance method
117
+ # * a TaskEventGenerator object represents a particular event model
118
+ # *bound* to a particular task instance, a Method object represents a particular method
119
+ # bound to a particular object
120
+ class TaskEventGenerator < EventGenerator
121
+ # The task we are part of
122
+ attr_reader :task
123
+ # The event symbol (its name as a Symbol object)
124
+ attr_reader :symbol
125
+ # The event class
126
+ attr_reader :event_model
127
+ def initialize(task, model)
128
+ @task, @event_model = task, model
129
+ @symbol = model.symbol
130
+ super(model.respond_to?(:call))
131
+ end
132
+
133
+ def default_command(context)
134
+ event_model.call(task, context)
135
+ end
136
+
137
+ # See PlanObject::child_plan_object.
138
+ child_plan_object :task
139
+
140
+ # The event plan. It is the same as task.plan and is actually updated
141
+ # by task.plan=. It is redefined here for performance reasons.
142
+ attr_accessor :plan
143
+
144
+ # Fire the event
145
+ def fire(event)
146
+ task.fire_event(event)
147
+ super
148
+ end
149
+
150
+ # See EventGenerator#calling
151
+ #
152
+ # In TaskEventGenerator, this hook checks that the task is running
153
+ def calling(context)
154
+ super if defined? super
155
+ if task.finished? && !terminal?
156
+ raise CommandFailed.new(nil, self),
157
+ "#{symbol}!(#{context})) called by #{Propagation.sources} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources}."
158
+ elsif task.pending? && symbol != :start
159
+ raise CommandFailed.new(nil, self),
160
+ "#{symbol}!(#{context})) called by #{Propagation.sources} but the task is not running"
161
+ elsif task.running? && symbol == :start
162
+ raise CommandFailed.new(nil, self),
163
+ "#{symbol}!(#{context})) called by #{Propagation.sources} but the task is already running. Task has been started by #{task.event(:start).history.first.sources}."
164
+ end
165
+ end
166
+
167
+ # See EventGenerator#fired
168
+ #
169
+ # In TaskEventGenerator, this hook calls the unreachable handlers added
170
+ # by EventGenerator#if_unreachable when the task has finished, not
171
+ # before
172
+ def fired(event)
173
+ super if defined? super
174
+
175
+ if symbol == :stop
176
+ task.each_event { |ev| ev.unreachable!(task.terminal_event) }
177
+ end
178
+ end
179
+
180
+ def related_tasks(result = nil)
181
+ tasks = super
182
+ tasks.delete(task)
183
+ tasks
184
+ end
185
+
186
+ def each_handler
187
+ super
188
+
189
+ if self_owned?
190
+ task.each_handler(event_model.symbol) { |o| yield(o) }
191
+ end
192
+ end
193
+ def each_precondition
194
+ super
195
+ task.each_precondition(event_model.symbol) { |o| yield(o) }
196
+ end
197
+
198
+ def controlable?; event_model.controlable? end
199
+ attr_accessor :terminal_flag
200
+ def terminal?; !!@terminal_flag end
201
+ def success?; @terminal_flag == :success end
202
+ def failure?; @terminal_flag == :failure end
203
+ def added_child_object(child, relations, info)
204
+ super if defined? super
205
+
206
+ if relations.include?(EventStructure::CausalLink) &&
207
+ child.respond_to?(:task) && child.task == task &&
208
+ child.terminal_flag != terminal_flag
209
+
210
+ task.update_terminal_flag
211
+ end
212
+ end
213
+ def removed_child_object(child, relations)
214
+ super if defined? super
215
+
216
+ if relations.include?(EventStructure::CausalLink) &&
217
+ child.respond_to?(:task) && child.task == task &&
218
+ terminal_flag
219
+
220
+ task.update_terminal_flag
221
+ end
222
+ end
223
+ def new(context); event_model.new(task, self, Propagation.propagation_id, context) end
224
+
225
+ def to_s
226
+ "#{task}/#{symbol}"
227
+ end
228
+ def inspect
229
+ "#{task.inspect}/#{symbol}: #{history.to_s}"
230
+ end
231
+ def pretty_print(pp)
232
+ pp.text "#{symbol} event of #{task.class}:0x#{task.address.to_s(16)}"
233
+ end
234
+
235
+ def achieve_with(obj)
236
+ child_task, child_event = case obj
237
+ when Roby::Task: [obj, obj.event(:success)]
238
+ when Roby::TaskEventGenerator: [obj.task, obj]
239
+ end
240
+
241
+ if child_task
242
+ unless task.realized_by?(child_task)
243
+ task.realized_by child_task,
244
+ :success => [child_event.symbol],
245
+ :remove_when_done => true
246
+ end
247
+ super(child_event)
248
+ else
249
+ super(obj)
250
+ end
251
+ end
252
+ end
253
+
254
+ class TaskArguments < Hash
255
+ private :delete, :delete_if
256
+
257
+ attr_reader :task
258
+ def initialize(task)
259
+ @task = task
260
+ super()
261
+ end
262
+
263
+ def writable?(key)
264
+ !(has_key?(key) && task.model.arguments.include?(key))
265
+ end
266
+
267
+ def dup; self.to_hash end
268
+ def to_hash
269
+ inject({}) { |h, (k, v)| h[k] = v ; h }
270
+ end
271
+
272
+ alias :update! :[]=
273
+ def []=(key, value)
274
+ if writable?(key)
275
+ if !task.read_write?
276
+ raise OwnershipError, "cannot change the argument set of a task which is not owned #{task} is owned by #{task.owners} and #{task.plan} by #{task.plan.owners}"
277
+ end
278
+
279
+ updating
280
+ super
281
+ updated
282
+ else
283
+ raise ArgumentError, "cannot override task arguments"
284
+ end
285
+ end
286
+ def updating; super if defined? super end
287
+ def updated; super if defined? super end
288
+
289
+ alias :do_merge! :merge!
290
+ def merge!(hash)
291
+ super do |key, old, new|
292
+ if old == new then old
293
+ elsif writable?(key) then new
294
+ else
295
+ raise ArgumentError, "cannot override task argument #{key}: trying to replace #{old} by #{new}"
296
+ end
297
+ end
298
+ end
299
+ end
300
+
301
+ # In a plan, Task objects represent the activities of the robot.
302
+ #
303
+ # === Task models
304
+ #
305
+ # A task model is mainly described by:
306
+ #
307
+ # <b>a set of named arguments</b>, which are required to parametrize the
308
+ # task instance. The argument list is described using Task.argument and
309
+ # arguments are either set at object creation by passing an argument hash
310
+ # to Task.new, or by calling Task#argument explicitely.
311
+ #
312
+ # <b>a set of events</b>, which are situations describing the task
313
+ # progression. The base Roby::Task model defines the
314
+ # +start+,+success+,+failed+ and +stop+ events. Events can be defined on
315
+ # the models by using Task.event:
316
+ #
317
+ # class MyTask < Roby::Task
318
+ # event :intermediate_event
319
+ # end
320
+ #
321
+ # defines a non-controllable event, i.e. an event which can be emitted, but
322
+ # cannot be triggered explicitely by the system. Controllable events are defined
323
+ # by associating a block of code with the event, this block being responsible for
324
+ # making the event emitted either in the future or just now. For instance,
325
+ #
326
+ # class MyTask < Roby::Task
327
+ # event :intermediate_event do |context|
328
+ # emit :intermediate_event
329
+ # end
330
+ #
331
+ # event :other_event do |context|
332
+ # Roby.once { emit :other_event }
333
+ # end
334
+ # end
335
+ #
336
+ # define two controllable event. In the first case, the event is
337
+ # immediately emitted, and in the second case it will be emitted at the
338
+ # beginning of the next execution cycle.
339
+ #
340
+ # === Executability
341
+ #
342
+ # By default, a task is not executable, which means that no event command
343
+ # can be called and no event can be emitted. A task becomes executable
344
+ # either because Task#executable= has explicitely been called or because it
345
+ # has been inserted in a Plan object. Note that forcing executability with
346
+ # #executable= is only useful for testing. When the Roby controller manages
347
+ # a real systems, the executability property enforces the constraint that a
348
+ # task cannot be executed outside of the plan supervision.
349
+ #
350
+ # Finally, it is possible to describe _abstract_ task models: tasks which
351
+ # do represent an action, but for which the _means_ to perform that action
352
+ # are still unknown. This is done by calling Task.abstract in the task definition:
353
+ #
354
+ # class AbstTask < Roby::Task
355
+ # abstract
356
+ # end
357
+ #
358
+ # An instance of an abstract model cannot be executed, even if it is included
359
+ # in a plan.
360
+ #
361
+ # === Inheritance rules
362
+ #
363
+ # On task models, a submodel can inherit from a parent model if the actions
364
+ # described by the parent model are also performed by the child model. For
365
+ # instance, a <tt>Goto(x, y)</tt> model could be subclassed into a
366
+ # <tt>Goto::ByFoot(x, y)</tt> model.
367
+ #
368
+ # The following constraints apply when subclassing a task model:
369
+ # * a task subclass has at least the same events than the parent class
370
+ # * changes to event attributes are limited. The rules are:
371
+ # - a controlable event must remain controlable. Nonetheless, a
372
+ # non-controlable event can become a controlable one
373
+ # - a terminal event (i.e. a terminal event which ends the task
374
+ # execution) cannot become non-terminal. Nonetheless, a non-terminal
375
+ # event can become terminal.
376
+ #
377
+ class Task < PlanObject
378
+ unless defined? RootTaskTag
379
+ RootTaskTag = TaskModelTag.new
380
+ include RootTaskTag
381
+ end
382
+
383
+ # Clears all definitions saved in this model. This is to be used by the
384
+ # reloading code
385
+ def self.clear_model
386
+ class_eval do
387
+ # Remove event models
388
+ events.each_key do |ev_symbol|
389
+ remove_const ev_symbol.to_s.camelize
390
+ end
391
+
392
+ [@events, @signal_sets, @forwarding_sets, @causal_link_sets,
393
+ @argument_set, @handler_sets, @precondition_sets].each do |set|
394
+ set.clear if set
395
+ end
396
+ end
397
+ end
398
+
399
+ # Declares an attribute set which follows the task models inheritance
400
+ # hierarchy. Define the corresponding enumeration methods as well.
401
+ #
402
+ # For instance,
403
+ # model_attribute_list 'signal'
404
+ #
405
+ # defines the model-level signals, which can be accessed through
406
+ # .each_signal(model)
407
+ # .signals(model)
408
+ # #each_signal(model)
409
+ #
410
+ def self.model_attribute_list(name) # :nodoc:
411
+ inherited_enumerable("#{name}_set", "#{name}_sets", :map => true) { Hash.new { |h, k| h[k] = ValueSet.new } }
412
+ class_eval <<-EOD
413
+ def self.each_#{name}(model)
414
+ for obj in #{name}s(model)
415
+ yield(obj)
416
+ end
417
+ self
418
+ end
419
+ def self.#{name}s(model)
420
+ result = ValueSet.new
421
+ each_#{name}_set(model, false) do |set|
422
+ result.merge set
423
+ end
424
+ result
425
+ end
426
+ def each_#{name}(model); self.model.each_#{name}(model) { |o| yield(o) } end
427
+ EOD
428
+ end
429
+
430
+ model_attribute_list('signal')
431
+ model_attribute_list('forwarding')
432
+ model_attribute_list('causal_link')
433
+ model_attribute_list('handler')
434
+ model_attribute_list('precondition')
435
+
436
+ # The task arguments as symbol => value associative container
437
+ attr_reader :arguments
438
+ # The part of +arguments+ that is meaningful for this task model
439
+ def meaningful_arguments(task_model = self.model)
440
+ arguments.slice(*task_model.arguments)
441
+ end
442
+ # The task name
443
+ def name
444
+ @name ||= "#{model.name || self.class.name}#{arguments.to_s}:0x#{address.to_s(16)}"
445
+ end
446
+
447
+ # This predicate is true if this task is a mission for its owners. If
448
+ # you want to know if it a mission for the local pDB, use Plan#mission?
449
+ attr_predicate :mission?, true
450
+
451
+ def inspect
452
+ state = if pending? then 'pending'
453
+ elsif starting? then 'starting'
454
+ elsif running? then 'running'
455
+ elsif finishing? then 'finishing'
456
+ else 'finished'
457
+ end
458
+ "#<#{to_s} executable=#{executable?} state=#{state} plan=#{plan.to_s}>"
459
+ end
460
+
461
+ # Builds a task object using this task model
462
+ #
463
+ # The task object can be configured by a given block. After the
464
+ # block is called, two things are checked:
465
+ # * the task shall have a +start+ event
466
+ # * the task shall have at least one terminal event. If no +stop+ event
467
+ # is defined, then all terminal events are aliased to +stop+
468
+ def initialize(arguments = nil) #:yields: task_object
469
+ super() if defined? super
470
+
471
+ @arguments = TaskArguments.new(self)
472
+ @arguments.merge!(arguments) if arguments
473
+
474
+ @model = self.class
475
+
476
+ yield(self) if block_given?
477
+
478
+ initialize_events
479
+ end
480
+
481
+ # Helper methods which creates all the necessary TaskEventGenerator
482
+ # objects and stores them in the #bound_events map
483
+ def initialize_events # :nodoc:
484
+ @instantiated_model_events = false
485
+
486
+ # Create all event generators
487
+ bound_events = Hash.new
488
+ model.each_event do |ev_symbol, ev_model|
489
+ bound_events[ev_symbol.to_sym] = TaskEventGenerator.new(self, ev_model)
490
+ end
491
+ @bound_events = bound_events
492
+ end
493
+
494
+ def model; self.class end
495
+
496
+ def initialize_copy(old) # :nodoc:
497
+ super
498
+
499
+ @name = nil
500
+ @history = old.history.dup
501
+
502
+ @arguments = TaskArguments.new(self)
503
+ arguments.do_merge! old.arguments
504
+ arguments.instance_variable_set(:@task, self)
505
+
506
+ initialize_events
507
+ plan.discover(self)
508
+ end
509
+
510
+ def instantiate_model_event_relations
511
+ return if @instantiated_model_events
512
+ # Add the model-level signals to this instance
513
+ @instantiated_model_events = true
514
+
515
+ left_border = bound_events.values.to_value_set
516
+ right_border = bound_events.values.to_value_set
517
+
518
+ model.each_signal_set do |generator, signalled_events|
519
+ next if signalled_events.empty?
520
+ generator = bound_events[generator]
521
+ right_border.delete(generator)
522
+
523
+ for signalled in signalled_events
524
+ signalled = bound_events[signalled]
525
+ generator.signal signalled
526
+ left_border.delete(signalled)
527
+ end
528
+ end
529
+
530
+
531
+ model.each_forwarding_set do |generator, signalled_events|
532
+ next if signalled_events.empty?
533
+ generator = bound_events[generator]
534
+ right_border.delete(generator)
535
+
536
+ for signalled in signalled_events
537
+ signalled = bound_events[signalled]
538
+ generator.forward signalled
539
+ left_border.delete(signalled)
540
+ end
541
+ end
542
+
543
+ model.each_causal_link_set do |generator, signalled_events|
544
+ next if signalled_events.empty?
545
+ generator = bound_events[generator]
546
+ right_border.delete(generator)
547
+
548
+ for signalled in signalled_events
549
+ signalled = bound_events[signalled]
550
+ generator.add_causal_link signalled
551
+ left_border.delete(signalled)
552
+ end
553
+ end
554
+
555
+ update_terminal_flag
556
+
557
+ # WARNING: this works only because:
558
+ # * there is always at least updated_data as an intermediate event
559
+ # * there is always one terminal event which is not stop
560
+ start_event = bound_events[:start]
561
+ stop_event = bound_events[:stop]
562
+ left_border.delete(start_event)
563
+ right_border.delete(start_event)
564
+ left_border.delete(stop_event)
565
+ right_border.delete(stop_event)
566
+
567
+ for generator in left_border
568
+ start_event.add_precedence(generator) unless generator.terminal?
569
+ end
570
+
571
+ # WARN: the start event CAN be terminal: it can be a signal from
572
+ # :start to a terminal event
573
+ #
574
+ # Create the precedence relations between 'normal' events and the terminal events
575
+ for terminal in left_border
576
+ next unless terminal.terminal?
577
+ for generator in right_border
578
+ unless generator.terminal?
579
+ generator.add_precedence(terminal)
580
+ end
581
+ end
582
+ end
583
+ end
584
+
585
+ def plan=(new_plan) # :nodoc:
586
+ if plan != new_plan
587
+ if plan && plan.include?(self)
588
+ raise ModelViolation.new, "still included in #{plan}, cannot change the plan"
589
+ elsif self_owned? && running?
590
+ raise ModelViolation.new, "cannot change the plan of a running task"
591
+ end
592
+ end
593
+
594
+ super
595
+
596
+ for _, ev in bound_events
597
+ ev.plan = plan
598
+ end
599
+ end
600
+
601
+ class << self
602
+ # If this task is an abstract task
603
+ # Abstract tasks are not executable. This attribute is
604
+ # not inherited in the task hierarchy
605
+ attr_reader :abstract
606
+ alias :abstract? :abstract
607
+
608
+ # Mark this task model as an abstract model
609
+ def abstract
610
+ @abstract = true
611
+ end
612
+
613
+ # Declare that nothing special is required to stop this task.
614
+ # This makes +failed+ and +stop+ controlable events, and
615
+ # makes the interruption sequence be stop! => calls failed! =>
616
+ # emits +failed+ => emits +stop+.
617
+ def terminates
618
+ event :failed, :command => true, :terminal => true
619
+ interruptible
620
+ end
621
+
622
+ # Sets up a command for +stop+ in the case where +failed+ is also
623
+ # controllable, if the command of +failed+ should be used to stop
624
+ # the task.
625
+ def interruptible
626
+ if !has_event?(:failed) || !event_model(:failed).controlable?
627
+ raise ArgumentError, "failed is not controlable"
628
+ end
629
+ event(:stop) do |context|
630
+ if starting?
631
+ on :start, self, :stop
632
+ return
633
+ end
634
+ failed!(context)
635
+ end
636
+ end
637
+
638
+ def setup_poll_method(block) # :nodoc:
639
+ define_method(:poll) do
640
+ return unless self_owned?
641
+ begin
642
+ poll_handler
643
+ rescue Exception => e
644
+ emit :failed, e
645
+ end
646
+ end
647
+
648
+ define_method(:poll_handler, &block)
649
+ end
650
+
651
+ # Defines a block which will be called at each execution cycle for
652
+ # each running task of this model. The block is called in the
653
+ # instance context of the target task (i.e. using instance_eval)
654
+ def poll(&block)
655
+ if !block_given?
656
+ raise "no block given"
657
+ end
658
+
659
+ setup_poll_method(block)
660
+
661
+ on(:start) { Control.event_processing << method(:poll) }
662
+ on(:stop) { Control.event_processing.delete(method(:poll)) }
663
+ end
664
+ end
665
+
666
+ # Roby::Task is an abstract model. See Task::abstract
667
+ abstract
668
+
669
+ # Returns true if this task is from an abstract model. If it is the
670
+ # case, the task is not executable.
671
+ def abstract?; self.class.abstract? end
672
+ # Check if this task is executable
673
+ def executable?; !abstract? && !partially_instanciated? && super end
674
+ # Returns true if this task's stop event is controlable
675
+ def interruptible?; event(:stop).controlable? end
676
+ # Set the executable flag. executable cannot be set to +false+ is the
677
+ # task is running, and cannot be set to true on a finished task.
678
+ def executable=(flag)
679
+ return if flag == @executable
680
+ return unless self_owned?
681
+ if flag && !pending?
682
+ raise ModelViolation, "cannot set the executable flag on a task which is not pending"
683
+ elsif !flag && running?
684
+ raise ModelViolation, "cannot unset the executable flag on a task which is running"
685
+ end
686
+ super
687
+ end
688
+
689
+ # True if all arguments defined by Task.argument on the task model are set.
690
+ def fully_instanciated?
691
+ @fully_instanciated ||= model.arguments.all? { |name| arguments.has_key?(name) }
692
+ end
693
+ # True if at least one argument required by the task model is not set.
694
+ # See Task.argument.
695
+ def partially_instanciated?; !fully_instanciated? end
696
+
697
+ # True if this task has an event of the required model. The event model
698
+ # can either be a event class or an event name.
699
+ def has_event?(event_model)
700
+ bound_events.has_key?(event_model) ||
701
+ self.class.has_event?(event_model)
702
+ end
703
+
704
+ # True if this task is starting, i.e. if its start event is pending
705
+ # (has been called, but is not emitted yet)
706
+ def starting?; event(:start).pending? end
707
+ # True if this task has never been started
708
+ def pending?; !starting? && !started? end
709
+ # True if this task is currently running (i.e. is has already started,
710
+ # and is not finished)
711
+ def running?; started? && !finished? end
712
+ # True if the task is finishing, i.e. if a terminal event is pending.
713
+ def finishing?
714
+ if running?
715
+ each_event { |ev| return true if ev.terminal? && ev.pending? }
716
+ end
717
+ false
718
+ end
719
+
720
+ attr_predicate :started?, true
721
+ attr_predicate :finished?, true
722
+ attr_predicate :success?, true
723
+
724
+ # True if the +failed+ event of this task has been fired
725
+ def failed?; finished? && @success == false end
726
+
727
+ # Remove all relations in which +self+ or its event are involved
728
+ def clear_relations
729
+ each_event { |ev| ev.clear_relations }
730
+ super
731
+ end
732
+
733
+ # Update the terminal flag for the event models that are defined in
734
+ # this task model. The event is terminal if model-level signals (set up
735
+ # by Task::on) lead to the emission of the +stop+ event
736
+ def self.update_terminal_flag # :nodoc:
737
+ events = enum_events.map { |name, _| name }
738
+ terminal_events = [:stop]
739
+ events.delete(:stop)
740
+
741
+ loop do
742
+ old_size = terminal_events.size
743
+ events.delete_if do |ev|
744
+ if signals(ev).any? { |sig_ev| terminal_events.include?(sig_ev) } ||
745
+ forwardings(ev).any? { |sig_ev| terminal_events.include?(sig_ev) }
746
+ terminal_events << ev
747
+ true
748
+ end
749
+ end
750
+ break if old_size == terminal_events.size
751
+ end
752
+
753
+ terminal_events.each do |sym|
754
+ if ev = self.events[sym]
755
+ ev.terminal = true
756
+ else
757
+ ev = superclass.event_model(sym)
758
+ unless ev.terminal?
759
+ event sym, :model => ev, :terminal => true,
760
+ :command => (ev.method(:call) rescue nil)
761
+ end
762
+ end
763
+ end
764
+ end
765
+
766
+ # Updates the terminal flag for all events in the task. An event is
767
+ # terminal if the +stop+ event of the task will be called because this
768
+ # event is.
769
+ def update_terminal_flag # :nodoc:
770
+ return unless @instantiated_model_events
771
+
772
+ for _, ev in bound_events
773
+ ev.terminal_flag = nil
774
+ end
775
+
776
+ success_events, failure_events, terminal_events =
777
+ [event(:success)].to_value_set,
778
+ [event(:failed)].to_value_set,
779
+ [event(:stop), event(:success), event(:failed)].to_value_set
780
+
781
+ loop do
782
+ old_size = terminal_events.size
783
+ for _, ev in bound_events
784
+ for relation in [EventStructure::Signal, EventStructure::Forwarding]
785
+ for target in ev.child_objects(relation)
786
+ next if !target.respond_to?(:task) || target.task != self
787
+ next if ev[target, relation]
788
+
789
+ if success_events.include?(target)
790
+ success_events << ev
791
+ terminal_events << ev
792
+ break
793
+ elsif failure_events.include?(target)
794
+ failure_events << ev
795
+ terminal_events << ev
796
+ break
797
+ elsif terminal_events.include?(target)
798
+ terminal_events << ev
799
+ end
800
+ end
801
+ end
802
+
803
+ success_events.include?(ev) || failure_events.include?(ev) || terminal_events.include?(ev)
804
+ end
805
+ break if old_size == terminal_events.size
806
+ end
807
+
808
+ for ev in success_events
809
+ ev.terminal_flag = :success if ev.respond_to?(:task) && ev.task == self
810
+ end
811
+ for ev in failure_events
812
+ ev.terminal_flag = :failure if ev.respond_to?(:task) && ev.task == self
813
+ end
814
+ for ev in (terminal_events - success_events - failure_events)
815
+ ev.terminal_flag = true if ev.respond_to?(:task) && ev.task == self
816
+ end
817
+ end
818
+
819
+ # Returns a sorted list of Event objects, for all events that have been
820
+ # fired by this task
821
+ def history
822
+ history = []
823
+ each_event do |event|
824
+ history += event.history
825
+ end
826
+
827
+ history.sort_by { |ev| ev.time }
828
+ end
829
+
830
+ # Returns the set of tasks directly related to this task, either
831
+ # because of task relations or because of task events that are related
832
+ # to other task events
833
+ def related_tasks(result = nil)
834
+ result = related_objects(nil, result)
835
+ each_event do |ev|
836
+ ev.related_tasks(result)
837
+ end
838
+
839
+ result
840
+ end
841
+
842
+ # Returns the set of events directly related to this task
843
+ def related_events(result = nil)
844
+ each_event do |ev|
845
+ result = ev.related_events(result)
846
+ end
847
+
848
+ result.reject { |ev| ev.respond_to?(:task) && ev.task == self }.
849
+ to_value_set
850
+ end
851
+
852
+ # This method is called by TaskEventGenerator#fire just before the event handlers
853
+ # and commands are called
854
+ def fire_event(event) # :nodoc:
855
+ if !executable?
856
+ raise TaskNotExecutable.new(self), "trying to fire #{event.generator.symbol} on #{self} but #{self} is not executable"
857
+ end
858
+
859
+ if finished? && !event.terminal?
860
+ raise EmissionFailed.new(nil, self),
861
+ "emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task has finished. Task has been terminated by #{event(:stop).history.first.sources}."
862
+ elsif pending? && event.symbol != :start
863
+ raise EmissionFailed.new(nil, self),
864
+ "emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task is not running"
865
+ elsif running? && event.symbol == :start
866
+ raise EmissionFailed.new(nil, self),
867
+ "emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task is already running. Task has been started by #{event(:start).history.first.sources}."
868
+ end
869
+
870
+ update_task_status(event)
871
+
872
+ super if defined? super
873
+ end
874
+
875
+ # The event which has finished the task (if there is one)
876
+ attr_reader :terminal_event
877
+
878
+ # Call to update the task status because of +event+
879
+ def update_task_status(event) # :nodoc:
880
+ if event.success?
881
+ plan.task_index.set_state(self, :success?)
882
+ self.success = true
883
+ self.finished = true
884
+ @terminal_event ||= event
885
+ elsif event.failure?
886
+ plan.task_index.set_state(self, :failed?)
887
+ self.success = false
888
+ self.finished = true
889
+ @terminal_event ||= event
890
+ elsif event.terminal? && !finished?
891
+ plan.task_index.set_state(self, :finished?)
892
+ self.finished = true
893
+ @terminal_event ||= event
894
+ end
895
+
896
+ if event.symbol == :start
897
+ plan.task_index.set_state(self, :running?)
898
+ self.started = true
899
+ end
900
+ end
901
+
902
+ # List of EventGenerator objects bound to this task
903
+ attr_reader :bound_events
904
+
905
+ # call-seq:
906
+ # emit(event_model, *context) => self
907
+ #
908
+ # Emits +event_model+ in the given +context+. Event handlers are fired.
909
+ # This is equivalent to
910
+ # event(event_model).emit(*context)
911
+ #
912
+ def emit(event_model, *context)
913
+ event(event_model).emit(*context)
914
+ self
915
+ end
916
+
917
+ # Returns the TaskEventGenerator which describes the required event
918
+ # model. +event_model+ can either be an event name or an Event class.
919
+ def event(event_model)
920
+ unless event = bound_events[event_model]
921
+ event_model = self.event_model(event_model)
922
+ unless event = bound_events[event_model.symbol]
923
+ raise "cannot find #{event_model.symbol.inspect} in the set of bound events in #{self}. Known events are #{bound_events}."
924
+ end
925
+ end
926
+ event
927
+ end
928
+
929
+ # call-seq:
930
+ # on(event, task[, event1, event2, ...])
931
+ # on(event) { |event| ... }
932
+ # on(event[, task, event1, event2, ...]) { |event| ... }
933
+ # on(event, task[, event1, event2, ...], delay)
934
+ #
935
+ # Adds a signal from the given event to the specified targets, and/or
936
+ # defines an event handler. Note that <tt>on(event, task)</tt> is
937
+ # equivalent to <tt>on(event, task, event)</tt>
938
+ #
939
+ # +delay+, if given, specifies that the signal must be postponed for as
940
+ # much time as specified. See EventGenerator#signal for valid values.
941
+ def on(event_model, to = nil, *to_task_events, &user_handler)
942
+ unless to || user_handler
943
+ raise ArgumentError, "you must provide either a task or an event handler (got nil for both)"
944
+ end
945
+
946
+ generator = event(event_model)
947
+ if Hash === to_task_events.last
948
+ delay = to_task_events.pop
949
+ end
950
+ to_events = case to
951
+ when Task
952
+ if to_task_events.empty?
953
+ [to.event(generator.symbol)]
954
+ else
955
+ to_task_events.map { |ev_model| to.event(ev_model) }
956
+ end
957
+ when EventGenerator: [to]
958
+ else []
959
+ end
960
+
961
+ to_events.push delay if delay
962
+ generator.on(*to_events, &user_handler)
963
+ self
964
+ end
965
+
966
+ # call-seq:
967
+ # forward source_event, dest_task, ev1, ev2, ev3, ...
968
+ # forward source_event, dest_task, ev1, ev2, ev3, delay_options
969
+ #
970
+ # Fowards +name+ to the events in +to_task_events+ on task +to+. The
971
+ # target events will be emitted as soon as the +name+ event is emitted
972
+ # on the receiving task, without calling any command.
973
+ #
974
+ # To call an event whenever other events are emitted, use the Signal
975
+ # relation. See Task#on, Task.on and EventGenerator#on. As for Task#on,
976
+ # <tt>forward(:start, task)</tt> is a shortcut to <tt>forward(:start,
977
+ # task, :start)</tt>.
978
+ #
979
+ # If a +delay_options+ hash is provided, the forwarding is not performed
980
+ # immediately, but with a given delay. See EventGenerator#forward for
981
+ # the delay specification.
982
+ def forward(name, to, *to_task_events)
983
+ generator = event(name)
984
+ if Hash === to_task_events.last
985
+ delay = to_task_events.pop
986
+ end
987
+
988
+ to_events = if to.respond_to?(:event)
989
+ if to_task_events.empty?
990
+ [to.event(generator.symbol)]
991
+ else
992
+ to_task_events.map { |ev| to.event(ev) }
993
+ end
994
+ elsif to.kind_of?(EventGenerator)
995
+ [to]
996
+ else
997
+ raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})"
998
+ end
999
+
1000
+ to_events.each do |ev|
1001
+ generator.forward ev, delay
1002
+ end
1003
+ end
1004
+
1005
+ attr_accessor :calling_event
1006
+ def method_missing(name, *args, &block) # :nodoc:
1007
+ if calling_event && calling_event.respond_to?(name)
1008
+ calling_event.send(name, *args, &block)
1009
+ else
1010
+ super
1011
+ end
1012
+ rescue
1013
+ raise $!, $!.message, $!.backtrace[1..-1]
1014
+ end
1015
+
1016
+ @@event_command_id = 0
1017
+ def self.allocate_event_command_id # :nodoc:
1018
+ @@event_command_id += 1
1019
+ end
1020
+ # call-seq:
1021
+ # self.event(name, options = nil) { ... } -> event class or nil
1022
+ #
1023
+ # Define a new event in this task.
1024
+ #
1025
+ # ==== Available options
1026
+ #
1027
+ # <tt>command</tt>::
1028
+ # either true, false or an event command for the new event. In that
1029
+ # latter case, the command is an object which must respond to
1030
+ # #to_proc. If it is true, a default handler is defined which simply
1031
+ # emits the event. If a block is given, it is used as the event
1032
+ # command.
1033
+ #
1034
+ # <tt>terminal</tt>::
1035
+ # set to true if this event is a terminal event, i.e. if its emission
1036
+ # means that the task has finished.
1037
+ #
1038
+ # <tt>model</tt>::
1039
+ # base class for the event model (see "Event models" below). The default is the
1040
+ # TaskEvent class
1041
+ #
1042
+ # ==== Event models
1043
+ #
1044
+ # When a task event (for instance +start+) is emitted, a Roby::Event
1045
+ # object is created to describe the information related to this
1046
+ # emission (time, sources, context information, ...). Task.event
1047
+ # defines a specific event model MyTask::MyEvent for each task event
1048
+ # with name :my_event. This specific model is by default a subclass of
1049
+ # Roby::TaskEvent, but it is possible to override that by using the +model+
1050
+ # option.
1051
+ def self.event(ev, options = Hash.new, &block)
1052
+ options = validate_options(options, :command => nil, :terminal => nil, :model => TaskEvent)
1053
+
1054
+ ev_s = ev.to_s
1055
+ ev = ev.to_sym
1056
+
1057
+ if !options.has_key?(:command)
1058
+ if block
1059
+ define_method("event_command_#{ev_s}", &block)
1060
+ method = instance_method("event_command_#{ev_s}")
1061
+ end
1062
+
1063
+ if method
1064
+ check_arity(method, 1)
1065
+ options[:command] = lambda do |dst_task, *event_context|
1066
+ begin
1067
+ dst_task.calling_event = dst_task.event(ev)
1068
+ method.bind(dst_task).call(*event_context)
1069
+ ensure
1070
+ dst_task.calling_event = nil
1071
+ end
1072
+ end
1073
+ end
1074
+ end
1075
+ validate_event_definition_request(ev, options)
1076
+
1077
+ command_handler = options[:command] if options[:command].respond_to?(:call)
1078
+
1079
+ # Define the event class
1080
+ task_klass = self
1081
+ new_event = Class.new(options[:model]) do
1082
+ @terminal = options[:terminal]
1083
+ @symbol = ev
1084
+ @command_handler = command_handler
1085
+
1086
+ define_method(:name) { "#{task.name}::#{ev_s.camelize}" }
1087
+ singleton_class.class_eval do
1088
+ attr_reader :command_handler
1089
+ define_method(:name) { "#{task_klass.name}::#{ev_s.camelize}" }
1090
+ def to_s; name end
1091
+ end
1092
+ end
1093
+
1094
+ setup_terminal_handler = false
1095
+ old_model = find_event_model(ev)
1096
+ if new_event.symbol != :stop && options[:terminal] && (!old_model || !old_model.terminal?)
1097
+ setup_terminal_handler = true
1098
+ end
1099
+
1100
+ events[new_event.symbol] = new_event
1101
+ if setup_terminal_handler
1102
+ forward(new_event => :stop)
1103
+ end
1104
+ const_set(ev_s.camelize, new_event)
1105
+
1106
+ if options[:command]
1107
+ # check that the supplied command handler can take two arguments
1108
+ check_arity(command_handler, 2) if command_handler
1109
+
1110
+ # define #call on the event model
1111
+ new_event.singleton_class.class_eval do
1112
+ if command_handler
1113
+ define_method(:call, &command_handler)
1114
+ else
1115
+ def call(task, context) # :nodoc:
1116
+ task.emit(symbol, *context)
1117
+ end
1118
+ end
1119
+ end
1120
+
1121
+ # define an instance method which calls the event command
1122
+ define_method("#{ev_s}!") do |*context|
1123
+ begin
1124
+ generator = event(ev)
1125
+ generator.call(*context)
1126
+ rescue EventNotExecutable => e
1127
+ if partially_instanciated?
1128
+ raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} which is partially instanciated"
1129
+ elsif !plan
1130
+ raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} but the task is in no plan"
1131
+ elsif !plan.executable?
1132
+ raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} but the plan is not executable"
1133
+ elsif abstract?
1134
+ raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} but the task is abstract"
1135
+ else
1136
+ raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} which is not executable: #{e.message}"
1137
+ end
1138
+ end
1139
+ end
1140
+ end
1141
+
1142
+ new_event
1143
+ end
1144
+
1145
+ def self.validate_event_definition_request(ev, options) #:nodoc:
1146
+ if options[:command] && options[:command] != true && !options[:command].respond_to?(:call)
1147
+ raise ArgumentError, "Allowed values for :command option: true, false, nil and an object responding to #call. Got #{options[:command]}"
1148
+ end
1149
+
1150
+ if ev.to_sym == :stop
1151
+ if options.has_key?(:terminal) && !options[:terminal]
1152
+ raise ArgumentError, "the 'stop' event cannot be non-terminal"
1153
+ end
1154
+ options[:terminal] = true
1155
+ end
1156
+
1157
+ # Check for inheritance rules
1158
+ if events.include?(ev)
1159
+ raise ArgumentError, "event #{ev} already defined"
1160
+ elsif old_event = find_event_model(ev)
1161
+ if old_event.terminal? && !options[:terminal]
1162
+ raise ArgumentError, "trying to override a terminal event into a non-terminal one", caller(2)
1163
+ elsif old_event.controlable? && !options[:command]
1164
+ raise ArgumentError, "trying to override a controlable event into a non-controlable one", caller(2)
1165
+ end
1166
+ end
1167
+ end
1168
+
1169
+ # Events defined by the task model
1170
+ inherited_enumerable(:event, :events, :map => true) { Hash.new }
1171
+ def self.enum_events
1172
+ @__enum_events__ ||= enum_for(:each_event)
1173
+ end
1174
+
1175
+ # Iterates on all the events defined for this task
1176
+ def each_event # :yield:bound_event
1177
+ for _, ev in bound_events
1178
+ yield(ev)
1179
+ end
1180
+ end
1181
+ alias :each_plan_child :each_event
1182
+
1183
+ # Returns the set of terminal events this task has. A terminal event is
1184
+ # an event whose emission announces the end of the task. In most case,
1185
+ # it is an event which is forwarded directly on indirectly to +stop+.
1186
+ def terminal_events
1187
+ bound_events.values.find_all { |ev| ev.terminal? }
1188
+ end
1189
+
1190
+ # Get the list of terminal events for this task model
1191
+ def self.terminal_events
1192
+ enum_events.find_all { |_, e| e.terminal? }.
1193
+ map { |_, e| e }
1194
+ end
1195
+
1196
+ # Get the event model for +event+
1197
+ def event_model(model); self.model.event_model(model) end
1198
+
1199
+ # Find the event class for +event+, or nil if +event+ is not an event name for this model
1200
+ def self.find_event_model(name)
1201
+ name = name.to_sym
1202
+ each_event { |sym, e| return e if sym == name }
1203
+ nil
1204
+ end
1205
+
1206
+ # Checks that all events in +events+ are valid events for this task.
1207
+ # The requested events can be either an event name (symbol or string)
1208
+ # or an event class
1209
+ #
1210
+ # Returns the corresponding array of event classes
1211
+ def self.event_model(model_def) #:nodoc:
1212
+ if model_def.respond_to?(:to_sym)
1213
+ ev_model = find_event_model(model_def.to_sym)
1214
+ unless ev_model
1215
+ all_events = enum_events.map { |name, _| name }
1216
+ raise ArgumentError, "#{model_def} is not an event of #{name}: #{all_events}" unless ev_model
1217
+ end
1218
+ elsif model_def.respond_to?(:has_ancestor?) && model_def.has_ancestor?(TaskEvent)
1219
+ # Check that model_def is an event class for us
1220
+ ev_model = find_event_model(model_def.symbol)
1221
+ if !ev_model
1222
+ raise ArgumentError, "no #{model_def.symbol} event in #{name}"
1223
+ elsif ev_model != model_def
1224
+ raise ArgumentError, "the event model #{model_def} is not a model for #{name} (found #{ev_model} with the same name)"
1225
+ end
1226
+ else
1227
+ raise ArgumentError, "wanted either a symbol or an event class, got #{model_def}"
1228
+ end
1229
+
1230
+ ev_model
1231
+ end
1232
+
1233
+ class << self
1234
+ # Checks if _name_ is a name for an event of this task
1235
+ alias :has_event? :find_event_model
1236
+
1237
+ private :validate_event_definition_request
1238
+ end
1239
+
1240
+ # call-seq:
1241
+ # on(event_model) { |event| ... }
1242
+ # on(event_model => ev1, ev2 => [ ev3, ev4 ]) { |event| ... }
1243
+ #
1244
+ # Adds an event handler for the given event model. When the event is fired,
1245
+ # all events given in argument will be called. If they are controlable,
1246
+ # then the command is called. If not, they are just fired
1247
+ def self.on(mappings, &user_handler)
1248
+ if user_handler
1249
+ check_arity(user_handler, 1)
1250
+ end
1251
+
1252
+ mappings = [*mappings].zip([]) unless Hash === mappings
1253
+ mappings.each do |from, to|
1254
+ from = event_model(from).symbol
1255
+ to = if to
1256
+ Array[*to].map do |ev|
1257
+ model = event_model(ev)
1258
+ raise ArgumentError, "trying to signal #{ev} which is not controlable" unless model.controlable?
1259
+ model.symbol
1260
+ end
1261
+ else; []
1262
+ end
1263
+
1264
+ signal_sets[from].merge to.to_value_set
1265
+ update_terminal_flag
1266
+
1267
+ if user_handler
1268
+ method_name = "event_handler_#{from}_#{Object.address_from_id(user_handler.object_id).to_s(16)}"
1269
+ define_method(method_name, &user_handler)
1270
+ handler_sets[from] << lambda { |event| event.task.send(method_name, event) }
1271
+ end
1272
+ end
1273
+ end
1274
+
1275
+ # call-seq:
1276
+ # causal_link(:from => :to)
1277
+ #
1278
+ # Declares a causal link between two events in the task. See
1279
+ # EventStructure::CausalLink for a description of the causal link
1280
+ # relation.
1281
+ def self.causal_link(mappings)
1282
+ mappings.each do |from, to|
1283
+ from = event_model(from).symbol
1284
+ causal_link_sets[from].merge Array[*to].map { |ev| event_model(ev).symbol }.to_value_set
1285
+ end
1286
+ update_terminal_flag
1287
+ end
1288
+
1289
+ # call-seq:
1290
+ # forward :from => :to
1291
+ #
1292
+ # Defines a forwarding relation between two events of the same task
1293
+ # instance. See EventStructure::Forward for a description of the
1294
+ # forwarding relation.
1295
+ #
1296
+ # See also Task#forward and EventGenerator#forward.
1297
+ def self.forward(mappings)
1298
+ mappings.each do |from, to|
1299
+ from = event_model(from).symbol
1300
+ forwarding_sets[from].merge Array[*to].map { |ev| event_model(ev).symbol }.to_value_set
1301
+ end
1302
+ update_terminal_flag
1303
+ end
1304
+
1305
+ def self.precondition(event, reason, &block)
1306
+ event = event_model(event)
1307
+ precondition_sets[event.symbol] << [reason, block]
1308
+ end
1309
+
1310
+ def to_s # :nodoc:
1311
+ s = name.dup
1312
+ id = owners.map do |owner|
1313
+ next if owner == Roby::Distributed
1314
+ sibling = remote_siblings[owner]
1315
+ "#{sibling ? Object.address_from_id(sibling.ref).to_s(16) : 'nil'}@#{owner.remote_name}"
1316
+ end
1317
+ unless id.empty?
1318
+ s << "[" << id.join(",") << "]"
1319
+ end
1320
+ s
1321
+ end
1322
+
1323
+ def pretty_print(pp) # :nodoc:
1324
+ pp.text "#{self.class.name}:0x#{self.address.to_s(16)}"
1325
+ pp.breakable
1326
+ pp.nest(2) do
1327
+ pp.text " owners: "
1328
+ pp.seplist(owners) { |r| pp.text r.to_s }
1329
+
1330
+ pp.breakable
1331
+ pp.text "arguments: "
1332
+ arguments.pretty_print(pp)
1333
+ end
1334
+ end
1335
+
1336
+ # True if this task is a null task. See NullTask.
1337
+ def null?; false end
1338
+ # Converts this object into a task object
1339
+ def to_task; self end
1340
+
1341
+ event :start, :command => true
1342
+
1343
+ # Define :stop before any other terminal event
1344
+ event :stop
1345
+ event :success, :terminal => true
1346
+ event :failed, :terminal => true
1347
+
1348
+ event :aborted
1349
+ forward :aborted => :failed
1350
+
1351
+ # The internal data for this task
1352
+ attr_reader :data
1353
+ # Sets the internal data value for this task. This calls the
1354
+ # #updated_data hook, and emits +updated_data+ if the task is running.
1355
+ def data=(value)
1356
+ @data = value
1357
+ updated_data
1358
+ emit :updated_data if running?
1359
+ end
1360
+ # This hook is called whenever the internal data of this task is
1361
+ # updated. See #data, #data= and the +updated_data+ event
1362
+ def updated_data
1363
+ super if defined? super
1364
+ end
1365
+ event :updated_data, :command => false
1366
+
1367
+ # Checks if +task+ is in the same execution state than +self+
1368
+ # Returns true if they are either both running or both pending
1369
+ def compatible_state?(task)
1370
+ finished? || !(running? ^ task.running?)
1371
+ end
1372
+
1373
+ # Returns the lists of tags this model fullfills.
1374
+ def self.tags
1375
+ ancestors.find_all { |m| m.instance_of?(TaskModelTag) }
1376
+ end
1377
+
1378
+ # The fullfills? predicate checks if this task can be used
1379
+ # to fullfill the need of the given +model+ and +arguments+
1380
+ # The default is to check if
1381
+ # * the needed task model is an ancestor of this task
1382
+ # * the task
1383
+ # * +args+ is included in the task arguments
1384
+ def fullfills?(models, args = {})
1385
+ if models.kind_of?(Task)
1386
+ klass, args =
1387
+ models.class,
1388
+ models.meaningful_arguments
1389
+ models = [klass]
1390
+ else
1391
+ models = [*models]
1392
+ end
1393
+ self_model = self.model
1394
+ self_args = self.arguments
1395
+ args = args.dup
1396
+
1397
+ # Check the arguments that are required by the model
1398
+ for tag in models
1399
+ unless self_model.has_ancestor?(tag)
1400
+ return false
1401
+ end
1402
+
1403
+ unless args.empty?
1404
+ for arg_name in tag.arguments
1405
+ if user_arg = args.delete(arg_name)
1406
+ return false unless user_arg == self_args[arg_name]
1407
+ end
1408
+ break if args.empty?
1409
+ end
1410
+ end
1411
+ end
1412
+
1413
+ if !args.empty?
1414
+ raise ArgumentError, "the arguments '#{args.keys.join(", ")}' are unknown to the tags #{models.join(", ")}"
1415
+ end
1416
+ true
1417
+ end
1418
+
1419
+ include ExceptionHandlingObject
1420
+ inherited_enumerable('exception_handler', 'exception_handlers') { Array.new }
1421
+
1422
+ # Lists all exception handlers attached to this task
1423
+ def each_exception_handler(&iterator); model.each_exception_handler(&iterator) end
1424
+
1425
+ @@exception_handler_id = 0
1426
+
1427
+ # call-seq:
1428
+ # on_exception(TaskModelViolation, ...) { |task, exception_object| ... }
1429
+ #
1430
+ # Defines an exception handler. matcher === exception_object is used to
1431
+ # determine if the handler should be called when +exception_object+ has
1432
+ # been fired. The first matching handler is called. Call #pass_exception to pass
1433
+ # the exception to previous handlers
1434
+ #
1435
+ # on_exception(TaskModelViolation, ...) do |task, exception_object|
1436
+ # if cannot_handle
1437
+ # task.pass_exception # send to the next handler
1438
+ # end
1439
+ # do_handle
1440
+ # end
1441
+ def self.on_exception(*matchers, &handler)
1442
+ check_arity(handler, 1)
1443
+ id = (@@exception_handler_id += 1)
1444
+ define_method("exception_handler_#{id}", &handler)
1445
+ exception_handlers.unshift [matchers, instance_method("exception_handler_#{id}")]
1446
+ end
1447
+
1448
+ # We can't add relations on objects we don't own
1449
+ def add_child_object(child, type, info)
1450
+ unless read_write? && child.read_write?
1451
+ raise OwnershipError, "cannot add a relation between tasks we don't own. #{self} by #{owners.to_a} and #{child} is owned by #{child.owners.to_a}"
1452
+ end
1453
+
1454
+ super
1455
+ end
1456
+
1457
+ # This method is called during the commit process to
1458
+ def commit_transaction
1459
+ super if defined? super
1460
+
1461
+ arguments.dup.each do |key, value|
1462
+ if value.kind_of?(Roby::Transactions::Proxy)
1463
+ arguments.update!(key, value.__getobj__)
1464
+ end
1465
+ end
1466
+ end
1467
+
1468
+ # Create a new task of the same model and with the same arguments
1469
+ # than this one. Insert this task in the plan and make it replace
1470
+ # the fresh one.
1471
+ #
1472
+ # See Plan#respawn
1473
+ def respawn
1474
+ plan.respawn(self)
1475
+ end
1476
+
1477
+ # Replaces, in the plan, the subplan generated by this plan object by
1478
+ # the one generated by +object+. In practice, it means that we transfer
1479
+ # all parent edges whose target is +self+ from the receiver to
1480
+ # +object+. It calls the various add/remove hooks defined in
1481
+ # DirectedRelationSupport.
1482
+ def replace_subplan_by(object)
1483
+ super
1484
+
1485
+ # Compute the set of tasks that are in our subtree and not in
1486
+ # object's *after* the replacement
1487
+ own_subtree = ValueSet.new
1488
+ TaskStructure.each_root_relation do |rel|
1489
+ own_subtree.merge generated_subgraph(rel)
1490
+ own_subtree -= object.generated_subgraph(rel)
1491
+ end
1492
+
1493
+ changes = []
1494
+ each_event do |event|
1495
+ next unless object.has_event?(event.symbol)
1496
+ changes.clear
1497
+
1498
+ event.each_relation do |rel|
1499
+ parents = []
1500
+ event.each_parent_object(rel) do |parent|
1501
+ if !parent.respond_to?(:task) || !own_subtree.include?(parent.task)
1502
+ parents << parent << parent[event, rel]
1503
+ end
1504
+ end
1505
+ children = []
1506
+ event.each_child_object(rel) do |child|
1507
+ if !child.respond_to?(:task) || !own_subtree.include?(child.task)
1508
+ children << child << event[child, rel]
1509
+ end
1510
+ end
1511
+ changes << rel << parents << children
1512
+ end
1513
+
1514
+ event.apply_relation_changes(object.event(event.symbol), changes)
1515
+ end
1516
+ end
1517
+
1518
+ # Replaces +self+ by +object+ in all relations +self+ is part of, and
1519
+ # do the same for the task's event generators.
1520
+ def replace_by(object)
1521
+ each_event do |event|
1522
+ event_name = event.symbol
1523
+ if object.has_event?(event_name)
1524
+ event.replace_by object.event(event_name)
1525
+ end
1526
+ end
1527
+ super
1528
+ end
1529
+
1530
+ # Define a polling block for this task. The polling block is called at
1531
+ # each execution cycle while the task is running (i.e. in-between the
1532
+ # emission of +start+ and the emission of +stop+.
1533
+ #
1534
+ # Raises ArgumentError if the task is already running.
1535
+ #
1536
+ # See also Task::poll
1537
+ def poll(&block)
1538
+ if !pending?
1539
+ raise ArgumentError, "cannot set a polling block when the task is not pending"
1540
+ end
1541
+
1542
+
1543
+ singleton_class.class_eval do
1544
+ setup_poll_method(block)
1545
+ end
1546
+ on(:start) { Control.event_processing << method(:poll) }
1547
+ on(:stop) { Control.event_processing.delete(method(:poll)) }
1548
+ end
1549
+ end
1550
+
1551
+ # A special task model which does nothing and emits +success+
1552
+ # as soon as it is started.
1553
+ class NullTask < Task
1554
+ event :start, :command => true
1555
+ event :stop
1556
+ forward :start => :success
1557
+
1558
+ # Always true. See Task#null?
1559
+ def null?; true end
1560
+ end
1561
+
1562
+ # A virtual task is a task representation for a combination of two events.
1563
+ # This allows to combine two unrelated events, one being the +start+ event
1564
+ # of the virtual task and the other its success event.
1565
+ #
1566
+ # The task fails if the success event becomes unreachable.
1567
+ #
1568
+ # See VirtualTask.create
1569
+ class VirtualTask < Task
1570
+ # The start event
1571
+ attr_reader :start_event
1572
+ # The success event
1573
+ attr_accessor :success_event
1574
+ # Set the start event
1575
+ def start_event=(ev)
1576
+ if !ev.controlable?
1577
+ raise ArgumentError, "the start event of a virtual task must be controlable"
1578
+ end
1579
+ @start_event = ev
1580
+ end
1581
+
1582
+ event :start do
1583
+ event(:start).achieve_with(start_event)
1584
+ start_event.call
1585
+ end
1586
+ on :start do
1587
+ success_event.forward_once event(:success)
1588
+ success_event.if_unreachable(true) do
1589
+ emit :failed if executable?
1590
+ end
1591
+ end
1592
+
1593
+ terminates
1594
+
1595
+ # Creates a new VirtualTask with the given start and success events
1596
+ def self.create(start, success)
1597
+ task = VirtualTask.new
1598
+ task.start_event = start
1599
+ task.success_event = success
1600
+
1601
+ if start.respond_to?(:task)
1602
+ task.realized_by start.task
1603
+ end
1604
+ if success.respond_to?(:task)
1605
+ task.realized_by success.task
1606
+ end
1607
+
1608
+ task
1609
+ end
1610
+ end
1611
+
1612
+ unless defined? TaskStructure
1613
+ TaskStructure = RelationSpace(Task)
1614
+ end
1615
+ end
1616
+
1617
+ require 'roby/task-operations'
1618
+