Simo 0.0.1
Loading...
Searching...
No Matches
Port.h
1/*
2 * Copyright 2026 Matteo Fusi and Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#ifndef SIMO_PORT_HH
18#define SIMO_PORT_HH
19
20#include <expected>
21#include <functional>
22#include <utility>
23
24#include "Simo/compiler/BoostTypeIndexRuntimeCast.h"
25#include "Simo/compiler/Compiler.h"
26
27namespace Simo {
28
30class SIMO_PUBLIC Port {
31 public:
32 virtual ~Port() = default;
33 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS()
34
35 using TypeId = boost::typeindex::type_index;
36
38 template <typename Self>
39 TypeId get_type_id(this Self& /*unused*/) {
40 return boost::typeindex::type_id<Self>();
41 }
42
43 TypeId get_runtime_type() const {
44 return boost::typeindex::type_id_runtime(*this);
45 }
46
47 [[nodiscard]]
48 virtual bool connect(Port* other) = 0;
49
50 virtual bool connected() const = 0;
51
52 [[nodiscard]] std::string_view name() const { return name_; }
53 void name(const std::string_view name) { name_ = name; }
54
55 protected:
56 std::string name_;
57};
58
59namespace Ports {
60
61template <typename Payload>
62class InPort;
63
64template <typename Payload, typename ReturnType>
65class CallbackOutPort;
66
71template <typename Payload>
72class SIMO_PUBLIC OutPort : public Port {
73 public:
74 friend class InPort<Payload>;
75
76 enum struct PORT_STATE : std::uint8_t {
77 EMPTY,
78 FILLED,
79 };
80
81 enum struct SEND_OUTCOME : std::uint8_t {
82 NEW,
83 REPLACED,
84 };
85
86 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
87 [[nodiscard]]
88 bool connect(Port* other) override;
89
90 [[nodiscard]]
91 bool connected() const override {
92 return connecting_port != nullptr;
93 }
94
95 SEND_OUTCOME send(Payload&& payload) {
96 storage = std::move(payload);
97 switch (state_) {
98 case PORT_STATE::EMPTY:
99 state_ = PORT_STATE::FILLED;
100 return SEND_OUTCOME::NEW;
101 case PORT_STATE::FILLED:
102 state_ = PORT_STATE::FILLED;
103 return SEND_OUTCOME::REPLACED;
104 }
105 std::abort();
106 }
107
108 SEND_OUTCOME send(Payload& payload) {
109 storage = payload;
110 switch (state_) {
111 case PORT_STATE::EMPTY:
112 state_ = PORT_STATE::FILLED;
113 return SEND_OUTCOME::NEW;
114 case PORT_STATE::FILLED:
115 state_ = PORT_STATE::FILLED;
116 return SEND_OUTCOME::REPLACED;
117 }
118 std::abort();
119 }
120
121 void clear() { state_ = PORT_STATE::EMPTY; }
122
123 PORT_STATE state() const { return state_; }
124
125 protected:
126 Payload storage;
127 PORT_STATE state_ = PORT_STATE::EMPTY;
128 InPort<Payload>* connecting_port = nullptr;
129};
130
135template <typename Payload>
136class SIMO_PUBLIC InPort : public Port {
137 public:
138 friend class OutPort<Payload>;
139 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
140 [[nodiscard]]
141 bool connect(Port* other) override;
142
143 [[nodiscard]]
144 bool connected() const override {
145 return connecting_port != nullptr;
146 }
147
148 Payload receive() {
149 SIMO_ASSERT(connecting_port != nullptr);
150 SIMO_ASSERT(connecting_port->state() !=
151 OutPort<Payload>::PORT_STATE::EMPTY);
152 Payload out_p = std::move(connecting_port->storage);
153 connecting_port->clear();
154 return out_p;
155 }
156
157 Payload* peek() {
158 if (connecting_port == nullptr ||
159 connecting_port->state() == OutPort<Payload>::PORT_STATE::EMPTY) {
160 return nullptr;
161 }
162 return &connecting_port->storage;
163 }
164
165 void clear() {
166 if (connecting_port != nullptr) {
167 connecting_port->clear();
168 }
169 }
170
171 protected:
172 OutPort<Payload>* connecting_port = nullptr;
173};
174
177template <typename Payload, typename ReturnType>
178class SIMO_PUBLIC CallbackInPort : public Port {
179 public:
180 friend class CallbackOutPort<Payload, ReturnType>;
181
182 using Callback = std::function<ReturnType(Payload)>;
183
184 CallbackInPort() = default;
185 explicit CallbackInPort(Callback callback) : callback_(std::move(callback)) {}
186
187 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
188 [[nodiscard]]
189 bool connect(Port* other) override;
190
191 [[nodiscard]]
192 bool connected() const override {
193 return connecting_port != nullptr;
194 }
195
196 void callback(Callback callback) { callback_ = std::move(callback); }
197
198 [[nodiscard]]
199 bool has_callback() const {
200 return static_cast<bool>(callback_);
201 }
202
203 protected:
204 void receive(Payload payload)
205 requires(std::is_same_v<ReturnType, void>)
206 {
207 if (callback_) {
208 callback_(std::forward<Payload>(payload));
209 }
210 }
211
212 [[nodiscard]]
213 std::optional<ReturnType> receive(Payload payload)
214 requires(!std::is_same_v<ReturnType, void>)
215 {
216 if (callback_) {
217 return callback_(std::forward<Payload>(payload));
218 }
219 return std::nullopt;
220 }
221
222 Callback callback_;
223 CallbackOutPort<Payload, ReturnType>* connecting_port = nullptr;
224};
225
226enum struct SIMO_PUBLIC VERIFY_CONTRACT_ERROR : std::uint8_t {
227 NO_CONNECTED_PORT,
228 NO_CONNECTED_PORT_CONTRACT,
229 NO_PREDICATE,
230};
231
236template <typename Contract>
237class SIMO_PUBLIC ContractInterface {
238 public:
239 std::optional<Contract> contract() { return contract_; }
240 void contract(Contract contract) { contract_ = contract; }
241
242 void connected_port(ContractInterface* port) { connected_port_ = port; }
243
244 void connected_port_contract_predicate(
245 std::function<bool(Contract)> predicate) {
246 connected_port_contract_predicate_ = predicate;
247 }
248 std::expected<bool, VERIFY_CONTRACT_ERROR> verify_connected_port_contract() {
249 if (connected_port_ == nullptr) {
250 return std::unexpected(VERIFY_CONTRACT_ERROR::NO_CONNECTED_PORT);
251 }
252 if (connected_port_->contract() == std::nullopt) {
253 return std::unexpected(VERIFY_CONTRACT_ERROR::NO_CONNECTED_PORT_CONTRACT);
254 }
255 if (!connected_port_contract_predicate_) {
256 return std::unexpected(VERIFY_CONTRACT_ERROR::NO_PREDICATE);
257 }
258 return connected_port_contract_predicate_(*connected_port_->contract());
259 }
260
261 protected:
262 std::optional<Contract> contract_;
263 ContractInterface* connected_port_ = nullptr;
264 std::function<bool(Contract)> connected_port_contract_predicate_{};
265};
266
270template <typename Payload, typename ReturnType, typename Contract>
271class SIMO_PUBLIC CallbackContractOutPort
272 : public CallbackOutPort<Payload, ReturnType>,
273 public ContractInterface<Contract> {
274 public:
275 CallbackContractOutPort() = default;
276
277 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(CallbackOutPort<Payload, ReturnType>)
278 [[nodiscard]]
279 bool connect(Port* other) override;
280};
281
285template <typename Payload, typename ReturnType, typename Contract>
286class SIMO_PUBLIC CallbackContractInPort
287 : public CallbackInPort<Payload, ReturnType>,
288 public ContractInterface<Contract> {
289 public:
290 CallbackContractInPort() = default;
291
292 explicit CallbackContractInPort(
293 CallbackInPort<Payload, ReturnType>::Callback callback)
294 : CallbackInPort<Payload, ReturnType>(std::move(callback)) {}
295
296 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(CallbackInPort<Payload, ReturnType>)
297 [[nodiscard]]
298 bool connect(Port* other) override;
299};
300
303template <typename Payload, typename ReturnType>
304class SIMO_PUBLIC CallbackOutPort : public Port {
305 public:
306 friend class CallbackInPort<Payload, ReturnType>;
307 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
308 [[nodiscard]]
309 bool connect(Port* other) override;
310
311 [[nodiscard]]
312 bool connected() const override {
313 return connecting_port != nullptr;
314 }
315
316 template <typename Arg>
317 requires std::constructible_from<Payload, Arg&&> &&
318 (!std::is_void_v<ReturnType>)
319 [[nodiscard]]
320 std::optional<ReturnType> send(Arg&& payload) {
321 if (connecting_port == nullptr) {
322 return std::nullopt;
323 }
324
325 return connecting_port->receive(std::forward<Arg>(payload));
326 }
327
328 template <typename Arg>
329 requires std::constructible_from<Payload, Arg&&> &&
330 std::is_void_v<ReturnType>
331 void send(Arg&& payload) {
332 if (connecting_port == nullptr || !connecting_port->has_callback()) {
333 return;
334 }
335
336 connecting_port->receive(std::forward<Arg>(payload));
337 }
338
339 protected:
340 CallbackInPort<Payload, ReturnType>* connecting_port = nullptr;
341};
342
343template <typename Payload>
344bool OutPort<Payload>::connect(Port* other) {
345 if (other == nullptr) {
346 return false;
347 }
348 auto* other_casted = boost::typeindex::runtime_cast<InPort<Payload>*>(other);
349 if (other_casted == nullptr) {
350 return false;
351 }
352 connecting_port = other_casted;
353 other_casted->connecting_port = this;
354 return true;
355}
356
357template <typename Payload>
358bool InPort<Payload>::connect(Port* other) {
359 if (other == nullptr) {
360 return false;
361 }
362 auto* other_casted = boost::typeindex::runtime_cast<OutPort<Payload>*>(other);
363 if (other_casted == nullptr) {
364 return false;
365 }
366 connecting_port = other_casted;
367 other_casted->connecting_port = this;
368 return true;
369}
370
371template <typename Payload, typename ReturnType>
372bool CallbackOutPort<Payload, ReturnType>::connect(Port* other) {
373 if (other == nullptr) {
374 return false;
375 }
376 auto* other_casted =
377 boost::typeindex::runtime_cast<CallbackInPort<Payload, ReturnType>*>(
378 other);
379 if (other_casted == nullptr) {
380 return false;
381 }
382 connecting_port = other_casted;
383 other_casted->connecting_port = this;
384 return true;
385}
386
387template <typename Payload, typename ReturnType>
388bool CallbackInPort<Payload, ReturnType>::connect(Port* other) {
389 if (other == nullptr) {
390 return false;
391 }
392 auto* other_casted =
393 boost::typeindex::runtime_cast<CallbackOutPort<Payload, ReturnType>*>(
394 other);
395 if (other_casted == nullptr) {
396 return false;
397 }
398 connecting_port = other_casted;
399 other_casted->connecting_port = this;
400 return true;
401}
402
403template <typename Payload, typename ReturnType, typename Contract>
404bool CallbackContractOutPort<Payload, ReturnType, Contract>::connect(
405 Port* other) {
406 if (other == nullptr) {
407 return false;
408 }
409 auto* other_casted = boost::typeindex::runtime_cast<
411 if (other_casted == nullptr) {
412 return false;
413 }
414 ContractInterface<Contract>::connected_port(other_casted);
415 other_casted->connected_port(this);
416 return CallbackOutPort<Payload, ReturnType>::connect(other_casted);
417}
418
419template <typename Payload, typename ReturnType, typename Contract>
420bool CallbackContractInPort<Payload, ReturnType, Contract>::connect(
421 Port* other) {
422 if (other == nullptr) {
423 return false;
424 }
425 auto* other_casted = boost::typeindex::runtime_cast<
427 if (other_casted == nullptr) {
428 return false;
429 }
430 ContractInterface<Contract>::connected_port(other_casted);
431 other_casted->connected_port(this);
432 return CallbackInPort<Payload, ReturnType>::connect(other_casted);
433}
434
438template <typename OutPayload, typename InPayload>
439class SIMO_PUBLIC BidirectionalPortTyped : public Port {
440 public:
441 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
442 bool connect(Port* other) override;
443
444 [[nodiscard]]
445 bool connected() const override {
446 return out_port.connected() && in_port.connected();
447 }
448
450 OutPort<OutPayload>::SEND_OUTCOME send_out(OutPayload&& payload) {
451 return out_port.send(std::move(payload));
452 }
453
454 OutPort<OutPayload>::SEND_OUTCOME send_out(OutPayload& payload) {
455 return out_port.send(payload);
456 }
457
458 void clear_out() { out_port.clear(); }
459
460 void clear_in() { in_port.clear(); }
461
463 InPayload&& receive_in() { return in_port.receive(); }
464
466 InPayload* peek_in() { return in_port.peek(); }
467
468 protected:
469 OutPort<OutPayload> out_port;
470 InPort<InPayload> in_port;
471};
472
473template <typename Payload>
474using BidirectionalPort = BidirectionalPortTyped<Payload, Payload>;
475
476template <typename OutPayload, typename InPayload>
477bool BidirectionalPortTyped<OutPayload, InPayload>::connect(Port* other) {
478 auto* other_casted = boost::typeindex::runtime_cast<
479 BidirectionalPortTyped<InPayload, OutPayload>*>(other);
480 if (other_casted == nullptr) {
481 return false;
482 }
483 return out_port.connect(&other_casted->in_port) &&
484 in_port.connect(&other_casted->out_port);
485}
486
487} // namespace Ports
488
489} // namespace Simo
490
491#endif // SIMO_PORT_HH
Generic port. It offers the virtual method connect.
Definition Port.h:30
TypeId get_type_id(this Self &)
Utility to the get type of class. Need the type at compile time.
Definition Port.h:39
InPayload && receive_in()
Extract the payload from the in port.
Definition Port.h:463
OutPort< OutPayload >::SEND_OUTCOME send_out(OutPayload &&payload)
Push a payload on the out port.
Definition Port.h:450
InPayload * peek_in()
Peek the payload from the in port.
Definition Port.h:466
Definition Port.h:178
Definition Port.h:304
Definition Port.h:237
Definition Port.h:136
Definition Port.h:72
General namespace for Simo.
Definition Collection.h:51