C# Client¶
This client provides a C# API for interacting with a kRPC server. It is distributed as an assembly
named KRPC.Client.dll
.
Installing the Library¶
The C# client can be installed using NuGet or downloaded from GitHub. The client is compatible with .NET 4.5+.
You also need to install Google.Protobuf using NuGet.
Note
The copy of Google.Protobuf.dll
in the GameData folder included with the kRPC server plugin
should not be used with the client library. It is a modified version to work within KSP.
Connecting to the Server¶
To connect to a server, create a Connection
object. All interaction with the server is done
via this object. When constructed without any arguments, it will connect to the local machine on the
default port numbers. You can specify different connection settings, and also a descriptive name for
the connection, as follows:
using System;
using System.Net;
using KRPC.Client;
using KRPC.Client.Services.KRPC;
class Program {
public static void Main() {
using (var connection = new Connection(
name: "My Example Program",
address: IPAddress.Parse("192.168.0.10"),
rpcPort: 1000,
streamPort: 1001)) {
var krpc = connection.KRPC();
Console.WriteLine(krpc.GetStatus().Version);
}
}
}
The Connection
object needs to be disposed of correctly when finished with, so that the
network connection it manages can be released. This can be done with a using
block (as in the
example above) or by calling Connection.Dispose
directly.
Calling Remote Procedures¶
The kRPC server provides procedures that a client can run. These procedures are arranged in groups
called services to keep things organized. The functionality for the services are defined in the
namespace KRPC.Client.Services.*
. For example, all of the functionality provided by the
SpaceCenter
service is contained in the namespace KRPC.Client.Services.SpaceCenter
.
To interact with a service, you must first instantiate it. You can then call its methods and
properties to invoke remote procedures. The following example demonstrates how to do this. It
instantiates the SpaceCenter
service and calls
KRPC.Client.Services.SpaceCenter.SpaceCenter.ActiveVessel
to get an object representing the
active vessel (of type KRPC.Client.Services.SpaceCenter.Vessel
). It sets the name of the
vessel and then prints out its altitude:
using System;
using KRPC.Client;
using KRPC.Client.Services.SpaceCenter;
class Program {
public static void Main() {
using (var connection = new Connection()) {
var spaceCenter = connection.SpaceCenter();
var vessel = spaceCenter.ActiveVessel;
vessel.Name = "My Vessel";
var flightInfo = vessel.Flight();
Console.WriteLine(flightInfo.MeanAltitude);
}
}
}
Streaming Data from the Server¶
A common use case for kRPC is to continuously extract data from the game. The naive approach to do this would be to repeatedly call a remote procedure, such as in the following which repeatedly prints the position of the active vessel:
using System;
using KRPC.Client;
using KRPC.Client.Services.SpaceCenter;
class Program {
public static void Main() {
var connection = new Connection();
var spaceCenter = connection.SpaceCenter();
var vessel = spaceCenter.ActiveVessel;
var refFrame = vessel.Orbit.Body.ReferenceFrame;
while (true)
Console.WriteLine(vessel.Position(refFrame));
}
}
This approach requires significant communication overhead as request/response messages are repeatedly sent between the client and server. kRPC provides a more efficient mechanism to achieve this, called streams.
A stream repeatedly executes a procedure on the server (with a fixed set of argument values) and sends the result to the client. It only requires a single message to be sent to the server to establish the stream, which will then continuously send data to the client until the stream is closed.
The following example does the same thing as above using streams:
using System;
using KRPC.Client;
using KRPC.Client.Services.SpaceCenter;
class Program {
public static void Main() {
var connection = new Connection();
var spaceCenter = connection.SpaceCenter();
var vessel = spaceCenter.ActiveVessel;
var refFrame = vessel.Orbit.Body.ReferenceFrame;
var posStream = connection.AddStream(() => vessel.Position(refFrame));
while (true)
Console.WriteLine(posStream.Get());
}
}
It calls Connection.AddStream
once at the start of the program to create the stream, and
then repeatedly prints the position returned by the stream. The stream is automatically closed when
the client disconnects.
A stream can be created for any method call by calling Connection.AddStream
and passing it
a lambda expression that invokes the desired method. This lambda expression must take zero arguments
and be either a method call expression or a parameter call expression. It returns a stream object of
type Stream
. The most recent value of the stream can be obtained by calling
Stream.Get
. A stream can be stopped and removed from the server by calling
Stream.Remove
on the stream object. All of a clients streams are automatically stopped when
it disconnects.
Synchronizing with Stream Updates¶
A common use case for kRPC is to wait until the value returned by a method or attribute changes, and then take some action. kRPC provides two mechanisms to do this efficiently: condition variables and callbacks.
Condition Variables¶
Each stream has a condition variable associated with it, that is notified whenever the value of the stream changes. These can be used to block the current thread of execution until the value of the stream changes.
The following example waits until the abort button is pressed in game, by waiting for the value of
KRPC.Client.Services.SpaceCenter.Control.Abort
to change to true:
using System;
using KRPC.Client;
using KRPC.Client.Services.SpaceCenter;
class Program {
public static void Main() {
var connection = new Connection();
var spaceCenter = connection.SpaceCenter();
var control = spaceCenter.ActiveVessel.Control;
var abort = connection.AddStream(() => control.Abort);
lock (abort.Condition) {
while (!abort.Get())
abort.Wait();
}
}
}
This code creates a stream, acquires a lock on the streams condition variable (by using a lock
statement) and then repeatedly checks the value of Abort
. It leaves the loop when it changes to
true.
The body of the loop calls Wait
on the stream, which causes the program to block until the value
changes. This prevents the loop from ‘spinning’ and so it does not consume processing resources
whilst waiting.
Note
The stream does not start receiving updates until the first call to Wait
. This means that the
example code will not miss any updates to the streams value, as it will have already locked the
condition variable before the first stream update is received.
Callbacks¶
Streams allow you to register callback functions that are called whenever the value of the stream changes. Callback functions should take a single argument, which is the new value of the stream, and should return nothing.
For example the following program registers two callbacks that are invoked when the value of
KRPC.Client.Services.SpaceCenter.Control.Abort
changes:
using System;
using KRPC.Client;
using KRPC.Client.Services.SpaceCenter;
class Program {
public static void Main() {
var connection = new Connection();
var spaceCenter = connection.SpaceCenter();
var control = spaceCenter.ActiveVessel.Control;
var abort = connection.AddStream(() => control.Abort);
abort.AddCallback(
(bool x) => {
Console.WriteLine("Abort 1 called with a value of " + x);
});
abort.AddCallback(
(bool x) => {
Console.WriteLine("Abort 2 called with a value of " + x);
});
abort.Start();
// Keep the program running...
while (true) {
}
}
}
Note
When a stream is created it does not start receiving updates until Start
is called. This is
implicitly called when accessing the value of a stream, but as this example does not do this an
explicit call to Start
is required.
Note
The callbacks are registered before the call to Start
so that stream updates are not missed.
Note
The callback function may be called from a different thread to that which created the stream. Any changes to shared state must therefore be protected with appropriate synchronization.
Custom Events¶
Some procedures return event objects of type Event
. These allow you to wait until an event
occurs, by calling Event.Wait
. Under the hood, these are implemented using streams and
condition variables.
Custom events can also be created. An expression API allows you to create code that runs on the
server and these can be used to build a custom event. For example, the following creates the
expression MeanAltitude > 1000
and then creates an event that will be triggered when the
expression returns true:
using System;
using KRPC.Client;
using KRPC.Client.Services.KRPC;
using KRPC.Client.Services.SpaceCenter;
class Program {
public static void Main() {
var connection = new Connection();
var krpc = connection.KRPC();
var spaceCenter = connection.SpaceCenter();
var flight = spaceCenter.ActiveVessel.Flight();
// Get the remote procedure call as a message object,
// so it can be passed to the server
var meanAltitude = Connection.GetCall(() => flight.MeanAltitude);
// Create an expression on the server
var expr = Expression.GreaterThan(connection,
Expression.Call(connection, meanAltitude),
Expression.ConstantDouble(connection, 1000));
var evnt = krpc.AddEvent(expr);
lock (evnt.Condition) {
evnt.Wait();
Console.WriteLine("Altitude reached 1000m");
}
}
}
Client API Reference¶
- class IConnection¶
Interface implemented by the
Connection
class.
- class Connection¶
A connection to the kRPC server. All interaction with kRPC is performed via an instance of this class.
- Connection (String name = "", Net.IPAddress address = null, Int32 rpcPort = 50000, Int32 streamPort = 50001)¶
Connect to a kRPC server.
- Parameters:
name – A descriptive name for the connection. This is passed to the server and appears in the in-game server window.
address – The address of the server to connect to. Defaults to 127.0.0.1.
rpc_port – The port number of the RPC Server. Defaults to 50000. This should match the RPC port number of the server you want to connect to.
stream_port – The port number of the Stream Server. Defaults to 50001. This should match the stream port number of the server you want to connect to.
- Stream<ReturnType> AddStream<ReturnType> (LambdaExpression expression)¶
Create a new stream from the given lambda expression.
- KRPC.Schema.KRPC.ProcedureCall GetCall (LambdaExpression expression)¶
Returns a procedure call message for the given lambda expression. This allows descriptions of procedure calls to be passed to the server, for example when constructing custom events. See Custom Events.
- void Dispose ()¶
Closes the connection and frees the resources associated with it.
- class Stream<ReturnType>¶
This class represents a stream. See Streaming Data from the Server.
Stream objects implement
GetHashCode
,Equals
,operator ==
andoperator !=
such that two stream objects are equal if they are bound to the same stream on the server.- void Start (Boolean wait = true)¶
Starts the stream. When a stream is created by calling
Connection.AddStream
it does not start sending updates to the client until this method is called.If wait is true, this method will block until at least one update has been received from the server.
If wait is false, the method starts the stream and returns immediately. Subsequent calls to
Get
may raise anInvalidOperationException
if the stream does not yet contain a value.
- Single Rate { get; set; }¶
The update rate of the stream in Hertz. When set to zero, the rate is unlimited.
- ReturnType Get ()¶
Returns the most recent value for the stream. If executing the remote procedure for the stream throws an exception, calling this method will rethrow the exception. Raises an
InvalidOperationException
if no update has been received from the server.If the stream has not been started this method calls
Start(true)
to start the stream and wait until at least one update has been received.
- object Condition { get; }¶
A condition variable that is notified (using
Monitor.PulseAll
) whenever the value of the stream changes.
- void Wait (Double timeout = -1)¶
This method blocks until the value of the stream changes or the operation times out.
The streams condition variable must be locked before calling this method.
If timeout is specified and is greater than or equal to 0, it is the timeout in seconds for the operation.
If the stream has not been started this method calls
Start(false)
to start the stream (without waiting for at least one update to be received).
- Int32 AddCallback (Action<ReturnType> callback)¶
Adds a callback function that is invoked whenever the value of the stream changes. The callback function should take one argument, which is passed the new value of the stream. Returns a unique identifier for the callback which can be used to remove it.
Note
The callback function may be called from a different thread to that which created the stream. Any changes to shared state must therefore be protected with appropriate synchronization.
- void RemoveCallback (Int32 tag)¶
Removes a callback from the stream. The tag is the identifier returned when the callback was added.
- void Remove ()¶
Removes the stream from the server.
- class Event¶
This class represents an event. See Custom Events. It is wrapper around a Boolean that indicates when the event occurs.
Event objects implement
GetHashCode
,Equals
,operator ==
andoperator !=
such that two event objects are equal if they are bound to the same underlying stream on the server.- void Start ()¶
Starts the event. When an event is created, it will not receive updates from the server until this method is called.
- object Condition { get; }¶
The condition variable that is notified (using
Monitor.PulseAll
) whenever the event occurs.
- void Wait (Double timeout = -1)¶
This method blocks until the event occurs or the operation times out.
The events condition variable must be locked before calling this method.
If timeout is specified and is greater than or equal to 0, it is the timeout in seconds for the operation.
If the event has not been started this method calls
Start()
to start the underlying stream.
- Int32 AddCallback (Action callback)¶
Adds a callback function that is invoked whenever the event occurs. The callback function should be a function that takes zero arguments. Returns a unique identifier for the callback which can be used to remove it.
- void RemoveCallback (Int32 tag)¶
Removes a callback from the event. The tag is the identifier returned when the callback was added.
- void Remove ()¶
Removes the event from the server.