Class: KRPC::Client

Inherits:
Object
  • Object
show all
Includes:
Doc::SuffixMethods
Defined in:
lib/krpc/client.rb

Overview

A kRPC client, through which all Remote Procedure Calls are made. To make RPC calls client must first connect to server. This can be achieved by calling Client#connect or Client#connect! methods. Client object can connect and disconnect from the server many times during it’s lifetime. RPCs can be made by calling Client#execute_rpc method. After generating the services API (with Client#generate_services_api! call), RPCs can be also made using ‘client.service_name.procedure_name(parameter, …)`

### Example:

client = KRPC::Client.new(name: "my client").connect! # Notice that Client#connect! is shorthand for calling Client#connect and Client#generate_services_api! subsequently
ctrl = client.space_center.active_vessel.control
ctrl.activate_next_stage
ctrl.throttle = 1 # Full ahead!
client.close # Gracefully disconnect - and allow the spacecraft to crash ;)
client.connect do # Connect to server again
  client.space_center.active_vessel.control.throttle = 0 # Save the spacecraft from imminent destruction ;)
end # Gracefully disconnect

Constant Summary collapse

DEFAULT_NAME =
""

Constants included from Doc::SuffixMethods

Doc::SuffixMethods::DOCSTRING_SUFFIX, Doc::SuffixMethods::DOCSTRING_SUFFIX_REGEX

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Doc::SuffixMethods

included, #method_missing, #respond_to_missing?

Constructor Details

#initialize(name: DEFAULT_NAME, host: Connection::DEFAULT_SERVER_HOST, rpc_port: Connection::DEFAULT_SERVER_RPC_PORT, stream_port: Connection::DEFAULT_SERVER_STREAM_PORT) ⇒ Client

Create new Client object, optionally specifying IP address and port numbers on witch kRPC server is listening and the name for this client.



39
40
41
42
43
44
45
46
47
# File 'lib/krpc/client.rb', line 39

def initialize(name: DEFAULT_NAME, host: Connection::DEFAULT_SERVER_HOST, rpc_port: Connection::DEFAULT_SERVER_RPC_PORT, stream_port: Connection::DEFAULT_SERVER_STREAM_PORT)
  @name = name
  @rpc_connection = RPCConnection.new(name, host, rpc_port)
  @stream_connection = StreamConnection.new(rpc_connection, host, stream_port)
  @streams_manager = Streaming::StreamsManager.new(self)
  @services = {}
  @core = Services::Core.new(self)
  Doc.add_docstring_info(false, self.class, "core", return_type: @core.class, xmldoc: "<doc><summary>Core kRPC service, e.g. for querying for the available services. Most of this functionality is used internally by the Ruby client and therefore does not need to be used directly from application code. This service is hardcoded (in kRPC Ruby client) version of 'krpc' service, so 1) it is available even before the services API is generated, but 2) can be out of sync with 'krpc' service.</summary></doc>")
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class KRPC::Doc::SuffixMethods

Instance Attribute Details

#coreObject (readonly)

Returns the value of attribute core.



35
36
37
# File 'lib/krpc/client.rb', line 35

def core
  @core
end

#nameObject (readonly)

Returns the value of attribute name.



35
36
37
# File 'lib/krpc/client.rb', line 35

def name
  @name
end

#rpc_connectionObject (readonly)

Returns the value of attribute rpc_connection.



35
36
37
# File 'lib/krpc/client.rb', line 35

def rpc_connection
  @rpc_connection
end

#stream_connectionObject (readonly)

Returns the value of attribute stream_connection.



35
36
37
# File 'lib/krpc/client.rb', line 35

def stream_connection
  @stream_connection
end

#streams_managerObject (readonly)

Returns the value of attribute streams_manager.



35
36
37
# File 'lib/krpc/client.rb', line 35

def streams_manager
  @streams_manager
end

Instance Method Details

#build_exception(error) ⇒ Object

Build an exception from an PB::Error object.



167
168
169
170
171
172
# File 'lib/krpc/client.rb', line 167

def build_exception(error)
  msg = error.description
  msg = "#{error.service}.#{error.name}: #{msg}" unless error.field_empty?(:service) || error.field_empty?(:name)
  msg += "\nServer stack trace:\n#{error.stack_trace}" unless error.field_empty?(:stack_trace)
  RPCError.new(msg)
end

#build_procedure_call(service, procedure, args = [], kwargs = {}, param_names = [], param_types = [], param_default = []) ⇒ Object

Build an PB::ProcedureCall object.



153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/krpc/client.rb', line 153

def build_procedure_call(service, procedure, args=[], kwargs={}, param_names=[], param_types=[], param_default=[])
  begin
    raise(ArgumentError, "param_names and param_types should be equal length\n\tparam_names = #{param_names}\n\tparam_types = #{param_types}") unless param_names.length == param_types.length
    raise(ArgumentError, "param_names and param_default should be equal length\n\tparam_names = #{param_names}\n\tparam_default = #{param_default}") unless param_names.length == param_default.length
    required_params_count = param_default.take_while{|pd| pd == :no_default_value}.count
    raise ArgumentsNumberErrorSig.new(args.count, required_params_count..param_names.count) unless args.count <= param_names.count
    call_args = construct_arguments(args, kwargs, param_names, param_types, param_default, required_params_count)
  rescue ArgumentErrorSig => err
    raise err.with_signature(Doc.docstring_for_procedure(service, procedure, false))
  end
  PB::ProcedureCall.new(service: service, procedure: procedure, arguments: call_args)
end

#build_request(service, procedure, args = [], kwargs = {}, param_names = [], param_types = [], param_default = []) ⇒ Object

Build an PB::Request object.



147
148
149
150
# File 'lib/krpc/client.rb', line 147

def build_request(service, procedure, args=[], kwargs={}, param_names=[], param_types=[], param_default=[])
  call = build_procedure_call(service, procedure, args, kwargs, param_names, param_types, param_default)
  PB::Request.new(calls: [call])
end

#closeObject

Close connection to kRPC server. Returns ‘true` if the connection has closed or `false` if the client had been already disconnected.



73
74
75
76
77
78
# File 'lib/krpc/client.rb', line 73

def close
  streams_manager.remove_all_streams
  streams_manager.stop_streaming_thread
  stream_connection.close
  rpc_connection.close
end

#connect(&block) ⇒ Object

Connect to a kRPC server on the IP address and port numbers specified during this client object creation and return ‘self`. Calling this method while the client is already connected will raise an exception. If the block is given, then it’s called passing ‘self` and the connection to kRPC server is closed at the end of the block.



53
54
55
56
57
58
59
# File 'lib/krpc/client.rb', line 53

def connect(&block)
  rpc_connection.connect
  stream_connection.connect
  streams_manager.start_streaming_thread
  call_block_and_close(block) if block_given?
  self
end

#connect!(&block) ⇒ Object

Connect to a kRPC server, generate the services API and return ‘self`. Shorthand for calling Client#connect and Client#generate_services_api! subsequently. If the block is given, then it’s called passing ‘self` and the connection to kRPC server is closed at the end of the block.



64
65
66
67
68
69
# File 'lib/krpc/client.rb', line 64

def connect!(&block)
  connect
  generate_services_api!
  call_block_and_close(block) if block_given?
  self
end

#connected?Boolean

Returns ‘true` if the client is connected to a server, `false` otherwise.

Returns:



81
82
83
# File 'lib/krpc/client.rb', line 81

def connected?
  rpc_connection.connected?
end

#execute_rpc(service, procedure, args = [], kwargs = {}, param_names = [], param_types = [], param_default = [], return_type: nil) ⇒ Object

Execute an RPC.



132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/krpc/client.rb', line 132

def execute_rpc(service, procedure, args=[], kwargs={}, param_names=[], param_types=[], param_default=[], return_type: nil)
  send_request(service, procedure, args, kwargs, param_names, param_types, param_default)
  result = receive_result
  raise build_exception(result.error) unless result.field_empty? :error
  unless return_type.nil?
    Decoder.decode(result.value, return_type, self)
  else
    nil
  end
rescue IOError => e
  raise(Error, "RPC call attempt while not connected to a server -- call Client#connect first") if not connected?
  raise e
end

#generate_services_api!Object

Interrogates the server to find out what functionality it provides and dynamically creates all of the classes and methods that form the services API. For each service that server provides:

  1. Class ‘KRPC::Services::name here`, and module `KRPC::Gen::name here` are created.

  2. ‘KRPC::Gen::name here` module is filled with dynamically created classes.

  3. Those classes in turn are filled with dynamically created methods, which form the API for this service.

  4. Instance method ‘name here` is created in this client object that returns `KRPC::Services::name here` object. This object is entry point for accessing functionality provided by `name here` service.

Returns ‘self`. Invoking this method the second and subsequent times doesn’t regenerate the API. To regenerate the API create new Client object and call #generate_services_api! on it.

### Example

client = KRPC::Client.new(name: "my client").connect # Notice that it is 'Client#connect' being called, not 'Client#connect!'
sc = client.space_center # => Exception (undefined method "space_center")
client.generate_services_api!
sc = client.space_center # => KRPC::Services::SpaceCenter object
v  = sc.active_vessel    # => KRPC::Gen::SpaceCenter::Vessel object
v.mass                   # => {some number here}
client.close

Raises:



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/krpc/client.rb', line 108

def generate_services_api!
  return self if services_api_generated?
  raise(Error, "Can't generate the services API while not connected to a server -- call Client#connect! to connect to server and generate the services API in one call") if not connected?

  resp = core.get_services
  resp.services.each do |service_msg|
    service_class = Services.create_service(service_msg)
    method_name = service_class.class_name.underscore
    self.class.instance_eval do
      define_method method_name do
        @services[service_class.class_name] ||= service_class.new(self)
      end
    end
    Doc.add_docstring_info(false, self.class, method_name, return_type: service_class, xmldoc: service_msg.documentation)
  end
  self
end

#services_api_generated?Boolean

Returns ‘true` if the services API has been already generated, `false` otherwise.

Returns:



127
128
129
# File 'lib/krpc/client.rb', line 127

def services_api_generated?
  respond_to? :space_center or respond_to? :test_service
end