-
Notifications
You must be signed in to change notification settings - Fork 0
Methods
Methods are the primary mechanism for inter-service calls in SERP. They provide a typed, thread-safe way to invoke functionality across service boundaries — locally within a process or transparently across processes via transport.
The current primitive is serp::Call<Context, Ret, Args...>. The legacy primitive serp::Method is still supported but deprecated in new code.
template<typename Context, typename Ret, typename... Args>
class serp::Call;| Template Parameter | Description |
|---|---|
Context |
The service type that owns this call (used for routing and diagnostics). |
Ret |
Return type. Use void for fire-and-forget. Use serp::Response<T> for async returns. |
Args... |
Argument types passed to the handler. |
serp::Call instances are declared as members of the generated interface class (IService). The generated ServiceBase class wires each call to a handler lambda automatically.
Use setHandler() to bind an implementation lambda to a call. This is typically done in onInitialize() or the service constructor via the generated base class:
greet.setHandler([this](std::string msg) {
logInfo()->info("Got: " + msg);
});The lambda runs on the owning service's EventLoop. Access to service-owned state inside the lambda is safe without additional locking.
The caller dispatches the call and does not wait for completion. The handler executes asynchronously on the callee's EventLoop.
// Interface declaration (generated)
serp::Call<MyService, void, std::string> greet;
// Handler registration (in MyService)
greet.setHandler([this](std::string msg) {
logInfo()->info("Hello: " + msg);
});
// Caller (from any service or thread)
myService.greet("world");For calls that produce a result, the return type is serp::Response<T>. The caller provides a callback that is invoked on the caller's EventLoop when the result is ready.
// Interface declaration (generated)
serp::Call<MyService, serp::Response<int>, std::string> compute;
// Handler registration
compute.setHandler([this](std::string input, serp::Response<int> response) {
int result = heavyComputation(input);
response.resolve(result);
});
// Caller with callback
myService.compute("data", [](int result) {
// Called on caller's EventLoop
use(result);
});Some generated interfaces expose calls that follow a subscribe/notify pattern, where the caller registers interest and receives future updates via the callback mechanism.
myService.subscribeToStatus([this](Status s) {
updateDisplay(s);
});For cases where blocking is acceptable (e.g., initialization sequences, CLI tools), a call can be awaited synchronously with a timeout:
auto result = myService.compute("data").wait(std::chrono::seconds(5));
if (result.hasValue()) {
use(result.value());
} else {
// Timed out or rejected
}Blocking calls should never be issued from within a service's own EventLoop — this will deadlock.
serp::Response<T> is the async result carrier passed to method handlers that need to return a value asynchronously. The handler may capture it and resolve it later — for example after an I/O operation completes.
call.setHandler([this](std::string input, serp::Response<std::string> response) {
// Dispatch async work; capture response by value
processAsync(input, [response](std::string result) mutable {
response.resolve(result);
});
});| Method | Description |
|---|---|
response.resolve(value) |
Deliver a successful result to the caller. |
response.reject(error) |
Signal failure to the caller. |
Response is move-only. It may be captured by value in lambdas and passed across thread boundaries safely.
The SerpIDL code generator produces two classes per service:
IMyService — the pure interface exposed to callers:
class IMyService {
public:
serp::Call<MyService, void, std::string> greet;
serp::Call<MyService, serp::Response<int>, std::string> compute;
serp::Property<double> temperature;
serp::Notification<std::string, int> trackChanged;
};MyServiceBase — the base class for the implementation, which wires handlers:
class MyServiceBase : public IMyService, public serp::Service {
protected:
// Override these in your implementation:
virtual void onGreet(std::string msg) = 0;
virtual void onCompute(std::string input, serp::Response<int> response) = 0;
};You implement MyService : public MyServiceBase and override the on* methods.
When a service is hosted in a remote process, the caller holds a proxy generated from the same interface. The call syntax is identical — the transport layer handles serialization and delivery transparently:
// Local call and remote call look the same to the caller:
myService.greet("hello"); // local
remoteMyService.greet("hello"); // via transport (IPC/network)Serialization is generated from the SerpIDL type definitions. No manual marshaling code is required.
serp::Method is the pre-Phase-2 untyped call primitive. It accepts arguments as a variant bag and returns results via a callback. New services should use serp::Call instead.
- Services and Lifecycle — EventLoop binding and thread safety
- Properties — typed shared state
- Notifications — broadcast events
- Promises and Async — async result chaining
Getting Started
The Development Model
Architecture Language
Code Generator
- Generator Overview
- Generated Code Layout
- Deployment Configurations
- Lifecycle Backends
- CMake Integration
Framework Internals
- Core Concepts
- Services & Lifecycle
- Methods
- Properties
- Notifications
- Timers & Watchdog
- Promises & Async
- Streams
- Commands
- Logging
- Test Engine
- Transports
- Runtime & Debug Tools
VS Code Plugin
Examples