Java Client#

This client provides a Java API for interacting with a kRPC server. A jar containing the krpc.client package can be downloaded from GitHub. It requires Java 17 or later.

Getting Started#

The kRPC client library depends on the protobuf and javatuples libraries. A prebuilt jar for protobuf is available via Maven.

The following example program connects to the server, queries it for its version and prints it out:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.services.KRPC;

import java.io.IOException;

public class Basic {
  public static void main(String[] args) throws IOException, RPCException {
    try (Connection connection = Connection.newInstance()) {
      KRPC krpc = KRPC.newInstance(connection);
      System.out.println("Connected to kRPC version " + krpc.getStatus().getVersion());
    }
  }
}

To compile this program using javac on the command line, save the source as Example.java and run the following:

javac -cp krpc-java-0.7.0.jar:protobuf-java-4.36.2.jar:javatuples-1.2.jar Example.java

Note that you may need to change the paths to the JAR files.

Connecting to the Server#

To connect to a server, call Connection.newInstance() which returns 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:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.services.KRPC;

import java.io.IOException;

public class Connecting {
  public static void main(String[] args) throws IOException, RPCException {
    try (Connection connection = Connection.newInstance("Remote example", "my.domain.name", 1000, 1001)) {
      System.out.println(KRPC.newInstance(connection).getStatus().getVersion());
    }
  }
}

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 package krpc.client.services. For example, all of the functionality provided by the SpaceCenter service is contained in the class 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.getActiveVessel() 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:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.Vessel;

import java.io.IOException;

public class RemoteProcedures {
  public static void main(String[] args) throws IOException, RPCException {
    try (Connection connection = Connection.newInstance("Vessel Name")) {
      SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
      Vessel vessel = spaceCenter.getActiveVessel();
      System.out.println(vessel.getName());
    }
  }
}

Many procedures return an object identifying something in the game, such as a vessel or a part. See Object Lifetime for how such an object behaves when a game is loaded or what it identifies is destroyed.

Server Side Functions#

A server side function is code that runs inside the game, on the server. Connection.run evaluates one once and returns its value. Every value it reads comes from the same physics tick. For example, the following reads the vessel’s altitude and speed together:

import java.io.IOException;
import java.util.Arrays;
import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.services.KRPC.Expression;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.Flight;
import krpc.client.services.SpaceCenter.Vessel;
import krpc.schema.KRPC.ProcedureCall;
import org.javatuples.Pair;

public class RunFunction {
  public static void main(String[] args) throws IOException, RPCException {
    try (Connection connection = Connection.newInstance()) {
      Vessel vessel = SpaceCenter.newInstance(connection).getActiveVessel();
      Flight flight = vessel.flight(vessel.getOrbit().getBody().getReferenceFrame());

      // Get the remote procedure calls as message objects,
      // so they can be passed to the server
      ProcedureCall meanAltitude = connection.getCall(flight, "getMeanAltitude");
      ProcedureCall speed = connection.getCall(flight, "getSpeed");

      // Build a function that reads the altitude and the speed
      Expression expr = Expression.createTuple(connection, Arrays.asList(
          Expression.call(connection, meanAltitude),
          Expression.call(connection, speed)));

      // Run the function once, so both values come from the same physics tick
      Pair<Double, Double> result = connection.run(expr);
      System.out.println(
          "Altitude: " + result.getValue0() + " m, speed: " + result.getValue1() + " m/s");
    }
  }
}

Use run for a function with effects, such as setting a property. An event or a function stream evaluates its function on every update, repeating the effects.

The server side functions tutorial shows how to build larger functions.

Streaming Data#

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:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.services.KRPC;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.ReferenceFrame;
import krpc.client.services.SpaceCenter.Vessel;

import java.io.IOException;

public class Streaming1 {
  public static void main(String[] args) throws IOException, RPCException {
    try (Connection connection = Connection.newInstance()) {
      SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
      Vessel vessel = spaceCenter.getActiveVessel();
      ReferenceFrame refframe = vessel.getOrbit().getBody().getReferenceFrame();
      while (true) {
        System.out.println(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:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.Stream;
import krpc.client.StreamException;
import krpc.client.services.KRPC;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.ReferenceFrame;
import krpc.client.services.SpaceCenter.Vessel;

import org.javatuples.Triplet;

import java.io.IOException;

public class Streaming2 {
  public static void main(String[] args) throws IOException, RPCException, StreamException {
    try (Connection connection = Connection.newInstance()) {
      SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
      Vessel vessel = spaceCenter.getActiveVessel();
      ReferenceFrame refframe = vessel.getOrbit().getBody().getReferenceFrame();
      Stream<Triplet<Double,Double,Double>> vesselStream = connection.addStream(vessel, "position", refframe);
      while (true) {
        System.out.println(vesselStream.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 information about which method to stream. The example above passes a remote object, the name of the method to call, followed by the arguments to pass to the method (if any). Connection.addStream 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.

Note

A stream runs on the server until you remove it or the connection closes, even once the stream object is garbage collected. Each call to addStream adds another stream.

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 that is notified whenever the value of the stream changes, and when the stream ends. You can wait on it to block the current thread until one of these happens.

The following example waits until the abort button is pressed in game, by waiting for the value of krpc.client.services.SpaceCenter.Control.getAbort() to change to true:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.Stream;
import krpc.client.StreamException;
import krpc.client.services.KRPC;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.Control;

import java.io.IOException;

public class ConditionVariables {
  public static void main(String[] args) throws IOException, RPCException, StreamException {
    try (Connection connection = Connection.newInstance()) {
      SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
      Control control = spaceCenter.getActiveVessel().getControl();
      Stream<Boolean> abort = connection.addStream(control, "getAbort");
      synchronized (abort.getCondition()) {
        while (!abort.get()) {
          abort.waitForUpdate();
        }
      }
    }
  }
}

This code creates a stream, acquires a lock on the streams condition variable (by using a synchronized block) and then repeatedly checks the value of getAbort. It leaves the loop when it changes to true.

The body of the loop calls waitForUpdate 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.

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.getAbort() changes:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.Stream;
import krpc.client.StreamException;
import krpc.client.services.KRPC;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.Control;

import java.io.IOException;

public class Callbacks {
  public static void main(String[] args) throws IOException, RPCException, StreamException {
    try (Connection connection = Connection.newInstance()) {
      SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
      Control control = spaceCenter.getActiveVessel().getControl();
      Stream<Boolean> abort = connection.addStream(control, "getAbort");
      abort.addCallback(
        (Boolean x) -> {
          System.out.println("Abort 1 called with a value of " + x);
        });
      abort.addCallback(
        (Boolean x) -> {
          System.out.println("Abort 2 called with a value of " + x);
        });

      // Keep the program running...
      while (true) {
      }
    }
  }
}

A callback added to a stream that already has a value is called with that value straight away.

Note

The callback function may be called from a different thread than the one that created the stream. Any changes to shared state must therefore be protected with appropriate synchronization.

Stream Endings#

The following example streams the temperature of a part until the part is destroyed:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.Stream;
import krpc.client.StreamException;
import krpc.client.services.KRPC;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.Part;

import java.io.IOException;

public class StreamEnd {
  public static void main(String[] args) throws IOException, RPCException, StreamException {
    try (Connection connection = Connection.newInstance()) {
      SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
      Part part = spaceCenter.getActiveVessel().getParts().getRoot();
      Stream<Double> temperature = connection.addStream(part, "getTemperature");
      try {
        synchronized (temperature.getCondition()) {
          while (true) {
            temperature.waitForUpdate();
            System.out.println("Temperature " + temperature.get());
          }
        }
      } catch (KRPC.ObjectDestroyedException exn) {
        System.out.println("The part was destroyed");
      }
    }
  }
}

Once a stream ends, getting its value or waiting on it throws the exception that ended it. A stream ends when its procedure throws an exception, when the server removes it, when you remove it, or when the connection closes.

A stream that the server removes, such as an event that has finished, ends with KRPC.StreamRemovedException. A stream you remove ends with a StreamException. A closed connection ends it with a StreamException whose cause is a ConnectionException.

An exception for an object that is not loaded, or a procedure not available in the game scene, holds the stream instead. It resumes once the object or scene is available.

See also

Object Lifetime

To handle an ending in a callback, add an end callback with Stream.addEndCallback. It is called once, with the exception.

Streaming Server Side Functions#

Pass a server side function to Connection.addStream to stream its value. The server evaluates the function on each stream update, within one physics tick. The value can be any type the server can send, including collections and objects. For example, the following streams the vessel’s altitude in kilometers:

import java.io.IOException;
import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.Stream;
import krpc.client.StreamException;
import krpc.client.services.KRPC.Expression;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.Flight;

public class FunctionStream {
  public static void main(String[] args)
      throws IOException, RPCException, StreamException, InterruptedException {
    try (Connection connection = Connection.newInstance()) {
      SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
      Flight flight = spaceCenter.getActiveVessel().flight(null);

      // Build a function that computes
      // the vessel's altitude in kilometers
      Expression expr = Expression.divide(
          connection,
          Expression.call(connection, connection.getCall(flight, "getMeanAltitude")),
          Expression.constantDouble(connection, 1000));

      // Stream the value of the function
      Stream<Double> stream = connection.addStream(expr);
      while (true) {
        System.out.println("Altitude: " + stream.get() + " km");
        Thread.sleep(1000);
      }
    }
  }
}

The type parameter must be able to hold the function’s value. Otherwise the assignment of a value throws a ClassCastException.

A function stream or event ends on any exception its function throws. This includes KRPC.ObjectNotLoadedException, which a game load or an unloaded vessel causes. To keep running through a load, the function catches that exception with Expression.tryCatch.

Events#

Some procedures return event objects of type Event. These allow you to wait until an event occurs, by calling Event.waitFor. Under the hood, these are implemented using streams and condition variables.

A call to waitFor returns straight away if the event has fired since the previous call on the same event object, so no firing is missed between two calls. Several firings between two calls make waitFor return once.

You can also create a custom event from a server side function. The following builds the function MeanAltitude > 1000, and creates an event that triggers when the function returns true:

import krpc.client.Connection;
import krpc.client.RPCException;
import krpc.client.Event;
import krpc.client.StreamException;
import krpc.client.services.KRPC.Expression;
import krpc.client.services.SpaceCenter;
import krpc.client.services.SpaceCenter.Flight;
import krpc.schema.KRPC.ProcedureCall;

import java.io.IOException;

public class CustomEvent {
  public static void main(String[] args) throws IOException, RPCException, StreamException {
    try (Connection connection = Connection.newInstance()) {
      SpaceCenter spaceCenter = SpaceCenter.newInstance(connection);
      Flight flight = spaceCenter.getActiveVessel().flight(null);

      // Get the remote procedure call as a message object,
      // so it can be passed to the server
      ProcedureCall meanAltitude = connection.getCall(flight, "getMeanAltitude");

      // Build the function on the server
      Expression expr = Expression.greaterThan(
        connection,
        Expression.call(connection, meanAltitude),
        Expression.constantDouble(connection, 1000));

      Event event = connection.addEvent(expr);
      synchronized (event.getCondition()) {
        event.waitFor();
        System.out.println("Altitude reached 1000m");
      }
    }
  }
}

Client API Reference#

class Connection#

A connection to the kRPC server. All interaction with kRPC is performed via an instance of this class.

static Connection newInstance()#
static Connection newInstance(String name)#
static Connection newInstance(String name, String address)#
static Connection newInstance(String name, String address, int rpcPort, int streamPort)#
static Connection newInstance(String name, java.net.InetAddress address)#
static Connection newInstance(String name, java.net.InetAddress address, int rpcPort, int streamPort)#
static Connection newInstance(String name, String address, int rpcPort, int streamPort, java.time.Duration timeout)#
static Connection newInstance(String name, java.net.InetAddress address, int rpcPort, int streamPort, java.time.Duration timeout)#

Create a connection to the server, using the given connection details.

Parameters:
  • name (String) – A descriptive name for the connection. This is passed to the server and appears in the in-game server window.

  • address (String) – The address of the server to connect to. Can either be a hostname, an IP address as a string or a java.net.InetAddress object. Defaults to 127.0.0.1.

  • rpc_port (int) – 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 (int) – 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.

  • timeout (java.time.Duration) – How long to wait for a connection before giving up. Defaults to zero, which waits indefinitely. A network that drops a connection attempt rather than refusing it otherwise leaves the client waiting.

static Connection newLocalInstance()#
static Connection newLocalInstance(String name)#
static Connection newLocalInstance(String name, String rpcPath, String streamPath)#

Create a connection to a server running on the same machine, over unix domain sockets rather than TCP/IP. The connection behaves identically once established.

Unix domain sockets are available on Linux, macOS, and Windows 10 1803 and Windows Server 2019 or later.

Parameters:
  • name (String) – A descriptive name for the connection. This is passed to the server and appears in the in-game server window.

  • rpcPath (String) – The path of the socket the RPC Server is listening on. This should match the RPC socket path shown in the in-game server window.

  • streamPath (String) – The path of the socket the Stream Server is listening on. This should match the stream socket path shown in the in-game server window.

Stream<T> addStream(Class<?> clazz, String method, Object... args)#

Create a stream for a static method call to the given class.

Stream<T> addStream(RemoteObject instance, String method, Object... args)#

Create a stream for a method call to the given remote object.

Stream<T> addStream(KRPC.Expression function)#

Create a stream from a server side function. Each call creates a new stream. Throws a StreamException if the function has no value, and an IllegalArgumentException if it has parameters.

T run(KRPC.Expression function, Object... args)#

Run a server side function within a single physics tick, and return its value. The value is null for a function with no value. The arguments are passed to the function’s parameters, with a number widened as Java widens it. An int passes to an unsigned parameter bit for bit, and a long to an unsigned 64-bit parameter, as the client holds unsigned values. A byte or short that is not negative converts to an unsigned parameter, and an int that is not negative to an unsigned 64-bit parameter. Throws an IllegalArgumentException if the number of arguments differs from the number of parameters, or if a parameter cannot take its argument. The server checks a null for a parameter that is not nullable, and an object of another class, and these also throw an IllegalArgumentException.

Event addEvent(KRPC.Expression function)#

Create an event from a server side function that evaluates to a boolean value. Each call creates a new event. Throws an IllegalArgumentException if the function does not evaluate to a boolean value, or has parameters.

krpc.schema.KRPC.ProcedureCall getCall(Class<?> clazz, String method, Object... args)#

Returns a procedure call message for the given static method call. This allows descriptions of procedure calls to be passed to the server, for example when constructing custom events. See Events.

krpc.schema.KRPC.ProcedureCall getCall(RemoteObject instance, String method, Object... args)#

Returns a procedure call message for the given method call. This allows descriptions of procedure calls to be passed to the server, for example when constructing custom events. See Events.

void close()#

Close the connection.

class Stream<T>#

This class represents a stream. See Streaming Data.

Stream objects implement hashCode and equals such that two stream objects are equal if they are bound to the same stream on the server. Each call to Connection.addStream creates a new stream, so two streams added for the same call are not equal.

float getRate()#
void setRate(float rate)#

The update rate of the stream in Hertz. When set to zero, the rate is unlimited.

T get()#

Returns the most recent value for the stream, waiting for the first value if none has arrived yet. Throws the exception that ended the stream, if it has ended, such as an exception thrown by the remote procedure.

Object getCondition()#

A condition variable that is notified (using notifyAll) whenever the value of the stream changes, and when the stream ends.

void waitForUpdate()#
boolean waitForUpdateWithTimeout(double timeout)#

These methods block until the value of the stream changes or the operation times out. waitForUpdateWithTimeout returns true if the value changed, and false on a timeout.

The streams condition variable must be locked before calling this method.

If timeout is specified it is the timeout in seconds for the operation.

Throws the exception that ended the stream, if it ends before or during the wait.

int addCallback(java.util.function.Consumer<T> 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. If the stream already has a value, the callback is invoked with it straight away. An error ends the stream and does not invoke the callback. Calling get() throws it. 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 than the one that created the stream. Any changes to shared state must therefore be protected with appropriate synchronization.

void removeCallback(int tag)#

Removes a callback function from the stream. The tag is the identifier returned when the callback was added.

int addEndCallback(java.util.function.Consumer<Exception> callback)#

Adds a callback function that is invoked once when the stream ends. The callback function should take one argument, which is passed the exception that ended the stream. It is invoked straight away if the stream has already ended. Returns a unique identifier for the callback which can be used to remove it.

void removeEndCallback(int tag)#

Removes an end callback function from the stream. The tag is the identifier returned when the callback was added.

void remove()#

Remove the stream from the server. Other streams added for the same call keep running.

class Event#

This class represents an event. See Events. It is wrapper around a Stream that indicates when the event occurs.

Event objects implement hashCode and equals such that two event objects are equal if they are bound to the same underlying stream on the server.

Object getCondition()#

The condition variable that is notified (using notifyAll) whenever the event occurs, and when the event ends.

void waitFor()#
boolean waitForWithTimeout(double timeout)#

These methods block until the event occurs or the operation times out. They return straight away if the event has occurred since the previous call on this event object. waitForWithTimeout returns true if the event occurred, and false on a timeout.

The events condition variable must be locked before calling this method.

If timeout is specified it is the timeout in seconds for the operation.

Throws the exception that ended the event, if it ends before or during the wait.

int addCallback(java.lang.Runnable callback)#

Adds a callback function that is invoked whenever the event occurs. The callback function should be a function that takes zero arguments. If the event has occurred since the last wait on this event object, and no callback has been invoked for it, the callback is invoked straight away. A callback added after the event has ended is not invoked for an earlier firing. An error ends the event and does not invoke the callback. Returns an integer tag identifying the callback which can be used to remove it later.

void removeCallback(int tag)#

Removes a callback function from the event.

int addEndCallback(java.util.function.Consumer<Exception> callback)#

Adds a callback function that is invoked once when the event ends, with the exception that ended it. It is invoked straight away if the event has already ended. Returns an integer tag identifying the callback which can be used to remove it later.

void removeEndCallback(int tag)#

Removes an end callback function from the event. The tag is the identifier returned when the callback was added.

void remove()#

Removes the event from the server.

Stream<Boolean> getStream()#

Returns the underlying stream for the event.

abstract class RemoteObject#

The abstract base class for all remote objects.

static long getObjectId(RemoteObject obj)#

The identifier of a remote object on the server, e.g. to pass to krpc.client.services.KRPC.Expression.constantObject(Connection, long). Throws a NullPointerException if the object is null.