#include "my_timer.hpp" #include #include #include #include #include // The generated bindings never throw: every call returns a Result. We // branch on isErr() and read value()/error() instead of using try/catch. int main() { auto ctxRes = MyTimerCtx::create(TimerConfig{"cpp-demo"}); if (ctxRes.isErr()) { std::cerr << "Error: " << ctxRes.error() << "\n"; return 1; } auto ctx = std::move(ctxRes.value()); std::cout << "[1] Context created\n"; auto versionFuture = ctx->versionAsync(); auto echo1Future = ctx->echoAsync(EchoRequest{"hello from C++", 200}); auto echo2Future = ctx->echoAsync(EchoRequest{"second C++ request", 50}); auto version = versionFuture.get(); if (version.isErr()) { std::cerr << "Error: " << version.error() << "\n"; return 1; } std::cout << "[2] Version: " << version.value() << "\n"; auto echo = echo1Future.get(); if (echo.isErr()) { std::cerr << "Error: " << echo.error() << "\n"; return 1; } std::cout << "[3] Echo 1: echoed=" << echo->echoed << ", timerName=" << echo->timerName << "\n"; auto echo2 = echo2Future.get(); if (echo2.isErr()) { std::cerr << "Error: " << echo2.error() << "\n"; return 1; } std::cout << "[4] Echo 2: echoed=" << echo2->echoed << ", timerName=" << echo2->timerName << "\n"; auto complexReq = ComplexRequest{ std::vector{EchoRequest{"one", 10}, EchoRequest{"two", 20}}, std::vector{"fast", "async"}, std::optional("extra note"), std::optional(3) }; auto complex = ctx->complexAsync(complexReq).get(); if (complex.isErr()) { std::cerr << "Error: " << complex.error() << "\n"; return 1; } std::cout << "[5] Complex: summary=" << complex->summary << ", itemCount=" << complex->itemCount << ", hasNote=" << complex->hasNote << "\n"; // ── 6. Call with three complex parameters ───────────────────── // Each parameter is its own generated C++ struct. The nim-ffi // macro packs all three into one CBOR envelope on the wire — at // the call site, this is just a typed method invocation. auto job = JobSpec{ /*name*/ "nightly-rollup", /*payload*/ std::vector{"rollup", "v2"}, /*priority*/ 10, }; auto retry = RetryPolicy{ /*maxAttempts*/ 3, /*backoffMs*/ 500, /*retryOn*/ std::vector{"timeout", "5xx"}, }; auto schedule = ScheduleConfig{ /*startAtMs*/ 1000, /*intervalMs*/ 15000, /*jitter*/ std::optional(250), }; auto scheduleRes = ctx->scheduleAsync(job, retry, schedule).get(); if (scheduleRes.isErr()) { std::cerr << "Error: " << scheduleRes.error() << "\n"; return 1; } std::cout << "[6] Schedule (3 complex params): jobId=" << scheduleRes->jobId << ", willRunCount=" << scheduleRes->willRunCount << ", firstRunAtMs=" << scheduleRes->firstRunAtMs << ", effectiveBackoffMs=" << scheduleRes->effectiveBackoffMs << "\n"; // Each `{.ffiEvent.}` declared on the Nim side gets a typed // registration method — `addOnEchoFiredListener(handler)` here. // A second `addEventListener` overload registers a catch-all // wildcard listener that receives every event as raw envelope // bytes plus the FFI return code. Both fire from the lib's // dispatch thread, so synchronise via std::promise / atomics. std::promise echoEvtPromise; auto echoEvtFuture = echoEvtPromise.get_future(); const auto typedHandle = ctx->addOnEchoFiredListener( [&](const EchoEvent& evt) { echoEvtPromise.set_value(evt); }); std::atomic wildcardHits{0}; // Wildcard listener receives every event with the wire `eventId` // pre-extracted plus a span view over the raw CBOR envelope // bytes (zero-copy; valid only for the duration of this call). // Dispatch on `eventId` and use `decodeEventPayload` to lift // the payload into a typed value without hand-parsing CBOR. const auto wildcardHandle = ctx->addEventListener( [&](int retCode, const std::string& eventId, std::span envelope) { wildcardHits.fetch_add(1); std::cout << "[7] wildcard event: retCode=" << retCode << ", eventId=" << eventId << ", envelope bytes=" << envelope.size() << "\n"; if (retCode != 0) return; if (eventId == "on_echo_fired") { EchoEvent decoded{}; if (decodeEventPayload(envelope, decoded)) { std::cout << " decoded EchoEvent: message=" << decoded.message << ", echoCount=" << decoded.echoCount << "\n"; } } }); ctx->echo(EchoRequest{"event-demo", 1}); const auto evt = echoEvtFuture.get(); std::cout << "[7] typed event onEchoFired: message=" << evt.message << ", echoCount=" << evt.echoCount << ", wildcardHits=" << wildcardHits.load() << "\n"; // Drop the typed listener — only the wildcard fires for the // follow-up echo. Sleep briefly to give the lib thread time to // deliver before we tear the ctx down. ctx->removeEventListener(typedHandle); ctx->echo(EchoRequest{"event-demo-after-remove", 1}); std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::cout << "[7] after removeEventListener: wildcardHits=" << wildcardHits.load() << "\n"; ctx->removeEventListener(wildcardHandle); std::cout << "\nDone.\n"; return 0; }