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 [[nodiscard]]
44 virtual bool connect(Port* other) = 0;
45
46 [[nodiscard]] std::string_view name() const { return name_; }
47 void name(const std::string_view name) { name_ = name; }
48
49 protected:
50 std::string name_;
51};
52
53namespace Ports {
54
55template <typename Payload>
56class InPort;
57
58template <typename Payload, typename ReturnType>
59class CallbackOutPort;
60
65template <typename Payload>
66class SIMO_PUBLIC OutPort : public Port {
67 public:
68 friend class InPort<Payload>;
69
70 enum struct PORT_STATE : std::uint8_t {
71 EMPTY,
72 FILLED,
73 };
74
75 enum struct SEND_OUTCOME : std::uint8_t {
76 NEW,
77 REPLACED,
78 };
79
80 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
81 [[nodiscard]]
82 bool connect(Port* other) override;
83
84 SEND_OUTCOME send(Payload&& payload) {
85 storage = std::move(payload);
86 switch (state_) {
87 case PORT_STATE::EMPTY:
88 state_ = PORT_STATE::FILLED;
89 return SEND_OUTCOME::NEW;
90 case PORT_STATE::FILLED:
91 state_ = PORT_STATE::FILLED;
92 return SEND_OUTCOME::REPLACED;
93 }
94 std::abort();
95 }
96
97 SEND_OUTCOME send(Payload& payload) {
98 storage = payload;
99 switch (state_) {
100 case PORT_STATE::EMPTY:
101 state_ = PORT_STATE::FILLED;
102 return SEND_OUTCOME::NEW;
103 case PORT_STATE::FILLED:
104 state_ = PORT_STATE::FILLED;
105 return SEND_OUTCOME::REPLACED;
106 }
107 std::abort();
108 }
109
110 void clear() { state_ = PORT_STATE::EMPTY; }
111
112 PORT_STATE state() const { return state_; }
113
114 protected:
115 Payload storage;
116 PORT_STATE state_ = PORT_STATE::EMPTY;
117};
118
123template <typename Payload>
124class SIMO_PUBLIC InPort : public Port {
125 public:
126 friend class OutPort<Payload>;
127 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
128 [[nodiscard]]
129 bool connect(Port* other) override;
130
131 Payload receive() {
132 SIMO_ASSERT(connecting_port != nullptr);
133 SIMO_ASSERT(connecting_port->state() !=
134 OutPort<Payload>::PORT_STATE::EMPTY);
135 Payload out_p = std::move(connecting_port->storage);
136 connecting_port->clear();
137 return out_p;
138 }
139
140 Payload* peek() {
141 if (connecting_port == nullptr ||
142 connecting_port->state() == OutPort<Payload>::PORT_STATE::EMPTY) {
143 return nullptr;
144 }
145 return &connecting_port->storage;
146 }
147
148 void clear() {
149 if (connecting_port != nullptr) {
150 connecting_port->clear();
151 }
152 }
153
154 protected:
155 OutPort<Payload>* connecting_port = nullptr;
156};
157
160template <typename Payload, typename ReturnType>
161class SIMO_PUBLIC CallbackInPort : public Port {
162 public:
163 friend class CallbackOutPort<Payload, ReturnType>;
164
165 using Callback = std::function<ReturnType(Payload)>;
166
167 CallbackInPort() = default;
168 explicit CallbackInPort(Callback callback) : callback_(std::move(callback)) {}
169
170 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
171 [[nodiscard]]
172 bool connect(Port* other) override;
173
174 void callback(Callback callback) { callback_ = std::move(callback); }
175
176 [[nodiscard]]
177 bool has_callback() const {
178 return static_cast<bool>(callback_);
179 }
180
181 protected:
182 void receive(Payload payload)
183 requires(std::is_same_v<ReturnType, void>)
184 {
185 if (callback_) {
186 callback_(std::forward<Payload>(payload));
187 }
188 }
189
190 [[nodiscard]]
191 std::optional<ReturnType> receive(Payload payload)
192 requires(!std::is_same_v<ReturnType, void>)
193 {
194 if (callback_) {
195 return callback_(std::forward<Payload>(payload));
196 }
197 return std::nullopt;
198 }
199
200 Callback callback_;
201};
202
203enum struct SIMO_PUBLIC VERIFY_CONTRACT_ERROR : std::uint8_t {
204 NO_CONNECTED_PORT,
205 NO_CONNECTED_PORT_CONTRACT,
206 NO_PREDICATE,
207};
208
213template <typename Contract>
214class SIMO_PUBLIC ContractInterface {
215 public:
216 std::optional<Contract> contract() { return contract_; }
217 void contract(Contract contract) { contract_ = contract; }
218
219 void connected_port(ContractInterface* port) { connected_port_ = port; }
220
221 void connected_port_contract_predicate(
222 std::function<bool(Contract)> predicate) {
223 connected_port_contract_predicate_ = predicate;
224 }
225 std::expected<bool, VERIFY_CONTRACT_ERROR> verify_connected_port_contract() {
226 if (connected_port_ == nullptr) {
227 return std::unexpected(VERIFY_CONTRACT_ERROR::NO_CONNECTED_PORT);
228 }
229 if (connected_port_->contract() == std::nullopt) {
230 return std::unexpected(VERIFY_CONTRACT_ERROR::NO_CONNECTED_PORT_CONTRACT);
231 }
232 if (!connected_port_contract_predicate_) {
233 return std::unexpected(VERIFY_CONTRACT_ERROR::NO_PREDICATE);
234 }
235 return connected_port_contract_predicate_(*connected_port_->contract());
236 }
237
238 protected:
239 std::optional<Contract> contract_;
240 ContractInterface* connected_port_ = nullptr;
241 std::function<bool(Contract)> connected_port_contract_predicate_{};
242};
243
247template <typename Payload, typename ReturnType, typename Contract>
248class SIMO_PUBLIC CallbackContractOutPort
249 : public CallbackOutPort<Payload, ReturnType>,
250 public ContractInterface<Contract> {
251 public:
252 CallbackContractOutPort() = default;
253
254 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(CallbackOutPort<Payload, ReturnType>)
255 [[nodiscard]]
256 bool connect(Port* other) override;
257};
258
262template <typename Payload, typename ReturnType, typename Contract>
263class SIMO_PUBLIC CallbackContractInPort
264 : public CallbackInPort<Payload, ReturnType>,
265 public ContractInterface<Contract> {
266 public:
267 CallbackContractInPort() = default;
268
269 explicit CallbackContractInPort(
270 CallbackInPort<Payload, ReturnType>::Callback callback)
271 : CallbackInPort<Payload, ReturnType>(std::move(callback)) {}
272
273 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(CallbackInPort<Payload, ReturnType>)
274 [[nodiscard]]
275 bool connect(Port* other) override;
276};
277
280template <typename Payload, typename ReturnType>
281class SIMO_PUBLIC CallbackOutPort : public Port {
282 public:
283 friend class CallbackInPort<Payload, ReturnType>;
284 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
285 [[nodiscard]]
286 bool connect(Port* other) override;
287
288 template <typename Arg>
289 requires std::constructible_from<Payload, Arg&&> &&
290 (!std::is_void_v<ReturnType>)
291 [[nodiscard]]
292 std::optional<ReturnType> send(Arg&& payload) {
293 if (connecting_port == nullptr) {
294 return std::nullopt;
295 }
296
297 return connecting_port->receive(std::forward<Arg>(payload));
298 }
299
300 template <typename Arg>
301 requires std::constructible_from<Payload, Arg&&> &&
302 std::is_void_v<ReturnType>
303 void send(Arg&& payload) {
304 if (connecting_port == nullptr || !connecting_port->has_callback()) {
305 return;
306 }
307
308 connecting_port->receive(std::forward<Arg>(payload));
309 }
310
311 protected:
312 CallbackInPort<Payload, ReturnType>* connecting_port = nullptr;
313};
314
315template <typename Payload>
316bool OutPort<Payload>::connect(Port* other) {
317 if (other == nullptr) {
318 return false;
319 }
320 auto* other_casted = boost::typeindex::runtime_cast<InPort<Payload>*>(other);
321 if (other_casted == nullptr) {
322 return false;
323 }
324 other_casted->connecting_port = this;
325 return true;
326}
327
328template <typename Payload>
329bool InPort<Payload>::connect(Port* other) {
330 if (other == nullptr) {
331 return false;
332 }
333 auto* other_casted = boost::typeindex::runtime_cast<OutPort<Payload>*>(other);
334 if (other_casted == nullptr) {
335 return false;
336 }
337 connecting_port = other_casted;
338 return true;
339}
340
341template <typename Payload, typename ReturnType>
342bool CallbackOutPort<Payload, ReturnType>::connect(Port* other) {
343 if (other == nullptr) {
344 return false;
345 }
346 auto* other_casted =
347 boost::typeindex::runtime_cast<CallbackInPort<Payload, ReturnType>*>(
348 other);
349 if (other_casted == nullptr) {
350 return false;
351 }
352 connecting_port = other_casted;
353 return true;
354}
355
356template <typename Payload, typename ReturnType>
357bool CallbackInPort<Payload, ReturnType>::connect(Port* other) {
358 if (other == nullptr) {
359 return false;
360 }
361 auto* other_casted =
362 boost::typeindex::runtime_cast<CallbackOutPort<Payload, ReturnType>*>(
363 other);
364 if (other_casted == nullptr) {
365 return false;
366 }
367 other_casted->connecting_port = this;
368 return true;
369}
370
371template <typename Payload, typename ReturnType, typename Contract>
372bool CallbackContractOutPort<Payload, ReturnType, Contract>::connect(
373 Port* other) {
374 if (other == nullptr) {
375 return false;
376 }
377 auto* other_casted = boost::typeindex::runtime_cast<
379 if (other_casted == nullptr) {
380 return false;
381 }
382 ContractInterface<Contract>::connected_port(other_casted);
383 other_casted->connected_port(this);
384 return CallbackOutPort<Payload, ReturnType>::connect(other_casted);
385}
386
387template <typename Payload, typename ReturnType, typename Contract>
388bool CallbackContractInPort<Payload, ReturnType, Contract>::connect(
389 Port* other) {
390 if (other == nullptr) {
391 return false;
392 }
393 auto* other_casted = boost::typeindex::runtime_cast<
395 if (other_casted == nullptr) {
396 return false;
397 }
398 ContractInterface<Contract>::connected_port(other_casted);
399 other_casted->connected_port(this);
400 return CallbackInPort<Payload, ReturnType>::connect(other_casted);
401}
402
406template <typename OutPayload, typename InPayload>
407class SIMO_PUBLIC BidirectionalPortTyped : public Port {
408 public:
409 BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port)
410 bool connect(Port* other) override;
411
413 OutPort<OutPayload>::SEND_OUTCOME send_out(OutPayload&& payload) {
414 return out_port.send(std::move(payload));
415 }
416
417 OutPort<OutPayload>::SEND_OUTCOME send_out(OutPayload& payload) {
418 return out_port.send(payload);
419 }
420
421 void clear_out() { out_port.clear(); }
422
423 void clear_in() { in_port.clear(); }
424
426 InPayload&& receive_in() { return in_port.receive(); }
427
429 InPayload* peek_in() { return in_port.peek(); }
430
431 protected:
432 OutPort<OutPayload> out_port;
433 InPort<InPayload> in_port;
434};
435
436template <typename Payload>
437using BidirectionalPort = BidirectionalPortTyped<Payload, Payload>;
438
439template <typename OutPayload, typename InPayload>
440bool BidirectionalPortTyped<OutPayload, InPayload>::connect(Port* other) {
441 auto* other_casted = boost::typeindex::runtime_cast<
442 BidirectionalPortTyped<InPayload, OutPayload>*>(other);
443 if (other_casted == nullptr) {
444 return false;
445 }
446 return out_port.connect(&other_casted->in_port) &&
447 in_port.connect(&other_casted->out_port);
448}
449
450} // namespace Ports
451
452} // namespace Simo
453
454#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:426
OutPort< OutPayload >::SEND_OUTCOME send_out(OutPayload &&payload)
Push a payload on the out port.
Definition Port.h:413
InPayload * peek_in()
Peek the payload from the in port.
Definition Port.h:429
Definition Port.h:161
Definition Port.h:281
Definition Port.h:214
Definition Port.h:124
Definition Port.h:66
General namespace for Simo.
Definition Collection.h:51