roby 0.7

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