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
@@ -1,34 +1,50 @@
1
- require 'roby/task'
2
- require 'roby/relations/planned_by'
3
- require 'roby/control'
4
- require 'roby/transactions'
5
-
6
1
  module Roby
7
2
  # An asynchronous planning task using Ruby threads
8
3
  class PlanningTask < Roby::Task
9
4
  attr_reader :planner, :transaction
10
5
 
11
- arguments :planner_model, :method_name,
12
- :method_options, :planned_model,
13
- :planning_owners
6
+ arguments :planner_model, :method_options, :method_name, :planned_model, :planning_owners
7
+
8
+ def planning_method
9
+ arguments[:planning_method]
10
+ end
11
+
12
+ def self.validate_planning_options(options)
13
+ options = options.dup
14
+ if options[:method_name]
15
+ method_name = options[:planning_method] = options[:method_name]
16
+ elsif options[:planning_method].respond_to?(:to_str) || options[:planning_method].respond_to?(:to_sym)
17
+ method_name = options[:method_name] = options[:planning_method].to_s
18
+ end
19
+
20
+ if !options[:planner_model]
21
+ raise ArgumentError, "missing required argument 'planner_model'"
22
+ elsif !options[:planning_method]
23
+ raise ArgumentError, "missing required argument 'planning_method'"
24
+ elsif !method_name
25
+ if options[:planning_method].kind_of?(Roby::Planning::MethodDefinition)
26
+ method_name = options[:method_name] = options[:planning_method].name
27
+ else
28
+ raise ArgumentError, "the planning_method argument is neither a method object nor a name"
29
+ end
30
+ end
31
+ options[:planned_model] ||= nil
32
+ options[:planning_owners] ||= nil
33
+ options
34
+ end
14
35
 
15
36
  def self.filter_options(options)
16
37
  task_options, method_options = Kernel.filter_options options,
17
38
  :planner_model => nil,
18
- :method_name => nil,
39
+ :planning_method => nil,
19
40
  :method_options => {},
41
+ :method_name => nil, # kept for backward compatibility
20
42
  :planned_model => nil,
21
43
  :planning_owners => nil
22
44
 
23
- if !task_options[:planner_model]
24
- raise ArgumentError, "missing required argument 'planner_model'"
25
- elsif !task_options[:method_name]
26
- raise ArgumentError, "missing required argument 'method_name'"
27
- end
28
- task_options[:planned_model] ||= nil
45
+ task_options = validate_planning_options(task_options)
29
46
  task_options[:method_options] ||= Hash.new
30
47
  task_options[:method_options].merge! method_options
31
- task_options[:planning_owners] ||= nil
32
48
  task_options
33
49
  end
34
50
 
@@ -38,12 +54,18 @@ module Roby
38
54
  end
39
55
 
40
56
  def planned_model
41
- arguments[:planned_model] ||= planner_model.model_of(method_name, method_options).returns || Roby::Task
57
+ arguments[:planned_model] ||= if method_name
58
+ planner_model.model_of(method_name, method_options).returns
59
+ else
60
+ planning_method.returns
61
+ end
62
+
63
+ arguments[:planned_model] ||= Roby::Task
42
64
  end
43
65
 
44
66
 
45
67
  def to_s
46
- "#{super}[#{method_name}:#{method_options}] -> #{@planned_task || "nil"}"
68
+ "#{super}[#{planning_method}:#{method_options}] -> #{@planned_task || "nil"}"
47
69
  end
48
70
 
49
71
  def planned_task
@@ -88,7 +110,11 @@ module Roby
88
110
  end
89
111
 
90
112
  def planning_thread(context)
91
- result_task = planner.send(method_name, method_options.merge(:context => context))
113
+ result_task = if planning_method.kind_of?(Roby::Planning::MethodDefinition)
114
+ planner.send(:call_planning_methods, Hash.new, method_options.merge(:context => context), planning_method)
115
+ else
116
+ planner.send(method_name, method_options.merge(:context => context))
117
+ end
92
118
 
93
119
  # Don't replace the planning task with ourselves if the
94
120
  # transaction specifies another planning task
@@ -118,7 +144,7 @@ module Roby
118
144
 
119
145
  # Check if the transaction has been committed. If it is not the
120
146
  # case, assume that the thread failed
121
- if transaction.freezed?
147
+ if transaction.committed?
122
148
  emit :success
123
149
  else
124
150
  error = begin
@@ -145,6 +171,7 @@ module Roby
145
171
  class TransactionProxy < Roby::Transactions::Task
146
172
  proxy_for PlanningTask
147
173
  def_delegator :@__getobj__, :planner
174
+ def_delegator :@__getobj__, :planning_method
148
175
  def_delegator :@__getobj__, :method_name
149
176
  def_delegator :@__getobj__, :method_options
150
177
  end
@@ -1,12 +1,12 @@
1
- require 'roby/plan'
2
- require 'roby/transactions'
3
- require 'roby/state/information'
4
-
5
1
  module Roby
6
2
  class Task
7
3
  # Returns a TaskMatcher object
8
- def self.match
9
- TaskMatcher.new
4
+ def self.match(*args)
5
+ matcher = TaskMatcher.new
6
+ if !args.empty?
7
+ matcher.which_fullfills(*args)
8
+ end
9
+ matcher
10
10
  end
11
11
  end
12
12
 
@@ -29,11 +29,17 @@ module Roby
29
29
  @improved_information = ValueSet.new
30
30
  @needed_information = ValueSet.new
31
31
  @interruptible = nil
32
+ @parents = Hash.new { |h, k| h[k] = Array.new }
33
+ @children = Hash.new { |h, k| h[k] = Array.new }
32
34
  end
33
35
 
34
36
  # Shortcut to set both model and argument
35
37
  def which_fullfills(model, arguments = nil)
36
- with_model(model).with_model_arguments(arguments || {})
38
+ with_model(model)
39
+ if arguments
40
+ with_model_arguments(arguments)
41
+ end
42
+ self
37
43
  end
38
44
 
39
45
  # Find by model
@@ -47,6 +53,11 @@ module Roby
47
53
  if !model
48
54
  raise ArgumentError, "set model first"
49
55
  end
56
+ if model.respond_to?(:to_ary)
57
+ valid_arguments = model.inject(Set.new) { |args, m| args | m.arguments.to_set }
58
+ else
59
+ valid_arguments = model.arguments
60
+ end
50
61
  with_arguments(arguments.slice(*model.arguments))
51
62
  self
52
63
  end
@@ -132,6 +143,53 @@ module Roby
132
143
  match_predicates :executable, :abstract, :partially_instanciated, :fully_instanciated,
133
144
  :pending, :running, :finished, :success, :failed, :interruptible, :finishing
134
145
 
146
+ # Helper method for #with_child and #with_parent
147
+ def handle_parent_child_arguments(other_query, relation, relation_options) # :nodoc:
148
+ if !other_query.kind_of?(TaskMatcher) && !other_query.kind_of?(Task)
149
+ if relation.kind_of?(Hash)
150
+ arguments = relation
151
+ relation = (arguments.delete(:relation) || arguments.delete('relation'))
152
+ relation_options = (arguments.delete(:relation_options) || arguments.delete('relation_options'))
153
+ else
154
+ arguments = Hash.new
155
+ end
156
+ other_query = TaskMatcher.which_fullfills(other_query, arguments)
157
+ end
158
+ return relation, [other_query, relation_options]
159
+ end
160
+
161
+ def with_child(other_query, relation = nil, relation_options = nil)
162
+ relation, spec = handle_parent_child_arguments(other_query, relation, relation_options)
163
+ @children[relation] << spec
164
+ self
165
+ end
166
+
167
+ def with_parent(other_query, relation = nil, relation_options = nil)
168
+ relation, spec = handle_parent_child_arguments(other_query, relation, relation_options)
169
+ @parents[relation] << spec
170
+ self
171
+ end
172
+
173
+ # Helper method for handling parent/child matches in #===
174
+ def handle_parent_child_match(task, match_spec) # :nodoc:
175
+ relation, matchers = *match_spec
176
+ return false if !relation && task.relations.empty?
177
+ for match_spec in matchers
178
+ m, relation_options = *match_spec
179
+ if relation
180
+ if !yield(relation, m, relation_options)
181
+ return false
182
+ end
183
+ else
184
+ result = task.relations.any? do |rel|
185
+ yield(rel, m, relation_options)
186
+ end
187
+ return false if !result
188
+ end
189
+ end
190
+ true
191
+ end
192
+
135
193
  # True if +task+ matches all the criteria defined on this object.
136
194
  def ===(task)
137
195
  return unless task.kind_of?(Roby::Task)
@@ -142,6 +200,22 @@ module Roby
142
200
  return unless task.arguments.slice(*arguments.keys) == arguments
143
201
  end
144
202
 
203
+ for parent_spec in @parents
204
+ result = handle_parent_child_match(task, parent_spec) do |relation, m, relation_options|
205
+ task.enum_parent_objects(relation).
206
+ any? { |parent| m === parent && (!relation_options || relation_options === parent[task, relation]) }
207
+ end
208
+ return false if !result
209
+ end
210
+
211
+ for child_spec in @children
212
+ result = handle_parent_child_match(task, child_spec) do |relation, m, relation_options|
213
+ task.enum_child_objects(relation).
214
+ any? { |child| m === child && (!relation_options || relation_options === task[child, relation]) }
215
+ end
216
+ return false if !result
217
+ end
218
+
145
219
  for info in improved_information
146
220
  return false if !task.improves?(info)
147
221
  end
@@ -159,15 +233,19 @@ module Roby
159
233
  true
160
234
  end
161
235
 
162
- STATE_PREDICATES = [:pending?, :running?, :finished?, :success?, :failed?].to_value_set
163
-
164
236
  # Filters the tasks in +initial_set+ by using the information in
165
237
  # +task_index+, and returns the result. The resulting set must
166
238
  # include all tasks in +initial_set+ which match with #===, but can
167
239
  # include tasks which do not match #===
168
240
  def filter(initial_set, task_index)
169
241
  if model
170
- initial_set &= task_index.by_model[model]
242
+ if model.respond_to?(:to_ary)
243
+ for m in model
244
+ initial_set &= task_index.by_model[m]
245
+ end
246
+ else
247
+ initial_set &= task_index.by_model[model]
248
+ end
171
249
  end
172
250
 
173
251
  if !owners.empty?
@@ -180,11 +258,11 @@ module Roby
180
258
  end
181
259
  end
182
260
 
183
- for pred in (predicates & STATE_PREDICATES)
261
+ for pred in (predicates & TaskIndex::STATE_PREDICATES)
184
262
  initial_set &= task_index.by_state[pred]
185
263
  end
186
264
 
187
- for pred in (neg_predicates & STATE_PREDICATES)
265
+ for pred in (neg_predicates & TaskIndex::STATE_PREDICATES)
188
266
  initial_set -= task_index.by_state[pred]
189
267
  end
190
268
 
@@ -193,7 +271,9 @@ module Roby
193
271
 
194
272
  # Enumerates all tasks of +plan+ which match this TaskMatcher object
195
273
  def each(plan, &block)
196
- plan.query_each(plan.query_result_set(self), &block)
274
+ plan.each_task do |t|
275
+ yield(t) if self === t
276
+ end
197
277
  self
198
278
  end
199
279
 
@@ -217,15 +297,36 @@ module Roby
217
297
  class Query < TaskMatcher
218
298
  # The plan this query acts on
219
299
  attr_reader :plan
300
+ # Search scope for queries on transactions. If equal to :local, the
301
+ # query will apply only on the scope of the searched transaction,
302
+ # otherwise it applies on a virtual plan that is the result of the
303
+ # transaction stack being applied.
304
+ #
305
+ # The default is :global.
306
+ #
307
+ # See #local_scope and #global_scope
308
+ attr_reader :scope
220
309
 
221
310
  # Create a query object on the given plan
222
311
  def initialize(plan)
312
+ @scope = :global
223
313
  @plan = plan
224
314
  super()
225
315
  @plan_predicates = Array.new
226
316
  @neg_plan_predicates = Array.new
227
317
  end
228
318
 
319
+ # Changes the scope of this query. See #scope.
320
+ def local_scope; @scope = :local end
321
+ # Changes the scope of this query. See #scope.
322
+ def global_scope; @scope = :global end
323
+
324
+ # Changes the plan this query works on
325
+ def plan=(new_plan)
326
+ reset
327
+ @plan = new_plan
328
+ end
329
+
229
330
  # The set of tasks which match in plan. This is a cached value, so use
230
331
  # #reset to actually recompute this set.
231
332
  def result_set
@@ -304,82 +405,6 @@ module Roby
304
405
  include Enumerable
305
406
  end
306
407
 
307
- # TaskIndex objects are used to maintain a set of tasks as classified sets,
308
- # speeding up query operations. See Plan#task_index.
309
- class TaskIndex
310
- # A model => ValueSet map of the tasks for each model
311
- attr_reader :by_model
312
- # A state => ValueSet map of tasks given their state. The state is
313
- # a symbol in [:pending, :starting, :running, :finishing,
314
- # :finished]
315
- attr_reader :by_state
316
- # A peer => ValueSet map of tasks given their owner.
317
- attr_reader :by_owner
318
- # The set of tasks which have an event which is being repaired
319
- attr_reader :repaired_tasks
320
-
321
- def initialize
322
- @by_model = Hash.new { |h, k| h[k] = ValueSet.new }
323
- @by_state = Hash.new
324
- TaskMatcher::STATE_PREDICATES.each do |state_name|
325
- by_state[state_name] = ValueSet.new
326
- end
327
- @by_owner = Hash.new
328
- @task_state = Hash.new
329
- @repaired_tasks = ValueSet.new
330
- end
331
-
332
- # Add a new task to this index
333
- def add(task)
334
- for klass in task.model.ancestors
335
- by_model[klass] << task
336
- end
337
- by_state[:pending?] << task
338
- for owner in task.owners
339
- add_owner(task, owner)
340
- end
341
- end
342
-
343
- # Updates the index to reflect that +new_owner+ now owns +task+
344
- def add_owner(task, new_owner)
345
- (by_owner[new_owner] ||= ValueSet.new) << task
346
- end
347
-
348
- # Updates the index to reflect that +peer+ no more owns +task+
349
- def remove_owner(task, peer)
350
- if set = by_owner[peer]
351
- set.delete(task)
352
- if set.empty?
353
- by_owner.delete(peer)
354
- end
355
- end
356
- end
357
-
358
- # Updates the index to reflect a change of state for +task+
359
- def set_state(task, new_state)
360
- for state_set in by_state
361
- state_set.last.delete(task)
362
- end
363
- by_state[new_state] << task
364
- if new_state == :success? || new_state == :failed?
365
- by_state[:finished?] << task
366
- end
367
- end
368
-
369
- # Remove all references of +task+ from the index.
370
- def remove(task)
371
- for klass in task.model.ancestors
372
- by_model[klass].delete(task)
373
- end
374
- for state_set in by_state
375
- state_set.last.delete(task)
376
- end
377
- for owner in task.owners
378
- remove_owner(task, owner)
379
- end
380
- end
381
- end
382
-
383
408
  # This task combines multiple task matching predicates through a OR boolean
384
409
  # operator.
385
410
  class OrTaskMatcher < TaskMatcher
@@ -472,6 +497,15 @@ module Roby
472
497
  q
473
498
  end
474
499
 
500
+ # Starts a local query on this plan
501
+ #
502
+ # See Query#scope
503
+ def find_local_tasks(*args, &block)
504
+ query = find_tasks(*args, &block)
505
+ query.local_scope
506
+ query
507
+ end
508
+
475
509
  # Called by TaskMatcher#result_set and Query#result_set to get the set
476
510
  # of tasks matching +matcher+
477
511
  def query_result_set(matcher)
@@ -563,9 +597,11 @@ module Roby
563
597
  # tasks matching it. The two sets are disjoint.
564
598
  def query_result_set(matcher)
565
599
  plan_set = ValueSet.new
566
- for task in plan.query_result_set(matcher)
567
- plan_set << task unless self[task, false]
568
- end
600
+ if matcher.scope == :global
601
+ for task in plan.query_result_set(matcher)
602
+ plan_set << task unless self[task, false]
603
+ end
604
+ end
569
605
 
570
606
  transaction_set = super
571
607
  [plan_set, transaction_set]
@@ -1,13 +1,16 @@
1
- require 'roby/support'
2
- require 'roby/graph'
3
-
4
1
  module Roby
5
2
  # This exception is raised when an edge is being added in a DAG, while this
6
3
  # edge would create a cycle.
7
4
  class CycleFoundError < RuntimeError; end
8
5
 
9
- # Base support for relations. It is mixed-in objects that are part of
10
- # relation networks (like Task and EventGenerator)
6
+ # Base support for relations. It is mixed in objects on which a
7
+ # RelationSpace applies on, like Task for TaskStructure and EventGenerator
8
+ # for EventStructure.
9
+ #
10
+ # See also the definition of RelationGraph#add_relation and
11
+ # RelationGraph#remove_relation for the possibility to define hooks that
12
+ # get called when a new edge involving +self+ as a vertex gets added and
13
+ # removed
11
14
  module DirectedRelationSupport
12
15
  include BGL::Vertex
13
16
 
@@ -19,8 +22,20 @@ module Roby
19
22
  alias :each_relation :each_graph
20
23
  alias :clear_relations :clear_vertex
21
24
 
25
+ ##
26
+ # :method: enum_relations => enumerator
27
+ # Returns an Enumerator object for the set of relations this object is
28
+ # included in. The same enumerator instance is always returned.
22
29
  cached_enum("graph", "relations", false)
30
+ ##
31
+ # :method: enum_parent_objects(relation) => enumerator
32
+ # Returns an Enumerator object for the set of parents this object has
33
+ # in +relation+. The same enumerator instance is always returned.
23
34
  cached_enum("parent_object", "parent_objects", true)
35
+ ##
36
+ # :method: enum_child_objects(relation) => enumerator
37
+ # Returns an Enumerator object for the set of children this object has
38
+ # in +relation+. The same enumerator instance is always returned.
24
39
  cached_enum("child_object", "child_objects", true)
25
40
 
26
41
  # The array of relations this object is part of
@@ -65,24 +80,9 @@ module Roby
65
80
  parent.add_child_object(self, relation, info)
66
81
  end
67
82
 
68
- # Hook called before a new child is added in the +relation+ relation
69
- # def adding_child_object(child, relation, info)
70
- # child.adding_parent_object(self, relations, info)
71
- # super if defined? super
72
- # end
73
- # Hook called after a new child has been added in the +relation+ relation
74
- # def added_child_object(child, relations, info)
75
- # child.added_parent_object(self, relation, info)
76
- # super if defined? super
77
- # end
78
-
79
- # Hook called after a new parent has been added in the +relation+ relation
80
- #def added_parent_object(parent, relation, info); super if defined? super end
81
- ## Hook called after a new parent is being added in the +relation+ relation
82
- #def adding_parent_object(parent, relation, info); super if defined? super end
83
-
84
- # Remove the relation between +self+ and +child+. If +relation+ is
85
- # given, remove only a relations in this relation kind.
83
+ # Remove all edges in which +self+ is the source and +child+ the
84
+ # target. If +relation+ is given, it removes only the edge in that
85
+ # relation graph.
86
86
  def remove_child_object(child, relation = nil)
87
87
  check_is_relation(relation)
88
88
  apply_selection(relation, (relation || enum_relations)) do |relation|
@@ -90,8 +90,8 @@ module Roby
90
90
  end
91
91
  end
92
92
 
93
- # Remove relations where self is a parent. If +relation+ is given,
94
- # remove only the relations in this relation graph.
93
+ # Remove all edges in which +self+ is the source. If +relation+
94
+ # is given, it removes only the edges in that relation graph.
95
95
  def remove_children(relation = nil)
96
96
  apply_selection(relation, (relation || enum_relations)) do |relation|
97
97
  self.each_child_object(relation) do |child|
@@ -100,13 +100,15 @@ module Roby
100
100
  end
101
101
  end
102
102
 
103
- # Remove relations where +self+ is a child. If +relation+ is given,
104
- # remove only the relations in this relation graph
103
+ # Remove all edges in which +child+ is the source and +self+ the
104
+ # target. If +relation+ is given, it removes only the edge in that
105
+ # relation graph.
105
106
  def remove_parent_object(parent, relation = nil)
106
107
  parent.remove_child_object(self, relation)
107
108
  end
108
- # Remove all parents of +self+. If +relation+ is given, remove only the
109
- # parents in this relation graph
109
+
110
+ # Remove all edges in which +self+ is the target. If +relation+
111
+ # is given, it removes only the edges in that relation graph.
110
112
  def remove_parents(relation = nil)
111
113
  check_is_relation(relation)
112
114
  apply_selection(relation, (relation || enum_relations)) do |relation|
@@ -116,24 +118,10 @@ module Roby
116
118
  end
117
119
  end
118
120
 
119
- # Hook called after a parent has been removed
120
- # def removing_parent_object(parent, relation); super if defined? super end
121
- # Hook called after a child has been removed
122
- # def removing_child_object(child, relation)
123
- # child.removing_parent_object(self, relation)
124
- # super if defined? super
125
- # end
126
-
127
- # Hook called after a parent has been removed
128
- # def removed_parent_object(parent, relation); super if defined? super end
129
- # Hook called after a child has been removed
130
- # def removed_child_object(child, relation)
131
- # child.removed_parent_object(self, relation)
132
- # super if defined? super
133
- # end
134
-
135
121
  # Remove all relations that point to or come from +to+ If +to+ is nil,
136
- # it removes all relations of +self+
122
+ # it removes all edges in which +self+ is involved.
123
+ #
124
+ # If +relation+ is not nil, only edges of that relation graph are removed.
137
125
  def remove_relations(to = nil, relation = nil)
138
126
  check_is_relation(relation)
139
127
  if to
@@ -172,16 +160,28 @@ module Roby
172
160
  end
173
161
 
174
162
  # This class manages the graph defined by an object relation in Roby.
163
+ #
175
164
  # Relation graphs are managed in hierarchies (for instance, in
176
165
  # EventStructure, Precedence is a superset of CausalLink, and CausalLink a
177
166
  # superset of both Forwarding and Signal). In this hierarchy, at each
178
167
  # level, an edge cannot be present in more than one graph. Nonetheless, it
179
168
  # is possible for a parent relation to have an edge which is present in
180
169
  # none of its children.
170
+ #
171
+ # Each relation define two things:
172
+ # * a graph, which is represented by the RelationGraph instance itself
173
+ # * support methods that are defined on the vertices of the relation. They
174
+ # allow to manage the vertex in its relations easily. Those methods are
175
+ # defined in a separate module (see #support)
176
+ #
177
+ # In general, relations are part of a RelationSpace instance, which manages
178
+ # the set of relations whose vertices are of the same kind (for instance
179
+ # TaskStructure manages all relations whose vertices are Task instances).
180
+ # In these cases, RelationSpace#relation allow to define new relations easily.
181
181
  class RelationGraph < BGL::Graph
182
182
  # The relation name
183
183
  attr_reader :name
184
- # The relation parent if any
184
+ # The relation parent (if any). See #superset_of.
185
185
  attr_accessor :parent
186
186
  # The set of graphs
187
187
  attr_reader :subsets
@@ -194,17 +194,18 @@ module Roby
194
194
  # if the graph is a DAG. If true, add_relation will check that
195
195
  # no cycle is created
196
196
  # +subsets+::
197
- # a set of RelationGraph objects that are children of this
198
- # one
197
+ # a set of RelationGraph objects that are children of this one.
198
+ # See #superset_of.
199
199
  # +distributed+::
200
200
  # if this relation graph should be seen by remote hosts
201
201
  def initialize(name, options = {})
202
- @name = name
202
+ @name = name
203
203
  @options = options
204
204
  @subsets = ValueSet.new
205
205
  @distribute = options[:distribute]
206
- @dag = options[:dag]
207
- @weak = options[:weak]
206
+ @dag = options[:dag]
207
+ @weak = options[:weak]
208
+ @embeds_info = !options[:noinfo]
208
209
 
209
210
  if options[:subsets]
210
211
  options[:subsets].each(&method(:superset_of))
@@ -220,17 +221,38 @@ module Roby
220
221
  # break cross-relations cycles (cycles which exist in the graph union
221
222
  # of all the relation graphs).
222
223
  attr_predicate :weak
224
+ # If this relation embeds some additional information
225
+ attr_predicate :embeds_info?
223
226
 
224
227
  def to_s; name end
225
228
 
226
229
  # True if this relation does not have a parent
227
230
  def root_relation?; !parent end
228
231
 
229
- # Add a relation between +from+ and +to+. The relation is added on all
230
- # parent relation graphs as well.
231
- #
232
- # If #dag? is true, it checks that the new relation does not create a
233
- # cycle
232
+ # Remove +vertex+ from this graph. It removes all relations that
233
+ # +vertex+ is part of, and calls the corresponding hooks
234
+ def remove(vertex)
235
+ vertex.remove_relations(nil, self)
236
+ super
237
+ end
238
+
239
+ # Add an edge between +from+ and +to+. The relation is added on all
240
+ # parent relation graphs as well. If #dag? is true on +self+ or on one
241
+ # of its parents, the method will raise CycleFoundError in case the new
242
+ # edge would create a cycle.
243
+ #
244
+ # If +from+ or +to+ define the following hooks:
245
+ # adding_parent_object(parent, relations, info)
246
+ # adding_child_object(child, relations, info)
247
+ # added_parent_object(parent, relations, info)
248
+ # added_child_object(child, relations, info)
249
+ #
250
+ # then these hooks get respectively called before and after having
251
+ # added the relation, where +relations+ is the set of RelationGraph
252
+ # instances where the edge has been added. It can be either [+self+] if
253
+ # the edge does not already exist in it, or [+self+, +parent+,
254
+ # <tt>parent.parent</tt>, ...] if the parent, grandparent, ... graphs
255
+ # do not include the edge either.
234
256
  def add_relation(from, to, info = nil)
235
257
  # Get the toplevel DAG in our relation hierarchy. We only test for the
236
258
  # DAG property on this one, as it is the union of all its children
@@ -239,37 +261,25 @@ module Roby
239
261
  rel = self
240
262
  while rel
241
263
  top_dag = rel if rel.dag?
242
- new_relations << rel
264
+ if !rel.linked?(from, to)
265
+ new_relations << rel
266
+ end
243
267
  rel = rel.parent
244
268
  end
245
269
  if top_dag && !top_dag.linked?(from, to) && top_dag.reachable?(to, from)
246
270
  raise CycleFoundError, "cannot add a #{from} -> #{to} relation since it would create a cycle"
247
271
  end
248
272
 
249
- # Now compute the set of relations in which we really have to add a
250
- # new relation
251
- top_rel = new_relations.last
252
- if top_rel.linked?(from, to)
253
- if !(old_info = from[to, top_rel]).nil?
254
- if old_info != info
255
- raise ArgumentError, "trying to change edge information"
256
- end
257
- end
258
-
259
- changed_info = [new_relations.pop]
260
-
261
- while !new_relations.empty?
262
- if new_relations.last.linked?(from, to)
263
- changed_info << new_relations.pop
264
- else
265
- break
266
- end
267
- end
268
-
269
- for rel in changed_info
270
- from[to, rel] = info
271
- end
272
- end
273
+ # Now check that we're not changing the edge info. This is ignored
274
+ # if +self+ has the noinfo flag set.
275
+ if linked?(from, to)
276
+ if !(old_info = from[to, self]).nil?
277
+ if old_info != info && !(info = merge_info(from, to, old_info, info))
278
+ raise ArgumentError, "trying to change edge information in #{self} for #{from} => #{to}: old was #{old_info} and new is #{info}"
279
+ end
280
+ end
281
+ from[to, self] = info
282
+ end
273
283
 
274
284
  unless new_relations.empty?
275
285
  if from.respond_to?(:adding_child_object)
@@ -280,7 +290,7 @@ module Roby
280
290
  end
281
291
 
282
292
  for rel in new_relations
283
- rel.__bgl_link(from, to, info)
293
+ rel.__bgl_link(from, to, (info if self == rel))
284
294
  end
285
295
 
286
296
  if from.respond_to?(:added_child_object)
@@ -292,22 +302,43 @@ module Roby
292
302
  end
293
303
  end
294
304
 
305
+ def merge_info(from, to, old, new)
306
+ end
307
+
295
308
  alias :__bgl_link :link
296
309
  # Reimplemented from BGL::Graph. Unlike this implementation, it is
297
310
  # possible to add an already existing edge if the +info+ parameter
298
311
  # matches.
299
312
  def link(from, to, info)
300
313
  if linked?(from, to)
301
- if info != from[to, self]
302
- raise ArgumentError, "trying to change edge information"
314
+ old_info = from[to, self]
315
+ if info != old_info
316
+ if info = merge_info(from, to, old_info, info)
317
+ from[to, self] = info
318
+ return
319
+ else
320
+ raise ArgumentError, "trying to change edge information"
321
+ end
303
322
  end
304
323
  return
305
324
  end
306
- super
325
+ super(from, to, info)
307
326
  end
308
327
 
309
- # Remove the relation between +from+ and +to+, in this graph and in its
310
- # parent graphs as well
328
+ # Remove the relation between +from+ and +to+, in this graph and in its
329
+ # parent graphs as well.
330
+ #
331
+ # If +from+ or +to+ define the following hooks:
332
+ # removing_parent_object(parent, relations)
333
+ # removing_child_object(child, relations)
334
+ # removed_parent_object(parent, relations)
335
+ # removed_child_object(child, relations)
336
+ #
337
+ # then these hooks get respectively called once before and once after
338
+ # having removed the relation, where +relations+ is the set of
339
+ # RelationGraph instances where the edge has been removed. It is always
340
+ # <tt>[self, parent, parent.parent, ...]</tt> up to the root relation
341
+ # which is a superset of +self+.
311
342
  def remove_relation(from, to)
312
343
  rel = self
313
344
  relations = []
@@ -337,17 +368,31 @@ module Roby
337
368
 
338
369
  # Returns true if +relation+ is included in this relation (i.e. it is
339
370
  # either the same relation or one of its children)
371
+ #
372
+ # See also #superset_of
340
373
  def subset?(relation)
341
374
  self.eql?(relation) || subsets.any? { |subrel| subrel.subset?(relation) }
342
375
  end
343
376
 
344
377
  # Returns +true+ if there is an edge +source+ -> +target+ in this graph
345
378
  # or in one of its parents
346
- def linked_in_hierarchy?(source, target) # :nodoc:
379
+ #
380
+ # See #superset_of for a description of the parent mechanism
381
+ def linked_in_hierarchy?(source, target)
347
382
  linked?(source, target) || (parent.linked?(source, target) if parent)
348
383
  end
349
384
 
350
- # Declare that +relation+ is a superset of this relation
385
+ # Declare that +self+ is a superset of +relation+. Once this is done,
386
+ # the system manages two constraints:
387
+ # * all new relations added in +relation+ are also added in +self+
388
+ # * it is not allowed for an edge to exist in two different subsets of
389
+ # +self+
390
+ # * of course, if +self+ is a DAG, then in effect +relation+ is constrained
391
+ # to be one as well.
392
+ #
393
+ # One single graph can be the superset of multiple subgraphs (these are
394
+ # stored in the #subsets attribute), but one graph can have only one
395
+ # parent (#parent).
351
396
  def superset_of(relation)
352
397
  relation.each_edge do |source, target, info|
353
398
  if linked_in_hierarchy?(source, target)
@@ -368,27 +413,44 @@ module Roby
368
413
  attr_accessor :support
369
414
  end
370
415
 
371
- # A relation space is a module which handles a list of relations and
372
- # applies them to a set of classes. In this context, a relation is both a
373
- # Ruby module which gets included in the classes this space is applied on,
374
- # and a RelationGraph object which holds the object graphs.
416
+ # A relation space is a module which handles a list of relations
417
+ # (RelationGraph instances) and applies them to a set of classes.
418
+ # For instance, the TaskStructure relation space is defined by
419
+ # TaskStructure = RelationSpace(Task)
420
+ #
421
+ # See the files in roby/relations to see example definitions of new
422
+ # relations
423
+ #
424
+ # Use RelationSpace#relation allow to define a new relation in a given
425
+ # space. For instance, one can either do
426
+ #
427
+ # TaskStructure.relation :NewRelation
375
428
  #
376
- # See the files in roby/relations to see definitions of new relations
429
+ # or
430
+ #
431
+ # module TaskStructure
432
+ # relation :NewRelation
433
+ # end
434
+ #
435
+ # This relation can then be referenced by
436
+ # <tt>TaskStructure::NewRelation</tt>
377
437
  class RelationSpace < Module
378
438
  # The set of relations included in this relation space
379
439
  attr_reader :relations
380
- # The set of klasses on which the relations have been applied
440
+ # The set of classes on which the relations have been applied
381
441
  attr_reader :applied
382
442
 
383
- def initialize
443
+ def initialize # :nodoc:
384
444
  @relations = Array.new
385
445
  @applied = Array.new
386
446
  super
387
447
  end
388
448
 
389
- # This relation applies on klass. It mainly means that a relation
449
+ # This relation applies on +klass+. It mainly means that a relation
390
450
  # defined on this RelationSpace will define the relation-access methods
391
- # and include its support module (if any) in +klass+.
451
+ # and include its support module (if any) in +klass+. Note that the
452
+ # DirectedRelationSupport module is automatically included in +klass+
453
+ # as well.
392
454
  def apply_on(klass)
393
455
  klass.include DirectedRelationSupport
394
456
  each_relation do |graph|
@@ -405,18 +467,20 @@ module Roby
405
467
  end
406
468
  end
407
469
 
408
- # Yields the root relations that are defined on this space
470
+ # Yields the root relations that are defined on this space. A relation
471
+ # is a root relation when it has no parent relation (i.e. it is the
472
+ # subset of no other relations).
409
473
  def each_root_relation
410
474
  for rel in relations
411
475
  yield(rel) unless rel.parent
412
476
  end
413
477
  end
414
478
 
415
- # Returns the set of objects that are reachable from +obj+ through any
416
- # of the relations. Note that +b+ will be included in the result if
417
- # there is an edge <tt>obj => a</tt> in one relation and another edge
418
- # <tt>a => b</tt> in another relation
419
- #
479
+ # Returns the set of objects that are reachable from +obj+ in the union
480
+ # graph of all the relations defined in this space. In other words, it
481
+ # returns the set of vertices so that it exists a path starting at
482
+ # +obj+ and ending at +v+ in the union graph of all the relations.
483
+ #
420
484
  # If +strict+ is true, +obj+ is not included in the returned set
421
485
  def children_of(obj, strict = true, relations = nil)
422
486
  set = compute_children_of([obj].to_value_set, relations || self.relations)
@@ -445,40 +509,77 @@ module Roby
445
509
 
446
510
  # Defines a relation in this relation space. This defines a relation
447
511
  # graph, and various iteration methods on the vertices. If a block is
448
- # given, it defines a set of functions which should be defined on the
449
- # vertex objects.
512
+ # given, it defines a set of functions which should additionally be
513
+ # defined on the vertex objects.
514
+ #
515
+ # The valid options are:
450
516
  #
451
- # = Options
452
517
  # child_name::
453
518
  # define a <tt>each_#{child_name}</tt> method to iterate
454
519
  # on the vertex children. Uses the relation name by default (a Child
455
520
  # relation would define a <tt>each_child</tt> method)
456
521
  # parent_name::
457
522
  # define a <tt>each_#{parent_name}</tt> method to iterate
458
- # on the vertex parents. If none is given, no method is defined
459
- # subsets:: a list of subgraphs. See RelationGraph#superset_of
523
+ # on the parent vertices. If none is given, no method is defined.
524
+ # subsets:: a list of subgraphs. See RelationGraph#superset_of [empty set by default]
460
525
  # noinfo::
461
- # if the relation embeds some additional information. If true,
526
+ # wether the relation embeds some additional information. If false,
462
527
  # the child iterator method (<tt>each_#{child_name}</tt>) will yield (child,
463
- # info) instead of only child [false]
464
- # graph:: the relation graph class
465
- # distribute:: if true, the relation can be seen by remote peers [true]
528
+ # info) instead of only child [false by default]
529
+ # graph:: the relation graph class [RelationGraph by default]
530
+ # distribute:: if true, the relation can be seen by remote peers [true by default]
466
531
  # single_child::
467
- # if the relations accepts only one child per vertex
468
- # [false]. If this option is set, defines a <tt>#{child_name}</tt>
469
- # method which returns the only child or nil
532
+ # if the relations accepts only one child per vertex. If this option
533
+ # is set, defines a <tt>#{child_name}</tt> method which returns the
534
+ # only child (or nil if there is no child at all) [false by default]
535
+ # dag::
536
+ # if true, CycleFoundError will be raised if a new vertex would
537
+ # create a cycle in this relation [true by default]
538
+ #
539
+ # For instance,
540
+ # relation :Children
541
+ #
542
+ # defines an instance of RelationGraph which is a DAG, defining the
543
+ # following methods on its vertices:
544
+ # each_children { |v, info| ... } => graph
545
+ # find_children { |v, info| ... } => object or nil
546
+ # add_children(v, info = nil) => graph
547
+ # remove_children(v) => graph
548
+ #
549
+ # and
550
+ #
551
+ # relation :Children, :child_name => :child
552
+ #
553
+ # would define
554
+ #
555
+ # each_child { |v, info| ... } => graph
556
+ # find_child { |v, info| ... } => object or nil
557
+ # add_child(v, info = nil) => graph
558
+ # remove_child(v) => graph
559
+ #
560
+ # * the DirectedRelationSupport module gets included in the vertex classes at the
561
+ # construction of the RelationSpace instance. See #apply_on.
562
+ # * the <tt>:noinfo</tt> option would then remove the 'info' parameter
563
+ # to the various blocks.
564
+ # * if <tt>:single_child</tt> is set to true, then an additional method is defined:
565
+ # child => object or nil
566
+ # * and finally if the following is used
567
+ # relation :Children, :child_name => :child, :parent_name => :parent
568
+ # then the following method is additionally defined
569
+ # each_parent { |v| ... }
570
+ #
470
571
  def relation(relation_name, options = {}, &block)
471
572
  options = validate_options options,
472
- :child_name => relation_name.to_s.underscore,
473
- :const_name => relation_name,
573
+ :child_name => relation_name.to_s.snakecase,
574
+ :const_name => relation_name,
474
575
  :parent_name => nil,
475
- :subsets => ValueSet.new,
476
- :noinfo => false,
477
- :graph => RelationGraph,
478
- :distribute => true,
479
- :dag => true,
576
+ :subsets => ValueSet.new,
577
+ :noinfo => false,
578
+ :graph => RelationGraph,
579
+ :distribute => true,
580
+ :dag => true,
480
581
  :single_child => false,
481
- :weak => false
582
+ :weak => false
482
583
 
483
584
  # Check if this relation is already defined. If it is the case, reuse it.
484
585
  # This is needed mostly by the reloading code
@@ -503,6 +604,10 @@ module Roby
503
604
  if parent_enumerator = options[:parent_name]
504
605
  mod.class_eval <<-EOD
505
606
  def each_#{parent_enumerator}(&iterator)
607
+ if !block_given?
608
+ return enum_parent_objects(@@__r_#{relation_name}__)
609
+ end
610
+
506
611
  self.each_parent_object(@@__r_#{relation_name}__, &iterator)
507
612
  end
508
613
  EOD
@@ -511,6 +616,10 @@ module Roby
511
616
  if options[:noinfo]
512
617
  mod.class_eval <<-EOD
513
618
  def each_#{options[:child_name]}
619
+ if !block_given?
620
+ return enum_child_objects(@@__r_#{relation_name}__)
621
+ end
622
+
514
623
  each_child_object(@@__r_#{relation_name}__) { |child| yield(child) }
515
624
  end
516
625
  def find_#{options[:child_name]}
@@ -522,10 +631,21 @@ module Roby
522
631
  EOD
523
632
  else
524
633
  mod.class_eval <<-EOD
525
- def each_#{options[:child_name]}
526
- each_child_object(@@__r_#{relation_name}__) do |child|
527
- yield(child, self[child, @@__r_#{relation_name}__])
528
- end
634
+ cached_enum("#{options[:child_name]}", "#{options[:child_name]}", true)
635
+ def each_#{options[:child_name]}(with_info = true)
636
+ if !block_given?
637
+ return enum_#{options[:child_name]}(with_info)
638
+ end
639
+
640
+ if with_info
641
+ each_child_object(@@__r_#{relation_name}__) do |child|
642
+ yield(child, self[child, @@__r_#{relation_name}__])
643
+ end
644
+ else
645
+ each_child_object(@@__r_#{relation_name}__) do |child|
646
+ yield(child)
647
+ end
648
+ end
529
649
  end
530
650
  def find_#{options[:child_name]}
531
651
  each_child_object(@@__r_#{relation_name}__) do |child|
@@ -562,6 +682,11 @@ module Roby
562
682
 
563
683
  graph
564
684
  end
685
+
686
+ # Remove +rel+ from the set of relations managed in this space
687
+ def remove_relation(rel)
688
+ relations.delete(rel)
689
+ end
565
690
  end
566
691
 
567
692
  # Creates a new relation space which applies on +klass+. If a block is
@@ -572,12 +697,5 @@ module Roby
572
697
  relation_space.apply_on klass
573
698
  relation_space
574
699
  end
575
-
576
- # Requires all Roby relation files (all files in roby/relations/)
577
- def self.load_all_relations
578
- Dir.glob("#{File.dirname(__FILE__)}/relations/*.rb").each do |file|
579
- require "roby/relations/#{File.basename(file, '.rb')}"
580
- end
581
- end
582
700
  end
583
701