roby 0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. data/.gitignore +29 -0
  2. data/History.txt +4 -0
  3. data/License-fr.txt +519 -0
  4. data/License.txt +515 -0
  5. data/Manifest.txt +245 -0
  6. data/NOTES +4 -0
  7. data/README.txt +163 -0
  8. data/Rakefile +161 -0
  9. data/TODO.txt +146 -0
  10. data/app/README.txt +24 -0
  11. data/app/Rakefile +8 -0
  12. data/app/config/ROBOT.rb +5 -0
  13. data/app/config/app.yml +91 -0
  14. data/app/config/init.rb +7 -0
  15. data/app/config/roby.yml +3 -0
  16. data/app/controllers/.gitattributes +0 -0
  17. data/app/controllers/ROBOT.rb +2 -0
  18. data/app/data/.gitattributes +0 -0
  19. data/app/planners/ROBOT/main.rb +6 -0
  20. data/app/planners/main.rb +5 -0
  21. data/app/scripts/distributed +3 -0
  22. data/app/scripts/generate/bookmarks +3 -0
  23. data/app/scripts/replay +3 -0
  24. data/app/scripts/results +3 -0
  25. data/app/scripts/run +3 -0
  26. data/app/scripts/server +3 -0
  27. data/app/scripts/shell +3 -0
  28. data/app/scripts/test +3 -0
  29. data/app/tasks/.gitattributes +0 -0
  30. data/app/tasks/ROBOT/.gitattributes +0 -0
  31. data/bin/roby +210 -0
  32. data/bin/roby-log +168 -0
  33. data/bin/roby-shell +25 -0
  34. data/doc/images/event_generalization.png +0 -0
  35. data/doc/images/exception_propagation_1.png +0 -0
  36. data/doc/images/exception_propagation_2.png +0 -0
  37. data/doc/images/exception_propagation_3.png +0 -0
  38. data/doc/images/exception_propagation_4.png +0 -0
  39. data/doc/images/exception_propagation_5.png +0 -0
  40. data/doc/images/replay_handler_error.png +0 -0
  41. data/doc/images/replay_handler_error_0.png +0 -0
  42. data/doc/images/replay_handler_error_1.png +0 -0
  43. data/doc/images/roby_cycle_overview.png +0 -0
  44. data/doc/images/roby_replay_02.png +0 -0
  45. data/doc/images/roby_replay_03.png +0 -0
  46. data/doc/images/roby_replay_04.png +0 -0
  47. data/doc/images/roby_replay_event_representation.png +0 -0
  48. data/doc/images/roby_replay_first_state.png +0 -0
  49. data/doc/images/roby_replay_relations.png +0 -0
  50. data/doc/images/roby_replay_startup.png +0 -0
  51. data/doc/images/task_event_generalization.png +0 -0
  52. data/doc/papers.rdoc +11 -0
  53. data/doc/styles/allison.css +314 -0
  54. data/doc/styles/allison.js +316 -0
  55. data/doc/styles/allison.rb +276 -0
  56. data/doc/styles/jamis.rb +593 -0
  57. data/doc/tutorials/01-GettingStarted.rdoc +86 -0
  58. data/doc/tutorials/02-GoForward.rdoc +220 -0
  59. data/doc/tutorials/03-PlannedPath.rdoc +268 -0
  60. data/doc/tutorials/04-EventPropagation.rdoc +236 -0
  61. data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
  62. data/doc/tutorials/06-Overview.rdoc +40 -0
  63. data/doc/videos.rdoc +69 -0
  64. data/ext/droby/dump.cc +175 -0
  65. data/ext/droby/extconf.rb +3 -0
  66. data/ext/graph/algorithm.cc +746 -0
  67. data/ext/graph/extconf.rb +7 -0
  68. data/ext/graph/graph.cc +529 -0
  69. data/ext/graph/graph.hh +183 -0
  70. data/ext/graph/iterator_sequence.hh +102 -0
  71. data/ext/graph/undirected_dfs.hh +226 -0
  72. data/ext/graph/undirected_graph.hh +421 -0
  73. data/lib/roby.rb +41 -0
  74. data/lib/roby/app.rb +870 -0
  75. data/lib/roby/app/rake.rb +56 -0
  76. data/lib/roby/app/run.rb +14 -0
  77. data/lib/roby/app/scripts/distributed.rb +13 -0
  78. data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
  79. data/lib/roby/app/scripts/replay.rb +31 -0
  80. data/lib/roby/app/scripts/results.rb +15 -0
  81. data/lib/roby/app/scripts/run.rb +26 -0
  82. data/lib/roby/app/scripts/server.rb +18 -0
  83. data/lib/roby/app/scripts/shell.rb +88 -0
  84. data/lib/roby/app/scripts/test.rb +40 -0
  85. data/lib/roby/basic_object.rb +151 -0
  86. data/lib/roby/config.rb +5 -0
  87. data/lib/roby/control.rb +747 -0
  88. data/lib/roby/decision_control.rb +17 -0
  89. data/lib/roby/distributed.rb +32 -0
  90. data/lib/roby/distributed/base.rb +440 -0
  91. data/lib/roby/distributed/communication.rb +871 -0
  92. data/lib/roby/distributed/connection_space.rb +592 -0
  93. data/lib/roby/distributed/distributed_object.rb +206 -0
  94. data/lib/roby/distributed/drb.rb +62 -0
  95. data/lib/roby/distributed/notifications.rb +539 -0
  96. data/lib/roby/distributed/peer.rb +550 -0
  97. data/lib/roby/distributed/protocol.rb +529 -0
  98. data/lib/roby/distributed/proxy.rb +343 -0
  99. data/lib/roby/distributed/subscription.rb +311 -0
  100. data/lib/roby/distributed/transaction.rb +498 -0
  101. data/lib/roby/event.rb +897 -0
  102. data/lib/roby/exceptions.rb +234 -0
  103. data/lib/roby/executives/simple.rb +30 -0
  104. data/lib/roby/graph.rb +166 -0
  105. data/lib/roby/interface.rb +390 -0
  106. data/lib/roby/log.rb +3 -0
  107. data/lib/roby/log/chronicle.rb +303 -0
  108. data/lib/roby/log/console.rb +72 -0
  109. data/lib/roby/log/data_stream.rb +197 -0
  110. data/lib/roby/log/dot.rb +279 -0
  111. data/lib/roby/log/event_stream.rb +151 -0
  112. data/lib/roby/log/file.rb +340 -0
  113. data/lib/roby/log/gui/basic_display.ui +83 -0
  114. data/lib/roby/log/gui/chronicle.rb +26 -0
  115. data/lib/roby/log/gui/chronicle_view.rb +40 -0
  116. data/lib/roby/log/gui/chronicle_view.ui +70 -0
  117. data/lib/roby/log/gui/data_displays.rb +172 -0
  118. data/lib/roby/log/gui/data_displays.ui +155 -0
  119. data/lib/roby/log/gui/notifications.rb +26 -0
  120. data/lib/roby/log/gui/relations.rb +248 -0
  121. data/lib/roby/log/gui/relations.ui +123 -0
  122. data/lib/roby/log/gui/relations_view.rb +185 -0
  123. data/lib/roby/log/gui/relations_view.ui +149 -0
  124. data/lib/roby/log/gui/replay.rb +327 -0
  125. data/lib/roby/log/gui/replay_controls.rb +200 -0
  126. data/lib/roby/log/gui/replay_controls.ui +259 -0
  127. data/lib/roby/log/gui/runtime.rb +130 -0
  128. data/lib/roby/log/hooks.rb +185 -0
  129. data/lib/roby/log/logger.rb +202 -0
  130. data/lib/roby/log/notifications.rb +244 -0
  131. data/lib/roby/log/plan_rebuilder.rb +470 -0
  132. data/lib/roby/log/relations.rb +1056 -0
  133. data/lib/roby/log/server.rb +550 -0
  134. data/lib/roby/log/sqlite.rb +47 -0
  135. data/lib/roby/log/timings.rb +164 -0
  136. data/lib/roby/plan-object.rb +247 -0
  137. data/lib/roby/plan.rb +762 -0
  138. data/lib/roby/planning.rb +13 -0
  139. data/lib/roby/planning/loops.rb +302 -0
  140. data/lib/roby/planning/model.rb +906 -0
  141. data/lib/roby/planning/task.rb +151 -0
  142. data/lib/roby/propagation.rb +562 -0
  143. data/lib/roby/query.rb +619 -0
  144. data/lib/roby/relations.rb +583 -0
  145. data/lib/roby/relations/conflicts.rb +70 -0
  146. data/lib/roby/relations/ensured.rb +20 -0
  147. data/lib/roby/relations/error_handling.rb +23 -0
  148. data/lib/roby/relations/events.rb +9 -0
  149. data/lib/roby/relations/executed_by.rb +193 -0
  150. data/lib/roby/relations/hierarchy.rb +239 -0
  151. data/lib/roby/relations/influence.rb +10 -0
  152. data/lib/roby/relations/planned_by.rb +63 -0
  153. data/lib/roby/robot.rb +7 -0
  154. data/lib/roby/standard_errors.rb +218 -0
  155. data/lib/roby/state.rb +5 -0
  156. data/lib/roby/state/events.rb +221 -0
  157. data/lib/roby/state/information.rb +55 -0
  158. data/lib/roby/state/pos.rb +110 -0
  159. data/lib/roby/state/shapes.rb +32 -0
  160. data/lib/roby/state/state.rb +353 -0
  161. data/lib/roby/support.rb +92 -0
  162. data/lib/roby/task-operations.rb +182 -0
  163. data/lib/roby/task.rb +1618 -0
  164. data/lib/roby/test/common.rb +399 -0
  165. data/lib/roby/test/distributed.rb +214 -0
  166. data/lib/roby/test/tasks/empty_task.rb +9 -0
  167. data/lib/roby/test/tasks/goto.rb +36 -0
  168. data/lib/roby/test/tasks/simple_task.rb +23 -0
  169. data/lib/roby/test/testcase.rb +519 -0
  170. data/lib/roby/test/tools.rb +160 -0
  171. data/lib/roby/thread_task.rb +87 -0
  172. data/lib/roby/transactions.rb +462 -0
  173. data/lib/roby/transactions/proxy.rb +292 -0
  174. data/lib/roby/transactions/updates.rb +139 -0
  175. data/plugins/fault_injection/History.txt +4 -0
  176. data/plugins/fault_injection/README.txt +37 -0
  177. data/plugins/fault_injection/Rakefile +18 -0
  178. data/plugins/fault_injection/TODO.txt +0 -0
  179. data/plugins/fault_injection/app.rb +52 -0
  180. data/plugins/fault_injection/fault_injection.rb +89 -0
  181. data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
  182. data/plugins/subsystems/README.txt +40 -0
  183. data/plugins/subsystems/Rakefile +18 -0
  184. data/plugins/subsystems/app.rb +171 -0
  185. data/plugins/subsystems/test/app/README +24 -0
  186. data/plugins/subsystems/test/app/Rakefile +8 -0
  187. data/plugins/subsystems/test/app/config/app.yml +71 -0
  188. data/plugins/subsystems/test/app/config/init.rb +9 -0
  189. data/plugins/subsystems/test/app/config/roby.yml +3 -0
  190. data/plugins/subsystems/test/app/planners/main.rb +20 -0
  191. data/plugins/subsystems/test/app/scripts/distributed +3 -0
  192. data/plugins/subsystems/test/app/scripts/replay +3 -0
  193. data/plugins/subsystems/test/app/scripts/results +3 -0
  194. data/plugins/subsystems/test/app/scripts/run +3 -0
  195. data/plugins/subsystems/test/app/scripts/server +3 -0
  196. data/plugins/subsystems/test/app/scripts/shell +3 -0
  197. data/plugins/subsystems/test/app/scripts/test +3 -0
  198. data/plugins/subsystems/test/app/tasks/services.rb +15 -0
  199. data/plugins/subsystems/test/test_subsystems.rb +71 -0
  200. data/test/distributed/test_communication.rb +178 -0
  201. data/test/distributed/test_connection.rb +282 -0
  202. data/test/distributed/test_execution.rb +373 -0
  203. data/test/distributed/test_mixed_plan.rb +341 -0
  204. data/test/distributed/test_plan_notifications.rb +238 -0
  205. data/test/distributed/test_protocol.rb +516 -0
  206. data/test/distributed/test_query.rb +102 -0
  207. data/test/distributed/test_remote_plan.rb +491 -0
  208. data/test/distributed/test_transaction.rb +463 -0
  209. data/test/mockups/tasks.rb +27 -0
  210. data/test/planning/test_loops.rb +380 -0
  211. data/test/planning/test_model.rb +427 -0
  212. data/test/planning/test_task.rb +106 -0
  213. data/test/relations/test_conflicts.rb +42 -0
  214. data/test/relations/test_ensured.rb +38 -0
  215. data/test/relations/test_executed_by.rb +149 -0
  216. data/test/relations/test_hierarchy.rb +158 -0
  217. data/test/relations/test_planned_by.rb +54 -0
  218. data/test/suite_core.rb +24 -0
  219. data/test/suite_distributed.rb +9 -0
  220. data/test/suite_planning.rb +3 -0
  221. data/test/suite_relations.rb +8 -0
  222. data/test/test_bgl.rb +508 -0
  223. data/test/test_control.rb +399 -0
  224. data/test/test_event.rb +894 -0
  225. data/test/test_exceptions.rb +592 -0
  226. data/test/test_interface.rb +37 -0
  227. data/test/test_log.rb +114 -0
  228. data/test/test_log_server.rb +132 -0
  229. data/test/test_plan.rb +584 -0
  230. data/test/test_propagation.rb +210 -0
  231. data/test/test_query.rb +266 -0
  232. data/test/test_relations.rb +180 -0
  233. data/test/test_state.rb +414 -0
  234. data/test/test_support.rb +16 -0
  235. data/test/test_task.rb +938 -0
  236. data/test/test_testcase.rb +122 -0
  237. data/test/test_thread_task.rb +73 -0
  238. data/test/test_transactions.rb +569 -0
  239. data/test/test_transactions_proxy.rb +198 -0
  240. metadata +570 -0
@@ -0,0 +1,47 @@
1
+ require 'roby/log'
2
+ require 'roby/distributed'
3
+ require 'sqlite3'
4
+ require 'stringio'
5
+
6
+ module Roby::Log
7
+ class SQLiteLogger
8
+ attr_reader :db, :insert
9
+ def initialize(filename)
10
+ @db = SQLite3::Database.new(filename)
11
+ db.execute("DROP TABLE IF EXISTS events")
12
+ db.execute("CREATE TABLE events (
13
+ method TEXT,
14
+ sec INTEGER,
15
+ usec INTEGER,
16
+ args BLOB)")
17
+ @insert = db.prepare("insert into events values (?, ?, ?, ?)")
18
+ end
19
+ def splat?; false end
20
+
21
+ Roby::Log.each_hook do |klass, m|
22
+ define_method(m) do |args|
23
+ begin
24
+ time = args[0]
25
+ args = SQLite3::Blob.new Roby::Distributed.dump(args[1..-1])
26
+ insert.execute(m.to_s, time.tv_sec, time.tv_usec, args)
27
+ rescue
28
+ STDERR.puts "failed to dump #{m}#{args}: #{$!.full_message}"
29
+ end
30
+ end
31
+ end
32
+
33
+ def self.replay(filename)
34
+ db = SQLite3::Database.new(filename)
35
+ method_name = nil
36
+ db.execute("select * from events") do |method_name, sec, usec, args|
37
+ time = Time.at(Integer(sec), Integer(usec))
38
+ args = Marshal.load(StringIO.new(args))
39
+ args.unshift time
40
+ yield(method_name.to_sym, args)
41
+ end
42
+ rescue
43
+ STDERR.puts "ignoring call to #{method_name}: #{$!.full_message}"
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,164 @@
1
+ module Roby
2
+ module Log
3
+ class Timings
4
+ REF_TIMING = :start
5
+ ALL_TIMINGS = [ :real_start, :events,
6
+ :structure_check, :exception_propagation,
7
+ :exceptions_fatal, :garbage_collect, :application_errors,
8
+ :expected_ruby_gc, :ruby_gc, :droby, :expected_sleep, :sleep, :end ]
9
+
10
+ NUMERIC_FIELDS = [:cycle_index, :live_objects, :object_allocation, :log_queue_size, :ruby_gc_duration,
11
+ :plan_task_count, :plan_event_count]
12
+ DELTAS = [:cpu_time]
13
+ ALL_NUMERIC_FIELDS = NUMERIC_FIELDS + DELTAS
14
+
15
+ ALL_FIELDS = ALL_TIMINGS + ALL_NUMERIC_FIELDS + [:event_count, :pos]
16
+
17
+ attr_reader :logfile
18
+ attr_reader :ignored_timings
19
+ def initialize(logfile)
20
+ @logfile = logfile
21
+ @ignored_timings = Set.new
22
+ rewind
23
+ end
24
+ def rewind; logfile.rewind end
25
+
26
+ def each_cycle(cumulative = false)
27
+ last_deltas = Hash.new
28
+ for data in logfile.index_data[1..-1]
29
+ result = []
30
+ timings = data
31
+ ref = Time.at(*timings.delete(REF_TIMING))
32
+ result << ref
33
+
34
+ unknown_timings = (timings.keys.to_set - ALL_FIELDS.to_set - ignored_timings)
35
+ if !unknown_timings.empty?
36
+ STDERR.puts "ignoring the following timings: #{unknown_timings.to_a.join(", ")}"
37
+ @ignored_timings |= unknown_timings
38
+ end
39
+ timings = ALL_TIMINGS.map do |name|
40
+ timings[name]
41
+ end
42
+
43
+ if cumulative
44
+ timings.inject(0) do |last, time|
45
+ time ||= last
46
+ result << time
47
+ time
48
+ end
49
+ else
50
+ timings.inject(0) do |last, time|
51
+ time ||= last
52
+ result << time - last
53
+ time
54
+ end
55
+ end
56
+
57
+ numeric = data.values_at(*NUMERIC_FIELDS)
58
+ deltas = DELTAS.map do |name|
59
+ value = if old_value = last_deltas[name]
60
+ data[name] - old_value
61
+ else
62
+ 0
63
+ end
64
+ last_deltas[name] = data[name]
65
+ value
66
+ end
67
+
68
+ yield(numeric + deltas, result)
69
+ end
70
+
71
+ rescue ArgumentError => e
72
+ if e.message =~ /marshal data too short/
73
+ STDERR.puts "File truncated"
74
+ else raise
75
+ end
76
+ end
77
+
78
+ def timeval_to_s(t)
79
+ '%02i:%02i:%02i.%03i' % [t.tv_sec / 3600, t.tv_sec % 3600 / 60, t.tv_sec % 60, t.tv_usec / 1000]
80
+ end
81
+
82
+ def stats
83
+ last_start = nil
84
+ mean, max, stdev = nil
85
+ count = 0
86
+
87
+ mean_cycle = 0
88
+ stdev_cycle = 0
89
+ max_cycle = nil
90
+
91
+ # Compute mean value
92
+ each_cycle(false) do |numeric, timings|
93
+ if !mean
94
+ mean = Array.new(numeric.size + timings.size, 0.0)
95
+ max = Array.new
96
+ end
97
+
98
+ # Compute mean value
99
+ start = timings.shift + timings.first
100
+ if last_start
101
+ cycle_length = start - last_start
102
+ if !max_cycle || max_cycle < cycle_length
103
+ max_cycle = cycle_length
104
+ end
105
+ mean_cycle += cycle_length
106
+ end
107
+ last_start = start
108
+
109
+ (numeric + timings).each_with_index do |v, i|
110
+ mean[i] += v if v
111
+ max[i] = v if v && (!max[i] || max[i] < v)
112
+ end
113
+ count += 1
114
+ end
115
+ mean.map! { |v| v / count }
116
+ mean_cycle /= count
117
+
118
+ last_start = nil
119
+ rewind
120
+ stdev = Array.new(mean.size, 0.0)
121
+ each_cycle(false) do |numeric, timings|
122
+ start = timings.shift + timings.first
123
+ if last_start
124
+ stdev_cycle += (start - last_start - mean_cycle)**2
125
+ end
126
+ last_start = start
127
+
128
+ (numeric + timings).each_with_index { |v, i| stdev[i] += (v - mean[i]) ** 2 if v }
129
+ end
130
+ stdev.map! { |v| Math.sqrt(v / count) if v }
131
+
132
+ format = "%-28s %-10.2f %-10.2f %-10.2f"
133
+
134
+ puts "\n" + "Per-cycle statistics".center(50)
135
+ puts "%-28s %-10s %-10s %-10s" % ['', 'mean', 'stddev', 'max']
136
+ puts format % ["cycle", mean_cycle * 1000, Math.sqrt(stdev_cycle / count) * 1000, max_cycle * 1000]
137
+ ALL_NUMERIC_FIELDS.each_with_index do |name, i|
138
+ puts format % [name, mean[i], stdev[i], (max[i] || 0)] unless name == :cycle_index
139
+ end
140
+
141
+ puts "\n" + "Broken down cycle timings".center(50)
142
+ puts "%-28s %-10s %-10s %-10s" % ['', 'mean', 'stddev', 'max']
143
+ (ALL_TIMINGS).each_with_index do |name, i|
144
+ i += ALL_NUMERIC_FIELDS.size
145
+ puts format % [name, mean[i] * 1000, stdev[i] * 1000, max[i] * 1000]
146
+ end
147
+ end
148
+
149
+ def display(cumulative)
150
+ header = ([REF_TIMING] + ALL_TIMINGS + ALL_NUMERIC_FIELDS).enum_for(:each_with_index).
151
+ map { |n, i| "#{i + 1}_#{n}" }.
152
+ join("\t")
153
+
154
+ puts header
155
+ each_cycle(cumulative) do |numeric, results|
156
+ print "#{timeval_to_s(results.shift)} "
157
+ print results.join(" ")
158
+ print " "
159
+ puts numeric.join(" ")
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,247 @@
1
+ require 'roby/relations'
2
+ require 'roby/distributed/base'
3
+ require 'roby/basic_object'
4
+
5
+ module Roby
6
+ # Base class for all objects which are included in a plan.
7
+ class PlanObject < BasicObject
8
+ include DirectedRelationSupport
9
+
10
+ # The plan this object belongs to
11
+ attr_reader :plan
12
+
13
+ # The place where this object has been removed from its plan. Once an
14
+ # object is removed from its plan, it cannot be added back again.
15
+ attr_accessor :removed_at
16
+
17
+ # True if this object has been included in a plan, but has been removed
18
+ # from it since
19
+ def finalized?; !!removed_at end
20
+
21
+ # Sets the new plan. Since it is forbidden to re-use a plan object that
22
+ # has been removed from a plan, it raises ArgumentError if it is the
23
+ # case
24
+ def plan=(new_plan)
25
+ if removed_at
26
+ raise ArgumentError, "#{self} has been removed from plan, cannot add it back\n" +
27
+ "Removed at\n #{removed_at.join("\n ")}"
28
+ end
29
+ @plan = new_plan
30
+ end
31
+
32
+ # A three-state flag with the following values:
33
+ # nil:: the object is executable if its plan is
34
+ # true:: the object is executable
35
+ # false:: the object is not executable
36
+ attr_writer :executable
37
+
38
+ # If this object is executable
39
+ def executable?
40
+ @executable || (@executable.nil? && plan && plan.executable?)
41
+ end
42
+
43
+ # True if we are explicitely subscribed to this object
44
+ def subscribed?
45
+ if root_object?
46
+ (plan && plan.subscribed?) ||
47
+ (!self_owned? && owners.any? { |peer| peer.subscribed_plan? }) ||
48
+ super
49
+ else
50
+ root_object.subscribed?
51
+ end
52
+ end
53
+
54
+ # True if we should send updates about this object to +peer+
55
+ def update_on?(peer); (plan && plan.update_on?(peer)) || super end
56
+ # True if we receive updates for this object from +peer+
57
+ def updated_by?(peer); (plan && plan.updated_by?(peer)) || super end
58
+ # True if this object is useful for one of our peers
59
+ def remotely_useful?; (plan && plan.remotely_useful?) || super end
60
+
61
+ # Checks that we do not link two objects from two different plans and
62
+ # updates the +plan+ attribute accordingly
63
+ #
64
+ # It raises RuntimeError if both objects are already included in a
65
+ # plan, but their plan mismatches.
66
+ def synchronize_plan(other) # :nodoc:
67
+ if plan == other.plan
68
+ elsif other.plan && plan
69
+ raise RuntimeError, "cannot add a relation between two objects from different plans. #{self} is from #{plan} and #{other} is from #{other.plan}"
70
+ elsif plan
71
+ self.plan.discover(other)
72
+ elsif other.plan
73
+ other.plan.discover(self)
74
+ end
75
+ end
76
+ protected :synchronize_plan
77
+
78
+ # Called when all links to +peer+ should be removed.
79
+ def forget_peer(peer)
80
+ if !root_object?
81
+ raise ArgumentError, "#{self} is not root"
82
+ end
83
+
84
+ each_plan_child do |child|
85
+ child.forget_peer(peer)
86
+ end
87
+ super
88
+ end
89
+
90
+ # Synchronizes the plan of this object from the one of its peer
91
+ def add_child_object(child, type, info = nil) # :nodoc:
92
+ if child.plan != plan
93
+ root_object.synchronize_plan(child.root_object)
94
+ end
95
+
96
+ super
97
+ end
98
+
99
+ # Return the root plan object for this object.
100
+ def root_object; self end
101
+ # True if this object is a root object in the plan.
102
+ def root_object?; root_object == self end
103
+ # Iterates on all the children of this root object
104
+ def each_plan_child; self end
105
+
106
+ # This class method sets up the enclosing class as a child object,
107
+ # with the root object being returned by the given attribute.
108
+ # Task event generators are for instance defined by
109
+ #
110
+ # class TaskEventGenerator < EventGenerator
111
+ # # The task this generator belongs to
112
+ # attr_reader :task
113
+ #
114
+ # child_plan_object :task
115
+ # end
116
+ def self.child_plan_object(attribute)
117
+ class_eval <<-EOD
118
+ def root_object; #{attribute} end
119
+ def root_object?; false end
120
+ def owners; #{attribute}.owners end
121
+ def distribute?; #{attribute}.distribute? end
122
+ def plan; #{attribute}.plan end
123
+ def executable?; #{attribute}.executable? end
124
+
125
+ def subscribed?; #{attribute}.subscribed? end
126
+ def updated?; #{attribute}.updated? end
127
+ def updated_by?(peer); #{attribute}.updated_by?(peer) end
128
+ def update_on?(peer); #{attribute}.update_on?(peer) end
129
+ def updated_peers; #{attribute}.updated_peers end
130
+ def remotely_useful?; #{attribute}.remotely_useful? end
131
+
132
+ def forget_peer(peer)
133
+ remove_sibling_for(peer)
134
+ end
135
+ def sibling_of(remote_object, peer)
136
+ if !distribute?
137
+ raise ArgumentError, "#{self} is local only"
138
+ end
139
+
140
+ add_sibling_for(peer, remote_object)
141
+ end
142
+
143
+ private :plan=
144
+ private :executable=
145
+ EOD
146
+ end
147
+
148
+ # Transfers a set of relations from this plan object to +object+.
149
+ # +changes+ is formatted as a sequence of <tt>relation, parents,
150
+ # children</tt> slices, where +parents+ and +children+ are sets of
151
+ # objects.
152
+ #
153
+ # For each of these slices, the method removes the
154
+ # <tt>parent->self</tt> and <tt>self->child</tt> edges in the given
155
+ # relation, and then adds the corresponding <tt>parent->object</tt> and
156
+ # <tt>object->child</tt> edges.
157
+ def apply_relation_changes(object, changes)
158
+ # The operation is done in two parts to avoid problems with
159
+ # creating cycles in the graph: first we remove the old edges, then
160
+ # we add the new ones.
161
+ changes.each_slice(3) do |rel, parents, children|
162
+ parents.each_slice(2) do |parent, info|
163
+ parent.remove_child_object(self, rel)
164
+ end
165
+ children.each_slice(2) do |child, info|
166
+ remove_child_object(child, rel)
167
+ end
168
+ end
169
+
170
+ changes.each_slice(3) do |rel, parents, children|
171
+ parents.each_slice(2) do |parent, info|
172
+ parent.add_child_object(object, rel, info)
173
+ end
174
+ children.each_slice(2) do |child, info|
175
+ object.add_child_object(child, rel, info)
176
+ end
177
+ end
178
+ end
179
+
180
+ # Replaces, in the plan, the subplan generated by this plan object by
181
+ # the one generated by +object+. In practice, it means that we transfer
182
+ # all parent edges whose target is +self+ from the receiver to
183
+ # +object+. It calls the various add/remove hooks defined in
184
+ # DirectedRelationSupport.
185
+ def replace_subplan_by(object)
186
+ changes = []
187
+ each_relation do |rel|
188
+ parents = []
189
+ each_parent_object(rel) do |parent|
190
+ unless parent.root_object == root_object
191
+ parents << parent << parent[self, rel]
192
+ end
193
+ end
194
+ changes << rel << parents << []
195
+ end
196
+
197
+ apply_relation_changes(object, changes)
198
+ end
199
+
200
+ # Replaces +self+ by +object+ in all graphs +self+ is part of. Unlike
201
+ # BGL::Vertex#replace_by, this calls the various add/remove hooks
202
+ # defined in DirectedRelationSupport
203
+ def replace_by(object)
204
+ changes = []
205
+ each_relation do |rel|
206
+ parents = []
207
+ each_parent_object(rel) do |parent|
208
+ unless parent.root_object == root_object
209
+ parents << parent << parent[self, rel]
210
+ end
211
+ end
212
+ children = []
213
+ each_child_object(rel) do |child|
214
+ unless child.root_object == root_object
215
+ children << child << self[child, rel]
216
+ end
217
+ end
218
+ changes << rel << parents << children
219
+ end
220
+
221
+ apply_relation_changes(object, changes)
222
+ end
223
+
224
+ # True if this object can be modified by the local plan manager
225
+ def read_write?
226
+ if (owners.include?(Distributed) || Distributed.updating?(root_object) || !plan)
227
+ true
228
+ elsif plan.owners.include?(Distributed)
229
+ for peer in owners
230
+ return false unless plan.owners.include?(peer)
231
+ end
232
+ true
233
+ end
234
+ end
235
+
236
+ # Checks if we have the right to remove a relation. Raises
237
+ # OwnershipError if it is not the case
238
+ def removing_child_object(child, type)
239
+ super if defined? super
240
+
241
+ unless read_write? || child.read_write?
242
+ raise OwnershipError, "cannot remove a relation between two objects we don't own"
243
+ end
244
+ end
245
+ end
246
+ end
247
+