Client libraries
Table of contents
The client libraries are designed with a compromise: similar naming and conventions, but adhering to the conventions of the respective language/platform. This page includes common use cases and examples for all the client libraries.
The client libraries are open-source and available on GitHub:
| Language | Package | Install |
|---|---|---|
| Python | PyPI | pip install inhumate-rti |
| TypeScript/JS | npm | npm install inhumate-rti |
| C# / .NET | NuGet | dotnet add package Inhumate.RTI |
| C++ | GitHub | See README |
For C# in the context of the Unity game engine, see the Unity integration instead.
Connecting to the RTI broker
All client libraries connect to the RTI broker over WebSocket. By default, they connect to ws://127.0.0.1:8000, which is the default address of a locally running RTI.
You can override the URL by passing it explicitly, or by setting the RTI_URL environment variable.
Clients try to maintain a persistent connection and should reconnect automatically when losing connection.
Auto-connect
By default, the client connects automatically when constructed. Pass connect: false (or the equivalent for your language) to disable this and connect manually later.
import inhumate_rti as RTI
# Auto-connects immediately (default)
rti = RTI.Client(application="My App")
# Or disable auto-connect
rti = RTI.Client(application="My App", connect=False)
rti.connect()
import * as RTI from "inhumate-rti"
// Auto-connects immediately (default)
const rti = new RTI.Client({ application: "My App" })
// Or disable auto-connect
const rti = new RTI.Client({ application: "My App", connect: false })
rti.connect()
using Inhumate.RTI;
// Auto-connects immediately (default)
var rti = new RTIClient { Application = "My App" };
// Or disable auto-connect
var rti = new RTIClient(connect: false) { Application = "My App" };
rti.Connect();
#include "inhumaterti.hpp"
// Auto-connects immediately (default)
inhumate::rti::RTIClient rti("My App");
// Or disable auto-connect
inhumate::rti::RTIClient rti("My App", false);
rti.Connect();
Waiting for connection
Since connecting is asynchronous, you often need to wait for the connection to be established before proceeding.
import inhumate_rti as RTI
rti = RTI.Client(application="My App")
# Block until connected
rti.wait_until_connected()
# Or use a callback
rti.on("connect", lambda: print("Connected!"))
You can also pass wait=True to the constructor to block until connected:
rti = RTI.Client(application="My App", wait=True)
# Already connected here
import * as RTI from "inhumate-rti"
const rti = new RTI.Client({ application: "My App" })
// Execute a callback once connected
rti.whenConnected(() => {
console.log("Connected!")
})
// Or listen for the connect event
rti.on("connect", () => {
console.log("Connected!")
})
using Inhumate.RTI;
var rti = new RTIClient { Application = "My App" };
// Block until connected
rti.WaitUntilConnected();
// Or use an event callback
rti.OnConnected += () => {
Console.WriteLine("Connected!");
};
#include "inhumaterti.hpp"
inhumate::rti::RTIClient rti("My App");
// Register a callback
rti.OnConnected([]() {
std::cout << "Connected!" << std::endl;
});
// Poll to process messages (required in C++)
while (true) {
rti.Poll();
}
Threading and polling
The Python and C# clients are multi-threaded by default — subscribe callbacks are called from a separate receive thread. The C++ client is single-threaded and requires you to call Poll() to process messages. The JavaScript/TypeScript client relies on the JavaScript event loop and requires no special handling.
In the default multi-threaded mode, the client runs the connection on a daemon thread and subscribe callbacks are called from that thread.
For applications that need single-threaded operation (e.g. game loops), pass a main_loop callback instead. The client will call your function repeatedly, with main_loop_idle_time seconds between calls:
import inhumate_rti as RTI
def main_loop():
if rti.connected:
rti.publish_text("foo", "bar")
rti = RTI.Client(
application="My App",
main_loop=main_loop,
main_loop_idle_time=1.0
)
rti.connect() # blocks, runs main_loop repeatedly
The JavaScript client uses the standard event loop — no threading or polling configuration is needed. Subscribe callbacks are called asynchronously as messages arrive.
import * as RTI from "inhumate-rti"
const rti = new RTI.Client({ application: "My App" })
rti.subscribeText("hello", (channel, message) => {
console.log(`Received: ${message}`)
})
In the default multi-threaded mode, subscribe callbacks are called from a separate receive thread.
For applications that need single-threaded operation, pass polling: true to the constructor and call Poll() to process messages on your own schedule:
using Inhumate.RTI;
var rti = new RTIClient(polling: true) { Application = "My App" };
rti.WaitUntilConnected();
var done = false;
rti.Subscribe("hello", (string channel, object message) => {
Console.WriteLine($"Received: {message}");
done = true;
});
while (!done) {
rti.Poll(100);
Thread.Sleep(10);
}
The C++ client is single-threaded. You must call Poll() regularly to process incoming messages and trigger callbacks. Use PollForever() for a simple blocking loop:
#include "inhumaterti.hpp"
inhumate::rti::RTIClient rti("My App");
rti.OnConnected([]() {
std::cout << "Connected!" << std::endl;
});
/* Process messages in your application loop */
while (running) {
rti.Poll();
}
/* Or block forever */
rti.PollForever();
Specifying a custom URL
To connect to an RTI broker running on a different host or port, pass the URL explicitly or set the RTI_URL environment variable.
rti = RTI.Client(application="My App", url="ws://192.168.1.100:8000")
const rti = new RTI.Client({ application: "My App", url: "ws://192.168.1.100:8000" })
var rti = new RTIClient(url: "ws://192.168.1.100:8000") { Application = "My App" };
inhumate::rti::RTIClient rti("My App", true, "ws://192.168.1.100:8000");
Disconnecting
rti.disconnect()
rti.disconnect()
rti.Disconnect();
rti.Disconnect();
Publish/subscribe text
The simplest way to send and receive data is as plain text strings on named channels.
import inhumate_rti as RTI
rti = RTI.Client(application="My App")
rti.wait_until_connected()
rti.subscribe_text("hello", lambda content: print(f"Received: {content}"))
rti.publish_text("hello", "Hello World!")
The handler can optionally receive the channel name as the first argument:
rti.subscribe_text("hello", lambda channel, content: print(f"{channel}: {content}"))
import * as RTI from "inhumate-rti"
const rti = new RTI.Client({ application: "My App" })
rti.subscribeText("hello", (channel, message) => {
console.log(`Received on ${channel}: ${message}`)
})
rti.whenConnected(() => {
rti.publishText("hello", "Hello World!")
})
using Inhumate.RTI;
var rti = new RTIClient { Application = "My App" };
rti.WaitUntilConnected();
rti.Subscribe("hello", (string channel, object message) => {
Console.WriteLine($"Received on {channel}: {message}");
});
rti.Publish("hello", "Hello World!");
#include "inhumaterti.hpp"
inhumate::rti::RTIClient rti("My App");
rti.Subscribe("hello", [](const std::string& channel, const std::string& message) {
std::cout << "Received on " << channel << ": " << message << std::endl;
});
rti.PollForever();
Publish/subscribe JSON
For structured data without a predefined schema, you can publish and subscribe JSON.
rti.subscribe_json("my/data", lambda data: print(data["temperature"]))
rti.publish_json("my/data", {"temperature": 22.5, "unit": "celsius"})
rti.subscribeJSON("my/data", (channel, data) => {
console.log(data.temperature)
})
rti.publishJSON("my/data", { temperature: 22.5, unit: "celsius" })
rti.SubscribeJson<Dictionary<string, object>>("my/data", (channel, data) => {
Console.WriteLine(data["temperature"]);
});
rti.PublishJson("my/data", new { temperature = 22.5, unit = "celsius" });
rti.Subscribe("my/data", [](const std::string& channel, const std::string& message) {
auto data = nlohmann::json::parse(message);
std::cout << data["temperature"] << std::endl;
});
rti.Publish("my/data", "{\"temperature\": 22.5, \"unit\": \"celsius\"}");
Publish/subscribe protobuf
For typed, efficient messaging, use Protocol Buffer messages. The RTI ships with a set of standard .proto definitions for common message types (entities, runtime control, commands, etc.) and corresponding pre-defined channel names.
import inhumate_rti as RTI
rti = RTI.Client(application="My App")
rti.wait_until_connected()
rti.subscribe(RTI.channel.entity, RTI.proto.Entity, lambda entity:
print(f"Entity: {entity.id}")
)
entity = RTI.proto.Entity()
entity.id = "player-1"
rti.publish(RTI.channel.entity, entity)
Standard channel names are in RTI.channel (e.g. RTI.channel.entity, RTI.channel.control, RTI.channel.commands). Standard protobuf message classes are in RTI.proto (e.g. RTI.proto.Entity, RTI.proto.Clients, RTI.proto.RuntimeControl).
import * as RTI from "inhumate-rti"
const rti = new RTI.Client({ application: "My App" })
rti.subscribe(RTI.channel.entity, RTI.proto.Entity, (entity) => {
console.log(`Entity: ${entity.id}`)
})
rti.whenConnected(() => {
rti.publish(RTI.channel.entity, RTI.proto.Entity, { id: "player-1" })
})
Standard channel names are in RTI.channel (e.g. RTI.channel.entity, RTI.channel.control, RTI.channel.commands). Standard protobuf message classes are in RTI.proto (e.g. RTI.proto.Entity, RTI.proto.Clients, RTI.proto.RuntimeControl).
using Inhumate.RTI;
using Inhumate.RTI.Proto;
var rti = new RTIClient { Application = "My App" };
rti.WaitUntilConnected();
rti.Subscribe<Entity>(RTIChannel.Entity, (channel, entity) => {
Console.WriteLine($"Entity: {entity.Id}");
});
rti.Publish(RTIChannel.Entity, new Entity { Id = "player-1" });
Standard channel names are in RTIChannel (e.g. RTIChannel.Entity, RTIChannel.Control, RTIChannel.Commands). Standard protobuf message classes are in the Inhumate.RTI.Proto namespace (e.g. Entity, Clients, RuntimeControl).
#include "inhumaterti.hpp"
inhumate::rti::RTIClient rti("My App");
rti.Subscribe<inhumate::rti::proto::Entity>(
inhumate::rti::ENTITY_CHANNEL,
[](const std::string& channel, const inhumate::rti::proto::Entity& entity) {
std::cout << "Entity: " << entity.id() << std::endl;
}
);
rti.PollForever();
Standard channel names are defined as constants in the inhumate::rti namespace (e.g. ENTITY_CHANNEL, CONTROL_CHANNEL, COMMANDS_CHANNEL). Standard protobuf message classes are in the inhumate::rti::proto namespace.