feat(docs) added openwiki to the project
All checks were successful
Verification / Is-Buildable (push) Successful in 3m8s
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:
parent
9de6f5a82d
commit
71c3d930d9
11
AGENTS.md
11
AGENTS.md
@ -143,3 +143,14 @@ class CameraController {
|
|||||||
- **Verification:** Write unit tests for new features in `tests/`.
|
- **Verification:** Write unit tests for new features in `tests/`.
|
||||||
- **Refactoring:** When refactoring, ensure existing behavior is preserved via tests.
|
- **Refactoring:** When refactoring, ensure existing behavior is preserved via tests.
|
||||||
- **Dependencies:** Do not introduce new dependencies without updating `meson.build` and `subprojects/`.
|
- **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.
|
||||||
|
|||||||
6
openwiki/.last-update.json
Normal file
6
openwiki/.last-update.json
Normal 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
158
openwiki/architecture.md
Normal 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.
|
||||||
184
openwiki/build-and-testing.md
Normal file
184
openwiki/build-and-testing.md
Normal 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
127
openwiki/c-api.md
Normal 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
66
openwiki/quickstart.md
Normal 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
166
openwiki/rpc-protocol.md
Normal 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.
|
||||||
Loading…
x
Reference in New Issue
Block a user