Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
rmlui_host_bridge.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4#include <cstdint>
5#include <functional>
6#include <limits>
7#include <memory>
8#include <string>
9#include <string_view>
10#include <utility>
11
13
14// Renderer and RmlUi headers deliberately stay outside this public contract.
15// An adapter must explicitly provide every capability before activation.
23
24enum class ProductEventKind : std::uint8_t {
28};
29
32 std::string id;
33 double value = 0.0;
34};
35
36class LuaHost {
37 public:
38 virtual ~LuaHost() = default;
39 // The implementation owns the Lua state and applies its capability policy.
40 virtual HostStatus initialise() = 0;
41 virtual HostStatus dispatch(std::string_view callback) = 0;
42};
43
45 std::string name;
46 std::function<HostStatus(std::string_view)> dispatch;
47};
48
50 public:
51 virtual ~ElementHost() = default;
53};
54
56 public:
57 virtual ~ProductHost() = default;
58 virtual HostStatus dispatch(ProductEvent event) = 0;
59};
60
61// A small, fail-closed handoff used by a future RmlUi adapter. It provides no
62// RmlUi or Lua state access: concrete adapters retain those implementation
63// details and expose only typed lifecycle and product events here. All methods
64// are UI-thread calls and must be serialized by the adapter. Provider calls are
65// synchronous; callback and registration names are copied at the boundary, and
66// provider exceptions are converted to rejected status. A retained element
67// callback is part of that same UI-thread serialization contract: providers
68// must invoke it on the owning UI thread and must not race close(). close()
69// releases the current graph; a fresh provider set may then be bound and
70// activated.
71class HostBridge final {
72 public:
73 HostBridge() = default;
75 close();
76 }
77 HostBridge(const HostBridge&) = delete;
78 HostBridge& operator=(const HostBridge&) = delete;
81
82 HostStatus bind_lua(std::shared_ptr<LuaHost> host) noexcept {
83 if (!host)
85 if (lua_ || active_ || generation_exhausted_)
87 if (!advance_generation())
89 lua_ = std::move(host);
90 return HostStatus::ok;
91 }
92
93 HostStatus bind_elements(std::shared_ptr<ElementHost> host) noexcept {
94 if (!host)
96 if (elements_ || active_ || generation_exhausted_)
98 if (!advance_generation())
100 elements_ = std::move(host);
101 return HostStatus::ok;
102 }
103
104 HostStatus bind_product(std::shared_ptr<ProductHost> host) noexcept {
105 if (!host)
107 if (product_ || active_ || generation_exhausted_)
109 if (!advance_generation())
111 product_ = std::move(host);
112 return HostStatus::ok;
113 }
114
116 if (!active_)
118 if (registering_)
120 if (registration.name.empty() || !registration.dispatch)
122 auto elements = elements_;
123 const auto identity = gate_->identity.load(std::memory_order_acquire);
124 auto gate = gate_;
125 auto dispatch = std::move(registration.dispatch);
126 registration.dispatch = [weak_gate = std::weak_ptr<BridgeGate>(gate), identity,
127 dispatch = std::move(dispatch)](std::string_view value) mutable {
128 const auto locked = weak_gate.lock();
129 if (!locked || locked->identity.load(std::memory_order_acquire) != identity ||
130 !is_active_identity(identity))
132 try {
133 return dispatch(value);
134 } catch (...) {
136 }
137 };
138 registering_ = true;
139 try {
140 const auto status = elements->register_element(std::move(registration));
141 registering_ = false;
142 return active_ && elements_ == elements &&
143 gate_->identity.load(std::memory_order_acquire) == identity
144 ? status
146 } catch (...) {
147 registering_ = false;
148 close();
150 }
151 }
152
154 if (active_ || activating_)
156 if (!lua_ || !elements_ || !product_)
158 auto lua = lua_;
159 if (!advance_generation())
161 const auto generation = generation_;
162 activating_ = true;
164 try {
165 status = lua->initialise();
166 } catch (...) {
167 activating_ = false;
168 close();
170 }
171 activating_ = false;
172 if (status != HostStatus::ok)
173 return status;
174 if (generation_ != generation || lua_ != lua || !elements_ || !product_)
176 active_ = true;
177 gate_->identity.store(pack_identity(true, generation_), std::memory_order_release);
178 return HostStatus::ok;
179 }
180
181 HostStatus dispatch_lua(std::string_view callback) {
182 if (!active_)
184 if (callback.empty())
186 auto lua = lua_;
187 const auto generation = generation_;
188 try {
189 const auto status = lua->dispatch(callback);
190 return active_ && lua_ == lua && generation_ == generation ? status
192 } catch (...) {
193 close();
195 }
196 }
197
199 if (!active_)
201 if (event.id.empty() || !valid_event_kind(event.kind))
203 auto product = product_;
204 const auto generation = generation_;
205 try {
206 const auto status = product->dispatch(std::move(event));
207 return active_ && product_ == product && generation_ == generation
208 ? status
210 } catch (...) {
211 close();
213 }
214 }
215
216 void close() noexcept {
217 active_ = false;
218 activating_ = false;
219 registering_ = false;
220 (void)advance_generation();
221 gate_->identity.store(pack_identity(false, generation_), std::memory_order_release);
222 product_.reset();
223 elements_.reset();
224 lua_.reset();
225 }
226 [[nodiscard]] bool active() const noexcept {
227 return active_;
228 }
229
230 private:
231 struct BridgeGate {
232 // The active bit and generation are one publication unit. Retained
233 // callbacks must never observe a newly active bridge with an older
234 // generation between two separate stores.
235 std::atomic<std::uint64_t> identity{0};
236 };
237
238 static constexpr std::uint64_t kMaxGeneration = std::numeric_limits<std::uint64_t>::max() >> 1;
239
240 static constexpr std::uint64_t pack_identity(bool active, std::uint64_t generation) noexcept {
241 return (generation << 1) | (active ? 1u : 0u);
242 }
243
244 static constexpr bool is_active_identity(std::uint64_t identity) noexcept {
245 return (identity & 1u) != 0;
246 }
247
248 bool advance_generation() noexcept {
249 // A wrapped identity would make an old retained callback valid again.
250 // Exhaust the bridge instead of allowing ABA after the finite counter.
251 if (generation_exhausted_ || generation_ == kMaxGeneration) {
252 generation_exhausted_ = true;
253 return false;
254 }
255 ++generation_;
256 return true;
257 }
258
259 static bool valid_event_kind(ProductEventKind kind) noexcept {
260 switch (kind) {
264 return true;
265 }
266 return false;
267 }
268
269 std::shared_ptr<LuaHost> lua_;
270 std::shared_ptr<ElementHost> elements_;
271 std::shared_ptr<ProductHost> product_;
272 bool active_ = false;
273 bool activating_ = false;
274 bool registering_ = false;
275 std::uint64_t generation_ = 0;
276 bool generation_exhausted_ = false;
277 std::shared_ptr<BridgeGate> gate_ = std::make_shared<BridgeGate>();
278};
279
280} // namespace pulp::view::rmlui
Definition rmlui_host_bridge.hpp:49
virtual HostStatus register_element(ElementRegistration registration)=0
virtual ~ElementHost()=default
HostStatus dispatch_product(ProductEvent event)
Definition rmlui_host_bridge.hpp:198
HostStatus register_element(ElementRegistration registration)
Definition rmlui_host_bridge.hpp:115
HostBridge(const HostBridge &)=delete
HostStatus bind_elements(std::shared_ptr< ElementHost > host) noexcept
Definition rmlui_host_bridge.hpp:93
HostStatus dispatch_lua(std::string_view callback)
Definition rmlui_host_bridge.hpp:181
HostStatus activate()
Definition rmlui_host_bridge.hpp:153
~HostBridge()
Definition rmlui_host_bridge.hpp:74
HostBridge & operator=(const HostBridge &)=delete
HostStatus bind_product(std::shared_ptr< ProductHost > host) noexcept
Definition rmlui_host_bridge.hpp:104
void close() noexcept
Definition rmlui_host_bridge.hpp:216
HostBridge(HostBridge &&)=delete
bool active() const noexcept
Definition rmlui_host_bridge.hpp:226
HostStatus bind_lua(std::shared_ptr< LuaHost > host) noexcept
Definition rmlui_host_bridge.hpp:82
HostBridge & operator=(HostBridge &&)=delete
Definition rmlui_host_bridge.hpp:36
virtual ~LuaHost()=default
virtual HostStatus initialise()=0
virtual HostStatus dispatch(std::string_view callback)=0
Definition rmlui_host_bridge.hpp:55
virtual ~ProductHost()=default
virtual HostStatus dispatch(ProductEvent event)=0
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
Definition rmlui_host_bridge.hpp:12
ProductEventKind
Definition rmlui_host_bridge.hpp:24
@ parameter
Definition rmlui_host_bridge.hpp:25
@ action
Definition rmlui_host_bridge.hpp:26
@ selection
Definition rmlui_host_bridge.hpp:27
HostStatus
Definition rmlui_host_bridge.hpp:16
@ missing_capability
Definition rmlui_host_bridge.hpp:18
@ not_active
Definition rmlui_host_bridge.hpp:20
@ already_bound
Definition rmlui_host_bridge.hpp:19
@ ok
Definition rmlui_host_bridge.hpp:17
@ rejected
Definition rmlui_host_bridge.hpp:21
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ u
Definition input_events.hpp:41
Definition rmlui_host_bridge.hpp:44
std::string name
Definition rmlui_host_bridge.hpp:45
std::function< HostStatus(std::string_view)> dispatch
Definition rmlui_host_bridge.hpp:46
Definition rmlui_host_bridge.hpp:30
std::string id
Definition rmlui_host_bridge.hpp:32
ProductEventKind kind
Definition rmlui_host_bridge.hpp:31
double value
Definition rmlui_host_bridge.hpp:33