mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-08-26 23:51:08 +00:00
Remove wildcard event listener; keep per-event dispatch (#70)
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user