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,160 @@
1
+ module Roby
2
+ module Test
3
+ class << self
4
+ def sampling(duration, period, *fields)
5
+ Test.info "starting sampling #{fields.join(", ")} every #{period}s for #{duration}s"
6
+
7
+ samples = Array.new
8
+ fields.map! { |n| n.to_sym }
9
+ if fields.include?(:dt)
10
+ raise ArgumentError, "dt is reserved by #sampling"
11
+ end
12
+
13
+ if compute_time = !fields.include?(:t)
14
+ fields << :t
15
+ end
16
+ fields << :dt
17
+
18
+ sample_type = Struct.new(*fields)
19
+
20
+ start = Time.now
21
+ Roby.condition_variable(true) do |cv, mt|
22
+ first_sample = nil
23
+ mt.synchronize do
24
+ id = Roby::Control.every(period) do
25
+ result = yield
26
+ if result
27
+ if compute_time
28
+ result << Roby.control.cycle_start
29
+ end
30
+ new_sample = sample_type.new(*result)
31
+
32
+ unless samples.empty?
33
+ new_sample.dt = new_sample.t- samples.last.t
34
+ end
35
+ samples << new_sample
36
+
37
+ if samples.last.t - samples.first.t > duration
38
+ mt.synchronize do
39
+ cv.broadcast
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ cv.wait(mt)
46
+ Roby::Control.remove_periodic_handler(id)
47
+ end
48
+ end
49
+
50
+ samples
51
+ end
52
+
53
+ Stat = Struct.new :total, :count, :mean, :stddev, :min, :max
54
+
55
+ # Computes mean and standard deviation about the samples in
56
+ # +samples+ +spec+ describes what to compute:
57
+ # * if nothing is specified, we compute the statistics on
58
+ # v(i - 1) - v(i)
59
+ # * if spec['fieldname'] is 'rate', we compute the statistics on
60
+ # (v(i - 1) - v(i)) / (t(i - 1) / t(i))
61
+ # * if spec['fieldname'] is 'absolute', we compute the
62
+ # statistics on
63
+ # v(i)
64
+ # * if spec['fieldname'] is 'absolute_rate', we compute the
65
+ # statistics on
66
+ # v(i) / (t(i - 1) / t(i))
67
+ #
68
+ # The returned value is a struct with the same fields than the
69
+ # samples. Each element is a Stats object
70
+ def stats(samples, spec)
71
+ return if samples.empty?
72
+ type = samples.first.class
73
+ spec = spec.inject(Hash.new) do |h, (k, v)|
74
+ spec[k.to_sym] = v.to_sym
75
+ spec
76
+ end
77
+ spec[:t] = :exclude
78
+ spec[:dt] = :absolute
79
+
80
+ # Initialize the result value
81
+ fields = type.members.
82
+ find_all { |n| spec[n.to_sym] != :exclude }.
83
+ map { |n| n.to_sym }
84
+ result = Struct.new(*fields).new
85
+ fields.each do |name|
86
+ result[name] = Stat.new(0, 0, 0, 0, nil, nil)
87
+ end
88
+
89
+ # Compute the deltas if the mode is not absolute
90
+ last_sample = nil
91
+ samples = samples.map do |original_sample|
92
+ sample = original_sample.dup
93
+ fields.each do |name|
94
+ next unless value = sample[name]
95
+ unless spec[name] == :absolute || spec[name] == :absolute_rate
96
+ if last_sample && last_sample[name]
97
+ sample[name] -= last_sample[name]
98
+ else
99
+ sample[name] = nil
100
+ next
101
+ end
102
+ end
103
+ end
104
+ last_sample = original_sample
105
+ sample
106
+ end
107
+
108
+ # Compute the rates if needed
109
+ samples = samples.map do |sample|
110
+ fields.each do |name|
111
+ next unless value = sample[name]
112
+ if spec[name] == :rate || spec[name] == :absolute_rate
113
+ if sample.dt
114
+ sample[name] = value / sample.dt
115
+ else
116
+ sample[name] = nil
117
+ next
118
+ end
119
+ end
120
+ end
121
+ sample
122
+ end
123
+
124
+ samples.each do |sample|
125
+ fields.each do |name|
126
+ next unless value = sample[name]
127
+ if !result[name].max || value > result[name].max
128
+ result[name].max = value
129
+ end
130
+ if !result[name].min || value < result[name].min
131
+ result[name].min = value
132
+ end
133
+
134
+ result[name].total += value
135
+ result[name].count += 1
136
+ end
137
+ last_sample = sample
138
+ end
139
+
140
+ result.each do |r|
141
+ r.mean = Float(r.total) / r.count
142
+ end
143
+
144
+ samples.each do |sample|
145
+ fields.each do |name|
146
+ next unless value = sample[name]
147
+ result[name].stddev += (value - result[name].mean) ** 2
148
+ end
149
+ end
150
+
151
+ result.each do |r|
152
+ r.stddev = Math.sqrt(r.stddev / r.count)
153
+ end
154
+
155
+ result
156
+ end
157
+ end
158
+ end
159
+ end
160
+
@@ -0,0 +1,87 @@
1
+ module Roby
2
+ # This task represents a separate thread in the plan. At definition, the
3
+ # thread's implementation is defined using the +implementation+ statement:
4
+ #
5
+ # class MyThread < ThreadTask
6
+ # implementation do
7
+ # do_some_stuff
8
+ # end
9
+ # end
10
+ #
11
+ # The task will emit +failed+ if the given block raises an exception,
12
+ # and emit +success+ otherwise. In that latter case, the returned value
13
+ # is saved in the +result+ attribute.
14
+ #
15
+ # By default, the task is not interruptible (i.e. +stop+ is not
16
+ # controllable). The +interruptible+ statement allows to change that, in
17
+ # which case, the thread must call #interruption_point explicitely when the
18
+ # interruption can be safely performed by raising an exception.
19
+ class ThreadTask < Roby::Task
20
+ # The thread object. Only valid when the task is running
21
+ attr_reader :thread
22
+ # The thread result if the execution was successful
23
+ attr_reader :result
24
+
25
+ class << self
26
+ # The implementation block for that task model
27
+ attr_reader :implementation_block
28
+
29
+ # Defines the block which should be executed in the separate
30
+ # thread. The currently defined block can be accessed
31
+ # through the implementation_block attribute.
32
+ def implementation(&block)
33
+ @implementation_block = block
34
+ end
35
+ end
36
+
37
+ # True if an interruption has been requested
38
+ attr_predicate :interruption_requested?, true
39
+
40
+ # Call that method in the interruption thread at points where an
41
+ # interruption is safe. It will raise Interrupt if an interruption has
42
+ # been requested through the task's events.
43
+ def interruption_point
44
+ if interruption_requested?
45
+ raise Interrupt, "interruption requested"
46
+ end
47
+ end
48
+
49
+ event :start do |context|
50
+ emit :start
51
+ @thread = Thread.new do
52
+ Thread.current.priority = 0
53
+ instance_eval(&self.class.implementation_block)
54
+ end
55
+ end
56
+
57
+ poll do
58
+ if thread.alive?
59
+ return
60
+ end
61
+
62
+ begin
63
+ result = thread.value
64
+ rescue Exception => e
65
+ error = e
66
+ end
67
+ @thread = nil
68
+
69
+ if error
70
+ emit :failed, error
71
+ else
72
+ @result = result
73
+ emit :success
74
+ end
75
+ end
76
+
77
+ # Call this method in the model definition to declare that the thread
78
+ # implementation will call #interruption_point regularly.
79
+ def self.interruptible
80
+ event :failed, :terminal => true do |context|
81
+ self.interruption_requested = true
82
+ end
83
+ super
84
+ end
85
+ end
86
+ end
87
+
@@ -0,0 +1,462 @@
1
+ require 'roby/plan'
2
+ require 'utilrb/value_set'
3
+ require 'utilrb/kernel/swap'
4
+ require 'roby/transactions/proxy'
5
+
6
+ module Roby
7
+ # Exception raised when someone tries do commit an invalid transaction
8
+ class InvalidTransaction < RuntimeError; end
9
+
10
+ # A transaction is a special kind of plan. It allows to build plans in a separate
11
+ # sandbox, and then to apply the modifications to the real plan (using #commit_transaction), or
12
+ # to discard all modifications (using #discard)
13
+ class Transaction < Plan
14
+ Proxy = Transactions::Proxy
15
+
16
+ # A transaction is not an executable plan
17
+ def executable?; false end
18
+ def freezed?; @freezed end
19
+
20
+ def do_wrap(object, do_include = false) # :nodoc:
21
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
22
+
23
+ proxy = proxy_objects[object] = Proxy.proxy_class(object).new(object, self)
24
+ if do_include && object.root_object?
25
+ proxy.plan = self
26
+ discover(proxy)
27
+ end
28
+
29
+ copy_object_relations(object, proxy)
30
+ proxy
31
+ end
32
+
33
+ def propose; end
34
+ def edit
35
+ yield if block_given?
36
+ end
37
+
38
+ # This method copies on +proxy+ all relations of +object+ for which
39
+ # both ends of the relation are already in the transaction.
40
+ def copy_object_relations(object, proxy)
41
+ Roby::Control.synchronize do
42
+ # Create edges between the neighbours that are really in the transaction
43
+ object.each_relation do |rel|
44
+ object.each_parent_object(rel) do |parent|
45
+ if parent_proxy = self[parent, false]
46
+ parent_proxy.add_child_object(proxy, rel, parent[object, rel])
47
+ end
48
+ end
49
+
50
+ object.each_child_object(rel) do |child|
51
+ if child_proxy = self[child, false]
52
+ proxy.add_child_object(child_proxy, rel, object[child, rel])
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # Get the transaction proxy for +object+
60
+ def wrap(object, create = true)
61
+ if object.kind_of?(PlanObject)
62
+ if object.plan == self then return object
63
+ elsif proxy = proxy_objects[object] then return proxy
64
+ end
65
+
66
+ if create
67
+ if !object.plan
68
+ object.plan = self
69
+ discover(object)
70
+ return object
71
+ elsif object.plan == self.plan
72
+ wrapped = do_wrap(object, true)
73
+ if plan.mission?(object)
74
+ insert(wrapped)
75
+ elsif plan.permanent?(object)
76
+ permanent(wrapped)
77
+ end
78
+ return wrapped
79
+ else
80
+ raise ArgumentError, "#{object} is in #{object.plan}, this transaction #{self} applies on #{self.plan}"
81
+ end
82
+ end
83
+ nil
84
+ elsif object.respond_to?(:each)
85
+ object.map { |o| wrap(o, create) }
86
+ else
87
+ raise TypeError, "don't know how to wrap #{object || 'nil'} of type #{object.class.ancestors}"
88
+ end
89
+ end
90
+ alias :[] :wrap
91
+
92
+ # Remove +proxy+ from this transaction. While #remove_object is also
93
+ # removing the object from the plan itself, this method only removes it
94
+ # from the transaction, forgetting all modifications that have been
95
+ # done on +object+ in the transaction
96
+ def discard_modifications(object)
97
+ object = may_unwrap(object)
98
+ if object.respond_to?(:each_plan_child)
99
+ object.each_plan_child do |child|
100
+ discard_modifications(child)
101
+ end
102
+ end
103
+ removed_objects.delete(object)
104
+ discarded_tasks.delete(object)
105
+ auto_tasks.delete(object)
106
+
107
+ return unless proxy = proxy_objects.delete(object)
108
+ proxy.clear_vertex
109
+
110
+ missions.delete(proxy)
111
+ known_tasks.delete(proxy)
112
+ free_events.delete(proxy)
113
+ end
114
+
115
+ def restore_relation(proxy, relation)
116
+ object = proxy.__getobj__
117
+
118
+ Control.synchronize do
119
+ proxy_children = proxy.child_objects(relation)
120
+ object.child_objects(relation).each do |object_child|
121
+ next unless proxy_child = wrap(object_child, false)
122
+ if proxy_children.include?(proxy_child)
123
+ relation.unlink(proxy, proxy_child)
124
+ end
125
+ end
126
+
127
+ proxy_parents = proxy.parent_objects(relation)
128
+ object.parent_objects(relation).each do |object_parent|
129
+ next unless proxy_parent = wrap(object_parent, false)
130
+ if proxy_parents.include?(proxy_parent)
131
+ relation.unlink(parent, proxy_parent)
132
+ end
133
+ end
134
+ end
135
+
136
+ discovered_objects.delete(proxy)
137
+ proxy.discovered_relations.delete(relation)
138
+ proxy.do_discover(relation, false)
139
+ end
140
+
141
+ alias :remove_plan_object :remove_object
142
+ def remove_object(object)
143
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
144
+
145
+ object = may_unwrap(object)
146
+ proxy = proxy_objects[object] || object
147
+
148
+ # removing the proxy may trigger some discovery (event relations
149
+ # for instance, if proxy is a task). Do it first, or #discover
150
+ # will be called and the modifications of internal structures
151
+ # nulled (like #removed_objects) ...
152
+ remove_plan_object(proxy)
153
+ proxy_objects.delete(object)
154
+
155
+ if object.plan == self.plan
156
+ # +object+ is new in the transaction
157
+ removed_objects.insert(object)
158
+ end
159
+ end
160
+
161
+ def may_wrap(object, create = true)
162
+ (wrap(object, create) || object) rescue object
163
+ end
164
+
165
+ # may_unwrap may return objects from transaction
166
+ def may_unwrap(object)
167
+ if object.respond_to?(:plan)
168
+ if object.plan == self && object.respond_to?(:__getobj__)
169
+ object.__getobj__
170
+ elsif object.plan == self.plan
171
+ object
172
+ else
173
+ object
174
+ end
175
+ else object
176
+ end
177
+ end
178
+
179
+ # The list of discarded
180
+ attr_reader :discarded_tasks
181
+ # The list of removed tasks and events
182
+ attr_reader :removed_objects
183
+ # The list of permanent tasks that have been auto'ed
184
+ attr_reader :auto_tasks
185
+ # The plan this transaction applies on
186
+ attr_reader :plan
187
+ # The proxy objects built for this transaction
188
+ attr_reader :proxy_objects
189
+
190
+ attr_reader :conflict_solver
191
+ attr_reader :options
192
+
193
+ def conflict_solver=(value)
194
+ @conflict_solver = case value
195
+ when :update
196
+ SolverUpdateRelations
197
+ when :invalidate
198
+ SolverInvalidateTransaction
199
+ when :ignore
200
+ SolverIgnoreUpdate.new
201
+ else value
202
+ end
203
+ end
204
+
205
+ # Creates a new transaction which applies on +plan+
206
+ def initialize(plan, options = {})
207
+ options = validate_options options,
208
+ :conflict_solver => :invalidate
209
+
210
+ @options = options
211
+ self.conflict_solver = options[:conflict_solver]
212
+ super()
213
+
214
+ @plan = plan
215
+
216
+ @proxy_objects = Hash.new
217
+ @removed_objects = ValueSet.new
218
+ @discarded_tasks = ValueSet.new
219
+ @auto_tasks = ValueSet.new
220
+
221
+ Roby::Control.synchronize do
222
+ plan.transactions << self
223
+ plan.added_transaction(self)
224
+ end
225
+ end
226
+
227
+ def discover_neighborhood(object)
228
+ self[object]
229
+ object.each_relation do |rel|
230
+ object.each_parent_object(rel) { |obj| self[obj] }
231
+ object.each_child_object(rel) { |obj| self[obj] }
232
+ end
233
+ end
234
+
235
+ def replace(from, to)
236
+ # Make sure +from+, its events and all the related tasks and events
237
+ # are in the transaction
238
+ from = may_unwrap(from)
239
+ discover_neighborhood(from)
240
+ from.each_event do |ev|
241
+ discover_neighborhood(ev)
242
+ end
243
+
244
+ super(self[from], self[to])
245
+ end
246
+
247
+ def insert(t)
248
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
249
+ if proxy = self[t, false]
250
+ discarded_tasks.delete(may_unwrap(proxy))
251
+ end
252
+ super(self[t, true])
253
+ end
254
+ def permanent(t)
255
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
256
+ if proxy = self[t, false]
257
+ auto_tasks.delete(may_unwrap(proxy))
258
+ end
259
+ super(self[t, true])
260
+ end
261
+ def discover(objects)
262
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
263
+ super(self[objects, true])
264
+ self
265
+ end
266
+
267
+ def auto(t)
268
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
269
+ if proxy = self[t, false]
270
+ super(proxy)
271
+ end
272
+
273
+ t = may_unwrap(t)
274
+ if t.plan == self.plan
275
+ auto_tasks.insert(t)
276
+ end
277
+ end
278
+
279
+ def discard(t)
280
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
281
+ if proxy = self[t, false]
282
+ super(proxy)
283
+ end
284
+
285
+ t = may_unwrap(t)
286
+ if t.plan == self.plan
287
+ discarded_tasks.insert(t)
288
+ end
289
+ end
290
+
291
+ attribute(:invalidation_reasons) { Array.new }
292
+
293
+ def invalid=(flag)
294
+ if !flag
295
+ invalidation_reasons.clear
296
+ end
297
+ @invalid = flag
298
+ end
299
+
300
+ def invalid?; @invalid end
301
+ def valid_transaction?; transactions.empty? && !invalid? end
302
+ def invalidate(reason = nil)
303
+ self.invalid = true
304
+ invalidation_reasons << [reason, caller(1)] if reason
305
+ Roby.debug do
306
+ "invalidating #{self}: #{reason}"
307
+ end
308
+ end
309
+ def check_valid_transaction
310
+ return if valid_transaction?
311
+
312
+ unless transactions.empty?
313
+ raise InvalidTransaction, "there is still transactions on top of this one"
314
+ end
315
+ message = invalidation_reasons.map do |reason, trace|
316
+ "#{trace[0]}: #{reason}\n #{trace[1..-1].join("\n ")}"
317
+ end.join("\n")
318
+ raise InvalidTransaction, "invalid transaction: #{message}"
319
+ end
320
+
321
+ # Commit all modifications that have been registered
322
+ # in this transaction
323
+ def commit_transaction
324
+ # if !Roby.control.running?
325
+ # raise "#commit_transaction requires the presence of a control thread"
326
+ # end
327
+
328
+ check_valid_transaction
329
+ freezed!
330
+
331
+ Roby.execute do
332
+ auto_tasks.each { |t| plan.auto(t) }
333
+ discarded_tasks.each { |t| plan.discard(t) }
334
+ removed_objects.each do |obj|
335
+ plan.remove_object(obj) if plan.include?(obj)
336
+ end
337
+
338
+ discover_tasks = ValueSet.new
339
+ discover_events = ValueSet.new
340
+ insert = ValueSet.new
341
+ permanent = ValueSet.new
342
+ known_tasks.dup.each do |t|
343
+ unwrapped = if t.kind_of?(Transactions::Proxy)
344
+ finalized_task(t)
345
+ t.__getobj__
346
+ else
347
+ known_tasks.delete(t)
348
+ t
349
+ end
350
+
351
+ if missions.include?(t) && t.self_owned?
352
+ missions.delete(t)
353
+ insert << unwrapped
354
+ elsif keepalive.include?(t) && t.self_owned?
355
+ keepalive.delete(t)
356
+ permanent << unwrapped
357
+ end
358
+
359
+ discover_tasks << unwrapped
360
+ end
361
+
362
+ free_events.dup.each do |ev|
363
+ unwrapped = if ev.kind_of?(Transactions::Proxy)
364
+ finalized_event(ev)
365
+ ev.__getobj__
366
+ else
367
+ free_events.delete(ev)
368
+ ev
369
+ end
370
+
371
+ discover_events << unwrapped
372
+ end
373
+
374
+ new_tasks = plan.discover_task_set(discover_tasks)
375
+ new_tasks.each do |task|
376
+ if task.respond_to?(:commit_transaction)
377
+ task.commit_transaction
378
+ end
379
+ end
380
+
381
+ new_events = plan.discover_event_set(discover_events)
382
+ new_events.each do |event|
383
+ if event.respond_to?(:commit_transaction)
384
+ event.commit_transaction
385
+ end
386
+ end
387
+
388
+ # Set the plan to nil in known tasks to avoid having the checks on
389
+ # #plan to raise an exception
390
+ proxy_objects.each_value { |proxy| proxy.commit_transaction }
391
+ proxy_objects.each_value { |proxy| proxy.clear_relations }
392
+
393
+ insert.each { |t| plan.insert(t) }
394
+ permanent.each { |t| plan.permanent(t) }
395
+
396
+ proxies = proxy_objects.dup
397
+ clear
398
+ # Replace proxies by forwarder objects
399
+ proxies.each do |object, proxy|
400
+ forwarder = Proxy.forwarder(object)
401
+ forwarder.freeze
402
+ Kernel.swap! proxy, forwarder
403
+ end
404
+
405
+ committed_transaction
406
+ plan.remove_transaction(self)
407
+ @plan = nil
408
+ end
409
+ end
410
+ def committed_transaction; super if defined? super end
411
+ def finalized?; !plan end
412
+
413
+ def enable_proxying; @disable_proxying = false end
414
+ def disable_proxying
415
+ @disable_proxying = true
416
+ if block_given?
417
+ begin
418
+ yield
419
+ ensure
420
+ @disable_proxying = false
421
+ end
422
+ end
423
+ end
424
+ def proxying?; !@freezed && !@disable_proxying end
425
+
426
+ # Discard all the modifications that have been registered
427
+ # in this transaction
428
+ def discard_transaction
429
+ # if !Roby.control.running?
430
+ # raise "#commit_transaction requires the presence of a control thread"
431
+ if !transactions.empty?
432
+ raise InvalidTransaction, "there is still transactions on top of this one"
433
+ end
434
+
435
+ freezed!
436
+ proxy_objects.each_value { |proxy| proxy.discard_transaction }
437
+ clear
438
+
439
+ discarded_transaction
440
+ Roby.execute do
441
+ plan.remove_transaction(self)
442
+ end
443
+ @plan = nil
444
+ end
445
+ def discarded_transaction; super if defined? super end
446
+
447
+ def freezed!
448
+ @freezed = true
449
+ end
450
+
451
+ def clear
452
+ removed_objects.clear
453
+ discarded_tasks.clear
454
+ proxy_objects.each_value { |proxy| proxy.clear_relations }
455
+ proxy_objects.clear
456
+ super
457
+ end
458
+ end
459
+ end
460
+
461
+ require 'roby/transactions/updates'
462
+