Remove wildcard event listener; keep per-event dispatch (#70)

This commit is contained in:
Ivan FB
2026-06-02 22:22:35 +02:00
committed by GitHub
parent e0bd74232b
commit f96a5b158a
16 changed files with 96 additions and 518 deletions
+2 -4
View File
@@ -1,10 +1,8 @@
cmake_minimum_required(VERSION 3.14)
project(my_timer_cpp_bindings CXX C)
# The generated bindings target C++20. The event-listener API uses
# std::span<const std::uint8_t> on its wildcard callback to hand the
# CBOR envelope to consumers as a zero-copy view; <span> only became
# part of the standard library in C++20.
# The generated bindings target C++20: designated initializers and other
# C++20 constructs are used throughout the emitted code.
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+6 -35
View File
@@ -92,54 +92,25 @@ int main() {
// 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
// Subscribe to each event separately; handlers fire from the lib's
// dispatch thread, so synchronise via std::promise / atomics.
std::promise<EchoEvent> echoEvtPromise;
auto echoEvtFuture = echoEvtPromise.get_future();
const auto typedHandle = ctx->addOnEchoFiredListener(
[&](const EchoEvent& evt) { echoEvtPromise.set_value(evt); });
std::atomic<int> 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<T>` 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<const std::uint8_t> 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";
<< ", echoCount=" << evt.echoCount << "\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.
// Drop the typed listener — no handler fires for the follow-up echo.
// Sleep briefly to give the lib thread time to settle 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 << "[7] after removeEventListener: typed listener removed\n";
std::cout << "\nDone.\n";
return 0;
+2 -54
View File
@@ -1,6 +1,6 @@
#pragma once
// Generated bindings require C++20 — the event-listener API uses
// std::span<const std::uint8_t> for the wildcard callback.
// Generated bindings require C++20 (designated initializers and other
// C++20 constructs are used throughout the emitted code).
// MSVC keeps __cplusplus at 199711L unless /Zc:__cplusplus is passed,
// so consult _MSVC_LANG when present (it always reflects the active
// /std:c++XX level).
@@ -30,7 +30,6 @@ extern "C" {
}
#include <unordered_map>
#include <span>
// ============================================================
// Result<T> — exception-free error channel
// ============================================================
@@ -773,19 +772,6 @@ inline Result<std::vector<std::uint8_t>> ffi_call_(
#endif // NIM_FFI_SYNC_CALL_HELPER_HPP_INCLUDED
template <class T>
inline bool decodeEventPayload(std::span<const std::uint8_t> envelope, T& out) {
if (envelope.empty()) return false;
CborParser parser; CborValue it;
if (cbor_parser_init(envelope.data(), envelope.size(), 0, &parser, &it) != CborNoError)
return false;
if (!cbor_value_is_map(&it)) return false;
CborValue payloadField;
if (cbor_value_map_find_value(&it, "payload", &payloadField) != CborNoError)
return false;
return decode_cbor(payloadField, out) == CborNoError;
}
// ============================================================
// High-level C++ context class
// ============================================================
@@ -853,16 +839,6 @@ public:
return ListenerHandle{id};
}
ListenerHandle addEventListener(std::function<void(int, const std::string&, std::span<const std::uint8_t>)> handler) {
auto owned = std::make_unique<WildcardListener>(std::move(handler));
auto* raw = owned.get();
const auto id = my_timer_add_event_listener(
ptr_, "", &MyTimerCtx::wildcardTrampoline, raw);
if (id == 0) return ListenerHandle{0};
listeners_.emplace(id, std::move(owned));
return ListenerHandle{id};
}
bool removeEventListener(ListenerHandle handle) {
if (handle.id == 0) return false;
const auto rc = my_timer_remove_event_listener(ptr_, handle.id);
@@ -945,11 +921,6 @@ private:
explicit TypedListener(std::function<void(const T&)> f) : fn(std::move(f)) {}
};
struct WildcardListener : ListenerBase {
std::function<void(int, const std::string&, std::span<const std::uint8_t>)> fn;
explicit WildcardListener(std::function<void(int, const std::string&, std::span<const std::uint8_t>)> f) : fn(std::move(f)) {}
};
template <class T>
static void typedTrampoline(int ret, const char* msg, std::size_t len, void* ud) {
if (!ud || ret != 0 || !msg || len == 0) return;
@@ -965,29 +936,6 @@ private:
listener->fn(payload);
}
static void wildcardTrampoline(int ret, const char* msg, std::size_t len, void* ud) {
if (!ud) return;
auto* listener = static_cast<WildcardListener*>(ud);
if (!listener->fn) return;
std::span<const std::uint8_t> envelope{};
if (msg && len > 0) {
envelope = std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(msg), len);
}
std::string eventId;
if (ret == 0 && !envelope.empty()) {
CborParser parser; CborValue it;
if (cbor_parser_init(envelope.data(), envelope.size(), 0, &parser, &it) == CborNoError
&& cbor_value_is_map(&it)) {
CborValue evtField;
if (cbor_value_map_find_value(&it, "eventType", &evtField) == CborNoError
&& cbor_value_is_text_string(&evtField)) {
(void)decode_cbor(evtField, eventId);
}
}
}
listener->fn(ret, eventId, envelope);
}
void* ptr_;
std::chrono::milliseconds timeout_;
std::unordered_map<std::uint64_t, std::unique_ptr<ListenerBase>> listeners_;