:Release Notes: - :Detailed Notes: - :Testing Performed: - was not verified, to be fair :D :QA Notes: - generated by glm-5.2 :Issues Addressed: TG-3
9.2 KiB
Architecture
Overview
The system follows a layered architecture for JSON-RPC 2.0 communication over TCP:
┌──────────────────────────────────────────────────────┐
│ Application / Unity │
│ (registers callbacks via C API or server_main.cpp) │
├──────────────────────────────────────────────────────┤
│ RPC Layer │
│ RpcServer (method dispatch, JSON-RPC 2.0 handling) │
│ RpcClient (typed method calls via jsonrpccxx) │
├──────────────────────────────────────────────────────┤
│ Service Layer │
│ Service (mock camera data: intrinsics, extrinsics, │
│ cloud points) │
├──────────────────────────────────────────────────────┤
│ Transport Layer │
│ TcpServer (accept loop, per-client threads) │
│ TCPConnector (client-side connector for jsonrpccxx) │
│ tcp_read (framed read with size prefix) │
├──────────────────────────────────────────────────────┤
│ Serialization │
│ serialize.hpp (size embedding, numeric (de)serialize)│
│ rpc_coder (Base64 encode/decode) │
├──────────────────────────────────────────────────────┤
│ Config │
│ ConfigLoader (YAML → ServerConfig + TestData) │
└──────────────────────────────────────────────────────┘
All C++ code lives in the score namespace. The rpc/ git submodule provides jsonrpcxx headers used by the client side.
Communication model
The PlantUML diagram at docs/communication_model.pu (rendered as docs/cm.png) describes the interaction flow:
- Unity side initializes a
CloudPointServerwhich starts aTcpServerawaiting connections. - Client side initializes a
CloudPointClientwhich creates aTCPConnectorand connects to the server. - When a Caller requests data, the client serializes a JSON-RPC request, sends it over TCP, and waits for the response.
- The server reads the request, dispatches it to
RpcServer, which calls the registered handler (either a C++ lambda or a C callback from Unity). - The handler result is serialized back as a JSON-RPC response and sent over TCP.
- The client receives the response and returns typed data to the caller.
The Unity integration model uses a static queue: the server enqueues tasks, and the Unity main loop dequeues and executes them, then sets the return value. This avoids calling Unity APIs from non-main threads.
Wire framing
Every TCP message (both directions) uses a simple length-prefixed framing protocol:
- Header: 8 bytes — a
uint64_tin native byte order containing the payload size. - Payload: exactly
payload_sizebytes of JSON-RPC text.
This is implemented in:
inplace_size_embedding()(include/cloud_point_rpc/serialize.hpp): Prepends the serializeduint64_tsize to astd::string. Used byTcpServer::handle_clientandTCPConnector::Send.tcp_read()(include/cloud_point_rpc/tcp_read.hpp): Reads the 8-byte header, deserializes the size, then reads the full payload — continuing to read if the payload arrives in multiple TCP segments.
// Sending: embed size before the JSON payload
std::string response = processor_(payload);
response += "\n";
inplace_size_embedding(response);
asio::write(*socket, asio::buffer(response));
// Receiving: read 8-byte header, then full payload
std::array<char, 8> header;
asio::read(socket, asio::buffer(header, header.size()));
uint64_t packet_size = deserialize<uint64_t>(v);
std::vector<char> payload(packet_size);
asio::read(socket, asio::buffer(payload));
TcpServer
include/cloud_point_rpc/tcp_server.hpp
- Accepts connections on a configurable IP/port using asio.
- Runs an accept thread (
std::jthread) that blocks onacceptor_.accept(). - Each client connection is handled in its own
std::jthread; finished threads are cleaned up via astd::list<pair<jthread, future<bool>>>with periodicremove_if. - The
RequestProcessor(astd::function<std::string(const std::string&)>) is called for each incoming request — typicallyRpcServer::process. stop()unblocks the accept loop by first connecting a dummy socket to the listening endpoint (to avoid a race withclose()), then closing the acceptor.- Thread safety:
cliThrMtx_guards the client thread list;acceptorMtx_guards the acceptor.
Key source: include/cloud_point_rpc/tcp_server.hpp
RpcServer
include/cloud_point_rpc/rpc_server.hpp, src/rpc_server.cpp
- Maintains a
std::map<std::string, Handler>of registered methods. - Handler type:
std::function<std::variant<nlohmann::json, std::string>(const nlohmann::json&)>— handlers can return either a JSON object or a raw string. - Supports two registration overloads:
register_method(name, Handler)— for C++ lambdas/functors.register_method(name, callback_t)— for C function pointers (rpc_string* (*)(rpc_string*)). The C callback receives the JSON params as a string and returns a string that is parsed as JSON if possible, or kept as a raw string otherwise.
process(request_str)parses the JSON-RPC 2.0 request, validates required fields (jsonrpc,method,id), dispatches to the handler, and builds the response. Returns JSON-RPC error objects for parse errors (-32700), invalid requests (-32600), method not found (-32601), and server errors (-32000).
Known issue
Non-object JSON requests (arrays, strings, numbers, null) throw nlohmann::json::type_error instead of returning a -32600 Invalid Request error. This is documented in tests/test_rpc_edge_cases.cpp.
RpcClient
include/cloud_point_rpc/rpc_client.hpp
- Extends
jsonrpccxx::JsonRpcClientfrom the json-rpc-cxx submodule. - Wraps a
TCPConnectoras the transport connector. - Provides typed methods:
get_intrinsic_params(),get_extrinsic_params(),get_cloud_point(). - Template
call<ReturnType>(name)wrapsCallMethodwith an auto-incrementing request ID.
TCPConnector
include/cloud_point_rpc/tcp_connector.hpp
- Implements
jsonrpccxx::IClientConnector. - On construction, opens a TCP connection to the server.
Send(request)embeds the size prefix, writes the full message, then callstcp_read()to receive the response.
Service
include/cloud_point_rpc/service.hpp, src/service.cpp
- Holds
TestData(intrinsic params, extrinsic params, cloud point). - Returns configured data if available, otherwise returns identity-matrix fallbacks.
- Used by
server_main.cppfor the standalone mock server. The C API path does not useService— Unity provides its own handlers via callbacks.
Config
include/cloud_point_rpc/config.hpp
ConfigLoader::load(path)parses a YAML file into aConfigstruct.ConfigcontainsServerConfig(ip, port) andTestData(camera parameters).- Falls back to
127.0.0.1:8080if noserversection is present. - Sample config:
config.yml(server IP127.0.0.1, port9095).
Executables
| Executable | Source | Description |
|---|---|---|
cloud_point_rpc_server |
src/server_main.cpp |
Standalone server with mock Service data |
cloud_point_rpc_cli |
src/main.cpp |
Interactive CLI client (menu-driven) |
minimal_client |
src/minimal_client.cpp |
Minimal client that sends a hardcoded ping request |
Shared libraries
| Library | Sources | Description |
|---|---|---|
libcloud_point_rpc |
rpc_coder.cpp, rpc_server.cpp, server_api.cpp, service.cpp |
Core RPC + server + config |
libcloud_point_rpc_cli |
cli.cpp |
CLI client logic (links against core lib) |
test_cloud_point |
test_api.cpp |
Test API library for method scheduling and auto-calling |
Threading model
- Server: Accept thread + per-client handler threads (all
std::jthread). Each client handler reads one request, processes it, sends the response, and exits. The server does not keep connections open for multiple round-trips per thread. - C API server (
server_api.cpp): The globalRpcServerandTcpServerare guarded byserver_mtx. Therpc_stringgarbage collector is guarded bygc_mtx. - Test API (
test_api.cpp): ATestThreadruns astd::jthreadwith a condition-variable-driven loop that can auto-call registered methods at a configurable interval or process queued one-shot calls.