feat(docs) added openwiki to the project
All checks were successful
Verification / Is-Buildable (push) Successful in 3m8s

:Release Notes:
-

:Detailed Notes:
-

:Testing Performed:
- was not verified, to be fair :D

:QA Notes:
- generated by glm-5.2

:Issues Addressed:
TG-3
This commit is contained in:
Artur Mukhamadiev 2026-07-03 01:47:34 +03:00
parent 9de6f5a82d
commit 71c3d930d9
7 changed files with 718 additions and 0 deletions

View File

@ -143,3 +143,14 @@ class CameraController {
- **Verification:** Write unit tests for new features in `tests/`.
- **Refactoring:** When refactoring, ensure existing behavior is preserved via tests.
- **Dependencies:** Do not introduce new dependencies without updating `meson.build` and `subprojects/`.
## OpenWiki
This repository has documentation located in the /openwiki directory.
Start here:
- [OpenWiki quickstart](openwiki/quickstart.md)
OpenWiki includes repository overview, architecture notes, workflows, domain concepts, operations, integrations, testing guidance, and source maps.
When working in this repository, read the OpenWiki quickstart first, then follow its links to the relevant architecture, workflow, domain, operation, and testing notes.

View File

@ -0,0 +1,6 @@
{
"updatedAt": "2026-07-02T22:45:41.669Z",
"command": "init",
"gitHead": "9de6f5a82d5e9cc1c1d52b60a5493f71ede6e700",
"model": "glm-5.2:cloud"
}

158
openwiki/architecture.md Normal file
View File

@ -0,0 +1,158 @@
# 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:
1. **Unity side** initializes a `CloudPointServer` which starts a `TcpServer` awaiting connections.
2. **Client side** initializes a `CloudPointClient` which creates a `TCPConnector` and connects to the server.
3. When a **Caller** requests data, the client serializes a JSON-RPC request, sends it over TCP, and waits for the response.
4. The server reads the request, dispatches it to `RpcServer`, which calls the registered handler (either a C++ lambda or a C callback from Unity).
5. The handler result is serialized back as a JSON-RPC response and sent over TCP.
6. 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_t` in native byte order containing the payload size.
- **Payload**: exactly `payload_size` bytes of JSON-RPC text.
This is implemented in:
- **`inplace_size_embedding()`** (`include/cloud_point_rpc/serialize.hpp`): Prepends the serialized `uint64_t` size to a `std::string`. Used by `TcpServer::handle_client` and `TCPConnector::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.
```cpp
// 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 on `acceptor_.accept()`.
- Each client connection is handled in its own **`std::jthread`**; finished threads are cleaned up via a `std::list<pair<jthread, future<bool>>>` with periodic `remove_if`.
- The `RequestProcessor` (a `std::function<std::string(const std::string&)>`) is called for each incoming request — typically `RpcServer::process`.
- **`stop()`** unblocks the accept loop by first connecting a dummy socket to the listening endpoint (to avoid a race with `close()`), 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::JsonRpcClient` from the json-rpc-cxx submodule.
- Wraps a `TCPConnector` as the transport connector.
- Provides typed methods: `get_intrinsic_params()`, `get_extrinsic_params()`, `get_cloud_point()`.
- Template `call<ReturnType>(name)` wraps `CallMethod` with 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 calls `tcp_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.cpp` for the standalone mock server. The C API path does not use `Service` — Unity provides its own handlers via callbacks.
## Config
`include/cloud_point_rpc/config.hpp`
- `ConfigLoader::load(path)` parses a YAML file into a `Config` struct.
- `Config` contains `ServerConfig` (ip, port) and `TestData` (camera parameters).
- Falls back to `127.0.0.1:8080` if no `server` section is present.
- Sample config: `config.yml` (server IP `127.0.0.1`, port `9095`).
## 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 global `RpcServer` and `TcpServer` are guarded by `server_mtx`. The `rpc_string` garbage collector is guarded by `gc_mtx`.
- **Test API** (`test_api.cpp`): A `TestThread` runs a `std::jthread` with a condition-variable-driven loop that can auto-call registered methods at a configurable interval or process queued one-shot calls.

View File

@ -0,0 +1,184 @@
# Build & Testing
## Build system
The project uses **Meson** with **Ninja** and requires a **C++20** compiler (GCC or Clang). The root `meson.build` declares the project and dependencies; `src/meson.build` and `tests/meson.build` define build targets.
### Dependencies
All dependencies are resolved via Meson wrap files in `subprojects/` or system packages:
| Dependency | Wrap file | Purpose |
|---|---|---|
| nlohmann_json | `nlohmann_json.wrap` | JSON parsing |
| asio | `asio.wrap` | TCP networking (header-only) |
| glog | `glog.wrap` (CMake subproject) | Logging |
| yaml-cpp | `yaml-cpp.wrap` | Config file parsing |
| aklomp-base64 | `aklomp-base64.wrap` | Base64 encode/decode |
| GoogleTest | `gtest.wrap` | Unit testing (gtest + gmock) |
The `rpc/` directory is a git submodule pointing to [json-rpc-cxx](https://github.com/jsonrpcx/json-rpc-cxx), providing `jsonrpccxx/` headers used by the client. It is included via `include_directories` in the root `meson.build`.
### Build targets
Defined in `src/meson.build`:
**Shared libraries:**
| Library | Sources | Notes |
|---|---|---|
| `libcloud_point_rpc` | `rpc_coder.cpp`, `rpc_server.cpp`, `server_api.cpp`, `service.cpp` | Core library, installed with `install_rpath: '$ORIGIN'` |
| `libcloud_point_rpc_cli` | `cli.cpp` | CLI client logic |
| `test_cloud_point` | `test_api.cpp` | Test API library |
**Executables:**
| Executable | Source | Description |
|---|---|---|
| `cloud_point_rpc_server` | `server_main.cpp` | Standalone mock server |
| `cloud_point_rpc_cli` | `main.cpp` | Interactive CLI client |
| `minimal_client` | `minimal_client.cpp` | Minimal client sending a hardcoded `ping` |
### Linux build
```bash
git submodule init && git submodule update
meson setup build
meson compile -C build
```
Run the server:
```bash
./build/src/cloud_point_rpc_server config.yaml
```
Run the CLI client:
```bash
./build/src/cloud_point_rpc_cli config.yaml
```
### Windows build
Windows requires static linking and a Python venv for Meson:
```powershell
git submodule init
git submodule update
python3 -m venv .\venv
.\venv\Scripts\Activate.ps1
pip install meson cmake
meson setup -Ddefault_library=static build
meson compile -C build
# To get DLLs on PATH:
meson devenv -C build
```
The root `meson.build` adds a `devenv` on Windows that appends subproject DLL directories to `PATH` when `default_library=shared`. When building static, the build defines `BASE64_STATIC_DEFINE` and `YAML_CPP_STATIC_DEFINE`.
### Clean build
```bash
meson compile --clean -C build
```
## Configuration
The server reads a YAML config file. Sample: `config.yml`:
```yaml
server:
ip: "127.0.0.1"
port: 9095
```
Full config schema (parsed by `ConfigLoader` in `include/cloud_point_rpc/config.hpp`):
| Section | Field | Type | Default | Description |
|---|---|---|---|---|
| `server.ip` | string | `127.0.0.1` | Server bind address |
| `server.port` | int | `8080` | Server listen port |
| `test_data.intrinsic_params` | list of double | empty (fallback to identity 3×3) | Camera intrinsic parameters |
| `test_data.extrinsic_params` | list of double | empty (fallback to identity 4×4) | Camera extrinsic parameters |
| `test_data.cloud_point` | list of lists of double | empty (fallback to 3 sample points) | Point cloud data |
The `test_data` section is only used by the standalone mock server (`server_main.cpp`). The C API path (`crpc_init`) loads config for server address but does not use `test_data` — Unity provides its own handlers.
## Docker
The `Dockerfile` uses Ubuntu 24.04 and builds the CLI client. It installs build dependencies, copies the project, runs `meson setup build && meson compile -C build`, and starts the CLI by default.
```bash
docker build -t cloud-point-rpc .
docker run --network=host -it cloud-point-rpc
```
Mount a custom config:
```bash
docker run --network=host -it -v $(pwd)/my_config.yaml:/app/config.yaml cloud-point-rpc
```
> The server is not configured to run inside Docker — only the CLI client. The `--network=host` flag simplifies connectivity to a server running on the host.
## CI
`.gitea/workflows/test.yaml` defines a Gitea Actions workflow named "Verification" that runs on push to `master`:
1. Install build tools (cmake, make, ninja, gcc)
2. Install Meson via pip in a venv
3. Checkout with submodules
4. `meson setup build && meson compile -C build -j2`
5. `meson test -C build`
## Testing
All tests are in `tests/` and compiled into a single `unit_tests` executable (defined in `tests/meson.build`) linked against `cloud_point_rpc_dep`, `cloud_point_rpc_cli_dep`, `cloud_point_rpc_test_dep`, and GoogleTest/GMock.
### Run tests
```bash
meson test -C build # all tests
meson test -C build -v # verbose
meson test -C build unit_tests # explicit
```
### Test suites
| File | Area | Description |
|---|---|---|
| `test_rpc.cpp` | RPC server | Basic request/response, method dispatch |
| `test_rpc_edge_cases.cpp` | RPC server | Edge cases: invalid JSON, missing fields, non-object requests |
| `test_tcp.cpp` | TCP server | TCP connection and message round-trip |
| `test_tcp_edge_cases.cpp` | TCP server | TCP edge cases |
| `test_integration.cpp` | Integration | Full server+client stack with mock data, real TCP |
| `test_cli.cpp` | CLI | CLI client menu and output |
| `test_c_api.cpp` | C API | `crpc_test_*` functions, callback registration, auto-call |
| `test_c_api_edge_cases.cpp` | C API | Multiple methods, removal, scheduling |
| `test_base64.cpp` | Base64 | Encode/decode round-trip |
| `test_base64_edge_cases.cpp` | Base64 | Edge cases (empty input, binary with nulls) |
| `test_serialize.cpp` | Serialization | `serialize`/`deserialize` for numeric types, `inplace_size_embedding` |
| `test_service.cpp` | Service | Default fallbacks, configured data, empty data |
### Test conventions
- All test fixtures initialize Google Logging in `SetUp()` with `FLAGS_logtostderr = true`.
- Integration tests (`test_integration.cpp`) create a temporary `config.yaml`, start a real `TcpServer` in a thread, and connect via `TCPConnector`/`RpcClient`.
- C API tests use `crpc_test_init()` / `crpc_test_deinit()` and verify callback invocation via `std::promise`/`std::future`.
- The latest commit (`9de6f5a`) added `google::InitGoogleLogging` calls in test files to ensure logging is initialized before glog macros are used.
### Linting
The project uses clang-format with LLVM base style and 4-space indent (`.clang-format`):
```bash
ninja -C build clang-format
# or
find src include tests -name "*.cpp" -o -name "*.hpp" | xargs clang-format -i
```
## Source references
- `meson.build` — Root build config, dependency declarations
- `src/meson.build` — Library and executable targets
- `tests/meson.build` — Test executable definition
- `config.yml` — Sample config
- `Dockerfile` — Container build
- `.gitea/workflows/test.yaml` — CI pipeline
- `.clang-format` — Code formatting config

127
openwiki/c-api.md Normal file
View File

@ -0,0 +1,127 @@
# C API for Unity Integration
The C API allows Unity (or any C/C++ consumer) to embed the RPC server as a shared library, register custom RPC handlers as C function pointers, and manage the server lifecycle without touching C++ directly.
Two C API surfaces exist:
| API | Header | Library | Purpose |
|---|---|---|---|
| Server API | `include/server_api.h` | `libcloud_point_rpc` | Start/stop the TCP server, register RPC methods |
| Test API | `include/test_api.h` | `test_cloud_point` | Internal test harness: register methods, schedule calls, auto-call loop |
Both APIs use the `rpc_string` type and `callback_t` function pointer typedef.
## Export macros
`include/export.h` defines `CRPC_EXPORT`. On Windows, it resolves to `__declspec(dllexport)` when `CRPC_SERVER_API_EXPORT` is defined (set in `src/meson.build`) and `__declspec(dllimport)` otherwise. On GCC/Clang, it uses `__attribute__((visibility("default")))`. This allows the same headers to be used when building the library and when consuming it.
## `rpc_string`
Defined in `include/cloud_point_rpc/rpc_server.hpp` inside an `extern "C"` block:
```c
struct rpc_string {
std::string s; // C++ std::string, but the struct is C-ABI compatible
};
```
Although the struct contains a `std::string`, it is allocated and managed by the library. Consumers interact with it through opaque pointers and accessor functions:
| Function | Description |
|---|---|
| `crpc_str_create(data, size)` | Allocate a new `rpc_string` with the given data. Tracked by internal GC. |
| `crpc_str_destroy(ptr)` | Manually free a `rpc_string`. |
| `crpc_str_get_data(ptr)` | Get the raw C string pointer. |
| `crpc_str_get_size(ptr)` | Get the string length. |
### Garbage collector
`src/server_api.cpp` maintains a static `std::list<std::unique_ptr<rpc_string>> gc` protected by `gc_mtx`. All `crpc_str_create` allocations are tracked in this list. `crpc_deinit()` clears the entire list. `crpc_str_destroy` removes a specific entry. This prevents memory leaks if Unity forgets to call `destroy`, though manual destruction is recommended to avoid excessive memory usage.
> **Important**: `rpc_string` pointers returned from callbacks are owned by the library's GC. Do not `free()` them — use `crpc_str_destroy()`.
## Server API (`server_api.h`)
### Lifecycle
```c
// Initialize and start the server from a config file
crpc_init("config.yaml");
// Or initialize with an explicit address (no config file needed)
crpc_init_with_address("127.0.0.1", 9095);
// ... register methods and serve ...
// Stop server and free all GC-tracked rpc_strings
crpc_deinit();
```
- `crpc_init(config_path)` — Loads YAML config via `ConfigLoader`, creates a `TcpServer` with the configured IP/port, and starts it. Initializes Google Logging if not already initialized. See [Architecture → Config](architecture.md#config).
- `crpc_init_with_address(ip, port)` — Same but without a config file. Used when the consumer wants to set the address directly.
- `crpc_deinit()` — Stops the server (resets the `TcpServer` unique_ptr) and clears the `rpc_string` GC list.
### Registering methods
```c
rpc_string* my_handler(rpc_string* params_json) {
// params_json->s contains the JSON params as a string
// Build your result (JSON or raw string)
return crpc_str_create("{\"key\":\"value\"}", 15);
}
rpc_string method_name;
method_name.s = "my-method";
crpc_add_method(my_handler, &method_name);
```
- `callback_t` is `rpc_string* (*)(rpc_string*)` — a C function pointer.
- The callback receives the JSON `params` object serialized as a string in `rpc_string->s`.
- The callback returns a `rpc_string*` whose string is parsed as JSON if possible, or used as a raw string in the `result` field. See [RPC Protocol → Handler registration](rpc-protocol.md#handler-registration).
- `crpc_add_method` is guarded by `server_mtx` and registers the callback on the global `RpcServer`.
### Global state
`server_api.cpp` uses file-level statics: `rpc_server` (the global `RpcServer`), `server` (the `TcpServer` unique_ptr), `gc` (the string GC list), and two mutexes (`gc_mtx`, `server_mtx`). This means only one server instance is supported per process.
## Test API (`test_api.h`)
The test API is built into a separate shared library (`test_cloud_point`) and provides a `TestThread` class (in `src/test_api.cpp`) that runs a background `std::jthread` for testing registered methods without a real TCP connection.
### Lifecycle
```c
crpc_test_init(); // Start the test thread + Google Logging
// ... register methods, schedule calls ...
crpc_test_deinit(); // Stop thread, call crpc_deinit(), reset state
```
### Methods
| Function | Description |
|---|---|
| `crpc_test_add_method(cb, name)` | Register a method on the test `RpcServer`. Duplicates are ignored. |
| `crpc_test_remove_method(name)` | Remove a registered method. Returns 0 on success, -1 if not found. |
| `crpc_test_schedule_call(name)` | Enqueue a one-shot call to the named method (processed by the test thread). |
| `crpc_test_change_duration(ms)` | Set the auto-call sleep interval (default 50ms). |
| `crpc_test_duration()` | Get the current sleep interval. |
| `crpc_test_auto_call(state)` | Enable (1) or disable (0) auto-calling registered methods on each sleep cycle. |
### Test thread behavior
The `TestThread::routine()` loop:
1. If there are queued one-shot calls, process them (build a JSON-RPC request and call `server.process()`).
2. If auto-call is enabled and methods exist, call the next method in round-robin order.
3. If auto-call is enabled and the queue is empty, wait on a condition variable for the configured duration (or until stop is requested).
4. Stop when `jthread` stop is requested via `crpc_test_deinit()`.
> **Note**: The test API does **not** start a TCP server. It processes JSON-RPC requests directly through `RpcServer::process()`, logging results. It is designed for testing handler registration and callback behavior in C, as demonstrated in `tests/test_c_api.cpp` and `tests/test_c_api_edge_cases.cpp`.
## Source references
- `include/server_api.h` — Server C API declarations
- `src/server_api.cpp` — Server C API implementation, `rpc_string` GC
- `include/test_api.h` — Test C API declarations
- `src/test_api.cpp``TestThread` implementation and test C API
- `include/export.h``CRPC_EXPORT` macro
- `include/cloud_point_rpc/rpc_server.hpp``rpc_string` struct and `callback_t` typedef

66
openwiki/quickstart.md Normal file
View File

@ -0,0 +1,66 @@
# Cloud Point RPC — Quickstart
## What is this?
Cloud Point RPC is a **C++20 JSON-RPC 2.0** server and client implementation designed to bridge a C++ backend with a **Unity Scene** over TCP. The server exposes RPC methods that retrieve camera intrinsic/extrinsic parameters and point cloud data. A C API (`server_api.h`) allows Unity (or other C consumers) to embed the server, register custom RPC handlers, and manage the server lifecycle from native code.
The project is a work in progress: the server side with C-API is implemented, while the client side with OpenCV integration is still planned (see README TODO).
## Repository layout
| Path | Purpose |
|---|---|
| `include/cloud_point_rpc/` | C++ public headers: TCP server/client, RPC server/client, config, serialization, service, coder |
| `include/server_api.h` | C API for embedding the server in Unity/native consumers |
| `include/test_api.h` | C API for test-driven method registration and scheduled calls |
| `include/export.h` | Cross-platform shared-library export macros (`CRPC_EXPORT`) |
| `src/` | Implementation files and executable entrypoints |
| `tests/` | GTest/GMock unit and integration tests (single `unit_tests` executable) |
| `rpc/` | Git submodule — [json-rpc-cxx](https://github.com/jsonrpcx/json-rpc-cxx) providing `jsonrpccxx` headers |
| `subprojects/` | Meson wrap dependencies (asio, nlohmann_json, glog, yaml-cpp, base64, gtest) |
| `docs/` | PlantUML communication model diagram |
| `config.yml` | Sample server configuration (IP and port) |
| `Dockerfile` | Container image for the CLI client |
| `.gitea/workflows/test.yaml` | CI pipeline (build + test on push to master) |
## Build and run
```bash
git submodule init && git submodule update
meson setup build
meson compile -C build
```
Start the test server (uses mock camera data from `config.yml`):
```bash
./build/src/cloud_point_rpc_server config.yaml
```
Run the interactive CLI client:
```bash
./build/src/cloud_point_rpc_cli config.yaml
```
Run all tests:
```bash
meson test -C build -v
```
For Windows build instructions and Docker usage, see [Build & Testing](build-and-testing.md).
## Documentation sections
- [Architecture](architecture.md) — Layered design, TCP framing, threading model, communication flow
- [RPC Protocol](rpc-protocol.md) — JSON-RPC 2.0 methods, request/response format, error codes, Base64 encoding
- [C API](c-api.md) — C interface for Unity integration, `rpc_string` memory management, test API
- [Build & Testing](build-and-testing.md) — Meson build system, dependencies, config, Docker, CI, test suite overview
## Key concepts
- **Namespace**: All C++ code lives in `score` (renamed from `cloud_point_rpc` early in development).
- **Wire framing**: Every TCP message is prefixed with an 8-byte little-endian `uint64_t` payload size, then the JSON-RPC payload follows. See [Architecture → Wire framing](architecture.md#wire-framing).
- **Two server entrypoints**: `server_main.cpp` is a standalone executable with mock data; `server_api.cpp` provides the embeddable C API that Unity uses to start the server and register callbacks.
- **Base64**: Camera parameter arrays and point cloud data are Base64-encoded for ASCII-safe transport over JSON. Encoding/decoding is done on the Unity side per API.md.

166
openwiki/rpc-protocol.md Normal file
View File

@ -0,0 +1,166 @@
# RPC Protocol
## JSON-RPC 2.0
The server implements **JSON-RPC 2.0** over TCP with length-prefixed framing (see [Architecture → Wire framing](architecture.md#wire-framing)).
Batch requests are not supported — only single request objects are processed.
### Request format
```json
{
"jsonrpc": "2.0",
"method": "<method_name>",
"params": {},
"id": <integer|string>
}
```
The `params` field is accepted but currently ignored by all implemented methods. The `id` field is required; requests without it receive a `-32600 Invalid Request` error.
### Success response
```json
{
"jsonrpc": "2.0",
"result": <method_specific_result>,
"id": <matching_request_id>
}
```
### Error response
```json
{
"jsonrpc": "2.0",
"error": {
"code": <integer>,
"message": "<string>"
},
"id": <matching_request_id>
}
```
### Error codes
| Code | Meaning | When |
|---|---|---|
| `-32700` | Parse error | Request is not valid JSON |
| `-32600` | Invalid Request | Missing `jsonrpc`, `method`, or `id` fields, or `jsonrpc != "2.0"` |
| `-32601` | Method not found | No handler registered for the requested method name |
| `-32000` | Server error | Handler threw an exception |
> **Known issue**: Non-object JSON (arrays, strings, numbers, null) causes a `nlohmann::json::type_error` instead of a clean `-32600` response. See `tests/test_rpc_edge_cases.cpp`.
Source: `src/rpc_server.cpp``RpcServer::process()` and `create_error()`.
## Methods
### `get-intrinsic-params`
Retrieves intrinsic camera parameters as a flat 3×3 matrix (row-major, 9 doubles).
**Request:**
```json
{"jsonrpc": "2.0", "method": "get-intrinsic-params", "id": 1}
```
**Response:**
```json
{"jsonrpc": "2.0", "result": <base64-encoded-array>, "id": 1}
```
Result type: `vector<double>` (size 9), Base64-encoded.
### `get-extrinsic-params`
Retrieves extrinsic camera parameters as a flat 4×4 matrix (row-major, 16 doubles).
**Request:**
```json
{"jsonrpc": "2.0", "method": "get-extrinsic-params", "id": 2}
```
**Response:**
```json
{"jsonrpc": "2.0", "result": <base64-encoded-array>, "id": 2}
```
Result type: `vector<double>` (size 16), Base64-encoded.
### `get-cloud-point`
Retrieves the current field-of-view point cloud.
**Request:**
```json
{"jsonrpc": "2.0", "method": "get-cloud-point", "id": 3}
```
**Response:**
```json
{"jsonrpc": "2.0", "result": {"width": int, "height": int, "data": <base64-encoded-array>}, "id": 3}
```
Result type: `matrix WxH` (list of `[x, y, z]` points), Base64-encoded.
> **Note**: The standalone mock server (`server_main.cpp`) returns these as raw JSON arrays (not Base64-encoded) via `Service`. The Base64 encoding is expected to be implemented on the Unity side per `API.md`.
## Handler registration
Handlers are registered with `RpcServer::register_method()`. Two forms exist:
### C++ handler
```cpp
rpc_server.register_method("get-intrinsic-params", [&](const json& params) {
return service.get_intrinsic_params(); // returns vector<double>
});
```
The handler returns `std::variant<nlohmann::json, std::string>`. If a `json` is returned, it is placed directly in the `result` field. If a `std::string` is returned, it is placed as-is.
### C callback handler
```cpp
rpc_string* my_callback(rpc_string* params_json) {
// params_json->s contains the JSON params as a string
// return a result string (JSON or raw)
return crpc_str_create("result_data", 11);
}
rpc_server.register_method("my-method", my_callback);
```
The C callback receives the JSON `params` as a string in `rpc_string->s`. The return value's string is parsed as JSON if possible; otherwise it is used as a raw string in the `result` field.
Source: `src/rpc_server.cpp``register_method(name, callback_t)` overload.
## Base64 encoding
`include/cloud_point_rpc/rpc_coder.hpp`, `src/rpc_coder.cpp`
- `Base64RPCCoder` implements the `IRPCCoder` interface with `encode()` and `decode()` methods.
- Uses the [aklomp/base64](https://github.com/aklomp/base64) library (Meson wrap `subprojects/aklomp-base64.wrap`).
- `encode(vector<char>) → string` and `decode(string) → vector<char>`.
- Includes overflow protection: throws `std::length_error` if input exceeds safe size limits.
> Per `API.md`, Base64 encoding/decoding of camera data is expected to be done on the **Unity side**. The `Base64RPCCoder` class is available for C++ consumers but is not used by the mock server's response path.
## Client-side usage
`include/cloud_point_rpc/rpc_client.hpp`
`RpcClient` wraps `jsonrpccxx::JsonRpcClient` and provides typed access:
```cpp
score::TCPConnector connector(ip, port);
score::RpcClient client(connector);
auto intrinsic = client.get_intrinsic_params(); // vector<double>
auto extrinsic = client.get_extrinsic_params(); // vector<double>
auto cloud = client.get_cloud_point(); // vector<vector<double>>
```
The interactive CLI (`src/cli.cpp`) and `minimal_client` (`src/minimal_client.cpp`) demonstrate client usage.