Repclient
Documentation Objective
This document provides an overview of how clients can authenticate to, send and receive data from a simulation.
Overview
The repclient (replication client) is a module responsible for authentication and communication with the simulation. The repclient code is provided as a part of the SDK and shared by all simulation clients.
Prerequisites
To start working with the repclient you need to have a client project set up, with aether-sdk directory imported into the project. The aether-sdk contains the repclient header repclient.hh
. You also need a running simulation project to connect to. This guide assumes you have the Aether\Example\PhysxExample\PhysicsDemo\Simulation
running and you have the Aether\Example\PhysxExample\PhysicsDemo\Client
code available (see Physics Demo).
First run
To see repclient in action, run the Aether\Example\PhysxExample\PhysicsDemo\Simulation
simulation and Aether\Example\PhysxExample\PhysicsDemo\Client
client.
A high level overview of the usage of repclient in the Physics demo client:
// Replication client header#include <aether/repclient.hh>int main() {// 1. Create repclientaether::replication::client repstate;// 2. Connect to simulationif (repstate.connect("aether-sdk.mshome.net", "8881") != 0) {fprintf(stderr, "Failed to connect to simulation\n");exit(EXIT_FAILURE);}// 3. Authenticateconst uint64_t player_id = 0;const std::array<unsigned char, 32> authentication_token{};repstate.authenticate_player_id_with_token(player_id, authentication_token);// game loopwhile (repstate.is_connected()) {// 4. poll available updatessize_t msg_size;while(void *msg = repstate.tick(&msg_size)) {// handle any received messages}// 5. send messages to the simulationstd::vector<unsigned char> buf;repstate.send(&buf.front(), buf.size());}return EXIT_SUCCESS;}
Repclient characteristics
In the case above, the object is used from a single thread, but the repclient is thread-safe so it’s possible to have separate threads handle sending and receiving of messages.
The repclient will buffer received messages until they’re consumed using the
repstate.tick(size_t*)
method. The size of the message in bytes is written to thesize_t
valued pointed to by the parameter. The returned pointer is valid until the next call to any non-const repclient function.In the case of a connection failure the repclient will not attempt to reconnect on its own.
The repclient communicates with the muxer using TCP/IP.
The exact binary protocol used for communication between simulation and repclient is simulation and netcode-specific. Aether Engine SDK provides utilities for implementing a protocol tailored to your simulation.
Configuration
Specifying the muxer hostname and port
The hostname and port of the muxer used by the client is specified in the constructor. The connection parameters for connecting to the muxer hosted in the SDK virtual machine are as follows:
auto repstate = std::make_unique<aether::replication::client>();{const auto ret = repstate->connect("aether-sdk.mshome.net", "8881");assert(ret == 0 && "Failed to connect to simulation");}
Authentication
If the muxer used by the simulation requires authentication, you need to configure the authentication on the client side too. To do this, call the authenticate_player_id_with_token
method which takes an authentication token:
// We assume the existance of get_authentication_token() which fetches// an authentication token which has been stored elsewhere.const std::array<unsigned char, 32> token = get_authentication_token();repstate.authenticate_player_id_with_token(player_id, token);
Recording Messages
You can configure repclient to record the messages received from the simulation, or to play the recording back, by using different constructors
Recording data received from a simulation to path/to/file.dump
:
auto repstate = std::make_unique<aether::replication::client>();{const auto ret = repstate->connect_record("aether-sdk.mshome.net", "8881","path/to/file.dump");assert(ret == 0 && "Failed to connect to simulation");}
Playing back a recording from path/to/file.dump
:
auto repstate = std::make_unique<aether::replication::client>();{const auto ret = repstate->connect_replay("path/to/file.dump");assert(ret == 0 && "Failed to connect to simulation");}
In playback mode the repclient doesn’t connect to a simulation, instead playing back the original recording. The delays between messages are preserved so that the recording plays back at the original speed.
Walkthrough for the Physx demo client
1. Initialization
// in physx_client.cppphysx_client::physx_client(const arguments &args) : token(args.token) {if (args.mode == arguments::client_mode::connect) {// Connect to host and port from parsed argumentsconst auto ret = repstate.connect(args.host.c_str(), args.port.c_str());if (ret != 0) {fprintf(stderr, "Couldn't connect to host: %s port: %s\n",args.host.c_str(), args.host.c_str());exit(EXIT_FAILURE);}}...authenticate();...
2. Authentication - initialise connection to the muxer
// in physx_client.cppvoid physx_client::authenticate() {// use the security token if providedif(token.has_value()) {repstate.authenticate_player_id_with_token(current_player, *token);} else {repstate.authenticate_player_id(current_player);}}
3. Send and receive updates in the client update loop
// in physx_client.cpp// client update loopbool physx_client::tick() {if (!repstate.is_connected()) {fprintf(stderr, "Disconnected from server so terminating client\n");exit(EXIT_SUCCESS);}/*...*/size_t msg_size;// process all the messages that arrived since last updatewhile (void *msg = repstate.tick(&msg_size)) {process_packet(msg, msg_size);}/* ... */}void physx_client::process_packet(uint64_t id, void *message_data, size_t count) {auto demarshaller = aether::trivial_marshalling<trivial_marshalling_traits>().create_demarshaller();// use the marshaling utilities from aether to decode the network packetconst auto ret = demarshaller.decode(message_data, count);assert(ret == 0 && "Failed to decode packet from simulation");const auto headers = demarshaller.get_worker_data();for(const auto &[id, header] : headers) {// process cell headers, each cell is a single worker}const auto message_entities = demarshaller.get_entities();for(const auto &entity : message_entities) {const vec3f position =protocol::base::net_decode_position_3f(entity.net_encoded_position);ui_point point;point.p = { position.x, position.y, position.z };// process entity updates received from the server// in this case we update the cache in the physx client}}