From 71c3d930d937fd2a1072a5a43d406bcd61a1bc9c Mon Sep 17 00:00:00 2001 From: Artur Mukhamadiev Date: Fri, 3 Jul 2026 01:47:34 +0300 Subject: [PATCH] feat(docs) added openwiki to the project :Release Notes: - :Detailed Notes: - :Testing Performed: - was not verified, to be fair :D :QA Notes: - generated by glm-5.2 :Issues Addressed: TG-3 --- AGENTS.md | 11 ++ openwiki/.last-update.json | 6 ++ openwiki/architecture.md | 158 +++++++++++++++++++++++++++++ openwiki/build-and-testing.md | 184 ++++++++++++++++++++++++++++++++++ openwiki/c-api.md | 127 +++++++++++++++++++++++ openwiki/quickstart.md | 66 ++++++++++++ openwiki/rpc-protocol.md | 166 ++++++++++++++++++++++++++++++ 7 files changed, 718 insertions(+) create mode 100644 openwiki/.last-update.json create mode 100644 openwiki/architecture.md create mode 100644 openwiki/build-and-testing.md create mode 100644 openwiki/c-api.md create mode 100644 openwiki/quickstart.md create mode 100644 openwiki/rpc-protocol.md diff --git a/AGENTS.md b/AGENTS.md index 2889a48..754ef8a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json new file mode 100644 index 0000000..6b7b170 --- /dev/null +++ b/openwiki/.last-update.json @@ -0,0 +1,6 @@ +{ + "updatedAt": "2026-07-02T22:45:41.669Z", + "command": "init", + "gitHead": "9de6f5a82d5e9cc1c1d52b60a5493f71ede6e700", + "model": "glm-5.2:cloud" +} diff --git a/openwiki/architecture.md b/openwiki/architecture.md new file mode 100644 index 0000000..a0eb457 --- /dev/null +++ b/openwiki/architecture.md @@ -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 header; +asio::read(socket, asio::buffer(header, header.size())); +uint64_t packet_size = deserialize(v); +std::vector 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>>` with periodic `remove_if`. +- The `RequestProcessor` (a `std::function`) 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` of registered methods. +- **Handler** type: `std::function(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(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. \ No newline at end of file diff --git a/openwiki/build-and-testing.md b/openwiki/build-and-testing.md new file mode 100644 index 0000000..bf5b314 --- /dev/null +++ b/openwiki/build-and-testing.md @@ -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 \ No newline at end of file diff --git a/openwiki/c-api.md b/openwiki/c-api.md new file mode 100644 index 0000000..e2746cf --- /dev/null +++ b/openwiki/c-api.md @@ -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> 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 \ No newline at end of file diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md new file mode 100644 index 0000000..78923bf --- /dev/null +++ b/openwiki/quickstart.md @@ -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. \ No newline at end of file diff --git a/openwiki/rpc-protocol.md b/openwiki/rpc-protocol.md new file mode 100644 index 0000000..74e612b --- /dev/null +++ b/openwiki/rpc-protocol.md @@ -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": "", + "params": {}, + "id": +} +``` + +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": , + "id": +} +``` + +### Error response + +```json +{ + "jsonrpc": "2.0", + "error": { + "code": , + "message": "" + }, + "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": , "id": 1} +``` + +Result type: `vector` (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": , "id": 2} +``` + +Result type: `vector` (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": }, "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 +}); +``` + +The handler returns `std::variant`. 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) → string` and `decode(string) → vector`. +- 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 +auto extrinsic = client.get_extrinsic_params(); // vector +auto cloud = client.get_cloud_point(); // vector> +``` + +The interactive CLI (`src/cli.cpp`) and `minimal_client` (`src/minimal_client.cpp`) demonstrate client usage. \ No newline at end of file