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,55 @@
|
|
|
1
|
+
module Distributions
|
|
2
|
+
class Gaussian
|
|
3
|
+
attr_reader :mean, :dev
|
|
4
|
+
def initialize(mean, dev)
|
|
5
|
+
@mean, @dev = mean, dev
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Roby
|
|
11
|
+
# A TimeDistribution object describes the evolution of a
|
|
12
|
+
# value object w.r.t. time. Time can be described by absolute
|
|
13
|
+
# values using Time objects or w.r.t. the plan by using
|
|
14
|
+
# EventGenerator objects
|
|
15
|
+
class TimeDistribution
|
|
16
|
+
attr_reader :info
|
|
17
|
+
def initialize(info)
|
|
18
|
+
@info = info
|
|
19
|
+
@timepoints = Array.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Set the predicted value of the distribution at +event+
|
|
23
|
+
def set_value(event, value)
|
|
24
|
+
@timepoints << [event, :set_value, value]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Set a decay function which is valid from +event+
|
|
28
|
+
def set_decay(event, decay)
|
|
29
|
+
@timepoints << [event, :set_decay, decay]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Set the knowledge value for the distribution at +event+
|
|
33
|
+
# For now, the only valid values are 0 (nothing known)
|
|
34
|
+
# and 1 (perfectly known)
|
|
35
|
+
def set_knowledge(event, value = 1.0)
|
|
36
|
+
@timepoints << [event, :set_knowledge, value]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Task < PlanObject
|
|
41
|
+
inherited_enumerable(:needed_information) { Array.new }
|
|
42
|
+
inherited_enumerable(:improved_information) { Array.new }
|
|
43
|
+
|
|
44
|
+
# This task is influenced by the information contained in +info+
|
|
45
|
+
def self.needs(info); needed_information << info end
|
|
46
|
+
def self.needs?(info); enum_for(:each_needed_information).any? { |i| info === i } end
|
|
47
|
+
def needs?(info); self.model.needs?(info) end
|
|
48
|
+
|
|
49
|
+
# This task will improve the information contained in +info+
|
|
50
|
+
def self.improves(info); improved_information << info end
|
|
51
|
+
def self.improves?(info); enum_for(:each_improved_information).any? { |i| info === i } end
|
|
52
|
+
def improves?(info); self.model.improves?(info) end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# A namespace in which are defined position-related classes.
|
|
2
|
+
module Roby::Pos
|
|
3
|
+
# A (x, y, z) vector
|
|
4
|
+
class Vector3D
|
|
5
|
+
# The vector coordinates
|
|
6
|
+
attr_accessor :x, :y, :z
|
|
7
|
+
# Initializes a 3D vector
|
|
8
|
+
def initialize(x = 0, y = 0, z = 0)
|
|
9
|
+
@x, @y, @z = x, y, z
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_s # :nodoc:
|
|
13
|
+
"Vector3D(x=%f,y=%f,z=%f)" % [x,y,z]
|
|
14
|
+
end
|
|
15
|
+
def pretty_print(pp)
|
|
16
|
+
pp.text to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The length of the vector
|
|
20
|
+
def length; distance(0, 0, 0) end
|
|
21
|
+
# Returns self + v
|
|
22
|
+
def +(v); Vector3D.new(x + v.x, y + v.y, z + v.z) end
|
|
23
|
+
# Returns self - v
|
|
24
|
+
def -(v); Vector3D.new(x - v.x, y - v.y, z - v.z) end
|
|
25
|
+
# Returns the product of this vector with the scalar +a+
|
|
26
|
+
def *(a); Vector3D.new(x * a, y * a, z * a) end
|
|
27
|
+
# Returns the division of this vector with the scalar +a+
|
|
28
|
+
def /(a); Vector3D.new(x / a, y / a, z / a) end
|
|
29
|
+
# Returns the opposite of this vector
|
|
30
|
+
def -@; Vector3D.new(-x, -y, -z) end
|
|
31
|
+
|
|
32
|
+
# Returns the [x, y, z] array
|
|
33
|
+
def xyz; [x, y, z] end
|
|
34
|
+
# True if +v+ is the same vector than +self+
|
|
35
|
+
def ==(v)
|
|
36
|
+
v.kind_of?(Vector3D) &&
|
|
37
|
+
v.x == x && v.y == y && v.z == z
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# True if this vector is of zero length. If +tolerance+ is non-zero,
|
|
41
|
+
# returns true if length <= tolerance.
|
|
42
|
+
def null?(tolerance = 0)
|
|
43
|
+
length <= tolerance
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# call-seq:
|
|
47
|
+
# v.distance2d w
|
|
48
|
+
# v.distance2d x, y
|
|
49
|
+
#
|
|
50
|
+
# Returns the euclidian distance in the (X,Y) plane, between this vector
|
|
51
|
+
# and the given coordinates. In the first form, +w+ can be a vector in which
|
|
52
|
+
# case the distance is computed between (self.x, self.y) and (w.x, w.y).
|
|
53
|
+
# If +w+ is a scalar, it is taken as the X coordinate and y = 0.
|
|
54
|
+
#
|
|
55
|
+
# In the second form, both +x+ and +y+ must be scalars.
|
|
56
|
+
def distance2d(x = 0, y = nil)
|
|
57
|
+
if !y && x.respond_to?(:x)
|
|
58
|
+
x, y = x.x, x.y
|
|
59
|
+
else
|
|
60
|
+
y ||= 0
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Math.sqrt( (x - self.x) ** 2 + (y - self.y) ** 2 )
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# call-seq:
|
|
67
|
+
# v.distance2d w
|
|
68
|
+
# v.distance2d x, y
|
|
69
|
+
# v.distance2d x, y, z
|
|
70
|
+
#
|
|
71
|
+
# Returns the euclidian distance in the (X,Y,Z) space, between this vector
|
|
72
|
+
# and the given coordinates. In the first form, +w+ can be a vector in which
|
|
73
|
+
# case the distance is computed between (self.x, self.y, self.z) and (w.x, w.y, w.z).
|
|
74
|
+
# If +w+ is a scalar, it is taken as the X coordinate and y = z = 0.
|
|
75
|
+
#
|
|
76
|
+
# In the second form, both +x+ and +y+ must be scalars and z == 0.
|
|
77
|
+
def distance(x = 0, y = nil, z = nil)
|
|
78
|
+
if !y && x.respond_to?(:x)
|
|
79
|
+
x, y, z = x.x, x.y, x.z
|
|
80
|
+
else
|
|
81
|
+
y ||= 0
|
|
82
|
+
z ||= 0
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
Math.sqrt( (x - self.x) ** 2 + (y - self.y) ** 2 + (z - self.z) ** 2)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# This class represents both a position and an orientation
|
|
90
|
+
class Euler3D < Vector3D
|
|
91
|
+
# The orientation angles
|
|
92
|
+
attr_accessor :yaw, :pitch, :roll
|
|
93
|
+
|
|
94
|
+
# Create an euler position object
|
|
95
|
+
def initialize(x = 0, y = 0, z = 0, yaw = 0, pitch = 0, roll = 0)
|
|
96
|
+
super(x, y, z)
|
|
97
|
+
@yaw, @pitch, @roll = yaw, pitch, roll
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns [yaw, pitch, roll]
|
|
101
|
+
def ypr
|
|
102
|
+
[yaw, pitch, roll]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def to_s # :nodoc:
|
|
106
|
+
"Euler3D(x=%f,y=%f,z=%f,y=%f,p=%f,r=%f)" % [x,y,z,yaw,pitch,roll]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class Cylinder
|
|
2
|
+
attr_accessor :radius, :height, :axis
|
|
3
|
+
def initialize(radius, height, axis)
|
|
4
|
+
@radius, @height, @axis = radius.to_f, height.to_f, axis.to_f
|
|
5
|
+
end
|
|
6
|
+
def diameter(axis)
|
|
7
|
+
if axis == self.axis
|
|
8
|
+
radius * 2
|
|
9
|
+
else
|
|
10
|
+
raise NotImplementedError
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
alias :max_length :diameter
|
|
14
|
+
def length; diameter(:z) end
|
|
15
|
+
def width; diameter(:z) end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Cube
|
|
19
|
+
attr_accessor :length, :width, :height
|
|
20
|
+
def initialize(length, width, height)
|
|
21
|
+
@length, @width, @height = length.to_f, width.to_f, height.to_f
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def max_length(axis)
|
|
25
|
+
if axis == :z
|
|
26
|
+
[length, width].max
|
|
27
|
+
else
|
|
28
|
+
height
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
# ExtendedStruct objects are OpenStructs where attributes have a default
|
|
3
|
+
# class. They are used to build hierarchical data structure on-the-fly
|
|
4
|
+
#
|
|
5
|
+
# For instance
|
|
6
|
+
# root = ExtendedStruct.new
|
|
7
|
+
# root.child.value = 42
|
|
8
|
+
#
|
|
9
|
+
# However, you *cannot* check if a value is defined or not with
|
|
10
|
+
# if (root.child)
|
|
11
|
+
# <do something>
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# You'll have to test with respond_to? or #{name}?. The second one will
|
|
15
|
+
# return true only if the attribute is defined <b>and</b> it is not false
|
|
16
|
+
# if (root.respond_to?(:child)
|
|
17
|
+
# <do something>
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# == Handling of methods defined on parents
|
|
21
|
+
#
|
|
22
|
+
# Methods defined in Object or Kernel are automatically overriden if needed.
|
|
23
|
+
# For instance, if you're managing a (x, y, z) position using ExtendedStruct,
|
|
24
|
+
# you will want YAML#y to *not* get in the way. The exceptions are the methods
|
|
25
|
+
# listed in NOT_OVERRIDABLE
|
|
26
|
+
#
|
|
27
|
+
class ExtendedStruct
|
|
28
|
+
include DRbUndumped
|
|
29
|
+
|
|
30
|
+
# +attach_to+ and +attach_name+
|
|
31
|
+
# are used so that
|
|
32
|
+
# root = ExtendedStruct.new
|
|
33
|
+
# root.bla
|
|
34
|
+
# does *not* add a +bla+ attribute to root, while the following constructs
|
|
35
|
+
# root.bla.test = 20
|
|
36
|
+
# bla = root.bla
|
|
37
|
+
# bla.test = 20
|
|
38
|
+
# does
|
|
39
|
+
#
|
|
40
|
+
# Note, however that
|
|
41
|
+
# bla = root.bla
|
|
42
|
+
# root.bla = 10
|
|
43
|
+
# bla.test = 20
|
|
44
|
+
#
|
|
45
|
+
# will *not* make root.bla be the +bla+ object. And that
|
|
46
|
+
#
|
|
47
|
+
# bla = root.bla
|
|
48
|
+
# root.stable!
|
|
49
|
+
# bla.test = 20
|
|
50
|
+
#
|
|
51
|
+
# will not fail
|
|
52
|
+
def initialize(children_class = ExtendedStruct, attach_to = nil, attach_name = nil) # :nodoc
|
|
53
|
+
clear
|
|
54
|
+
@attach_as = [attach_to, attach_name.to_s] if attach_to
|
|
55
|
+
@children_class = children_class
|
|
56
|
+
@observers = Hash.new { |h, k| h[k] = [] }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def clear
|
|
60
|
+
@attach_as = nil
|
|
61
|
+
@stable = false
|
|
62
|
+
@members = Hash.new
|
|
63
|
+
@pending = Hash.new
|
|
64
|
+
@filters = Hash.new
|
|
65
|
+
@aliases = Hash.new
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self._load(io)
|
|
69
|
+
marshalled_members, aliases = Marshal.load(io)
|
|
70
|
+
members = marshalled_members.inject({}) do |h, (n, mv)|
|
|
71
|
+
begin
|
|
72
|
+
h[n] = Marshal.load(mv)
|
|
73
|
+
rescue Exception
|
|
74
|
+
Roby::Distributed.warn "cannot load #{n} #{mv}: #{$!.message}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
h
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
result = ExtendedStruct.new
|
|
81
|
+
result.instance_variable_set("@members", members)
|
|
82
|
+
result.instance_variable_set("@aliases", aliases)
|
|
83
|
+
result
|
|
84
|
+
|
|
85
|
+
rescue Exception
|
|
86
|
+
Roby::Distributed.warn "cannot load #{members} #{io}: #{$!.message}"
|
|
87
|
+
raise
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def _dump(lvl = -1)
|
|
91
|
+
marshalled_members = @members.map do |name, value|
|
|
92
|
+
[name, Marshal.dump(value)] rescue nil
|
|
93
|
+
end
|
|
94
|
+
marshalled_members.compact!
|
|
95
|
+
Marshal.dump([marshalled_members, @aliases])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
attr_reader :children_class
|
|
99
|
+
|
|
100
|
+
attr_reader :attach_as, :__parent_struct, :__parent_name
|
|
101
|
+
def attach # :nodoc:
|
|
102
|
+
if @attach_as
|
|
103
|
+
@__parent_struct, @__parent_name = @attach_as
|
|
104
|
+
@attach_as = nil
|
|
105
|
+
__parent_struct.attach_child(__parent_name, self)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
def detach
|
|
109
|
+
@attach_as = nil
|
|
110
|
+
end
|
|
111
|
+
def attach_child(name, obj)
|
|
112
|
+
@members[name.to_s] = obj
|
|
113
|
+
end
|
|
114
|
+
protected :detach, :attach_as
|
|
115
|
+
|
|
116
|
+
# Call +block+ with the new value if +name+ changes
|
|
117
|
+
def on(name = nil, &block)
|
|
118
|
+
name = name.to_s if name
|
|
119
|
+
@observers[name] << block
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Converts this ExtendedStruct into a corresponding hash, where all
|
|
123
|
+
# keys are symbols. If +recursive+ is true, any member which responds
|
|
124
|
+
# to #to_hash will be converted as well
|
|
125
|
+
def to_hash(recursive = true)
|
|
126
|
+
result = Hash.new
|
|
127
|
+
@members.each do |k, v|
|
|
128
|
+
result[k.to_sym] = if recursive && v.respond_to?(:to_hash)
|
|
129
|
+
v.to_hash
|
|
130
|
+
else v
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
result
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Iterates on all defined members of this object
|
|
137
|
+
def each_member(&block)
|
|
138
|
+
@members.each(&block)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Update a set of values on this struct
|
|
142
|
+
# If a hash is given, it is an name => value hash of attribute
|
|
143
|
+
# values. A given block is yield with self, so that the construct
|
|
144
|
+
#
|
|
145
|
+
# my.extendable.struct.very.deep.update do |deep|
|
|
146
|
+
# <update deep>
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# can be used
|
|
150
|
+
def update(hash = nil)
|
|
151
|
+
attach
|
|
152
|
+
hash.each { |k, v| send("#{k}=", v) } if hash
|
|
153
|
+
yield(self) if block_given?
|
|
154
|
+
self
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def delete(name = nil)
|
|
158
|
+
raise TypeError, "#{self} is stable" if stable?
|
|
159
|
+
if name
|
|
160
|
+
name = name.to_s
|
|
161
|
+
if child = @members.delete(name)
|
|
162
|
+
child.instance_variable_set(:@__parent_struct, nil)
|
|
163
|
+
child.instance_variable_set(:@__parent_name, nil)
|
|
164
|
+
elsif child = @pending.delete(name)
|
|
165
|
+
child.instance_variable_set(:@attach_as, nil)
|
|
166
|
+
elsif child = @aliases.delete(name)
|
|
167
|
+
# nothing to do here
|
|
168
|
+
else
|
|
169
|
+
raise ArgumentError, "no such child #{name}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# and remove aliases that point to +name+
|
|
173
|
+
@aliases.delete_if { |_, pointed_to| pointed_to == name }
|
|
174
|
+
else
|
|
175
|
+
if __parent_struct
|
|
176
|
+
__parent_struct.delete(__parent_name)
|
|
177
|
+
elsif @attach_as
|
|
178
|
+
@attach_as.first.delete(@attach_as.last)
|
|
179
|
+
else
|
|
180
|
+
raise ArgumentError, "#{self} is attached to nothing"
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Define a filter for the +name+ attribute on self. The given block
|
|
186
|
+
# is called when the attribute is written, and should return true if
|
|
187
|
+
# the new value if valid or false otherwise
|
|
188
|
+
def filter(name, &block)
|
|
189
|
+
@filters[name.to_s] = block
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# If self is stable, it cannot be updated. That is, calling a setter method
|
|
193
|
+
# raises NoMethodError
|
|
194
|
+
def stable?; @stable end
|
|
195
|
+
|
|
196
|
+
# Sets the stable attribute of +self+ to +is_stable+. If +recursive+ is true,
|
|
197
|
+
# set it on the child struct as well.
|
|
198
|
+
#
|
|
199
|
+
def stable!(recursive = false, is_stable = true)
|
|
200
|
+
@stable = is_stable
|
|
201
|
+
if recursive
|
|
202
|
+
@members.each { |name, object| object.stable!(recursive, is_stable) if object.respond_to?(:stable!) }
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def updated(name, value)
|
|
207
|
+
if @observers.has_key?(name)
|
|
208
|
+
@observers[name].each { |b| b.call(value) }
|
|
209
|
+
end
|
|
210
|
+
@observers[nil].each { |b| b.call(value) }
|
|
211
|
+
|
|
212
|
+
if __parent_struct
|
|
213
|
+
__parent_struct.updated(__parent_name, self)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Returns true if this object has no member
|
|
218
|
+
def empty?; @members.empty? end
|
|
219
|
+
|
|
220
|
+
def respond_to?(name) # :nodoc:
|
|
221
|
+
return true if super
|
|
222
|
+
|
|
223
|
+
name = name.to_s
|
|
224
|
+
return false if name =~ FORBIDDEN_NAMES_RX
|
|
225
|
+
|
|
226
|
+
if name =~ /=$/
|
|
227
|
+
!@stable
|
|
228
|
+
else
|
|
229
|
+
if @members.has_key?(name)
|
|
230
|
+
true
|
|
231
|
+
else
|
|
232
|
+
(alias_to = @aliases[name]) && respond_to?(alias_to)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def get(name, default_value)
|
|
238
|
+
if respond_to?(name)
|
|
239
|
+
send(name.to_sym)
|
|
240
|
+
else
|
|
241
|
+
default_value
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
FORBIDDEN_NAMES=%w{marshal each enum to}.map { |str| "^#{str}_" }
|
|
246
|
+
FORBIDDEN_NAMES_RX = /(?:#{FORBIDDEN_NAMES.join("|")})/
|
|
247
|
+
|
|
248
|
+
NOT_OVERRIDABLE = %w{class} + instance_methods(false)
|
|
249
|
+
NOT_OVERRIDABLE_RX = /(?:#{NOT_OVERRIDABLE.join("|")})/
|
|
250
|
+
|
|
251
|
+
def method_missing(name, *args, &update) # :nodoc:
|
|
252
|
+
name = name.to_s
|
|
253
|
+
|
|
254
|
+
super(name.to_sym, *args, &update) if name =~ FORBIDDEN_NAMES_RX
|
|
255
|
+
if name =~ /(.+)=$/
|
|
256
|
+
# Setter
|
|
257
|
+
name = $1
|
|
258
|
+
|
|
259
|
+
value = *args
|
|
260
|
+
if stable?
|
|
261
|
+
raise NoMethodError, "#{self} is stable"
|
|
262
|
+
elsif @filters.has_key?(name) && !@filters[name].call(value)
|
|
263
|
+
raise ArgumentError, "value #{value} is not valid for #{name}"
|
|
264
|
+
elsif !@members.has_key?(name) && !@aliases.has_key?(name) && respond_to?(name)
|
|
265
|
+
if NOT_OVERRIDABLE_RX =~ name
|
|
266
|
+
raise ArgumentError, "#{name} is already defined an cannot be overriden"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Override it
|
|
270
|
+
singleton_class.class_eval { private name }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
attach
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@aliases.delete(name)
|
|
277
|
+
pending = @pending.delete(name)
|
|
278
|
+
|
|
279
|
+
if pending && pending != value
|
|
280
|
+
pending.detach
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
@members[name] = value
|
|
284
|
+
updated(name, value)
|
|
285
|
+
return value
|
|
286
|
+
|
|
287
|
+
elsif name =~ /(.+)\?$/
|
|
288
|
+
# Test
|
|
289
|
+
name = $1
|
|
290
|
+
respond_to?(name) && send(name)
|
|
291
|
+
|
|
292
|
+
elsif args.empty? # getter
|
|
293
|
+
attach
|
|
294
|
+
|
|
295
|
+
if @members.has_key?(name)
|
|
296
|
+
member = @members[name]
|
|
297
|
+
else
|
|
298
|
+
if alias_to = @aliases[name]
|
|
299
|
+
return send(alias_to)
|
|
300
|
+
elsif stable?
|
|
301
|
+
raise NoMethodError, "no such attribute #{name} (#{self} is stable)"
|
|
302
|
+
else
|
|
303
|
+
member = children_class.new(children_class, self, name)
|
|
304
|
+
@pending[name] = member
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
if update
|
|
309
|
+
member.update(&update)
|
|
310
|
+
else
|
|
311
|
+
member
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
else
|
|
315
|
+
super(name.to_sym, *args, &update)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def alias(from, to)
|
|
320
|
+
@aliases[to.to_s] = from.to_s
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
class StateSpace < ExtendedStruct
|
|
325
|
+
def initialize
|
|
326
|
+
@exported_fields = Set.new
|
|
327
|
+
super
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def _dump(lvl = -1)
|
|
331
|
+
marshalled_members = @exported_fields.map do |name|
|
|
332
|
+
value = @members[name]
|
|
333
|
+
[name, Marshal.dump(value)] rescue nil
|
|
334
|
+
end
|
|
335
|
+
marshalled_members.compact!
|
|
336
|
+
Marshal.dump([marshalled_members, @aliases])
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def deep_copy
|
|
340
|
+
exported_fields, @exported_fields = @exported_fields, Set.new
|
|
341
|
+
Marshal.load(Marshal.dump(self))
|
|
342
|
+
ensure
|
|
343
|
+
@exported_fields = exported_fiels
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def testing?; Roby.app.testing? end
|
|
347
|
+
def simulation?; Roby.app.simulation? end
|
|
348
|
+
def export(*names)
|
|
349
|
+
@exported_fields.merge names.map { |n| n.to_s }.to_set
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|