Skip to content

Commit fd772ff

Browse files
added a new README and fix the comments
1 parent 4163172 commit fd772ff

File tree

3 files changed

+163
-37
lines changed

3 files changed

+163
-37
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ The SimpleRpc example demonstrates how to:
7171
- Observe round-trip times (RTT) for each RPC call
7272

7373
#### 🔑 Generate Tokens
74-
Before running any participant, create JWT tokens with "caller", "greeter" and "math-genius" identities and room name.
74+
Before running any participant, create JWT tokens with **caller**, **greeter** and **math-genius** identities and room name.
7575
```bash
7676
lk token create -r test -i caller --join --valid-for 99999h --dev --room=your_own_room
7777
lk token create -r test -i greeter --join --valid-for 99999h --dev --room=your_own_room

examples/simple_rpc/README

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# 📘 SimpleRpc Example — Technical Overview
2+
3+
This README provides deeper technical details about the RPC (Remote Procedure Call) support demonstrated in the SimpleRpc example.
4+
It complements the example instructions found in the root README.md.
5+
6+
If you're looking for how to run the example, see the root [README](https://github.com/livekit/client-sdk-cpp).
7+
8+
This document explains:
9+
- How LiveKit RPC works in the C++ SDK
10+
- Where the APIs are defined
11+
- How senders call RPC methods
12+
- How receivers register handlers
13+
- What happens if the receiver is absent
14+
- How long-running operations behave
15+
- Timeouts, disconnects, and unsupported methods
16+
- RPC lifecycle events and error propagation
17+
18+
## 🔧 Overview: How RPC Works
19+
LiveKit RPC allows one participant (the caller) to invoke a method on another participant (the receiver) using the data channel transport.
20+
It is:
21+
- Peer-to-peer within the room (not server-executed RPC)
22+
- Request/response (caller waits for a reply or an error)
23+
- Asynchronous under the hood, synchronous or blocking from the caller’s perspective
24+
- Delivery-guaranteed when using the reliable data channel
25+
26+
Each RPC call includes:
27+
| Field | Meaning |
28+
|--------------------------|-----------------------------------------------------|
29+
| **destination_identity** | Identity of the target participant |
30+
| **method** | Method name string (e.g., "square-root") |
31+
| **payload** | Arbitrary UTF-8 text |
32+
| **response_timeout** | Optional timeout (seconds) |
33+
| **invocation_id** | Server-generated ID used internally for correlation |
34+
35+
## 📍 Location of APIs in C++
36+
All public-facing RPC APIs live in:
37+
[include/livekit/local_participant.h](https://github.com/livekit/client-sdk-cpp/blob/main/include/livekit/local_participant.h#L124)
38+
39+
### Key methods:
40+
41+
#### Sender-side APIs
42+
```bash
43+
std::string performRpc(
44+
const std::string& destination_identity,
45+
const std::string& method,
46+
const std::string& payload,
47+
std::optional<double> response_timeout_sec = std::nullopt
48+
);
49+
50+
Receiver-side APIs
51+
void registerRpcMethod(
52+
const std::string& method_name,
53+
RpcHandler handler
54+
);
55+
56+
void unregisterRpcMethod(const std::string& method_name);
57+
58+
Handler signature
59+
using RpcHandler =
60+
std::function<std::optional<std::string>(const RpcInvocationData&)>;
61+
```
62+
63+
Handlers can:
64+
- Return a string (the RPC response payload)
65+
- Return std::nullopt (meaning “no return payload”)
66+
- Throw exceptions (mapped to APPLICATION_ERROR)
67+
- Throw a RpcError (mapped to specific error codes)
68+
69+
#### 🛰 Sender Behavior (performRpc)
70+
71+
When the caller invokes:
72+
```bash
73+
auto reply = lp->performRpc("math-genius", "square-root", "{\"number\":16}");
74+
```
75+
76+
The following occurs:
77+
78+
A PerformRpcRequest is sent through FFI to the SDK core.
79+
80+
The SDK transmits the invocation to the target participant (if present).
81+
82+
The caller begins waiting for a matching RpcMethodInvocationResponse.
83+
84+
One of the following happens:
85+
| Outcome | Meaning |
86+
|--------------------------|------------------------------------------|
87+
| **Success** | Receiver returned a payload |
88+
| **UNSUPPORTED_METHOD** | Receiver did not register the method |
89+
| **RECIPIENT_NOT_FOUND** | Target identity not present in room |
90+
| **RECIPIENT_DISCONNECTED** | Target left before replying |
91+
| **RESPONSE_TIMEOUT** | Receiver took too long |
92+
| **APPLICATION_ERROR** | Handler threw an exception |
93+
94+
#### 🔄 Round-trip time (RTT)
95+
96+
The caller can measure RTT externally (as SimpleRpc does), but the SDK does not measure RTT internally.
97+
98+
#### 📡 Receiver Behavior (registerRpcMethod)
99+
100+
A receiver must explicitly register handlers:
101+
```bash
102+
local_participant->registerRpcMethod("square-root",
103+
[](const RpcInvocationData& data) {
104+
double number = parse(data.payload);
105+
return make_json("result", std::sqrt(number));
106+
});
107+
```
108+
109+
When an invocation arrives:
110+
- Room receives a RpcMethodInvocationEvent
111+
- Room forwards it to the corresponding LocalParticipant
112+
- LocalParticipant::handleRpcMethodInvocation():
113+
- Calls the handler
114+
- Converts any exceptions into RpcError
115+
- Sends back RpcMethodInvocationResponse
116+
117+
⚠ If no handler exists:
118+
119+
Receiver returns: UNSUPPORTED_METHOD
120+
121+
122+
#### 🚨 What Happens if Receiver Is Absent?
123+
| Case | Behavior |
124+
|-----------------------------------------------------|---------------------------------------------------|
125+
| Receiver identity is not in the room | Caller immediately receives `RECIPIENT_NOT_FOUND` |
126+
| Receiver is present but disconnects before replying | Caller receives `RECIPIENT_DISCONNECTED` |
127+
| Receiver joins later | Caller must retry manually (no automatic waiting) |
128+
129+
**Important**:
130+
LiveKit does not queue RPC calls for offline participants.
131+
132+
#### ⏳ Timeout Behavior
133+
134+
If the caller specifies:
135+
136+
performRpc(..., /*response_timeout=*/10.0);
137+
138+
Then:
139+
- Receiver is given 10 seconds to respond.
140+
- If the receiver handler takes longer (e.g., sleep 30s), caller receives:
141+
RESPONSE_TIMEOUT
142+
143+
**If no response_timeout is provided explicitly, the default timeout is 15 seconds.**
144+
145+
146+
This is by design and demonstrated in the example.
147+
148+
#### 🧨 Errors & Failure Modes
149+
| Error Code | Cause |
150+
|------------------------|---------------------------------------------|
151+
| **APPLICATION_ERROR** | Handler threw a C++ exception |
152+
| **UNSUPPORTED_METHOD** | No handler registered for the method |
153+
| **RECIPIENT_NOT_FOUND** | Destination identity not in room |
154+
| **RECIPIENT_DISCONNECTED** | Participant left mid-flight |
155+
| **RESPONSE_TIMEOUT** | Handler exceeded allowed response time |
156+
| **CONNECTION_TIMEOUT** | Transport-level issue |
157+
| **SEND_FAILED** | SDK failed to send invocation |

examples/simple_rpc/main.cpp

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,13 @@
3232
#include <vector>
3333

3434
#include "livekit/livekit.h"
35-
#include "livekit_ffi.h" // same as simple_room; internal but used here
35+
#include "livekit_ffi.h"
3636

3737
using namespace livekit;
3838
using namespace std::chrono_literals;
3939

4040
namespace {
4141

42-
// ------------------------------------------------------------
43-
// Global control (same pattern as simple_room)
44-
// ------------------------------------------------------------
45-
4642
std::atomic<bool> g_running{true};
4743

4844
void handleSignal(int) { g_running.store(false); }
@@ -91,38 +87,31 @@ bool ensurePeerPresent(Room &room, const std::string &identity,
9187
std::cout << "[Caller] Waiting up to " << timeout.count() << "s for "
9288
<< friendly_role << " (identity=\"" << identity
9389
<< "\") to join...\n";
94-
9590
bool present = waitForParticipant(
9691
room, identity,
9792
std::chrono::duration_cast<std::chrono::milliseconds>(timeout));
98-
9993
if (present) {
10094
std::cout << "[Caller] " << friendly_role << " is present.\n";
10195
return true;
10296
}
103-
10497
// Timed out
10598
auto info = room.room_info();
10699
const std::string room_name = info.name;
107-
108100
std::cout << "[Caller] Timed out after " << timeout.count()
109101
<< "s waiting for " << friendly_role << " (identity=\"" << identity
110102
<< "\").\n";
111103
std::cout << "[Caller] No participant with identity \"" << identity
112104
<< "\" appears to be connected to room \"" << room_name
113105
<< "\".\n\n";
114-
115106
std::cout << "To start a " << friendly_role
116107
<< " in another terminal, run:\n\n"
117108
<< " lk token create -r test -i " << identity
118109
<< " --join --valid-for 99999h --dev --room=" << room_name << "\n"
119110
<< " ./build/examples/SimpleRpc " << url
120111
<< " $token --role=" << friendly_role << "\n\n";
121-
122112
return false;
123113
}
124114

125-
// Parse args similar to simple_room, plus optional --role / role positional
126115
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token,
127116
std::string &role) {
128117
// --help
@@ -206,26 +195,18 @@ bool parseArgs(int argc, char *argv[], std::string &url, std::string &token,
206195
return !(url.empty() || token.empty());
207196
}
208197

209-
// ------------------------------------------------------------
210-
// Tiny helpers for the simple JSON used in the sample
211-
// (to avoid bringing in a json library)
212-
// ------------------------------------------------------------
213-
214-
// create {"key":number}
215198
std::string makeNumberJson(const std::string &key, double value) {
216199
std::ostringstream oss;
217200
oss << "{\"" << key << "\":" << value << "}";
218201
return oss.str();
219202
}
220203

221-
// create {"key":"value"}
222204
std::string makeStringJson(const std::string &key, const std::string &value) {
223205
std::ostringstream oss;
224206
oss << "{\"" << key << "\":\"" << value << "\"}";
225207
return oss.str();
226208
}
227209

228-
// very naive parse of {"key":number}
229210
double parseNumberFromJson(const std::string &json) {
230211
auto colon = json.find(':');
231212
if (colon == std::string::npos)
@@ -236,7 +217,6 @@ double parseNumberFromJson(const std::string &json) {
236217
return std::stod(num_str);
237218
}
238219

239-
// very naive parse of {"key":"value"}
240220
std::string parseStringFromJson(const std::string &json) {
241221
auto colon = json.find(':');
242222
if (colon == std::string::npos)
@@ -250,10 +230,7 @@ std::string parseStringFromJson(const std::string &json) {
250230
return json.substr(first_quote + 1, second_quote - first_quote - 1);
251231
}
252232

253-
// ------------------------------------------------------------
254-
// RPC handler registration (for greeter & math-genius)
255-
// ------------------------------------------------------------
256-
233+
// RPC handler registration
257234
void registerReceiverMethods(Room &greeters_room, Room &math_genius_room) {
258235
LocalParticipant *greeter_lp = greeters_room.local_participant();
259236
LocalParticipant *math_genius_lp = math_genius_room.local_participant();
@@ -321,19 +298,15 @@ void registerReceiverMethods(Room &greeters_room, Room &math_genius_room) {
321298
std::cout << "[Math Genius] This will take 30 seconds even though "
322299
"you're only giving me "
323300
<< data.response_timeout_sec << " seconds\n";
324-
301+
// Sleep for 30 seconds to mimic a long running task.
325302
std::this_thread::sleep_for(30s);
326303
return makeStringJson("result", "Calculation complete!");
327304
});
328305

329306
// Note: we do NOT register "quantum-hypergeometric-series" here,
330-
// so the caller sees UNSUPPORTED_METHOD, just like in Python.
307+
// so the caller sees UNSUPPORTED_METHOD
331308
}
332309

333-
// ------------------------------------------------------------
334-
// Caller-side helpers (like perform_* in rpc.py)
335-
// ------------------------------------------------------------
336-
337310
void performGreeting(Room &room) {
338311
std::cout << "[Caller] Letting the greeter know that I've arrived\n";
339312
double t0 = nowMs();
@@ -460,10 +433,6 @@ void performLongCalculation(Room &room) {
460433

461434
} // namespace
462435

463-
// ------------------------------------------------------------
464-
// main – similar style to simple_room/main.cpp
465-
// ------------------------------------------------------------
466-
467436
int main(int argc, char *argv[]) {
468437
std::string url, token, role;
469438
if (!parseArgs(argc, argv, url, token, role)) {
@@ -479,7 +448,7 @@ int main(int argc, char *argv[]) {
479448
std::cout << "Connecting to: " << url << "\n";
480449
std::cout << "Role: " << role << "\n";
481450

482-
// Ctrl-C
451+
// Ctrl-C to quit the program
483452
std::signal(SIGINT, handleSignal);
484453

485454
Room room{};

0 commit comments

Comments
 (0)