FastLED 3.10.9
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
236
237 // =========================================================================
238 // Method Registration (Binding)
239 // =========================================================================
240
244 template<typename Callable>
245 void bind(const Config<Callable>& config) FL_NO_EXCEPT {
246 using Sig = typename callable_traits<typename decay<Callable>::type>::signature;
247 RpcFn<Sig> wrapped(config.fn);
248 registerMethod<Sig>(config.name.c_str(), wrapped, config.params, config.description, config.tags, config.mode);
249 }
250
252 template<typename Callable>
253 void bind(const char* name, Callable fn, fl::RpcMode mode = fl::RpcMode::SYNC) FL_NO_EXCEPT {
254 bind(Config<Callable>{name, fl::move(fn), mode});
255 }
256
268 void bindAsync(const char* name,
269 fl::function<void(ResponseSend&, const json&)> fn,
271
275 void bindStreaming(const char* name, fl::StreamingRpcHandler fn) FL_NO_EXCEPT;
276
277 // =========================================================================
278 // Method Retrieval
279 // =========================================================================
280
283 template<class Sig>
284 BindResult<Sig> get(const char* name) const FL_NO_EXCEPT {
285 fl::string key(name);
286 auto it = mRegistry.find(key);
287 if (it == mRegistry.end()) {
289 }
290 if (it->second.mTypeTag != detail::TypeTag<Sig>::id()) {
292 }
293 auto* holder = static_cast<detail::TypedCallableHolder<Sig>*>(
294 it->second.mTypedCallable.get());
295 if (!holder) {
297 }
298 return BindResult<Sig>(holder->mFn);
299 }
300
302 bool has(const char* name) const FL_NO_EXCEPT {
303 return mRegistry.find(fl::string(name)) != mRegistry.end();
304 }
305
308 bool unbind(const char* name) FL_NO_EXCEPT {
309 fl::string key(name);
310 auto it = mRegistry.find(key);
311 if (it != mRegistry.end()) {
312 mRegistry.erase(it);
313 return true;
314 }
315 return false;
316 }
317
320 mRegistry.clear();
321 }
322
323 // =========================================================================
324 // JSON-RPC Transport
325 // =========================================================================
326
330 json handle(const json& request) FL_NO_EXCEPT;
331
334
335 // =========================================================================
336 // Schema and Discovery
337 // =========================================================================
338
342 json methods() const FL_NO_EXCEPT;
343
347 json schema() const FL_NO_EXCEPT;
348
349#if FL_PLATFORM_HAS_LARGE_MEMORY
352 void writeSchema(fl::JsonStreamWriter& writer) const FL_NO_EXCEPT;
353#endif
354
356 fl::size count() const FL_NO_EXCEPT {
357 return mRegistry.size();
358 }
359
362
363 // =========================================================================
364 // Internal Registration (used by MethodBuilder)
365 // =========================================================================
366
367 template<class Sig>
368 bool registerMethod(const char* name, RpcFn<Sig> fn,
369 const fl::vector<fl::string>& paramNames,
370 const fl::string& description,
371 const fl::vector<fl::string>& tags,
372 fl::RpcMode mode = fl::RpcMode::SYNC) FL_NO_EXCEPT {
373 fl::string key(name);
374 auto it = mRegistry.find(key);
375 if (it != mRegistry.end()) {
376 if (it->second.mTypeTag != detail::TypeTag<Sig>::id()) {
377 return false;
378 }
379 }
380
381 detail::RpcEntry entry;
383 // Per-Sig dispatch fork (FastLED #3246). Low-memory targets pay only
384 // a ~50-150 B trampoline per signature via RuntimeRpcBinding instead
385 // of the ~2.1 KB TypedRpcBinding<Sig>::invokeWithReturn instantiation.
386 // Large-memory targets keep the existing fast inline-dispatch path.
387#if FL_PLATFORM_HAS_LARGE_MEMORY
389#else
391#endif
393#if FL_PLATFORM_HAS_LARGE_MEMORY
394 // Schema generator construction gated on Low-memory targets per
395 // FastLED #3081 / #3079. The schema generator is only consumed by
396 // `rpc.discover` (also gated). Skipping it on Low-memory drops
397 // `TypedSchemaGenerator<Sig>::params()` (~800 B per signature).
399#endif
401 entry.mTags = tags;
402 entry.mMode = mode;
403
404#if FL_PLATFORM_HAS_LARGE_MEMORY
405 if (!paramNames.empty()) {
406 entry.mSchemaGenerator->setParamNames(paramNames);
407 }
408#else
409 (void)paramNames;
410#endif
411
412 mRegistry[key] = fl::move(entry);
413 return true;
414 }
415
416private:
418 fl::function<void(const fl::json&)> mResponseSink; // For sending ACK responses
419#if FL_PLATFORM_HAS_LARGE_MEMORY
420 fl::ResponseStreamSink mResponseStreamSink;
421 fl::function<bool()> mResponseStreamAvailable;
422#endif
423};
424
425// RpcFactory is kept as an alias for backwards compatibility
427
428} // 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:49
fl::unordered_map< fl::string, detail::RpcEntry > mRegistry
Definition rpc.h:417
bool has(const char *name) const FL_NO_EXCEPT
Check if a method is registered (regardless of signature).
Definition rpc.h:302
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:368
void bind(const Config< Callable > &config) FL_NO_EXCEPT
Bind a method with configuration (name, function, optional metadata).
Definition rpc.h:245
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:84
void clear() FL_NO_EXCEPT
Clear all registered methods.
Definition rpc.h:319
fl::optional< json > handle_maybe(const json &request) FL_NO_EXCEPT
For notifications (no id), returns nullopt.
Definition rpc.cpp.hpp:327
void setResponseStreamSink(fl::ResponseStreamSink sink) FL_NO_EXCEPT
Set response stream sink for methods registered with bindStreaming().
Definition rpc.cpp.hpp:37
bool unbind(const char *name) FL_NO_EXCEPT
Unbind (unregister) a previously registered method.
Definition rpc.h:308
Rpc() FL_NO_EXCEPT=default
fl::function< void(const fl::json &)> mResponseSink
Definition rpc.h:418
json schema() const FL_NO_EXCEPT
Returns flat schema document.
Definition rpc.cpp.hpp:399
json handle(const json &request) FL_NO_EXCEPT
Process a JSON-RPC request.
Definition rpc.cpp.hpp:134
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:354
void setResponseStreamAvailable(fl::function< bool()> available) FL_NO_EXCEPT
Reports whether the stream sink currently reaches a transport.
Definition rpc.cpp.hpp:29
BindResult< Sig > get(const char *name) const FL_NO_EXCEPT
Get a registered method by name.
Definition rpc.h:284
fl::size count() const FL_NO_EXCEPT
Returns number of registered methods.
Definition rpc.h:356
json methods() const FL_NO_EXCEPT
Returns flat method array: [["name", "returnType", [["param1", "type1"], ...]], .....
Definition rpc.cpp.hpp:377
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:253
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
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
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition s16x16x4.h:30
constexpr T && forward(typename remove_reference< T >::type &t) FL_NO_EXCEPT
Definition s16x16x4.h:236
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:426
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
int available() FL_NO_EXCEPT
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.
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