roby 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +29 -0
- data/History.txt +4 -0
- data/License-fr.txt +519 -0
- data/License.txt +515 -0
- data/Manifest.txt +245 -0
- data/NOTES +4 -0
- data/README.txt +163 -0
- data/Rakefile +161 -0
- data/TODO.txt +146 -0
- data/app/README.txt +24 -0
- data/app/Rakefile +8 -0
- data/app/config/ROBOT.rb +5 -0
- data/app/config/app.yml +91 -0
- data/app/config/init.rb +7 -0
- data/app/config/roby.yml +3 -0
- data/app/controllers/.gitattributes +0 -0
- data/app/controllers/ROBOT.rb +2 -0
- data/app/data/.gitattributes +0 -0
- data/app/planners/ROBOT/main.rb +6 -0
- data/app/planners/main.rb +5 -0
- data/app/scripts/distributed +3 -0
- data/app/scripts/generate/bookmarks +3 -0
- data/app/scripts/replay +3 -0
- data/app/scripts/results +3 -0
- data/app/scripts/run +3 -0
- data/app/scripts/server +3 -0
- data/app/scripts/shell +3 -0
- data/app/scripts/test +3 -0
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby +210 -0
- data/bin/roby-log +168 -0
- data/bin/roby-shell +25 -0
- data/doc/images/event_generalization.png +0 -0
- data/doc/images/exception_propagation_1.png +0 -0
- data/doc/images/exception_propagation_2.png +0 -0
- data/doc/images/exception_propagation_3.png +0 -0
- data/doc/images/exception_propagation_4.png +0 -0
- data/doc/images/exception_propagation_5.png +0 -0
- data/doc/images/replay_handler_error.png +0 -0
- data/doc/images/replay_handler_error_0.png +0 -0
- data/doc/images/replay_handler_error_1.png +0 -0
- data/doc/images/roby_cycle_overview.png +0 -0
- data/doc/images/roby_replay_02.png +0 -0
- data/doc/images/roby_replay_03.png +0 -0
- data/doc/images/roby_replay_04.png +0 -0
- data/doc/images/roby_replay_event_representation.png +0 -0
- data/doc/images/roby_replay_first_state.png +0 -0
- data/doc/images/roby_replay_relations.png +0 -0
- data/doc/images/roby_replay_startup.png +0 -0
- data/doc/images/task_event_generalization.png +0 -0
- data/doc/papers.rdoc +11 -0
- data/doc/styles/allison.css +314 -0
- data/doc/styles/allison.js +316 -0
- data/doc/styles/allison.rb +276 -0
- data/doc/styles/jamis.rb +593 -0
- data/doc/tutorials/01-GettingStarted.rdoc +86 -0
- data/doc/tutorials/02-GoForward.rdoc +220 -0
- data/doc/tutorials/03-PlannedPath.rdoc +268 -0
- data/doc/tutorials/04-EventPropagation.rdoc +236 -0
- data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
- data/doc/tutorials/06-Overview.rdoc +40 -0
- data/doc/videos.rdoc +69 -0
- data/ext/droby/dump.cc +175 -0
- data/ext/droby/extconf.rb +3 -0
- data/ext/graph/algorithm.cc +746 -0
- data/ext/graph/extconf.rb +7 -0
- data/ext/graph/graph.cc +529 -0
- data/ext/graph/graph.hh +183 -0
- data/ext/graph/iterator_sequence.hh +102 -0
- data/ext/graph/undirected_dfs.hh +226 -0
- data/ext/graph/undirected_graph.hh +421 -0
- data/lib/roby.rb +41 -0
- data/lib/roby/app.rb +870 -0
- data/lib/roby/app/rake.rb +56 -0
- data/lib/roby/app/run.rb +14 -0
- data/lib/roby/app/scripts/distributed.rb +13 -0
- data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
- data/lib/roby/app/scripts/replay.rb +31 -0
- data/lib/roby/app/scripts/results.rb +15 -0
- data/lib/roby/app/scripts/run.rb +26 -0
- data/lib/roby/app/scripts/server.rb +18 -0
- data/lib/roby/app/scripts/shell.rb +88 -0
- data/lib/roby/app/scripts/test.rb +40 -0
- data/lib/roby/basic_object.rb +151 -0
- data/lib/roby/config.rb +5 -0
- data/lib/roby/control.rb +747 -0
- data/lib/roby/decision_control.rb +17 -0
- data/lib/roby/distributed.rb +32 -0
- data/lib/roby/distributed/base.rb +440 -0
- data/lib/roby/distributed/communication.rb +871 -0
- data/lib/roby/distributed/connection_space.rb +592 -0
- data/lib/roby/distributed/distributed_object.rb +206 -0
- data/lib/roby/distributed/drb.rb +62 -0
- data/lib/roby/distributed/notifications.rb +539 -0
- data/lib/roby/distributed/peer.rb +550 -0
- data/lib/roby/distributed/protocol.rb +529 -0
- data/lib/roby/distributed/proxy.rb +343 -0
- data/lib/roby/distributed/subscription.rb +311 -0
- data/lib/roby/distributed/transaction.rb +498 -0
- data/lib/roby/event.rb +897 -0
- data/lib/roby/exceptions.rb +234 -0
- data/lib/roby/executives/simple.rb +30 -0
- data/lib/roby/graph.rb +166 -0
- data/lib/roby/interface.rb +390 -0
- data/lib/roby/log.rb +3 -0
- data/lib/roby/log/chronicle.rb +303 -0
- data/lib/roby/log/console.rb +72 -0
- data/lib/roby/log/data_stream.rb +197 -0
- data/lib/roby/log/dot.rb +279 -0
- data/lib/roby/log/event_stream.rb +151 -0
- data/lib/roby/log/file.rb +340 -0
- data/lib/roby/log/gui/basic_display.ui +83 -0
- data/lib/roby/log/gui/chronicle.rb +26 -0
- data/lib/roby/log/gui/chronicle_view.rb +40 -0
- data/lib/roby/log/gui/chronicle_view.ui +70 -0
- data/lib/roby/log/gui/data_displays.rb +172 -0
- data/lib/roby/log/gui/data_displays.ui +155 -0
- data/lib/roby/log/gui/notifications.rb +26 -0
- data/lib/roby/log/gui/relations.rb +248 -0
- data/lib/roby/log/gui/relations.ui +123 -0
- data/lib/roby/log/gui/relations_view.rb +185 -0
- data/lib/roby/log/gui/relations_view.ui +149 -0
- data/lib/roby/log/gui/replay.rb +327 -0
- data/lib/roby/log/gui/replay_controls.rb +200 -0
- data/lib/roby/log/gui/replay_controls.ui +259 -0
- data/lib/roby/log/gui/runtime.rb +130 -0
- data/lib/roby/log/hooks.rb +185 -0
- data/lib/roby/log/logger.rb +202 -0
- data/lib/roby/log/notifications.rb +244 -0
- data/lib/roby/log/plan_rebuilder.rb +470 -0
- data/lib/roby/log/relations.rb +1056 -0
- data/lib/roby/log/server.rb +550 -0
- data/lib/roby/log/sqlite.rb +47 -0
- data/lib/roby/log/timings.rb +164 -0
- data/lib/roby/plan-object.rb +247 -0
- data/lib/roby/plan.rb +762 -0
- data/lib/roby/planning.rb +13 -0
- data/lib/roby/planning/loops.rb +302 -0
- data/lib/roby/planning/model.rb +906 -0
- data/lib/roby/planning/task.rb +151 -0
- data/lib/roby/propagation.rb +562 -0
- data/lib/roby/query.rb +619 -0
- data/lib/roby/relations.rb +583 -0
- data/lib/roby/relations/conflicts.rb +70 -0
- data/lib/roby/relations/ensured.rb +20 -0
- data/lib/roby/relations/error_handling.rb +23 -0
- data/lib/roby/relations/events.rb +9 -0
- data/lib/roby/relations/executed_by.rb +193 -0
- data/lib/roby/relations/hierarchy.rb +239 -0
- data/lib/roby/relations/influence.rb +10 -0
- data/lib/roby/relations/planned_by.rb +63 -0
- data/lib/roby/robot.rb +7 -0
- data/lib/roby/standard_errors.rb +218 -0
- data/lib/roby/state.rb +5 -0
- data/lib/roby/state/events.rb +221 -0
- data/lib/roby/state/information.rb +55 -0
- data/lib/roby/state/pos.rb +110 -0
- data/lib/roby/state/shapes.rb +32 -0
- data/lib/roby/state/state.rb +353 -0
- data/lib/roby/support.rb +92 -0
- data/lib/roby/task-operations.rb +182 -0
- data/lib/roby/task.rb +1618 -0
- data/lib/roby/test/common.rb +399 -0
- data/lib/roby/test/distributed.rb +214 -0
- data/lib/roby/test/tasks/empty_task.rb +9 -0
- data/lib/roby/test/tasks/goto.rb +36 -0
- data/lib/roby/test/tasks/simple_task.rb +23 -0
- data/lib/roby/test/testcase.rb +519 -0
- data/lib/roby/test/tools.rb +160 -0
- data/lib/roby/thread_task.rb +87 -0
- data/lib/roby/transactions.rb +462 -0
- data/lib/roby/transactions/proxy.rb +292 -0
- data/lib/roby/transactions/updates.rb +139 -0
- data/plugins/fault_injection/History.txt +4 -0
- data/plugins/fault_injection/README.txt +37 -0
- data/plugins/fault_injection/Rakefile +18 -0
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +52 -0
- data/plugins/fault_injection/fault_injection.rb +89 -0
- data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
- data/plugins/subsystems/README.txt +40 -0
- data/plugins/subsystems/Rakefile +18 -0
- data/plugins/subsystems/app.rb +171 -0
- data/plugins/subsystems/test/app/README +24 -0
- data/plugins/subsystems/test/app/Rakefile +8 -0
- data/plugins/subsystems/test/app/config/app.yml +71 -0
- data/plugins/subsystems/test/app/config/init.rb +9 -0
- data/plugins/subsystems/test/app/config/roby.yml +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +20 -0
- data/plugins/subsystems/test/app/scripts/distributed +3 -0
- data/plugins/subsystems/test/app/scripts/replay +3 -0
- data/plugins/subsystems/test/app/scripts/results +3 -0
- data/plugins/subsystems/test/app/scripts/run +3 -0
- data/plugins/subsystems/test/app/scripts/server +3 -0
- data/plugins/subsystems/test/app/scripts/shell +3 -0
- data/plugins/subsystems/test/app/scripts/test +3 -0
- data/plugins/subsystems/test/app/tasks/services.rb +15 -0
- data/plugins/subsystems/test/test_subsystems.rb +71 -0
- data/test/distributed/test_communication.rb +178 -0
- data/test/distributed/test_connection.rb +282 -0
- data/test/distributed/test_execution.rb +373 -0
- data/test/distributed/test_mixed_plan.rb +341 -0
- data/test/distributed/test_plan_notifications.rb +238 -0
- data/test/distributed/test_protocol.rb +516 -0
- data/test/distributed/test_query.rb +102 -0
- data/test/distributed/test_remote_plan.rb +491 -0
- data/test/distributed/test_transaction.rb +463 -0
- data/test/mockups/tasks.rb +27 -0
- data/test/planning/test_loops.rb +380 -0
- data/test/planning/test_model.rb +427 -0
- data/test/planning/test_task.rb +106 -0
- data/test/relations/test_conflicts.rb +42 -0
- data/test/relations/test_ensured.rb +38 -0
- data/test/relations/test_executed_by.rb +149 -0
- data/test/relations/test_hierarchy.rb +158 -0
- data/test/relations/test_planned_by.rb +54 -0
- data/test/suite_core.rb +24 -0
- data/test/suite_distributed.rb +9 -0
- data/test/suite_planning.rb +3 -0
- data/test/suite_relations.rb +8 -0
- data/test/test_bgl.rb +508 -0
- data/test/test_control.rb +399 -0
- data/test/test_event.rb +894 -0
- data/test/test_exceptions.rb +592 -0
- data/test/test_interface.rb +37 -0
- data/test/test_log.rb +114 -0
- data/test/test_log_server.rb +132 -0
- data/test/test_plan.rb +584 -0
- data/test/test_propagation.rb +210 -0
- data/test/test_query.rb +266 -0
- data/test/test_relations.rb +180 -0
- data/test/test_state.rb +414 -0
- data/test/test_support.rb +16 -0
- data/test/test_task.rb +938 -0
- data/test/test_testcase.rb +122 -0
- data/test/test_thread_task.rb +73 -0
- data/test/test_transactions.rb +569 -0
- data/test/test_transactions_proxy.rb +198 -0
- 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
|
+
|