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.
- 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
|
+
|