Aria
2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Toggle main menu visibility
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
60
namespace
aria::abi
{
61
67
using
SlotInvokeFailureHook
= void (*)(std::exception_ptr);
68
69
namespace
detail {
70
71
// Storage lives in libaria_abi (single TU) so every SHARED consumer
72
// reaches the same physical slot. See callback_boundary.cpp.
73
ARIA_ABI_API
std::atomic<SlotInvokeFailureHook>& slot_invoke_failure_hook()
noexcept
;
74
75
ARIA_ABI_API
void
report_slot_invoke_failure_(std::exception_ptr exception)
noexcept
;
76
77
}
// namespace detail
78
81
inline
SlotInvokeFailureHook
82
set_slot_invoke_failure_hook
(
SlotInvokeFailureHook
hook)
noexcept
{
83
return
detail::slot_invoke_failure_hook().exchange(hook, std::memory_order_acq_rel);
84
}
85
86
namespace
detail {
87
88
// Trampoline for the void*-args flavor: F is invoked with the raw args.
89
template
<
class
F>
90
inline
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.
102
template
<
class
F,
class
Bag>
103
inline
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.
113
template
<
class
F>
114
inline
constexpr
SlotErased::Destroyer
destroyer_v =
115
[](
void
* state)
noexcept
{
delete
static_cast<
F*
>
(state); };
116
117
}
// namespace detail
118
121
template
<
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
138
template
<
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
aria::abi::SlotErased
Type-erased callback wrapper.
Definition
slot.hpp:35
aria::abi::SlotErased::Destroyer
void(*)(void *state) noexcept Destroyer
Definition
slot.hpp:38
aria::abi::SlotErased::Invoker
void(*)(void *state, void *args) noexcept Invoker
Definition
slot.hpp:37
ARIA_ABI_API
#define ARIA_ABI_API
Definition
export.hpp:21
aria::abi
Definition
signal.hpp:12
aria::abi::make_slot_erased
SlotErased make_slot_erased(Fn &&fn)
Build a SlotErased from a callable that takes a raw void* args.
Definition
slot_factory.hpp:122
aria::abi::set_slot_invoke_failure_hook
SlotInvokeFailureHook set_slot_invoke_failure_hook(SlotInvokeFailureHook hook) noexcept
Install (or replace) the slot-invoke failure hook.
Definition
slot_factory.hpp:82
aria::abi::make_slot_for
SlotErased make_slot_for(Fn &&fn)
Build a SlotErased from a callable that takes const Bag&.
Definition
slot_factory.hpp:139
aria::abi::SlotInvokeFailureHook
void(*)(std::exception_ptr) SlotInvokeFailureHook
Hook invoked when the slot trampoline catches an exception escaping the user callable.
Definition
slot_factory.hpp:67
slot.hpp
modules
abi
include
aria
abi
slot_factory.hpp
Generated by
1.18.0