roby 0.7.3 → 0.8.0

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 (236) hide show
  1. data/History.txt +7 -5
  2. data/Manifest.txt +91 -16
  3. data/README.txt +24 -24
  4. data/Rakefile +92 -64
  5. data/app/config/app.yml +42 -43
  6. data/app/config/init.rb +26 -0
  7. data/benchmark/alloc_misc.rb +123 -0
  8. data/benchmark/discovery_latency.rb +67 -0
  9. data/benchmark/garbage_collection.rb +48 -0
  10. data/benchmark/genom.rb +31 -0
  11. data/benchmark/transactions.rb +62 -0
  12. data/bin/roby +1 -1
  13. data/bin/roby-log +16 -6
  14. data/doc/guide/.gitignore +2 -0
  15. data/doc/guide/config.yaml +34 -0
  16. data/doc/guide/ext/init.rb +14 -0
  17. data/doc/guide/ext/previous_next.rb +40 -0
  18. data/doc/guide/ext/rdoc_links.rb +33 -0
  19. data/doc/guide/index.rdoc +16 -0
  20. data/doc/guide/overview.rdoc +62 -0
  21. data/doc/guide/plan_modifications.rdoc +67 -0
  22. data/doc/guide/src/abstraction/achieve_with.page +8 -0
  23. data/doc/guide/src/abstraction/forwarding.page +8 -0
  24. data/doc/guide/src/abstraction/hierarchy.page +19 -0
  25. data/doc/guide/src/abstraction/index.page +28 -0
  26. data/doc/guide/src/abstraction/task_models.page +13 -0
  27. data/doc/guide/src/basics.template +6 -0
  28. data/doc/guide/src/basics/app.page +139 -0
  29. data/doc/guide/src/basics/code_examples.page +33 -0
  30. data/doc/guide/src/basics/dry.page +69 -0
  31. data/doc/guide/src/basics/errors.page +443 -0
  32. data/doc/guide/src/basics/events.page +179 -0
  33. data/doc/guide/src/basics/hierarchy.page +275 -0
  34. data/doc/guide/src/basics/index.page +11 -0
  35. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  36. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  37. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  38. data/doc/guide/src/basics/log_replay/goForward_4.png +0 -0
  39. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  40. data/doc/guide/src/basics/log_replay/hierarchy_error_1.png +0 -0
  41. data/doc/guide/src/basics/log_replay/hierarchy_error_2.png +0 -0
  42. data/doc/guide/src/basics/log_replay/hierarchy_error_3.png +0 -0
  43. data/doc/guide/src/basics/log_replay/plan_repair_1.png +0 -0
  44. data/doc/guide/src/basics/log_replay/plan_repair_2.png +0 -0
  45. data/doc/guide/src/basics/log_replay/plan_repair_3.png +0 -0
  46. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  47. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  48. data/doc/guide/src/basics/log_replay/roby_log_relation_window.png +0 -0
  49. data/doc/guide/src/basics/log_replay/roby_replay_event_representation.png +0 -0
  50. data/doc/guide/src/basics/plan_objects.page +71 -0
  51. data/doc/guide/src/basics/relations_display.page +203 -0
  52. data/doc/guide/src/basics/roby_cycle_overview.png +0 -0
  53. data/doc/guide/src/basics/shell.page +102 -0
  54. data/doc/guide/src/basics/summary.page +32 -0
  55. data/doc/guide/src/basics/tasks.page +357 -0
  56. data/doc/guide/src/basics_shell_header.txt +16 -0
  57. data/doc/guide/src/cycle/cycle-overview.png +0 -0
  58. data/doc/guide/src/cycle/cycle-overview.svg +208 -0
  59. data/doc/guide/src/cycle/error_handling.page +168 -0
  60. data/doc/guide/src/cycle/error_instantaneous_repair.png +0 -0
  61. data/doc/guide/src/cycle/error_instantaneous_repair.svg +1224 -0
  62. data/doc/guide/src/cycle/garbage_collection.page +10 -0
  63. data/doc/guide/src/cycle/index.page +23 -0
  64. data/doc/guide/src/cycle/propagation.page +154 -0
  65. data/doc/guide/src/cycle/propagation_diamond.png +0 -0
  66. data/doc/guide/src/cycle/propagation_diamond.svg +1279 -0
  67. data/doc/guide/src/default.css +319 -0
  68. data/doc/guide/src/default.template +74 -0
  69. data/doc/guide/src/htmldoc.metainfo +20 -0
  70. data/doc/guide/src/htmldoc.virtual +18 -0
  71. data/doc/guide/src/images/bodybg.png +0 -0
  72. data/doc/guide/src/images/contbg.png +0 -0
  73. data/doc/guide/src/images/footerbg.png +0 -0
  74. data/doc/guide/src/images/gradient1.png +0 -0
  75. data/doc/guide/src/images/gradient2.png +0 -0
  76. data/doc/guide/src/index.page +7 -0
  77. data/doc/guide/src/introduction/index.page +29 -0
  78. data/doc/guide/src/introduction/install.page +133 -0
  79. data/doc/{papers.rdoc → guide/src/introduction/publications.page} +5 -2
  80. data/doc/{videos.rdoc → guide/src/introduction/videos.page} +4 -2
  81. data/doc/guide/src/plugins/fault_tolerance.page +44 -0
  82. data/doc/guide/src/plugins/index.page +11 -0
  83. data/doc/guide/src/plugins/subsystems.page +45 -0
  84. data/doc/guide/src/relations/dependency.page +89 -0
  85. data/doc/guide/src/relations/index.page +12 -0
  86. data/doc/misc/update_github +24 -0
  87. data/doc/tutorials/02-GoForward.rdoc +3 -3
  88. data/ext/graph/graph.cc +46 -0
  89. data/lib/roby.rb +57 -22
  90. data/lib/roby/app.rb +132 -112
  91. data/lib/roby/app/plugins/rake.rb +21 -0
  92. data/lib/roby/app/rake.rb +0 -7
  93. data/lib/roby/app/run.rb +1 -1
  94. data/lib/roby/app/scripts/distributed.rb +1 -2
  95. data/lib/roby/app/scripts/generate/bookmarks.rb +1 -1
  96. data/lib/roby/app/scripts/results.rb +2 -1
  97. data/lib/roby/app/scripts/run.rb +6 -2
  98. data/lib/roby/app/scripts/shell.rb +11 -11
  99. data/lib/roby/config.rb +1 -1
  100. data/lib/roby/decision_control.rb +62 -3
  101. data/lib/roby/distributed.rb +4 -0
  102. data/lib/roby/distributed/base.rb +8 -0
  103. data/lib/roby/distributed/communication.rb +12 -8
  104. data/lib/roby/distributed/connection_space.rb +61 -44
  105. data/lib/roby/distributed/distributed_object.rb +1 -1
  106. data/lib/roby/distributed/notifications.rb +22 -30
  107. data/lib/roby/distributed/peer.rb +13 -8
  108. data/lib/roby/distributed/proxy.rb +5 -5
  109. data/lib/roby/distributed/subscription.rb +4 -4
  110. data/lib/roby/distributed/transaction.rb +3 -3
  111. data/lib/roby/event.rb +176 -110
  112. data/lib/roby/exceptions.rb +12 -4
  113. data/lib/roby/execution_engine.rb +1604 -0
  114. data/lib/roby/external_process_task.rb +225 -0
  115. data/lib/roby/graph.rb +0 -6
  116. data/lib/roby/interface.rb +221 -137
  117. data/lib/roby/log/console.rb +5 -3
  118. data/lib/roby/log/data_stream.rb +94 -16
  119. data/lib/roby/log/dot.rb +8 -8
  120. data/lib/roby/log/event_stream.rb +13 -3
  121. data/lib/roby/log/file.rb +43 -18
  122. data/lib/roby/log/gui/basic_display_ui.rb +89 -0
  123. data/lib/roby/log/gui/chronicle_view_ui.rb +90 -0
  124. data/lib/roby/log/gui/data_displays.rb +4 -5
  125. data/lib/roby/log/gui/data_displays_ui.rb +146 -0
  126. data/lib/roby/log/gui/relations.rb +18 -18
  127. data/lib/roby/log/gui/relations_ui.rb +120 -0
  128. data/lib/roby/log/gui/relations_view_ui.rb +144 -0
  129. data/lib/roby/log/gui/replay.rb +41 -13
  130. data/lib/roby/log/gui/replay_controls.rb +3 -0
  131. data/lib/roby/log/gui/replay_controls.ui +133 -110
  132. data/lib/roby/log/gui/replay_controls_ui.rb +249 -0
  133. data/lib/roby/log/hooks.rb +19 -18
  134. data/lib/roby/log/logger.rb +7 -6
  135. data/lib/roby/log/notifications.rb +4 -4
  136. data/lib/roby/log/plan_rebuilder.rb +20 -22
  137. data/lib/roby/log/relations.rb +44 -16
  138. data/lib/roby/log/server.rb +1 -4
  139. data/lib/roby/log/timings.rb +88 -19
  140. data/lib/roby/plan-object.rb +135 -11
  141. data/lib/roby/plan.rb +408 -224
  142. data/lib/roby/planning/loops.rb +32 -25
  143. data/lib/roby/planning/model.rb +157 -51
  144. data/lib/roby/planning/task.rb +47 -20
  145. data/lib/roby/query.rb +128 -92
  146. data/lib/roby/relations.rb +254 -136
  147. data/lib/roby/relations/conflicts.rb +6 -9
  148. data/lib/roby/relations/dependency.rb +358 -0
  149. data/lib/roby/relations/ensured.rb +0 -1
  150. data/lib/roby/relations/error_handling.rb +0 -1
  151. data/lib/roby/relations/events.rb +0 -2
  152. data/lib/roby/relations/executed_by.rb +26 -11
  153. data/lib/roby/relations/planned_by.rb +14 -14
  154. data/lib/roby/robot.rb +46 -0
  155. data/lib/roby/schedulers/basic.rb +34 -0
  156. data/lib/roby/standalone.rb +4 -0
  157. data/lib/roby/standard_errors.rb +21 -15
  158. data/lib/roby/state/events.rb +5 -4
  159. data/lib/roby/support.rb +107 -6
  160. data/lib/roby/task-operations.rb +23 -19
  161. data/lib/roby/task.rb +522 -148
  162. data/lib/roby/task_index.rb +80 -0
  163. data/lib/roby/test/common.rb +283 -44
  164. data/lib/roby/test/distributed.rb +53 -37
  165. data/lib/roby/test/testcase.rb +9 -204
  166. data/lib/roby/test/tools.rb +3 -3
  167. data/lib/roby/transactions.rb +154 -111
  168. data/lib/roby/transactions/proxy.rb +40 -7
  169. data/manifest.xml +20 -0
  170. data/plugins/fault_injection/README.txt +0 -3
  171. data/plugins/fault_injection/Rakefile +2 -8
  172. data/plugins/fault_injection/app.rb +1 -1
  173. data/plugins/fault_injection/fault_injection.rb +3 -3
  174. data/plugins/fault_injection/test/test_fault_injection.rb +19 -25
  175. data/plugins/subsystems/README.txt +0 -3
  176. data/plugins/subsystems/Rakefile +2 -7
  177. data/plugins/subsystems/app.rb +27 -16
  178. data/plugins/subsystems/test/app/config/init.rb +3 -0
  179. data/plugins/subsystems/test/app/planners/main.rb +1 -1
  180. data/plugins/subsystems/test/app/tasks/services.rb +1 -1
  181. data/plugins/subsystems/test/test_subsystems.rb +23 -16
  182. data/test/distributed/test_communication.rb +32 -15
  183. data/test/distributed/test_connection.rb +28 -26
  184. data/test/distributed/test_execution.rb +59 -54
  185. data/test/distributed/test_mixed_plan.rb +34 -34
  186. data/test/distributed/test_plan_notifications.rb +26 -26
  187. data/test/distributed/test_protocol.rb +57 -48
  188. data/test/distributed/test_query.rb +11 -7
  189. data/test/distributed/test_remote_plan.rb +71 -71
  190. data/test/distributed/test_transaction.rb +50 -47
  191. data/test/mockups/external_process +28 -0
  192. data/test/planning/test_loops.rb +163 -119
  193. data/test/planning/test_model.rb +3 -3
  194. data/test/planning/test_task.rb +27 -7
  195. data/test/relations/test_conflicts.rb +3 -3
  196. data/test/relations/test_dependency.rb +324 -0
  197. data/test/relations/test_ensured.rb +2 -2
  198. data/test/relations/test_executed_by.rb +94 -19
  199. data/test/relations/test_planned_by.rb +11 -9
  200. data/test/suite_core.rb +6 -3
  201. data/test/suite_distributed.rb +1 -0
  202. data/test/suite_planning.rb +1 -0
  203. data/test/suite_relations.rb +2 -2
  204. data/test/tasks/test_external_process.rb +126 -0
  205. data/test/{test_thread_task.rb → tasks/test_thread_task.rb} +17 -20
  206. data/test/test_bgl.rb +21 -1
  207. data/test/test_event.rb +229 -155
  208. data/test/test_exceptions.rb +79 -80
  209. data/test/test_execution_engine.rb +987 -0
  210. data/test/test_gui.rb +1 -1
  211. data/test/test_interface.rb +11 -5
  212. data/test/test_log.rb +18 -7
  213. data/test/test_log_server.rb +1 -0
  214. data/test/test_plan.rb +229 -395
  215. data/test/test_query.rb +193 -35
  216. data/test/test_relations.rb +88 -8
  217. data/test/test_state.rb +55 -37
  218. data/test/test_support.rb +1 -1
  219. data/test/test_task.rb +371 -218
  220. data/test/test_testcase.rb +32 -16
  221. data/test/test_transactions.rb +211 -170
  222. data/test/test_transactions_proxy.rb +37 -19
  223. metadata +169 -71
  224. data/.gitignore +0 -29
  225. data/doc/styles/allison.css +0 -314
  226. data/doc/styles/allison.js +0 -316
  227. data/doc/styles/allison.rb +0 -276
  228. data/doc/styles/jamis.rb +0 -593
  229. data/lib/roby/control.rb +0 -746
  230. data/lib/roby/executives/simple.rb +0 -30
  231. data/lib/roby/propagation.rb +0 -562
  232. data/lib/roby/relations/hierarchy.rb +0 -239
  233. data/lib/roby/transactions/updates.rb +0 -139
  234. data/test/relations/test_hierarchy.rb +0 -158
  235. data/test/test_control.rb +0 -399
  236. data/test/test_propagation.rb +0 -210
@@ -0,0 +1,225 @@
1
+ module Roby
2
+ trap 'SIGCHLD' do
3
+ begin
4
+ while pid = ::Process.wait(-1, ::Process::WNOHANG)
5
+ ExternalProcessTask.dead! pid, $?.dup
6
+ end
7
+ rescue Errno::ECHILD
8
+ end
9
+ end
10
+
11
+ # This task class can be used to monitor the execution of an external
12
+ # process. Among the useful features, it can redirect standard output and
13
+ # error output to files.
14
+ #
15
+ # The events will act as follows:
16
+ # * the start command starts the process per se. The event is emitted once
17
+ # exec() has been called with success
18
+ # * the signaled event is emitted when the process dies because of a signal
19
+ # * the failed event is emitted whenever the process exits with a nonzero
20
+ # status
21
+ # * the success event is emitted when the process exits with a zero status
22
+ # * the stop event is emitted when the process exits
23
+ class ExternalProcessTask < Roby::Task
24
+ ##
25
+ # :attr_reader:
26
+ # This task argument is an array whose first element is the executable
27
+ # to start and the rest the arguments that need to be passed to it.
28
+ #
29
+ # It can also be set to a simple string, which is interpreted as the
30
+ # executable name with no arguments.
31
+ argument :command_line
32
+
33
+ ##
34
+ # :attr_reader:
35
+ # The working directory. If not set, the current directory is used.
36
+ argument :working_directory
37
+
38
+ # Redirection specification. See #redirect_output
39
+ attr_reader :redirection
40
+
41
+ def initialize(arguments)
42
+ arguments[:working_directory] ||= nil
43
+ arguments[:command_line] = [arguments[:command_line]] unless arguments[:command_line].kind_of?(Array)
44
+ super(arguments)
45
+ end
46
+
47
+ class << self
48
+ # The set of running ExternalProcessTask instances. It is a mapping
49
+ # from the PID value to the instances.
50
+ attr_reader :processes
51
+ end
52
+ @processes = Hash.new
53
+
54
+ # Called by the SIGCHLD handler to announce that a particular process
55
+ # has finished. It calls #dead!(result) in the context of the execution
56
+ # thread, on the corresponding task
57
+ def self.dead!(pid, result) # :nodoc:
58
+ task = processes[pid]
59
+ return if !task
60
+ if engine = task.plan.engine
61
+ engine.once { task.dead!(result) }
62
+ end
63
+ end
64
+
65
+ # Called to announce that this task has been killed. +result+ is the
66
+ # corresponding Process::Status object.
67
+ def dead!(result)
68
+ if result.success?
69
+ emit :success
70
+ elsif result.signaled?
71
+ emit :signaled, result
72
+ else
73
+ emit :failed, result
74
+ end
75
+ end
76
+
77
+ # This event gets emitted if the process died because of a signal
78
+ event :signaled
79
+
80
+ forward :signaled => :failed
81
+
82
+ ##
83
+ # If set to a string, the process' standard output will be redirected to
84
+ # the given file. The following replacement is done:
85
+ # * '%p' is replaced by the process PID
86
+ #
87
+ # The last form (with nil argument) removes any redirection. A specific
88
+ # redirection can also be disabled using the hash form:
89
+ # redirect_output :stdout => nil
90
+ #
91
+ # :call-seq:
92
+ # redirect_output "file"
93
+ # redirect_output :stdout => "file-out", :stderr => "another-file"
94
+ # redirect_output nil
95
+ #
96
+ def redirect_output(args)
97
+ if !args
98
+ @redirection = nil
99
+ elsif args.respond_to? :to_str
100
+ @redirection = args.to_str
101
+ else
102
+ args = validate_options args, :stdout => nil, :stderr => nil
103
+ if args[:stdout] == args[:stderr]
104
+ @redirection = args[:stdout].to_str
105
+ else
106
+ @redirection = Hash.new
107
+ @redirection[:stdout] = args[:stdout].to_str if args[:stdout]
108
+ @redirection[:stderr] = args[:stderr].to_str if args[:stderr]
109
+ end
110
+ end
111
+ end
112
+
113
+ # The PID of the child process, or nil if the child process is not
114
+ # running
115
+ attr_reader :pid
116
+
117
+ # Error codes between the child and the parent. Note that the error
118
+ # codes must not be greater than 9
119
+ # :stopdoc:
120
+ KO_REDIRECTION = 1
121
+ KO_NO_SUCH_FILE = 2
122
+ KO_EXEC = 3
123
+ # :startdoc:
124
+
125
+ # Returns the file name based on the redirection pattern and the current
126
+ # PID values. This is called in the child process before exec().
127
+ def redirection_path(pattern) # :nodoc:
128
+ pattern.gsub '%p', Process.pid.to_s
129
+ end
130
+
131
+ # Starts the child process
132
+ def start_process # :nodoc:
133
+ # Open a pipe to monitor the child startup
134
+ r, w = IO.pipe
135
+
136
+ @pid = fork do
137
+ # Open the redirection outputs
138
+ stdout, stderr = nil
139
+ begin
140
+ if redirection.respond_to?(:to_str)
141
+ stdout = stderr = File.open(redirection_path(redirection), "w")
142
+ elsif redirection
143
+ if stdout_file = redirection[:stdout]
144
+ stdout = File.open(redirection_path(stdout_file), "w")
145
+ end
146
+ if stderr_file = redirection[:stderr]
147
+ stderr = File.open(redirection_path(stderr_file), "w")
148
+ end
149
+ end
150
+ rescue Exception => e
151
+ Roby.fatal e.message
152
+ w.write("#{KO_REDIRECTION}")
153
+ return
154
+ end
155
+
156
+ STDOUT.reopen(stdout) if stdout
157
+ STDERR.reopen(stderr) if stderr
158
+
159
+ r.close
160
+ w.fcntl(Fcntl::F_SETFD, 1) # set close on exit
161
+ ::Process.setpgrp
162
+ begin
163
+ exec(*command_line)
164
+ rescue Errno::ENOENT
165
+ w.write("#{KO_NO_SUCH_FILE}")
166
+ rescue Exception => e
167
+ Roby.fatal e.message
168
+ w.write("#{KO_EXEC}")
169
+ end
170
+ end
171
+
172
+ ExternalProcessTask.processes[pid] = self
173
+
174
+ is_running = begin
175
+ !Process.waitpid(pid, Process::WNOHANG)
176
+ rescue Errno::ECHILD
177
+ false
178
+ end
179
+ if !is_running
180
+ raise "child #{command_line.first} died unexpectedly during its startup"
181
+ end
182
+
183
+ w.close
184
+ control = Integer(r.read(1))
185
+ if control == KO_REDIRECTION
186
+ raise "could not start #{command_line.first}: cannot establish output redirections"
187
+ elsif control == KO_NO_SUCH_FILE
188
+ raise "could not start #{command_line.first}: provided command does not exist"
189
+ elsif control == KO_EXEC
190
+ raise "could not start #{command_line.first}: exec() call failed"
191
+ end
192
+
193
+
194
+ rescue Exception => e
195
+ ExternalProcessTask.processes.delete(pid)
196
+ raise e
197
+ end
198
+
199
+ ##
200
+ # :method: start!
201
+ #
202
+ # Starts the child process. Emits +start+ when the process is actually
203
+ # started.
204
+ event :start do |_|
205
+ if working_directory
206
+ Dir.chdir(working_directory) do
207
+ start_process
208
+ end
209
+ else
210
+ start_process
211
+ end
212
+ emit :start
213
+ end
214
+
215
+ # Kills the child process
216
+ def kill(signo)
217
+ Process.kill(signo, pid)
218
+ end
219
+
220
+ on :stop do |_|
221
+ ExternalProcessTask.processes.delete(pid)
222
+ end
223
+ end
224
+ end
225
+
@@ -1,9 +1,3 @@
1
- require 'utilrb/module'
2
- require 'utilrb/kernel'
3
- require 'utilrb/enumerable'
4
- require 'utilrb/value_set'
5
- require 'roby_bgl'
6
-
7
1
  Utilrb.unless_ext do
8
2
  raise LoadError, "Roby needs Utilrb's C extension to be compiled"
9
3
  end
@@ -1,60 +1,4 @@
1
- require 'thread'
2
- require 'roby'
3
- require 'roby/planning'
4
- require 'facets/basicobject'
5
1
  require 'utilrb/column_formatter'
6
- require 'stringio'
7
- require 'roby/robot'
8
-
9
- module Robot
10
- def self.prepare_action(name, arguments)
11
- control = Roby.control
12
-
13
- # Check if +name+ is a planner method, and in that case
14
- # add a planning method for it and plan it
15
- planner_model = control.planners.find do |planner_model|
16
- planner_model.has_method?(name)
17
- end
18
- if !planner_model
19
- raise ArgumentError, "no such planning method #{name}"
20
- end
21
-
22
- m = planner_model.model_of(name, arguments)
23
-
24
- # HACK: m.returns should not be nil, but it sometimes happen
25
- returns_model = (m.returns if m && m.returns) || Task
26
-
27
- if returns_model.kind_of?(Roby::TaskModelTag)
28
- task = Roby::Task.new
29
- task.extend returns_model
30
- else
31
- # Create an abstract task which will be planned
32
- task = returns_model.new
33
- end
34
-
35
- planner = Roby::PlanningTask.new(:planner_model => planner_model, :method_name => name, :method_options => arguments)
36
- task.planned_by planner
37
- return task, planner
38
- end
39
-
40
- def self.method_missing(name, *args)
41
- if name.to_s =~ /!$/
42
- name = $`.to_sym
43
- else
44
- super
45
- end
46
-
47
- if args.size > 1
48
- raise ArgumentError, "wrong number of arguments (#{args.size} for 1) in #{name}!"
49
- end
50
-
51
- options = args.first || {}
52
- task, planner = Robot.prepare_action(name, options)
53
- Roby.control.plan.insert(task)
54
-
55
- return task, planner
56
- end
57
- end
58
2
 
59
3
  module Roby
60
4
  # An augmented DRbObject which allow to properly interface with remotely
@@ -88,7 +32,32 @@ module Roby
88
32
  # object.
89
33
  def initialize(interface)
90
34
  @interface = interface
91
- end
35
+ reconnect
36
+ end
37
+
38
+ def reconnect
39
+ remote_models = @interface.task_models
40
+ remote_models.map do |klass|
41
+ klass = klass.proxy(nil)
42
+
43
+ if klass.respond_to?(:remote_name)
44
+ # This is a local proxy for a remote model. Add it in our
45
+ # namespace as well.
46
+ path = klass.remote_name.split '::'
47
+ klass_name = path.pop
48
+ mod = Object
49
+ while !path.empty?
50
+ name = path.shift
51
+ mod = begin
52
+ mod.const_get(name)
53
+ rescue NameError
54
+ mod.const_set(name, Module.new)
55
+ end
56
+ end
57
+ mod.const_set(klass_name, klass)
58
+ end
59
+ end
60
+ end
92
61
 
93
62
  # Returns a Query object which can be used to interactively query the
94
63
  # running plan
@@ -128,6 +97,167 @@ module Roby
128
97
  Interface.instance_methods(false).
129
98
  actions.map { |name| "#{name}!" }
130
99
  end
100
+
101
+ def actions_summary(with_advanced = false)
102
+ methods = @interface.actions
103
+ if !with_advanced
104
+ methods = methods.delete_if { |m| m.description.advanced? }
105
+ end
106
+
107
+ if !methods.empty?
108
+ puts
109
+ desc = methods.map do |p|
110
+ doc = p.description.doc || ["(no description set)"]
111
+ Hash['Name' => "#{p.name}!", 'Description' => doc.join("\n")]
112
+ end
113
+
114
+ ColumnFormatter.from_hashes(desc, STDOUT,
115
+ :header_delimiter => true,
116
+ :column_delimiter => "|",
117
+ :order => %w{Name Description})
118
+ puts
119
+ end
120
+
121
+ nil
122
+ end
123
+
124
+ def actions(with_advanced = false)
125
+ @interface.actions.each do |m|
126
+ next if m.description.advanced? if !with_advanced
127
+ display_action_description(m)
128
+ puts
129
+ end
130
+ nil
131
+ end
132
+
133
+ # Standard way to display a set of tasks
134
+ def task_set_to_s(task_set) # :nodoc:
135
+ if task_set.empty?
136
+ return "no tasks"
137
+ end
138
+
139
+ task = task_set.map do |task|
140
+ state_name = %w{pending starting running finishing finished}.find do |state_name|
141
+ task.send("#{state_name}?")
142
+ end
143
+
144
+ since = task.start_time
145
+ lifetime = task.lifetime
146
+ Hash['Task' => task.to_s,
147
+ 'State' => state_name,
148
+ 'Since' => (since.asctime if since),
149
+ 'Lifetime' => (Time.at(lifetime).to_hms if lifetime)
150
+ ]
151
+ end
152
+
153
+ io = StringIO.new
154
+ ColumnFormatter.from_hashes(task, STDOUT,
155
+ :header_delimiter => true,
156
+ :column_delimiter => "|",
157
+ :order => %w{Task State Lifetime Since})
158
+ end
159
+
160
+ # Displays information about the plan's missions
161
+ def missions
162
+ missions = find_tasks.mission.to_a
163
+ task_set_to_s(missions)
164
+ nil
165
+ end
166
+
167
+ # Displays information about the running tasks
168
+ def running_tasks
169
+ tasks = find_tasks.running.to_a
170
+ task_set_to_s(tasks)
171
+ nil
172
+ end
173
+
174
+ # Displays details about the actions matching 'regex'
175
+ def describe(name, with_advanced = false)
176
+ name = Regexp.new(name)
177
+ m = @interface.actions.find_all { |p| name === p.name }
178
+
179
+ if !with_advanced
180
+ filtered = m.find_all { |m| !m.description.advanced? }
181
+ m = filtered if !filtered.empty?
182
+ end
183
+
184
+ if m.empty?
185
+ puts "no such method"
186
+ else
187
+ m.each do |desc|
188
+ puts
189
+ display_action_description(desc)
190
+ puts
191
+ end
192
+ end
193
+ nil
194
+ end
195
+
196
+ # Displays a help message
197
+ def help
198
+ puts
199
+ puts "Available Actions"
200
+ puts "================="
201
+ actions_summary
202
+ puts ""
203
+
204
+
205
+ puts <<-EOHELP
206
+ each action is started with action_name!(:arg1 => value1, :arg2 => value2, ...)
207
+ and returns the corresponding task object. A message is displayed in the shell
208
+ when the task finishes."
209
+
210
+ Shell Commands
211
+ ==============
212
+ Command | Help
213
+ ---------------------------------------------------------------------------------------------
214
+ actions_summary(advanced = false) | displays the list of actions with a short documentation |
215
+ actions(advanced = false) | displays details for each available actions |
216
+ describe(regex) | displays details about the actions matching 'regex' |
217
+ missions | displays the set of running missions with their status |
218
+ running_tasks | displays the set of running tasks with their status |
219
+ | |
220
+ help | this help message |
221
+
222
+ EOHELP
223
+ end
224
+
225
+ # Standard display of an action description. +m+ is a PlanningMethod
226
+ # object.
227
+ def display_action_description(m) # :nodoc:
228
+ args = m.description.arguments.
229
+ sort_by { |arg_desc| arg_desc.name }
230
+
231
+ first = true
232
+ args_summary = args.map do |arg_desc|
233
+ name = arg_desc.name
234
+ is_required = arg_desc.required
235
+ format = if is_required then "%s"
236
+ else "[%s]"
237
+ end
238
+ text = format % ["#{", " if !first}:#{name} => #{name}"]
239
+ first = false
240
+ text
241
+ end
242
+
243
+ args_table = args.
244
+ map do |arg_desc|
245
+ Hash['Argument' => arg_desc.name,
246
+ 'Description' => (arg_desc.doc || "(no description set)")]
247
+ end
248
+
249
+ method_doc = m.description.doc || [""]
250
+ puts "#{m.name}! #{args_summary.join("")}\n#{method_doc.join("\n")}"
251
+ if m.description.arguments.empty?
252
+ puts "No arguments"
253
+ else
254
+ ColumnFormatter.from_hashes(args_table, STDOUT,
255
+ :left_padding => " ",
256
+ :header_delimiter => true,
257
+ :column_delimiter => "|",
258
+ :order => %w{Argument Description})
259
+ end
260
+ end
131
261
 
132
262
 
133
263
  def method_missing(m, *args) # :nodoc:
@@ -145,6 +275,8 @@ module Roby
145
275
  # This class is used to interface with the Roby event loop and plan. It is the
146
276
  # main front object when accessing a Roby core remotely
147
277
  class Interface
278
+ # This module defines the hooks needed to plug Interface objects onto
279
+ # ExecutionEngine
148
280
  module GatherExceptions
149
281
  # The set of Interface objects that have been registered to us
150
282
  attribute(:interfaces) { Array.new }
@@ -152,14 +284,14 @@ module Roby
152
284
  # Register a new Interface object so that it gets feedback information
153
285
  # from the running controller.
154
286
  def register_interface(iface)
155
- Roby::Control.synchronize do
287
+ Roby.synchronize do
156
288
  interfaces << iface
157
289
  end
158
290
  end
159
291
 
160
292
  # Pushes a exception message to all the already registered remote interfaces.
161
293
  def push_exception_message(name, error, tasks)
162
- Roby::Control.synchronize do
294
+ Roby.synchronize do
163
295
  msg = Roby.format_exception(error.exception).join("\n")
164
296
  msg << "\nThe following tasks have been killed:\n"
165
297
  tasks.each do |t|
@@ -190,38 +322,39 @@ module Roby
190
322
  end
191
323
  end
192
324
 
193
- # The Roby::Control object this interface is working on
194
- attr_reader :control
325
+ # The engine this interface is tied to
326
+ attr_reader :engine
195
327
  # The set of pending messages that are to be displayed on the remote interface
196
328
  attr_reader :pending_messages
197
329
  # Creates a local server for a remote interface, acting on +control+
198
- def initialize(control)
199
- @control = control
330
+ def initialize(engine)
200
331
  @pending_messages = Queue.new
332
+ @engine = engine
201
333
 
202
- Roby::Control.extend GatherExceptions
203
- Roby::Control.register_interface self
334
+ engine.extend GatherExceptions
335
+ engine.register_interface self
204
336
  end
205
337
 
206
338
  # Clear the current plan: remove all running and permanent tasks.
207
339
  def clear
208
- Roby.execute do
340
+ engine.execute do
209
341
  plan.missions.dup.each { |t| plan.discard(t) }
210
- plan.keepalive.dup.each { |t| plan.auto(t) }
342
+ plan.permanent_tasks.dup.each { |t| plan.auto(t) }
343
+ plan.permanent_events.dup.each { |t| plan.auto(t) }
211
344
  end
212
345
  end
213
346
 
214
347
  # Make the Roby event loop quit
215
- def stop; control.quit; nil end
348
+ def stop; engine.quit; nil end
216
349
  # The Roby plan
217
- def plan; Roby.plan end
350
+ def plan; engine.plan end
218
351
 
219
352
  # Synchronously call +m+ on +tasks+ with the given arguments. This,
220
353
  # along with the implementation of RemoteInterface#method_missing,
221
354
  # ensures that no interactive operations are performed outside the
222
355
  # control thread.
223
356
  def call(task, m, *args)
224
- Roby.execute do
357
+ engine.execute do
225
358
  if m.to_s =~ /!$/
226
359
  event_name = $`
227
360
  # Check if the called event is terminal. If it is the case,
@@ -229,7 +362,7 @@ module Roby
229
362
  # will get a message
230
363
  #
231
364
  if task.event(event_name).terminal?
232
- plan.discard(task)
365
+ plan.unmark_mission(task)
233
366
  task.on(:stop) { |ev| pending_messages << "task #{ev.task} stopped by user request" }
234
367
  else
235
368
  task.on(event_name) { |ev| pending_messages << "done emitting #{ev.generator}" }
@@ -270,76 +403,27 @@ module Roby
270
403
  nil
271
404
  end
272
405
 
273
- # Displays the set of models as well as their superclasses
274
- def models
406
+ # Returns the set of task models as DRobyTaskModel objects. The standard
407
+ # Roby task models are excluded.
408
+ def task_models
275
409
  task_models = []
276
- Roby.execute do
410
+ engine.execute do
277
411
  ObjectSpace.each_object(Class) do |obj|
278
- task_models << obj if obj <= Roby::Task && obj.name !~ /^Roby::/
412
+ if obj <= Roby::Task && obj.name !~ /^Roby::/
413
+ task_models << obj
414
+ end
279
415
  end
280
416
  end
281
-
282
- task_models.map do |model|
283
- "#{model} #{model.superclass}"
284
- end
417
+ task_models.map { |t| t.droby_dump(nil) }
285
418
  end
286
419
 
287
- # Displays the set of actions which are available through the planners
288
- # registered on #control. See Control#planners
420
+ # Returns the set of PlanningMethod objects that describe the methods
421
+ # exported in the application's planners.
289
422
  def actions
290
- control.planners.
291
- map { |p| p.planning_methods_names.to_a }.
292
- flatten.
293
- sort
294
- end
295
-
296
- # Pretty-prints a set of tasks
297
- def task_set_to_s(task_set) # :nodoc:
298
- if task_set.empty?
299
- return "no tasks"
300
- end
301
-
302
- task = task_set.map do |task|
303
- state_name = %w{pending starting running finishing finished}.find do |state_name|
304
- task.send("#{state_name}?")
305
- end
306
-
307
- start_event = task.history.find { |ev| ev.symbol == :start }
308
- since = if start_event then start_event.time
309
- else 'N/A'
310
- end
311
- { 'Task' => task.to_s, 'Since' => since, 'State' => state_name }
312
- end
313
-
314
- io = StringIO.new
315
- ColumnFormatter.from_hashes(task, io) { %w{Task Since State} }
316
- "\n#{io.string}"
317
- end
318
-
319
- # Returns a string representing the set of running tasks
320
- def running_tasks
321
- Roby.execute do
322
- task_set_to_s(Roby.plan.find_tasks.running.to_a)
323
- end
324
- end
325
-
326
- # Returns a string representing the set of missions
327
- def missions
328
- Roby.execute do
329
- task_set_to_s(control.plan.missions)
330
- end
331
- end
332
-
333
- # Returns a string representing the set of tasks present in the plan
334
- def tasks
335
- Roby.execute do
336
- task_set_to_s(Roby.plan.known_tasks)
337
- end
338
- end
339
-
340
- def methods
341
- result = super
342
- result + actions.map { |n| "#{n}!" }
423
+ Roby.app.planners.
424
+ map do |p|
425
+ p.planning_methods
426
+ end.flatten.sort_by { |p| p.name }
343
427
  end
344
428
 
345
429
  # Called every once in a while by RemoteInterface to read and clear the
@@ -369,15 +453,15 @@ module Roby
369
453
  options = args.first || {}
370
454
  task, planner = Robot.prepare_action(name, options)
371
455
  begin
372
- Roby.wait_until(planner.event(:success)) do
373
- control.plan.insert(task)
456
+ engine.wait_until(planner.event(:success)) do
457
+ plan.add_mission(task)
374
458
  yield(task, planner) if block_given?
375
459
  end
376
460
  rescue Roby::UnreachableEvent
377
461
  raise RuntimeError, "cannot start #{name}: #{planner.terminal_event.context.first}"
378
462
  end
379
463
 
380
- Roby.execute do
464
+ engine.execute do
381
465
  result = planner.result
382
466
  result.on(:failed) { |ev| pending_messages << "task #{ev.task} failed" }
383
467
  result.on(:success) { |ev| pending_messages << "task #{ev.task} finished successfully" }