Aria 2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
slot_factory.hpp
Go to the documentation of this file.
1#pragma once
2
3// ============================================================================
4// abi/slot_factory.hpp
5// ----------------------------------------------------------------------------
6// Header-only factories that turn an arbitrary callable into a
7// type-erased `aria::abi::SlotErased`.
8//
9// Why factories live here, not in each consumer:
10// Before this header existed, four independent translation units
11// (detail/typed_signal.hpp + qt6/appkit/uikit adapters) each spelled
12// out the same heap-allocate-state / invoker-trampoline / destroyer
13// pattern. That is bug-prone (subtly inconsistent exception policies
14// drifted across the four copies) and has no business being repeated.
15// Concentrating it here gives one canonical, exception-safe spelling.
16//
17// Two flavors are offered:
18//
19// make_slot_erased<Fn>(fn)
20// The callable accepts a raw `void* args` (the caller is
21// responsible for casting). Used by adapters that already build
22// a typed args bag and forward the pointer.
23//
24// make_slot_for<Bag, Fn>(fn)
25// The callable accepts `const Bag&`. The cast from `void*` is
26// performed inside the trampoline. This is the form ~all real
27// callers actually want.
28//
29// Exception policy:
30// The trampoline catches all exceptions from the user callable.
31// Slot invocation must be `noexcept` at the ABI boundary, so any
32// user exception is contained locally rather than being allowed to
33// cross the trampoline (which would terminate the process). When a
34// slot-invoke failure hook has been installed (typically by `core`
35// via `aria::abi::set_slot_invoke_failure_hook`), the captured
36// exception is reported through the hook before the trampoline
37// returns; otherwise it is reported through the unified callback-failure
38// boundary, whose default sink writes to stderr.
39//
40// The hook itself is **not** allowed to throw — it is invoked from a
41// noexcept boundary. A throwing hook falls back to the unified reporter.
42//
43// Allocation:
44// State is stored on the heap (one allocation) because we cannot
45// place arbitrary callables into the small `void*` slot. The state
46// is owned by the SlotErased and freed via the destroyer trampoline.
47// A `std::unique_ptr` holds the state until ownership transfers,
48// even though the SlotErased ctor itself is `noexcept` -- this keeps
49// the factory exception-safe in the face of future ABI changes.
50// ============================================================================
51
52#include "aria/abi/slot.hpp"
53
54#include <atomic>
55#include <exception>
56#include <memory>
57#include <type_traits>
58#include <utility>
59
60namespace aria::abi {
61
67using SlotInvokeFailureHook = void (*)(std::exception_ptr);
68
69namespace detail {
70
71// Storage lives in libaria_abi (single TU) so every SHARED consumer
72// reaches the same physical slot. See callback_boundary.cpp.
73ARIA_ABI_API std::atomic<SlotInvokeFailureHook>& slot_invoke_failure_hook() noexcept;
74
75ARIA_ABI_API void report_slot_invoke_failure_(std::exception_ptr exception) noexcept;
76
77} // namespace detail
78
83 return detail::slot_invoke_failure_hook().exchange(hook, std::memory_order_acq_rel);
84}
85
86namespace detail {
87
88// Trampoline for the void*-args flavor: F is invoked with the raw args.
89template <class F>
90inline constexpr SlotErased::Invoker raw_invoker_v =
91 [](void* state, void* args) noexcept {
92 try {
93 (*static_cast<F*>(state))(args);
94 } catch (...) {
95 detail::report_slot_invoke_failure_(std::current_exception());
96 }
97 };
98
99// Trampoline for the typed-bag flavor: F is invoked with `const Bag&`.
100// `args` must point to a valid Bag, including when Bag is an empty type.
101// Signals with no payload can instead use the raw void* flavor.
102template <class F, class Bag>
103inline constexpr SlotErased::Invoker bag_invoker_v =
104 [](void* state, void* args) noexcept {
105 try {
106 (*static_cast<F*>(state))(*static_cast<const Bag*>(args));
107 } catch (...) {
108 detail::report_slot_invoke_failure_(std::current_exception());
109 }
110 };
111
112// Common destroyer.
113template <class F>
114inline constexpr SlotErased::Destroyer destroyer_v =
115 [](void* state) noexcept { delete static_cast<F*>(state); };
116
117} // namespace detail
118
121template <class Fn>
122[[nodiscard]] SlotErased make_slot_erased(Fn&& fn) {
123 using F = std::decay_t<Fn>;
124 static_assert(std::is_invocable_v<F&, void*>,
125 "make_slot_erased: Fn must be callable with void*");
126
127 // Hold via unique_ptr until ownership transfers into SlotErased.
128 auto state = std::make_unique<F>(std::forward<Fn>(fn));
129 SlotErased slot{detail::raw_invoker_v<F>,
130 detail::destroyer_v<F>,
131 state.get()};
132 (void)state.release();
133 return slot;
134}
135
138template <class Bag, class Fn>
139[[nodiscard]] SlotErased make_slot_for(Fn&& fn) {
140 using F = std::decay_t<Fn>;
141 static_assert(std::is_invocable_v<F&, const Bag&>,
142 "make_slot_for: Fn must be callable with const Bag&");
143
144 auto state = std::make_unique<F>(std::forward<Fn>(fn));
145 SlotErased slot{detail::bag_invoker_v<F, Bag>,
146 detail::destroyer_v<F>,
147 state.get()};
148 (void)state.release();
149 return slot;
150}
151
152} // namespace aria::abi
Type-erased callback wrapper.
Definition slot.hpp:35
void(*)(void *state) noexcept Destroyer
Definition slot.hpp:38
void(*)(void *state, void *args) noexcept Invoker
Definition slot.hpp:37
#define ARIA_ABI_API
Definition export.hpp:21
Definition signal.hpp:12
SlotErased make_slot_erased(Fn &&fn)
Build a SlotErased from a callable that takes a raw void* args.
Definition slot_factory.hpp:122
SlotInvokeFailureHook set_slot_invoke_failure_hook(SlotInvokeFailureHook hook) noexcept
Install (or replace) the slot-invoke failure hook.
Definition slot_factory.hpp:82
SlotErased make_slot_for(Fn &&fn)
Build a SlotErased from a callable that takes const Bag&.
Definition slot_factory.hpp:139
void(*)(std::exception_ptr) SlotInvokeFailureHook
Hook invoked when the slot trampoline catches an exception escaping the user callable.
Definition slot_factory.hpp:67