roby 0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. data/.gitignore +29 -0
  2. data/History.txt +4 -0
  3. data/License-fr.txt +519 -0
  4. data/License.txt +515 -0
  5. data/Manifest.txt +245 -0
  6. data/NOTES +4 -0
  7. data/README.txt +163 -0
  8. data/Rakefile +161 -0
  9. data/TODO.txt +146 -0
  10. data/app/README.txt +24 -0
  11. data/app/Rakefile +8 -0
  12. data/app/config/ROBOT.rb +5 -0
  13. data/app/config/app.yml +91 -0
  14. data/app/config/init.rb +7 -0
  15. data/app/config/roby.yml +3 -0
  16. data/app/controllers/.gitattributes +0 -0
  17. data/app/controllers/ROBOT.rb +2 -0
  18. data/app/data/.gitattributes +0 -0
  19. data/app/planners/ROBOT/main.rb +6 -0
  20. data/app/planners/main.rb +5 -0
  21. data/app/scripts/distributed +3 -0
  22. data/app/scripts/generate/bookmarks +3 -0
  23. data/app/scripts/replay +3 -0
  24. data/app/scripts/results +3 -0
  25. data/app/scripts/run +3 -0
  26. data/app/scripts/server +3 -0
  27. data/app/scripts/shell +3 -0
  28. data/app/scripts/test +3 -0
  29. data/app/tasks/.gitattributes +0 -0
  30. data/app/tasks/ROBOT/.gitattributes +0 -0
  31. data/bin/roby +210 -0
  32. data/bin/roby-log +168 -0
  33. data/bin/roby-shell +25 -0
  34. data/doc/images/event_generalization.png +0 -0
  35. data/doc/images/exception_propagation_1.png +0 -0
  36. data/doc/images/exception_propagation_2.png +0 -0
  37. data/doc/images/exception_propagation_3.png +0 -0
  38. data/doc/images/exception_propagation_4.png +0 -0
  39. data/doc/images/exception_propagation_5.png +0 -0
  40. data/doc/images/replay_handler_error.png +0 -0
  41. data/doc/images/replay_handler_error_0.png +0 -0
  42. data/doc/images/replay_handler_error_1.png +0 -0
  43. data/doc/images/roby_cycle_overview.png +0 -0
  44. data/doc/images/roby_replay_02.png +0 -0
  45. data/doc/images/roby_replay_03.png +0 -0
  46. data/doc/images/roby_replay_04.png +0 -0
  47. data/doc/images/roby_replay_event_representation.png +0 -0
  48. data/doc/images/roby_replay_first_state.png +0 -0
  49. data/doc/images/roby_replay_relations.png +0 -0
  50. data/doc/images/roby_replay_startup.png +0 -0
  51. data/doc/images/task_event_generalization.png +0 -0
  52. data/doc/papers.rdoc +11 -0
  53. data/doc/styles/allison.css +314 -0
  54. data/doc/styles/allison.js +316 -0
  55. data/doc/styles/allison.rb +276 -0
  56. data/doc/styles/jamis.rb +593 -0
  57. data/doc/tutorials/01-GettingStarted.rdoc +86 -0
  58. data/doc/tutorials/02-GoForward.rdoc +220 -0
  59. data/doc/tutorials/03-PlannedPath.rdoc +268 -0
  60. data/doc/tutorials/04-EventPropagation.rdoc +236 -0
  61. data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
  62. data/doc/tutorials/06-Overview.rdoc +40 -0
  63. data/doc/videos.rdoc +69 -0
  64. data/ext/droby/dump.cc +175 -0
  65. data/ext/droby/extconf.rb +3 -0
  66. data/ext/graph/algorithm.cc +746 -0
  67. data/ext/graph/extconf.rb +7 -0
  68. data/ext/graph/graph.cc +529 -0
  69. data/ext/graph/graph.hh +183 -0
  70. data/ext/graph/iterator_sequence.hh +102 -0
  71. data/ext/graph/undirected_dfs.hh +226 -0
  72. data/ext/graph/undirected_graph.hh +421 -0
  73. data/lib/roby.rb +41 -0
  74. data/lib/roby/app.rb +870 -0
  75. data/lib/roby/app/rake.rb +56 -0
  76. data/lib/roby/app/run.rb +14 -0
  77. data/lib/roby/app/scripts/distributed.rb +13 -0
  78. data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
  79. data/lib/roby/app/scripts/replay.rb +31 -0
  80. data/lib/roby/app/scripts/results.rb +15 -0
  81. data/lib/roby/app/scripts/run.rb +26 -0
  82. data/lib/roby/app/scripts/server.rb +18 -0
  83. data/lib/roby/app/scripts/shell.rb +88 -0
  84. data/lib/roby/app/scripts/test.rb +40 -0
  85. data/lib/roby/basic_object.rb +151 -0
  86. data/lib/roby/config.rb +5 -0
  87. data/lib/roby/control.rb +747 -0
  88. data/lib/roby/decision_control.rb +17 -0
  89. data/lib/roby/distributed.rb +32 -0
  90. data/lib/roby/distributed/base.rb +440 -0
  91. data/lib/roby/distributed/communication.rb +871 -0
  92. data/lib/roby/distributed/connection_space.rb +592 -0
  93. data/lib/roby/distributed/distributed_object.rb +206 -0
  94. data/lib/roby/distributed/drb.rb +62 -0
  95. data/lib/roby/distributed/notifications.rb +539 -0
  96. data/lib/roby/distributed/peer.rb +550 -0
  97. data/lib/roby/distributed/protocol.rb +529 -0
  98. data/lib/roby/distributed/proxy.rb +343 -0
  99. data/lib/roby/distributed/subscription.rb +311 -0
  100. data/lib/roby/distributed/transaction.rb +498 -0
  101. data/lib/roby/event.rb +897 -0
  102. data/lib/roby/exceptions.rb +234 -0
  103. data/lib/roby/executives/simple.rb +30 -0
  104. data/lib/roby/graph.rb +166 -0
  105. data/lib/roby/interface.rb +390 -0
  106. data/lib/roby/log.rb +3 -0
  107. data/lib/roby/log/chronicle.rb +303 -0
  108. data/lib/roby/log/console.rb +72 -0
  109. data/lib/roby/log/data_stream.rb +197 -0
  110. data/lib/roby/log/dot.rb +279 -0
  111. data/lib/roby/log/event_stream.rb +151 -0
  112. data/lib/roby/log/file.rb +340 -0
  113. data/lib/roby/log/gui/basic_display.ui +83 -0
  114. data/lib/roby/log/gui/chronicle.rb +26 -0
  115. data/lib/roby/log/gui/chronicle_view.rb +40 -0
  116. data/lib/roby/log/gui/chronicle_view.ui +70 -0
  117. data/lib/roby/log/gui/data_displays.rb +172 -0
  118. data/lib/roby/log/gui/data_displays.ui +155 -0
  119. data/lib/roby/log/gui/notifications.rb +26 -0
  120. data/lib/roby/log/gui/relations.rb +248 -0
  121. data/lib/roby/log/gui/relations.ui +123 -0
  122. data/lib/roby/log/gui/relations_view.rb +185 -0
  123. data/lib/roby/log/gui/relations_view.ui +149 -0
  124. data/lib/roby/log/gui/replay.rb +327 -0
  125. data/lib/roby/log/gui/replay_controls.rb +200 -0
  126. data/lib/roby/log/gui/replay_controls.ui +259 -0
  127. data/lib/roby/log/gui/runtime.rb +130 -0
  128. data/lib/roby/log/hooks.rb +185 -0
  129. data/lib/roby/log/logger.rb +202 -0
  130. data/lib/roby/log/notifications.rb +244 -0
  131. data/lib/roby/log/plan_rebuilder.rb +470 -0
  132. data/lib/roby/log/relations.rb +1056 -0
  133. data/lib/roby/log/server.rb +550 -0
  134. data/lib/roby/log/sqlite.rb +47 -0
  135. data/lib/roby/log/timings.rb +164 -0
  136. data/lib/roby/plan-object.rb +247 -0
  137. data/lib/roby/plan.rb +762 -0
  138. data/lib/roby/planning.rb +13 -0
  139. data/lib/roby/planning/loops.rb +302 -0
  140. data/lib/roby/planning/model.rb +906 -0
  141. data/lib/roby/planning/task.rb +151 -0
  142. data/lib/roby/propagation.rb +562 -0
  143. data/lib/roby/query.rb +619 -0
  144. data/lib/roby/relations.rb +583 -0
  145. data/lib/roby/relations/conflicts.rb +70 -0
  146. data/lib/roby/relations/ensured.rb +20 -0
  147. data/lib/roby/relations/error_handling.rb +23 -0
  148. data/lib/roby/relations/events.rb +9 -0
  149. data/lib/roby/relations/executed_by.rb +193 -0
  150. data/lib/roby/relations/hierarchy.rb +239 -0
  151. data/lib/roby/relations/influence.rb +10 -0
  152. data/lib/roby/relations/planned_by.rb +63 -0
  153. data/lib/roby/robot.rb +7 -0
  154. data/lib/roby/standard_errors.rb +218 -0
  155. data/lib/roby/state.rb +5 -0
  156. data/lib/roby/state/events.rb +221 -0
  157. data/lib/roby/state/information.rb +55 -0
  158. data/lib/roby/state/pos.rb +110 -0
  159. data/lib/roby/state/shapes.rb +32 -0
  160. data/lib/roby/state/state.rb +353 -0
  161. data/lib/roby/support.rb +92 -0
  162. data/lib/roby/task-operations.rb +182 -0
  163. data/lib/roby/task.rb +1618 -0
  164. data/lib/roby/test/common.rb +399 -0
  165. data/lib/roby/test/distributed.rb +214 -0
  166. data/lib/roby/test/tasks/empty_task.rb +9 -0
  167. data/lib/roby/test/tasks/goto.rb +36 -0
  168. data/lib/roby/test/tasks/simple_task.rb +23 -0
  169. data/lib/roby/test/testcase.rb +519 -0
  170. data/lib/roby/test/tools.rb +160 -0
  171. data/lib/roby/thread_task.rb +87 -0
  172. data/lib/roby/transactions.rb +462 -0
  173. data/lib/roby/transactions/proxy.rb +292 -0
  174. data/lib/roby/transactions/updates.rb +139 -0
  175. data/plugins/fault_injection/History.txt +4 -0
  176. data/plugins/fault_injection/README.txt +37 -0
  177. data/plugins/fault_injection/Rakefile +18 -0
  178. data/plugins/fault_injection/TODO.txt +0 -0
  179. data/plugins/fault_injection/app.rb +52 -0
  180. data/plugins/fault_injection/fault_injection.rb +89 -0
  181. data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
  182. data/plugins/subsystems/README.txt +40 -0
  183. data/plugins/subsystems/Rakefile +18 -0
  184. data/plugins/subsystems/app.rb +171 -0
  185. data/plugins/subsystems/test/app/README +24 -0
  186. data/plugins/subsystems/test/app/Rakefile +8 -0
  187. data/plugins/subsystems/test/app/config/app.yml +71 -0
  188. data/plugins/subsystems/test/app/config/init.rb +9 -0
  189. data/plugins/subsystems/test/app/config/roby.yml +3 -0
  190. data/plugins/subsystems/test/app/planners/main.rb +20 -0
  191. data/plugins/subsystems/test/app/scripts/distributed +3 -0
  192. data/plugins/subsystems/test/app/scripts/replay +3 -0
  193. data/plugins/subsystems/test/app/scripts/results +3 -0
  194. data/plugins/subsystems/test/app/scripts/run +3 -0
  195. data/plugins/subsystems/test/app/scripts/server +3 -0
  196. data/plugins/subsystems/test/app/scripts/shell +3 -0
  197. data/plugins/subsystems/test/app/scripts/test +3 -0
  198. data/plugins/subsystems/test/app/tasks/services.rb +15 -0
  199. data/plugins/subsystems/test/test_subsystems.rb +71 -0
  200. data/test/distributed/test_communication.rb +178 -0
  201. data/test/distributed/test_connection.rb +282 -0
  202. data/test/distributed/test_execution.rb +373 -0
  203. data/test/distributed/test_mixed_plan.rb +341 -0
  204. data/test/distributed/test_plan_notifications.rb +238 -0
  205. data/test/distributed/test_protocol.rb +516 -0
  206. data/test/distributed/test_query.rb +102 -0
  207. data/test/distributed/test_remote_plan.rb +491 -0
  208. data/test/distributed/test_transaction.rb +463 -0
  209. data/test/mockups/tasks.rb +27 -0
  210. data/test/planning/test_loops.rb +380 -0
  211. data/test/planning/test_model.rb +427 -0
  212. data/test/planning/test_task.rb +106 -0
  213. data/test/relations/test_conflicts.rb +42 -0
  214. data/test/relations/test_ensured.rb +38 -0
  215. data/test/relations/test_executed_by.rb +149 -0
  216. data/test/relations/test_hierarchy.rb +158 -0
  217. data/test/relations/test_planned_by.rb +54 -0
  218. data/test/suite_core.rb +24 -0
  219. data/test/suite_distributed.rb +9 -0
  220. data/test/suite_planning.rb +3 -0
  221. data/test/suite_relations.rb +8 -0
  222. data/test/test_bgl.rb +508 -0
  223. data/test/test_control.rb +399 -0
  224. data/test/test_event.rb +894 -0
  225. data/test/test_exceptions.rb +592 -0
  226. data/test/test_interface.rb +37 -0
  227. data/test/test_log.rb +114 -0
  228. data/test/test_log_server.rb +132 -0
  229. data/test/test_plan.rb +584 -0
  230. data/test/test_propagation.rb +210 -0
  231. data/test/test_query.rb +266 -0
  232. data/test/test_relations.rb +180 -0
  233. data/test/test_state.rb +414 -0
  234. data/test/test_support.rb +16 -0
  235. data/test/test_task.rb +938 -0
  236. data/test/test_testcase.rb +122 -0
  237. data/test/test_thread_task.rb +73 -0
  238. data/test/test_transactions.rb +569 -0
  239. data/test/test_transactions_proxy.rb +198 -0
  240. metadata +570 -0
@@ -0,0 +1,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
+