FastLED 3.10.8
Loading...
Searching...
No Matches
rpc.h
Go to the documentation of this file.
1#pragma once
2
3#include "fl/stl/json.h" // IWYU pragma: keep
4
5// =============================================================================
6// RPC System - Main Public API
7// =============================================================================
8//
9// This header provides the complete typed RPC system for FastLED.
10//
11// PUBLIC API:
12// - fl::Rpc : Main RPC registry class
13// - fl::RpcHandle<S> : Callable handle returned from method() registration
14//
15// EXAMPLE USAGE:
16//
17// #include "fl/remote/rpc/rpc.h"
18//
19// fl::Rpc rpc;
20//
21// // Bind (register) method - simple (constructor style)
22// rpc.bind(Rpc::Config{"add", [](int a, int b) { return a + b; }});
23//
24// // Bind method - simple (designated initializer style)
25// rpc.bind({.name = "add", .fn = [](int a, int b) { return a + b; }});
26//
27// // With metadata - designated initializer style
28// rpc.bind({
29// .name = "multiply",
30// .fn = [](int a, int b) { return a * b; },
31// .params = {"a", "b"},
32// .description = "Multiplies two integers",
33// .tags = {"math"}
34// });
35//
36// // With metadata - constructor style
37// rpc.bind(Rpc::Config{
38// "divide", // name
39// [](int a, int b) { return a / b; }, // function
40// {"dividend", "divisor"}, // params
41// "Divides two integers" // description
42// });
43//
44// // Group methods using dot notation for namespacing:
45// rpc.bind({"led.setBrightness", [](int b) { /* ... */ }});
46// rpc.bind({"led.setColor", [](int r, int g, int b) { /* ... */ }});
47// rpc.bind({"system.status", []() -> fl::string { return "ok"; }});
48//
49// // Get by name and call later
50// auto addFn = rpc.get<int(int, int)>("add");
51// if (addFn) { // Check if binding succeeded
52// int sum = addFn(5, 7); // Call directly!
53// } else if (addFn.error() == fl::BindError::NotFound) {
54// // Method not registered
55// } else { // fl::BindError::SignatureMismatch
56// // Method exists but signature doesn't match
57// }
58//
59// // JSON-RPC transport
60// fl::json request = fl::json::parse(R"({"method":"add","params":[6,7],"id":1})");
61// fl::json response = rpc.handle(request);
62//
63// // Schema generation (flat tuple format) - always available
64// fl::json schema = rpc.schema(); // Flat schema document
65// fl::json methods = rpc.methods(); // Flat method array
66//
67// // Built-in rpc.discover method (always available)
68// fl::json request = fl::json::parse(R"({"method":"rpc.discover","id":1})");
69// fl::json response = rpc.handle(request); // Returns flat schema
70//
71// =============================================================================
72
73#include "fl/stl/json.h" // IWYU pragma: keep
74// Internal RPC headers
79#include "fl/remote/rpc/runtime_rpc_binding.h" // RuntimeRpcBinding (low-memory dispatch, #3246)
80#include "fl/system/sketch_macros.h" // FL_PLATFORM_HAS_LARGE_MEMORY
81
82// STL headers required for public API
83#include "fl/stl/stdint.h"
84#include "fl/stl/string.h"
85#include "fl/stl/vector.h"
86#include "fl/stl/optional.h"
87#include "fl/stl/expected.h"
88#include "fl/stl/function.h"
90#include "fl/stl/type_traits.h"
91#include "fl/stl/initializer_list.h" // IWYU pragma: keep
92#include "fl/stl/noexcept.h"
93
94namespace fl {
95
96// Forward declarations
97class ResponseSend;
98
99// =============================================================================
100// BindError - Error codes for bind() failures
101// =============================================================================
102
103enum class BindError {
104 NotFound, // No method registered with that name
105 SignatureMismatch // Method exists but signature doesn't match
106};
107
108// =============================================================================
109// RPC Schema Types
110// =============================================================================
111
119
130
131// =============================================================================
132// BindResult - Result wrapper for bind() operations
133// =============================================================================
134
138template<typename Sig>
140
141template<typename R, typename... Args>
142struct BindResult<R(Args...)> {
144
147
149 BindResult(RpcFn<R(Args...)> fn) FL_NO_EXCEPT : inner(fl::expected<RpcFn<R(Args...)>, BindError>::success(fl::move(fn))) {}
150
152 bool has_value() const FL_NO_EXCEPT { return inner.has_value(); }
153 bool ok() const FL_NO_EXCEPT { return inner.has_value(); }
154 explicit operator bool() const FL_NO_EXCEPT { return inner.has_value(); }
155
157 RpcFn<R(Args...)> value() const FL_NO_EXCEPT { return inner.value(); }
158 RpcFn<R(Args...)>& value() FL_NO_EXCEPT { return inner.value(); }
159
161 BindError error() const FL_NO_EXCEPT { return inner.error(); }
162
164 const fl::expected<RpcFn<R(Args...)>, BindError>& get() const FL_NO_EXCEPT { return inner; }
165
168 R operator()(Args... args) const FL_NO_EXCEPT {
169 return inner.value()(fl::forward<Args>(args)...);
170 }
171};
172
173// =============================================================================
174// Rpc - Main typed RPC registry
175// =============================================================================
176//
177// The primary class for registering and invoking RPC methods.
178// Methods can be registered with auto-deduced signatures and called either
179// directly (via RpcHandle), by binding, or through JSON-RPC transport.
180
181class Rpc {
182public:
185 template<typename Callable>
212
213 Rpc() FL_NO_EXCEPT = default;
214 ~Rpc() FL_NO_EXCEPT = default;
215
216 // Non-copyable but movable
217 Rpc(const Rpc&) FL_NO_EXCEPT = delete;
218 Rpc& operator=(const Rpc&) FL_NO_EXCEPT = delete;
219 Rpc(Rpc&&) FL_NO_EXCEPT = default;
220 Rpc& operator=(Rpc&&) FL_NO_EXCEPT = default;
221
222 // =========================================================================
223 // Response Sink for Async ACKs
224 // =========================================================================
225
227 void setResponseSink(fl::function<void(const fl::json&)> sink) FL_NO_EXCEPT;
228
231
232 // =========================================================================
233 // Method Registration (Binding)
234 // =========================================================================
235
239 template<typename Callable>
240 void bind(const Config<Callable>& config) FL_NO_EXCEPT {
241 using Sig = typename callable_traits<typename decay<Callable>::type>::signature;
242 RpcFn<Sig> wrapped(config.fn);
243 registerMethod<Sig>(config.name.c_str(), wrapped, config.params, config.description, config.tags, config.mode);
244 }
245
247 template<typename Callable>
248 void bind(const char* name, Callable fn, fl::RpcMode mode = fl::RpcMode::SYNC) FL_NO_EXCEPT {
249 bind(Config<Callable>{name, fl::move(fn), mode});
250 }
251
263 void bindAsync(const char* name,
264 fl::function<void(ResponseSend&, const json&)> fn,
266
270 void bindStreaming(const char* name, fl::StreamingRpcHandler fn) FL_NO_EXCEPT;
271
272 // =========================================================================
273 // Method Retrieval
274 // =========================================================================
275
278 template<class Sig>
279 BindResult<Sig> get(const char* name) const FL_NO_EXCEPT {
280 fl::string key(name);
281 auto it = mRegistry.find(key);
282 if (it == mRegistry.end()) {
284 }
285 if (it->second.mTypeTag != detail::TypeTag<Sig>::id()) {
287 }
288 auto* holder = static_cast<detail::TypedCallableHolder<Sig>*>(
289 it->second.mTypedCallable.get());
290 if (!holder) {
292 }
293 return BindResult<Sig>(holder->mFn);
294 }
295
297 bool has(const char* name) const FL_NO_EXCEPT {
298 return mRegistry.find(fl::string(name)) != mRegistry.end();
299 }
300
303 bool unbind(const char* name) FL_NO_EXCEPT {
304 fl::string key(name);
305 auto it = mRegistry.find(key);
306 if (it != mRegistry.end()) {
307 mRegistry.erase(it);
308 return true;
309 }
310 return false;
311 }
312
315 mRegistry.clear();
316 }
317
318 // =========================================================================
319 // JSON-RPC Transport
320 // =========================================================================
321
325 json handle(const json& request) FL_NO_EXCEPT;
326
329
330 // =========================================================================
331 // Schema and Discovery
332 // =========================================================================
333
337 json methods() const FL_NO_EXCEPT;
338
342 json schema() const FL_NO_EXCEPT;
343
345 fl::size count() const FL_NO_EXCEPT {
346 return mRegistry.size();
347 }
348
351
352 // =========================================================================
353 // Internal Registration (used by MethodBuilder)
354 // =========================================================================
355
356 template<class Sig>
357 bool registerMethod(const char* name, RpcFn<Sig> fn,
358 const fl::vector<fl::string>& paramNames,
359 const fl::string& description,
360 const fl::vector<fl::string>& tags,
361 fl::RpcMode mode = fl::RpcMode::SYNC) FL_NO_EXCEPT {
362 fl::string key(name);
363 auto it = mRegistry.find(key);
364 if (it != mRegistry.end()) {
365 if (it->second.mTypeTag != detail::TypeTag<Sig>::id()) {
366 return false;
367 }
368 }
369
370 detail::RpcEntry entry;
372 // Per-Sig dispatch fork (FastLED #3246). Low-memory targets pay only
373 // a ~50-150 B trampoline per signature via RuntimeRpcBinding instead
374 // of the ~2.1 KB TypedRpcBinding<Sig>::invokeWithReturn instantiation.
375 // Large-memory targets keep the existing fast inline-dispatch path.
376#if FL_PLATFORM_HAS_LARGE_MEMORY
378#else
380#endif
382#if FL_PLATFORM_HAS_LARGE_MEMORY
383 // Schema generator construction gated on Low-memory targets per
384 // FastLED #3081 / #3079. The schema generator is only consumed by
385 // `rpc.discover` (also gated). Skipping it on Low-memory drops
386 // `TypedSchemaGenerator<Sig>::params()` (~800 B per signature).
388#endif
390 entry.mTags = tags;
391 entry.mMode = mode;
392
393#if FL_PLATFORM_HAS_LARGE_MEMORY
394 if (!paramNames.empty()) {
395 entry.mSchemaGenerator->setParamNames(paramNames);
396 }
397#else
398 (void)paramNames;
399#endif
400
401 mRegistry[key] = fl::move(entry);
402 return true;
403 }
404
405private:
407 fl::function<void(const fl::json&)> mResponseSink; // For sending ACK responses
408#if FL_PLATFORM_HAS_LARGE_MEMORY
409 fl::ResponseStreamSink mResponseStreamSink;
410#endif
411};
412
413// RpcFactory is kept as an alias for backwards compatibility
415
416} // namespace fl
fl::UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick")
Helper class for sending responses in async/streaming RPC methods.
void bindAsync(const char *name, fl::function< void(ResponseSend &, const json &)> fn, fl::RpcMode mode=fl::RpcMode::ASYNC) FL_NO_EXCEPT
Bind async method with ResponseSend& parameter (for ASYNC/ASYNC_STREAM modes) Signature: void(Respons...
Definition rpc.cpp.hpp:41
fl::unordered_map< fl::string, detail::RpcEntry > mRegistry
Definition rpc.h:406
bool has(const char *name) const FL_NO_EXCEPT
Check if a method is registered (regardless of signature).
Definition rpc.h:297
bool registerMethod(const char *name, RpcFn< Sig > fn, const fl::vector< fl::string > &paramNames, const fl::string &description, const fl::vector< fl::string > &tags, fl::RpcMode mode=fl::RpcMode::SYNC) FL_NO_EXCEPT
Definition rpc.h:357
void bind(const Config< Callable > &config) FL_NO_EXCEPT
Bind a method with configuration (name, function, optional metadata).
Definition rpc.h:240
void bindStreaming(const char *name, fl::StreamingRpcHandler fn) FL_NO_EXCEPT
Bind a method that streams its JSON-RPC result directly to the transport.
Definition rpc.cpp.hpp:76
void clear() FL_NO_EXCEPT
Clear all registered methods.
Definition rpc.h:314
fl::optional< json > handle_maybe(const json &request) FL_NO_EXCEPT
For notifications (no id), returns nullopt.
Definition rpc.cpp.hpp:297
void setResponseStreamSink(fl::ResponseStreamSink sink) FL_NO_EXCEPT
Set response stream sink for methods registered with bindStreaming().
Definition rpc.cpp.hpp:29
bool unbind(const char *name) FL_NO_EXCEPT
Unbind (unregister) a previously registered method.
Definition rpc.h:303
Rpc() FL_NO_EXCEPT=default
fl::function< void(const fl::json &)> mResponseSink
Definition rpc.h:407
json schema() const FL_NO_EXCEPT
Returns flat schema document.
Definition rpc.cpp.hpp:369
json handle(const json &request) FL_NO_EXCEPT
Process a JSON-RPC request.
Definition rpc.cpp.hpp:126
void setResponseSink(fl::function< void(const fl::json &)> sink) FL_NO_EXCEPT
Set response sink for sending ACK responses (used by async functions)
Definition rpc.cpp.hpp:25
fl::vector< fl::string > tags() const FL_NO_EXCEPT
Returns list of unique tags used across all methods.
Definition rpc.cpp.hpp:324
BindResult< Sig > get(const char *name) const FL_NO_EXCEPT
Get a registered method by name.
Definition rpc.h:279
fl::size count() const FL_NO_EXCEPT
Returns number of registered methods.
Definition rpc.h:345
json methods() const FL_NO_EXCEPT
Returns flat method array: [["name", "returnType", [["param1", "type1"], ...]], .....
Definition rpc.cpp.hpp:347
void bind(const char *name, Callable fn, fl::RpcMode mode=fl::RpcMode::SYNC) FL_NO_EXCEPT
Convenience overload: bind method by name, function, and optional mode.
Definition rpc.h:248
Definition rpc.h:181
expected type for operations that can fail (C++23-style)
Definition expected.h:79
Generic expected<T, E> type for error handling without exceptions.
FastLED's Elegant JSON Library: fl::json
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:30
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:423
constexpr T && forward(typename remove_reference< T >::type &t) FL_NO_EXCEPT
fl::shared_ptr< RuntimeRpcBinding > makeRuntimeRpcBinding(fl::function< R(Args...)> fn) FL_NO_EXCEPT
fl::shared_ptr< ErasedInvoker > mInvoker
fl::shared_ptr< ErasedSchemaGenerator > mSchemaGenerator
fl::shared_ptr< CallableHolderBase > mTypedCallable
fl::string mDescription
fl::vector< fl::string > mTags
const void * mTypeTag
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
fl::function< Sig > RpcFn
Definition rpc_handle.h:14
Optional< T > optional
Definition optional.h:16
Rpc RpcFactory
Definition rpc.h:414
BindError
Definition rpc.h:103
@ SignatureMismatch
Definition rpc.h:105
fl::function< void(fl::JsonStreamWriter &, const fl::json &, const fl::json &)> StreamingRpcHandler
fl::function< void(fl::JsonStreamCallback)> ResponseStreamSink
enable_if< is_fixed_point< T >::value, T >::type exp(T x) FL_NO_EXCEPT
RpcMode
Definition rpc_mode.h:9
Base definition for an LED controller.
Definition crgb.hpp:179
fl::vector< ParamInfo > params
Definition rpc.h:125
fl::vector< fl::string > tags
Definition rpc.h:128
fl::string name
Definition rpc.h:124
fl::string type
Definition rpc.h:117
fl::string description
Definition rpc.h:127
fl::string name
Definition rpc.h:116
fl::string returnType
Definition rpc.h:126
Method parameter information.
Definition rpc.h:115
Method information.
Definition rpc.h:123
Wraps the result of binding to a method by name.
Definition rpc.h:139
corkscrew_args args
Definition old.h:149
RpcFn< R(Args...)> & value() FL_NO_EXCEPT
Definition rpc.h:158
RpcFn< R(Args...)> value() const FL_NO_EXCEPT
Get the callable (requires has_value())
Definition rpc.h:157
bool ok() const FL_NO_EXCEPT
Definition rpc.h:153
BindResult(fl::expected< RpcFn< R(Args...)>, BindError > exp) FL_NO_EXCEPT
Construct from expected.
Definition rpc.h:146
const fl::expected< RpcFn< R(Args...)>, BindError > & get() const FL_NO_EXCEPT
Access the underlying expected.
Definition rpc.h:164
R operator()(Args... args) const FL_NO_EXCEPT
Call the bound function directly (undefined behavior if binding failed) Always check ok() or has_valu...
Definition rpc.h:168
BindResult(RpcFn< R(Args...)> fn) FL_NO_EXCEPT
Construct from RpcFn (success case)
Definition rpc.h:149
fl::expected< RpcFn< R(Args...)>, BindError > inner
Definition rpc.h:143
BindError error() const FL_NO_EXCEPT
Get the error (undefined if has_value())
Definition rpc.h:161
bool has_value() const FL_NO_EXCEPT
Check if binding succeeded.
Definition rpc.h:152
fl::vector< fl::string > params
Parameter names (optional)
Definition rpc.h:190
Config(fl::string n, Callable f, fl::RpcMode m=fl::RpcMode::SYNC) FL_NO_EXCEPT
Constructor requiring name and function (metadata is optional)
Definition rpc.h:195
fl::string name
Method name (REQUIRED)
Definition rpc.h:187
fl::vector< fl::string > tags
Tags for grouping (optional)
Definition rpc.h:192
Callable fn
Function to register (REQUIRED)
Definition rpc.h:188
Config(fl::string n, Callable f, fl::vector< fl::string > p, fl::RpcMode m=fl::RpcMode::SYNC) FL_NO_EXCEPT
Constructor with params.
Definition rpc.h:199
fl::RpcMode mode
Execution mode (SYNC or ASYNC, default SYNC)
Definition rpc.h:189
Config(fl::string n, Callable f, fl::vector< fl::string > p, fl::string desc, fl::vector< fl::string > t={}, fl::RpcMode m=fl::RpcMode::SYNC) FL_NO_EXCEPT
Constructor with all fields.
Definition rpc.h:203
fl::string description
Method description (optional)
Definition rpc.h:191
Configuration for method registration with optional metadata.
Definition rpc.h:186
static const void * id() FL_NO_EXCEPT