Class: Roby::Interface::Client
Overview
The client-side object that allows to access an interface (e.g. a Roby app) from another process than the Roby controller
Defined Under Namespace
Classes: BatchContext, Job, NoSuchAction
Instance Attribute Summary collapse
-
#actions ⇒ Array<Roby::Actions::Model::Action>
readonly
Set of known actions.
-
#commands ⇒ Hash
readonly
The set of available commands.
-
#cycle_index ⇒ Integer
readonly
Index of the last processed cycle.
-
#cycle_start_time ⇒ Time
readonly
Time of the last processed cycle.
-
#exception_queue ⇒ Array<Integer,Array>
readonly
List of existing exceptions.
-
#io ⇒ DRobyChannel
readonly
The IO to the server.
-
#job_progress_queue ⇒ Array<Integer,Array>
readonly
List of existing job progress information.
-
#notification_queue ⇒ Array<Integer,Array>
readonly
List of existing notifications.
-
#pending_async_calls ⇒ Array<Hash>
readonly
List of the pending async calls.
-
#ui_event_queue ⇒ Array<Integer,Array>
readonly
List of queued UI events.
Instance Method Summary collapse
-
#allocate_message_id ⇒ Object
private
Allocation of unique IDs for notification messages.
-
#async_call(path, m, *args, &block) ⇒ Object
private
Asynchronously call a method on the interface or on one of the interface’s subcommands.
-
#async_call_pending?(a_call) ⇒ Boolean
private
Whether the async call is still pending.
-
#call(path, m, *args) ⇒ Object
private
Call a method on the interface or on one of the interface’s subcommands.
-
#close ⇒ Object
Close the communication channel.
-
#closed? ⇒ Boolean
Whether the communication channel to the server is closed.
-
#create_batch ⇒ BatchContext
Create a batch context.
-
#each_job ⇒ Object
Enumerate the current jobs.
-
#find_action_by_name(name) ⇒ Actions::Models::Action?
Find an action by its name.
-
#find_all_actions_matching(matcher) ⇒ Array<Actions::Models::Action>
Finds all actions whose name matches a pattern.
-
#find_all_jobs_by_action_name(action_name) ⇒ Array<Job>
Find all the jobs that match the given action name.
- #find_subcommand_by_name(name) ⇒ Object
-
#has_action?(name) ⇒ Boolean
Tests whether the interface has an action with that name.
-
#has_exceptions? ⇒ Boolean
Whether some exception notifications have been queued.
-
#has_job_progress? ⇒ Boolean
Whether some job progress information is currently queued.
-
#has_notifications? ⇒ Boolean
Whether some generic notifications have been queued.
-
#has_ui_event? ⇒ Boolean
Whether some UI events have been queued.
-
#initialize(io, id) ⇒ Client
constructor
Create a client endpoint to a Roby interface [Server].
- #method_missing(m, *args) ⇒ Object
-
#poll(expected_count = 0) ⇒ Object
Polls for new data on the IO channel.
-
#pop_exception ⇒ (Integer,Array)
Remove and return the oldest exception notification.
-
#pop_job_progress ⇒ (Integer,Array)
Remove and return the oldest job information message.
-
#pop_notification ⇒ (Integer,Array)
Remove and return the oldest generic notification message.
-
#pop_ui_event ⇒ Object
Remove the oldest UI event and return it.
-
#process_batch(batch) ⇒ Array
Send all commands gathered in a batch for processing on the remote server.
-
#process_packet(m, *args) ⇒ Boolean
private
Process a message as received on #io.
-
#process_pending_async_call(error, result) ⇒ Object
private
Remove and call the block of a pending async call.
-
#queue_exception(kind, error, tasks, job_ids) ⇒ Object
private
Push an exception notification to #exception_queue.
-
#queue_job_progress(kind, job_id, job_name, *args) ⇒ Object
private
Push a job notification to #job_progress_queue.
-
#queue_notification(source, level, message) ⇒ Object
private
Push a generic notification to #notification_queue.
-
#queue_ui_event(event_name, *args) ⇒ Object
private
Push a UI event to #ui_event_queue.
- #reload_actions ⇒ Object
-
#start_job(action_name, **arguments) ⇒ Object
Start the given job within the batch.
-
#to_io ⇒ Object
The underlying IO object.
-
#wait(timeout: nil) ⇒ Boolean
Wait until there is data to process on the IO channel.
Constructor Details
#initialize(io, id) ⇒ Client
Create a client endpoint to a Roby interface [Server]
45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/roby/interface/client.rb', line 45 def initialize(io, id) @pending_async_calls = Array.new @io = io = 0 @notification_queue = Array.new @job_progress_queue = Array.new @exception_queue = Array.new @ui_event_queue = Array.new @actions, @commands = call([], :handshake, id) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(m, *args) ⇒ Object
543 544 545 546 547 548 549 |
# File 'lib/roby/interface/client.rb', line 543 def method_missing(m, *args) if sub = find_subcommand_by_name(m.to_s) SubcommandClient.new(self, m.to_s, sub.description, sub.commands) else call([], m, *args) end end |
Instance Attribute Details
#actions ⇒ Array<Roby::Actions::Model::Action> (readonly)
Returns set of known actions.
9 10 11 |
# File 'lib/roby/interface/client.rb', line 9 def actions @actions end |
#commands ⇒ Hash (readonly)
Returns the set of available commands.
11 12 13 |
# File 'lib/roby/interface/client.rb', line 11 def commands @commands end |
#cycle_index ⇒ Integer (readonly)
Returns index of the last processed cycle.
31 32 33 |
# File 'lib/roby/interface/client.rb', line 31 def cycle_index @cycle_index end |
#cycle_start_time ⇒ Time (readonly)
Returns time of the last processed cycle.
33 34 35 |
# File 'lib/roby/interface/client.rb', line 33 def cycle_start_time @cycle_start_time end |
#exception_queue ⇒ Array<Integer,Array> (readonly)
Returns list of existing exceptions. The integer is an ID that can be used to refer to the exception. It is always growing and will never collide with a notification ID.
24 25 26 |
# File 'lib/roby/interface/client.rb', line 24 def exception_queue @exception_queue end |
#io ⇒ DRobyChannel (readonly)
Returns the IO to the server.
7 8 9 |
# File 'lib/roby/interface/client.rb', line 7 def io @io end |
#job_progress_queue ⇒ Array<Integer,Array> (readonly)
Returns list of existing job progress information. The integer is an ID that can be used to refer to the job progress information. It is always growing and will never collide with a job progress and exception ID.
16 17 18 |
# File 'lib/roby/interface/client.rb', line 16 def job_progress_queue @job_progress_queue end |
#notification_queue ⇒ Array<Integer,Array> (readonly)
Returns list of existing notifications. The integer is an ID that can be used to refer to the notification. It is always growing and will never collide with an exception ID.
20 21 22 |
# File 'lib/roby/interface/client.rb', line 20 def notification_queue @notification_queue end |
#pending_async_calls ⇒ Array<Hash> (readonly)
Returns list of the pending async calls.
35 36 37 |
# File 'lib/roby/interface/client.rb', line 35 def pending_async_calls @pending_async_calls end |
#ui_event_queue ⇒ Array<Integer,Array> (readonly)
Returns list of queued UI events. The integer is an ID that can be used to refer to the exception. It is always growing and will never collide with a notification ID.
28 29 30 |
# File 'lib/roby/interface/client.rb', line 28 def ui_event_queue @ui_event_queue end |
Instance Method Details
#allocate_message_id ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Allocation of unique IDs for notification messages
186 187 188 |
# File 'lib/roby/interface/client.rb', line 186 def += 1 end |
#async_call(path, m, *args, &block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Asynchronously call a method on the interface or on one of the interface’s subcommands
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/roby/interface/client.rb', line 328 def async_call(path, m, *args, &block) raise RuntimeError, "no callback block given" unless block_given? if m.to_s =~ /(.*)!$/ action_name = $1 if find_action_by_name(action_name) path = [] m = :start_job args = [action_name, *args] else raise NoSuchAction, "there is no action called #{action_name} on #{self}" end end io.write_packet([path, m, *args]) pending_async_calls << { block: block, path: path, m: m, args: args } pending_async_calls.last.freeze end |
#async_call_pending?(a_call) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether the async call is still pending
350 351 352 |
# File 'lib/roby/interface/client.rb', line 350 def async_call_pending?(a_call) pending_async_calls.any? { |item| item.equal?(a_call) } end |
#call(path, m, *args) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Call a method on the interface or on one of the interface’s subcommands
306 307 308 309 310 311 312 313 314 315 |
# File 'lib/roby/interface/client.rb', line 306 def call(path, m, *args) if m.to_s =~ /(.*)!$/ action_name = $1 start_job(action_name, *args) else io.write_packet([path, m, *args]) result, _ = poll(1) result end end |
#close ⇒ Object
Close the communication channel
63 64 65 |
# File 'lib/roby/interface/client.rb', line 63 def close io.close end |
#closed? ⇒ Boolean
Whether the communication channel to the server is closed
58 59 60 |
# File 'lib/roby/interface/client.rb', line 58 def closed? io.closed? end |
#create_batch ⇒ BatchContext
Create a batch context
Messages sent to the returned object are validated as much as possible and gathered in a list. Call #process_batch to send all the gathered calls at once to the remote server
520 521 522 |
# File 'lib/roby/interface/client.rb', line 520 def create_batch BatchContext.new(self) end |
#each_job ⇒ Object
Enumerate the current jobs
497 498 499 500 501 502 |
# File 'lib/roby/interface/client.rb', line 497 def each_job return enum_for(__method__) if !block_given? jobs.each do |job_id, (job_state, placeholder_task, job_task)| yield(Job.new(job_id, job_state, placeholder_task, job_task)) end end |
#find_action_by_name(name) ⇒ Actions::Models::Action?
Find an action by its name
This is a local operation using the information gathered at connection time
84 85 86 |
# File 'lib/roby/interface/client.rb', line 84 def find_action_by_name(name) actions.find { |act| act.name == name } end |
#find_all_actions_matching(matcher) ⇒ Array<Actions::Models::Action>
Finds all actions whose name matches a pattern
93 94 95 |
# File 'lib/roby/interface/client.rb', line 93 def find_all_actions_matching(matcher) actions.find_all { |act| matcher === act.name } end |
#find_all_jobs_by_action_name(action_name) ⇒ Array<Job>
Find all the jobs that match the given action name
507 508 509 510 511 |
# File 'lib/roby/interface/client.rb', line 507 def find_all_jobs_by_action_name(action_name) each_job.find_all do |j| j.action_model.name == action_name end end |
#find_subcommand_by_name(name) ⇒ Object
539 540 541 |
# File 'lib/roby/interface/client.rb', line 539 def find_subcommand_by_name(name) commands[name] end |
#has_action?(name) ⇒ Boolean
Tests whether the interface has an action with that name
73 74 75 |
# File 'lib/roby/interface/client.rb', line 73 def has_action?(name) !!find_action_by_name(name) end |
#has_exceptions? ⇒ Boolean
Whether some exception notifications have been queued
265 266 267 |
# File 'lib/roby/interface/client.rb', line 265 def has_exceptions? !exception_queue.empty? end |
#has_job_progress? ⇒ Boolean
Whether some job progress information is currently queued
201 202 203 |
# File 'lib/roby/interface/client.rb', line 201 def has_job_progress? !job_progress_queue.empty? end |
#has_notifications? ⇒ Boolean
Whether some generic notifications have been queued
222 223 224 |
# File 'lib/roby/interface/client.rb', line 222 def has_notifications? !notification_queue.empty? end |
#has_ui_event? ⇒ Boolean
Whether some UI events have been queued
243 244 245 |
# File 'lib/roby/interface/client.rb', line 243 def has_ui_event? !ui_event_queue.empty? end |
#poll(expected_count = 0) ⇒ Object
Polls for new data on the IO channel
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/roby/interface/client.rb', line 159 def poll(expected_count = 0) result = nil timeout = if expected_count > 0 then nil else 0 end has_cycle_end = false while packet = io.read_packet(timeout) has_cycle_end = process_packet(*packet) do |reply_value| if result raise ProtocolError, "got more than one sync reply in a single poll call" end result = reply_value expected_count -= 1 end if expected_count <= 0 break if has_cycle_end timeout = 0 end end return result, has_cycle_end end |
#pop_exception ⇒ (Integer,Array)
Remove and return the oldest exception notification
274 275 276 |
# File 'lib/roby/interface/client.rb', line 274 def pop_exception exception_queue.shift end |
#pop_job_progress ⇒ (Integer,Array)
Remove and return the oldest job information message
210 211 212 |
# File 'lib/roby/interface/client.rb', line 210 def pop_job_progress job_progress_queue.shift end |
#pop_notification ⇒ (Integer,Array)
Remove and return the oldest generic notification message
231 232 233 |
# File 'lib/roby/interface/client.rb', line 231 def pop_notification notification_queue.shift end |
#pop_ui_event ⇒ Object
Remove the oldest UI event and return it
248 249 250 |
# File 'lib/roby/interface/client.rb', line 248 def pop_ui_event ui_event_queue.shift end |
#process_batch(batch) ⇒ Array
Send all commands gathered in a batch for processing on the remote server
530 531 532 533 |
# File 'lib/roby/interface/client.rb', line 530 def process_batch(batch) ret = call([], :process_batch, batch.__calls) BatchContext::Return.from_calls_and_return(batch.__calls, ret) end |
#process_packet(m, *args) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Process a message as received on #io
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/roby/interface/client.rb', line 102 def process_packet(m, *args) if m == :cycle_end @cycle_index, @cycle_start_time = *args return true end if m == :bad_call if !pending_async_calls.empty? process_pending_async_call(args.first, nil) else e = args.first raise e, e., (e.backtrace + caller) end elsif m == :reply if !pending_async_calls.empty? process_pending_async_call(nil, args.first) else yield args.first end elsif m == :job_progress queue_job_progress(*args) elsif m == :notification queue_notification(*args) elsif m == :ui_event queue_ui_event(*args) elsif m == :exception queue_exception(*args) else raise ProtocolError, "unexpected reply from #{io}: #{m} (#{args.map(&:to_s).join(",")})" end false end |
#process_pending_async_call(error, result) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Remove and call the block of a pending async call
148 149 150 151 |
# File 'lib/roby/interface/client.rb', line 148 def process_pending_async_call(error, result) current_call = pending_async_calls.shift current_call[:block].call(error, result) end |
#queue_exception(kind, error, tasks, job_ids) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Push an exception notification to #exception_queue
It can be retrieved with #pop_exception
See the yield parameters of Interface#on_exception for the overall argument format.
260 261 262 |
# File 'lib/roby/interface/client.rb', line 260 def queue_exception(kind, error, tasks, job_ids) exception_queue.push [, [kind, error, tasks, job_ids]] end |
#queue_job_progress(kind, job_id, job_name, *args) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Push a job notification to #job_progress_queue
See the yield parameters of Interface#on_job_notification for the overall argument format.
196 197 198 |
# File 'lib/roby/interface/client.rb', line 196 def queue_job_progress(kind, job_id, job_name, *args) job_progress_queue.push [, [kind, job_id, job_name, *args]] end |
#queue_notification(source, level, message) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Push a generic notification to #notification_queue
217 218 219 |
# File 'lib/roby/interface/client.rb', line 217 def queue_notification(source, level, ) notification_queue.push [, [source, level, ]] end |
#queue_ui_event(event_name, *args) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Push a UI event to #ui_event_queue
238 239 240 |
# File 'lib/roby/interface/client.rb', line 238 def queue_ui_event(event_name, *args) ui_event_queue.push [, [event_name, *args]] end |
#reload_actions ⇒ Object
535 536 537 |
# File 'lib/roby/interface/client.rb', line 535 def reload_actions @actions = call([], :reload_actions) end |
#start_job(action_name, **arguments) ⇒ Object
Start the given job within the batch
287 288 289 290 291 292 |
# File 'lib/roby/interface/client.rb', line 287 def start_job(action_name, **arguments) if find_action_by_name(action_name) call([], :start_job, action_name, arguments) else raise NoSuchAction, "there is no action called #{action_name} on #{self}" end end |
#to_io ⇒ Object
The underlying IO object
68 69 70 |
# File 'lib/roby/interface/client.rb', line 68 def to_io io.to_io end |
#wait(timeout: nil) ⇒ Boolean
Wait until there is data to process on the IO channel
141 142 143 |
# File 'lib/roby/interface/client.rb', line 141 def wait(timeout: nil) io.read_wait(timeout: timeout) end |