roby 0.7.3 → 0.8.0

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