class Roby::Interface::Client
The client-side object that allows to access an interface (e.g. a Roby
app) from another process than the Roby
controller
Constants
- Job
Attributes
@return [Array<Roby::Actions::Model::Action>] set of known actions
@return [Hash] the set of available commands
@return [Integer] index of the last processed cycle
@return [Time] time of the last processed cycle
@return [Array<Integer,Array>] 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
@return [DRobyChannel] the IO
to the server
@return [Array<Integer,Array>] 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
@return [Array<Integer,Array>] 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
@return [Array<Hash>] list of the pending async calls
@return [Array<Integer,Array>] 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
Public Class Methods
Create a client endpoint to a Roby
interface [Server]
@param [DRobyChannel] io a channel to the server @param [String] id a unique identifier for this client
(e.g. host:port of the local endpoint when using TCP). It is passed to the server through {Server#handshake}
@see Interface.connect_with_tcp_to
# File lib/roby/interface/client.rb, line 45 def initialize(io, id) @pending_async_calls = Array.new @io = io @message_id = 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
Public Instance Methods
@api private
Allocation of unique IDs for notification messages
# File lib/roby/interface/client.rb, line 186 def allocate_message_id @message_id += 1 end
@api private
Asynchronously call a method on the interface or on one of the interface's subcommands
@param [Array<String>] path path to the subcommand. Empty means on
the interface object itself.
@param [Symbol] m command or action name. Actions
are always
formatted as action_name!
@param [Object] args the command or action arguments @return [Object] an Object
associated with the call @see async_call_pending?
# 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
@api private
Whether the async call is still pending @param [Object] call the Object
associated with the call @return [Boolean] true if the async call is pending,
false otherwise
# File lib/roby/interface/client.rb, line 350 def async_call_pending?(a_call) pending_async_calls.any? { |item| item.equal?(a_call) } end
@api private
Call a method on the interface or on one of the interface's subcommands
@param [Array<String>] path path to the subcommand. Empty means on
the interface object itself.
@param [Symbol] m command or action name. Actions
are always
formatted as action_name!
@param [Object] args the command or action arguments @return [Object] the command result, or – in the case of an
action -- the job ID for the newly created action
# 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 the communication channel
# File lib/roby/interface/client.rb, line 63 def close io.close end
Whether the communication channel to the server is closed
# File lib/roby/interface/client.rb, line 58 def closed? io.closed? end
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
@return [BatchContext]
# File lib/roby/interface/client.rb, line 520 def create_batch BatchContext.new(self) end
Enumerate the current jobs
# 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 an action by its name
This is a local operation using the information gathered at connection time
@param [String] name the name of the action to look for @return [Actions::Models::Action,nil]
# File lib/roby/interface/client.rb, line 84 def find_action_by_name(name) actions.find { |act| act.name == name } end
Finds all actions whose name matches a pattern
@param [#===] matcher the matching object (usually a Regexp or
String)
@return [Array<Actions::Models::Action>]
# File lib/roby/interface/client.rb, line 93 def find_all_actions_matching(matcher) actions.find_all { |act| matcher === act.name } end
Find all the jobs that match the given action name
@return [Array<Job>]
# 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
# File lib/roby/interface/client.rb, line 539 def find_subcommand_by_name(name) commands[name] end
Tests whether the interface has an action with that name
# File lib/roby/interface/client.rb, line 73 def has_action?(name) !!find_action_by_name(name) end
Whether some exception notifications have been queued
# File lib/roby/interface/client.rb, line 265 def has_exceptions? !exception_queue.empty? end
Whether some job progress information is currently queued
# File lib/roby/interface/client.rb, line 201 def has_job_progress? !job_progress_queue.empty? end
Whether some generic notifications have been queued
# File lib/roby/interface/client.rb, line 222 def has_notifications? !notification_queue.empty? end
Whether some UI events have been queued
# File lib/roby/interface/client.rb, line 243 def has_ui_event? !ui_event_queue.empty? end
# 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
Polls for new data on the IO
channel
@return [Object] a call reply @raise [ComError] if the link seem to be broken @raise [ProtocolError] if some errors happened when validating the
protocol
# 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
Remove and return the oldest exception notification
@return [(Integer,Array)] a unique and monotonically-increasing
message ID and the generic notification information as specified by (Interface#on_exception)
# File lib/roby/interface/client.rb, line 274 def pop_exception exception_queue.shift end
Remove and return the oldest job information message
@return [(Integer,Array)] a unique and monotonically-increasing
message ID and the arguments to job progress as specified on {Interface#on_job_notification}.
# File lib/roby/interface/client.rb, line 210 def pop_job_progress job_progress_queue.shift end
Remove and return the oldest generic notification message
@return [(Integer,Array)] a unique and monotonically-increasing
message ID and the generic notification information as specified by (Application#notify)
# File lib/roby/interface/client.rb, line 231 def pop_notification notification_queue.shift end
Remove the oldest UI event and return it
# File lib/roby/interface/client.rb, line 248 def pop_ui_event ui_event_queue.shift end
Send all commands gathered in a batch for processing on the remote server
@param [BatchContext] batch @return [Array] the return values of each of the calls gathered in
the batch
# 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
@api private
Process a message as received on {#io}
@return [Boolean] whether the message was a cycle_end message
# 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.message, (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
@api private
Remove and call the block of a pending async call
# 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
@api private
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.
# File lib/roby/interface/client.rb, line 260 def queue_exception(kind, error, tasks, job_ids) exception_queue.push [allocate_message_id, [kind, error, tasks, job_ids]] end
@api private
Push a job notification to {#job_progress_queue}
See the yield parameters of {Interface#on_job_notification} for the overall argument format.
# File lib/roby/interface/client.rb, line 196 def queue_job_progress(kind, job_id, job_name, *args) job_progress_queue.push [allocate_message_id, [kind, job_id, job_name, *args]] end
@api private
Push a generic notification to {#notification_queue}
# File lib/roby/interface/client.rb, line 217 def queue_notification(source, level, message) notification_queue.push [allocate_message_id, [source, level, message]] end
@api private
Push a UI event to {#ui_event_queue}
# File lib/roby/interface/client.rb, line 238 def queue_ui_event(event_name, *args) ui_event_queue.push [allocate_message_id, [event_name, *args]] end
# File lib/roby/interface/client.rb, line 535 def reload_actions @actions = call([], :reload_actions) end
Start the given job within the batch
@param [Symbol] action_name the action name @param [Hash<Symbol,Object>] arguments the action arguments
@raise [NoSuchAction] if the requested action does not exist
# 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
The underlying IO
object
# File lib/roby/interface/client.rb, line 68 def to_io io.to_io end
Wait until there is data to process on the IO
channel
@param [Numeric,nil] timeout a timeout after which the method
will return. Use nil for no timeout
@return [Boolean] falsy if the timeout was reached, true
otherwise
# File lib/roby/interface/client.rb, line 141 def wait(timeout: nil) io.read_wait(timeout: timeout) end