Ivan FB 1fd1ad07bb
feat(codegen): native C++ typed event handlers
Adds the ergonomic native event surface to the C++ generator:
`node.On<Event>(std::function<void(const <Payload>&)>)` registers a native
listener; a per-event extern "C" trampoline reads the typed POD
(`fromC(*reinterpret_cast<const ::<Payload>*>(msg))`) and invokes the handler —
no CBOR. The handler is owned by the node (a `std::map` of `ListenerBase`) so
its address stays valid until `removeEventListener`.

The example registers `OnEchoFired` and receives a typed `EchoEvent` when Echo
fires it. Verified end-to-end and ASAN-clean.

With this the native C++ generator covers the full surface: requests
(scalar/string/bool/seq/Option/nested), typed struct returns, and typed events.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:39:20 +02:00

432 lines
13 KiB
C++

// Generated by nim-ffi native C++ codegen. Do not edit by hand.
//
// Native (zero-serialization) wrapper over the C ABI in "my_timer.h". Struct params/returns cross as flat C-POD structs — no CBOR. For the
// inter-process path use the CBOR header (my_timer_cbor.hpp).
#ifndef NIM_FFI_GEN_MY_TIMER_NATIVE_HPP
#define NIM_FFI_GEN_MY_TIMER_NATIVE_HPP
#include "my_timer.h"
#include <cstdint>
#include <functional>
#include <future>
#include <map>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
namespace my_timer {
struct TimerConfig {
std::string name{};
};
struct TimerConfigC {
::TimerConfig c{};
};
inline TimerConfigC toC(const TimerConfig& v) {
TimerConfigC h;
h.c.name = v.name.c_str();
return h;
}
inline TimerConfig fromC(const ::TimerConfig& c) {
TimerConfig v{};
v.name = c.name ? std::string(c.name) : std::string();
return v;
}
struct EchoRequest {
std::string message{};
int64_t delayMs{};
};
struct EchoRequestC {
::EchoRequest c{};
};
inline EchoRequestC toC(const EchoRequest& v) {
EchoRequestC h;
h.c.message = v.message.c_str();
h.c.delayMs = v.delayMs;
return h;
}
inline EchoRequest fromC(const ::EchoRequest& c) {
EchoRequest v{};
v.message = c.message ? std::string(c.message) : std::string();
v.delayMs = c.delayMs;
return v;
}
struct EchoResponse {
std::string echoed{};
std::string timerName{};
};
struct EchoResponseC {
::EchoResponse c{};
};
inline EchoResponseC toC(const EchoResponse& v) {
EchoResponseC h;
h.c.echoed = v.echoed.c_str();
h.c.timerName = v.timerName.c_str();
return h;
}
inline EchoResponse fromC(const ::EchoResponse& c) {
EchoResponse v{};
v.echoed = c.echoed ? std::string(c.echoed) : std::string();
v.timerName = c.timerName ? std::string(c.timerName) : std::string();
return v;
}
struct ComplexRequest {
std::vector<EchoRequest> messages{};
std::vector<std::string> tags{};
std::optional<std::string> note{};
std::optional<int64_t> retries{};
};
struct ComplexRequestC {
::ComplexRequest c{};
std::vector<::EchoRequest> _messages;
std::vector<EchoRequestC> _messagesH;
std::vector<const char*> _tags;
};
inline ComplexRequestC toC(const ComplexRequest& v) {
ComplexRequestC h;
for (const auto& it : v.messages) {
h._messagesH.push_back(toC(it));
}
for (const auto& hh : h._messagesH) h._messages.push_back(hh.c);
h.c.messages = h._messages.empty() ? nullptr : h._messages.data();
h.c.messages_len = h._messages.size();
for (const auto& it : v.tags) {
h._tags.push_back(it.c_str());
}
h.c.tags = h._tags.empty() ? nullptr : h._tags.data();
h.c.tags_len = h._tags.size();
if (v.note.has_value()) {
h.c.note_present = 1;
h.c.note = (*v.note).c_str();
}
if (v.retries.has_value()) {
h.c.retries_present = 1;
h.c.retries = (*v.retries);
}
return h;
}
inline ComplexRequest fromC(const ::ComplexRequest& c) {
ComplexRequest v{};
for (std::size_t i = 0; i < c.messages_len; i++)
v.messages.push_back(fromC(c.messages[i]));
for (std::size_t i = 0; i < c.tags_len; i++)
v.tags.push_back((c.tags[i] ? std::string(c.tags[i]) : std::string()));
if (c.note_present)
v.note = (c.note ? std::string(c.note) : std::string());
if (c.retries_present)
v.retries = c.retries;
return v;
}
struct ComplexResponse {
std::string summary{};
int64_t itemCount{};
bool hasNote{};
};
struct ComplexResponseC {
::ComplexResponse c{};
};
inline ComplexResponseC toC(const ComplexResponse& v) {
ComplexResponseC h;
h.c.summary = v.summary.c_str();
h.c.itemCount = v.itemCount;
h.c.hasNote = v.hasNote ? 1 : 0;
return h;
}
inline ComplexResponse fromC(const ::ComplexResponse& c) {
ComplexResponse v{};
v.summary = c.summary ? std::string(c.summary) : std::string();
v.itemCount = c.itemCount;
v.hasNote = c.hasNote != 0;
return v;
}
struct EchoEvent {
std::string message{};
int64_t echoCount{};
};
struct EchoEventC {
::EchoEvent c{};
};
inline EchoEventC toC(const EchoEvent& v) {
EchoEventC h;
h.c.message = v.message.c_str();
h.c.echoCount = v.echoCount;
return h;
}
inline EchoEvent fromC(const ::EchoEvent& c) {
EchoEvent v{};
v.message = c.message ? std::string(c.message) : std::string();
v.echoCount = c.echoCount;
return v;
}
struct JobSpec {
std::string name{};
std::vector<std::string> payload{};
int64_t priority{};
};
struct JobSpecC {
::JobSpec c{};
std::vector<const char*> _payload;
};
inline JobSpecC toC(const JobSpec& v) {
JobSpecC h;
h.c.name = v.name.c_str();
for (const auto& it : v.payload) {
h._payload.push_back(it.c_str());
}
h.c.payload = h._payload.empty() ? nullptr : h._payload.data();
h.c.payload_len = h._payload.size();
h.c.priority = v.priority;
return h;
}
inline JobSpec fromC(const ::JobSpec& c) {
JobSpec v{};
v.name = c.name ? std::string(c.name) : std::string();
for (std::size_t i = 0; i < c.payload_len; i++)
v.payload.push_back((c.payload[i] ? std::string(c.payload[i]) : std::string()));
v.priority = c.priority;
return v;
}
struct RetryPolicy {
int64_t maxAttempts{};
int64_t backoffMs{};
std::vector<std::string> retryOn{};
};
struct RetryPolicyC {
::RetryPolicy c{};
std::vector<const char*> _retryOn;
};
inline RetryPolicyC toC(const RetryPolicy& v) {
RetryPolicyC h;
h.c.maxAttempts = v.maxAttempts;
h.c.backoffMs = v.backoffMs;
for (const auto& it : v.retryOn) {
h._retryOn.push_back(it.c_str());
}
h.c.retryOn = h._retryOn.empty() ? nullptr : h._retryOn.data();
h.c.retryOn_len = h._retryOn.size();
return h;
}
inline RetryPolicy fromC(const ::RetryPolicy& c) {
RetryPolicy v{};
v.maxAttempts = c.maxAttempts;
v.backoffMs = c.backoffMs;
for (std::size_t i = 0; i < c.retryOn_len; i++)
v.retryOn.push_back((c.retryOn[i] ? std::string(c.retryOn[i]) : std::string()));
return v;
}
struct ScheduleConfig {
int64_t startAtMs{};
int64_t intervalMs{};
std::optional<int64_t> jitter{};
};
struct ScheduleConfigC {
::ScheduleConfig c{};
};
inline ScheduleConfigC toC(const ScheduleConfig& v) {
ScheduleConfigC h;
h.c.startAtMs = v.startAtMs;
h.c.intervalMs = v.intervalMs;
if (v.jitter.has_value()) {
h.c.jitter_present = 1;
h.c.jitter = (*v.jitter);
}
return h;
}
inline ScheduleConfig fromC(const ::ScheduleConfig& c) {
ScheduleConfig v{};
v.startAtMs = c.startAtMs;
v.intervalMs = c.intervalMs;
if (c.jitter_present)
v.jitter = c.jitter;
return v;
}
struct ScheduleResult {
std::string jobId{};
int64_t willRunCount{};
int64_t firstRunAtMs{};
int64_t effectiveBackoffMs{};
};
struct ScheduleResultC {
::ScheduleResult c{};
};
inline ScheduleResultC toC(const ScheduleResult& v) {
ScheduleResultC h;
h.c.jobId = v.jobId.c_str();
h.c.willRunCount = v.willRunCount;
h.c.firstRunAtMs = v.firstRunAtMs;
h.c.effectiveBackoffMs = v.effectiveBackoffMs;
return h;
}
inline ScheduleResult fromC(const ::ScheduleResult& c) {
ScheduleResult v{};
v.jobId = c.jobId ? std::string(c.jobId) : std::string();
v.willRunCount = c.willRunCount;
v.firstRunAtMs = c.firstRunAtMs;
v.effectiveBackoffMs = c.effectiveBackoffMs;
return v;
}
namespace detail {
template <typename T> struct Capture {
int ret = RET_ERR;
T value{};
std::string err;
std::promise<void> done;
};
struct AckCapture {
int ret = RET_ERR;
std::string err;
std::promise<void> done;
};
inline std::string rawText(const char* msg, std::size_t len) {
return (msg && len) ? std::string(msg, len) : std::string();
}
// Event listener storage: a heap handler kept alive by the node so the
// native callback's userData stays valid until removed.
struct ListenerBase { virtual ~ListenerBase() = default; };
template <typename T> struct EventListener : ListenerBase {
std::function<void(const T&)> handler;
explicit EventListener(std::function<void(const T&)> h)
: handler(std::move(h)) {}
};
} // namespace detail
struct ListenerHandle { std::uint64_t id = 0; };
extern "C" {
inline void my_timer_native_ack(int ret, const char* msg, std::size_t len, void* ud) {
auto* c = static_cast<detail::AckCapture*>(ud);
c->ret = ret;
if (ret == RET_ERR) c->err = detail::rawText(msg, len);
c->done.set_value();
}
inline void my_timer_native_str(int ret, const char* msg, std::size_t len, void* ud) {
auto* c = static_cast<detail::Capture<std::string>*>(ud);
c->ret = ret;
if (ret == RET_OK) c->value = detail::rawText(msg, len);
else c->err = detail::rawText(msg, len);
c->done.set_value();
}
inline void my_timer_native_my_timer_echo(int ret, const char* msg, std::size_t len, void* ud) {
auto* c = static_cast<detail::Capture<EchoResponse>*>(ud);
c->ret = ret;
if (ret == RET_OK) c->value = fromC(*reinterpret_cast<const ::EchoResponse*>(msg));
else c->err = detail::rawText(msg, len);
c->done.set_value();
}
inline void my_timer_native_my_timer_complex(int ret, const char* msg, std::size_t len, void* ud) {
auto* c = static_cast<detail::Capture<ComplexResponse>*>(ud);
c->ret = ret;
if (ret == RET_OK) c->value = fromC(*reinterpret_cast<const ::ComplexResponse*>(msg));
else c->err = detail::rawText(msg, len);
c->done.set_value();
}
inline void my_timer_native_my_timer_schedule(int ret, const char* msg, std::size_t len, void* ud) {
auto* c = static_cast<detail::Capture<ScheduleResult>*>(ud);
c->ret = ret;
if (ret == RET_OK) c->value = fromC(*reinterpret_cast<const ::ScheduleResult*>(msg));
else c->err = detail::rawText(msg, len);
c->done.set_value();
}
inline void my_timer_evt_OnEchoFired(int ret, const char* msg, std::size_t, void* ud) {
auto* l = static_cast<detail::EventListener<EchoEvent>*>(ud);
if (ret == RET_OK && l->handler)
l->handler(fromC(*reinterpret_cast<const ::EchoEvent*>(msg)));
}
} // extern "C"
class My_timerNode {
public:
explicit My_timerNode(const TimerConfig& config) {
detail::AckCapture cap;
auto fut = cap.done.get_future();
auto c_config = toC(config);
ctx_ = my_timer_create(c_config.c, my_timer_native_ack, &cap);
if (!ctx_) throw std::runtime_error("my_timer_create returned null");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
}
EchoResponse Echo(const EchoRequest& req) {
detail::Capture<EchoResponse> cap;
auto fut = cap.done.get_future();
auto c_req = toC(req);
if (my_timer_echo(ctx_, my_timer_native_my_timer_echo, &cap, c_req.c) != RET_OK)
throw std::runtime_error("my_timer_echo dispatch failed");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
return cap.value;
}
std::string Version() {
detail::Capture<std::string> cap;
auto fut = cap.done.get_future();
if (my_timer_version(ctx_, my_timer_native_str, &cap) != RET_OK)
throw std::runtime_error("my_timer_version dispatch failed");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
return cap.value;
}
ComplexResponse Complex(const ComplexRequest& req) {
detail::Capture<ComplexResponse> cap;
auto fut = cap.done.get_future();
auto c_req = toC(req);
if (my_timer_complex(ctx_, my_timer_native_my_timer_complex, &cap, c_req.c) != RET_OK)
throw std::runtime_error("my_timer_complex dispatch failed");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
return cap.value;
}
ScheduleResult Schedule(const JobSpec& job, const RetryPolicy& retry, const ScheduleConfig& schedule) {
detail::Capture<ScheduleResult> cap;
auto fut = cap.done.get_future();
auto c_job = toC(job);
auto c_retry = toC(retry);
auto c_schedule = toC(schedule);
if (my_timer_schedule(ctx_, my_timer_native_my_timer_schedule, &cap, c_job.c, c_retry.c, c_schedule.c) != RET_OK)
throw std::runtime_error("my_timer_schedule dispatch failed");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
return cap.value;
}
ListenerHandle OnEchoFired(std::function<void(const EchoEvent&)> handler) {
auto l = std::make_unique<detail::EventListener<EchoEvent>>(std::move(handler));
auto* raw = l.get();
const auto id = my_timer_add_event_listener(ctx_, "on_echo_fired", &my_timer_evt_OnEchoFired, raw);
if (id == 0) return ListenerHandle{0};
listeners_.emplace(id, std::move(l));
return ListenerHandle{id};
}
bool removeEventListener(ListenerHandle handle) {
if (handle.id == 0) return false;
const auto rc = my_timer_remove_event_listener(ctx_, handle.id);
listeners_.erase(handle.id);
return rc == 0;
}
~My_timerNode() { if (ctx_) my_timer_destroy(ctx_); }
My_timerNode(const My_timerNode&) = delete;
My_timerNode& operator=(const My_timerNode&) = delete;
private:
void* ctx_ = nullptr;
std::map<std::uint64_t, std::unique_ptr<detail::ListenerBase>> listeners_;
};
} // namespace my_timer
#endif // NIM_FFI_GEN_MY_TIMER_NATIVE_HPP