diff --git a/include/Simo/port/Port.h b/include/Simo/port/Port.h index f0f10e5..4f24762 100644 --- a/include/Simo/port/Port.h +++ b/include/Simo/port/Port.h @@ -128,12 +128,13 @@ class SIMO_PUBLIC InPort : public Port { [[nodiscard]] bool connect(Port* other) override; - Payload&& receive() { + Payload receive() { SIMO_ASSERT(connecting_port != nullptr); - SIMO_ASSERT(connecting_port->state != OutPort::PORT_STATE::EMPTY); + SIMO_ASSERT(connecting_port->state() != + OutPort::PORT_STATE::EMPTY); Payload out_p = std::move(connecting_port->storage); connecting_port->clear(); - return std::move(out_p); + return out_p; } Payload* peek() { @@ -144,7 +145,11 @@ class SIMO_PUBLIC InPort : public Port { return &connecting_port->storage; } - void clear() { connecting_port->clear(); } + void clear() { + if (connecting_port != nullptr) { + connecting_port->clear(); + } + } protected: OutPort* connecting_port = nullptr; @@ -195,6 +200,81 @@ class SIMO_PUBLIC CallbackInPort : public Port { Callback callback_; }; +enum struct SIMO_PUBLIC VERIFY_CONTRACT_ERROR : std::uint8_t { + NO_CONNECTED_PORT, + NO_CONNECTED_PORT_CONTRACT, + NO_PREDICATE, +}; + +/// Class to implement the contract logic. +/// +/// A contract is just a structure that a connecting port can probe +/// And validate using a predicate +template +class SIMO_PUBLIC ContractInterface { + public: + std::optional contract() { return contract_; } + void contract(Contract contract) { contract_ = contract; } + + void connected_port(ContractInterface* port) { connected_port_ = port; } + + void connected_port_contract_predicate( + std::function predicate) { + connected_port_contract_predicate_ = predicate; + } + std::expected verify_connected_port_contract() { + if (connected_port_ == nullptr) { + return std::unexpected(VERIFY_CONTRACT_ERROR::NO_CONNECTED_PORT); + } + if (connected_port_->contract() == std::nullopt) { + return std::unexpected(VERIFY_CONTRACT_ERROR::NO_CONNECTED_PORT_CONTRACT); + } + if (!connected_port_contract_predicate_) { + return std::unexpected(VERIFY_CONTRACT_ERROR::NO_PREDICATE); + } + return connected_port_contract_predicate_(*connected_port_->contract()); + } + + protected: + std::optional contract_; + ContractInterface* connected_port_ = nullptr; + std::function connected_port_contract_predicate_{}; +}; + +/// Callback out port with contract logic. +/// +/// See CallbackOutPort and ContractInterface classes +template +class SIMO_PUBLIC CallbackContractOutPort + : public CallbackOutPort, + public ContractInterface { + public: + CallbackContractOutPort() = default; + + BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(CallbackOutPort) + [[nodiscard]] + bool connect(Port* other) override; +}; + +/// Callback out port with contract logic. +/// +/// See CallbackInPort and ContractInterface classes +template +class SIMO_PUBLIC CallbackContractInPort + : public CallbackInPort, + public ContractInterface { + public: + CallbackContractInPort() = default; + + explicit CallbackContractInPort( + CallbackInPort::Callback callback) + : CallbackInPort(std::move(callback)) {} + + BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(CallbackInPort) + [[nodiscard]] + bool connect(Port* other) override; +}; + /// Templated input port that sends payloads to a CallbackOutPort of the same /// type. template @@ -205,30 +285,27 @@ class SIMO_PUBLIC CallbackOutPort : public Port { [[nodiscard]] bool connect(Port* other) override; + template + requires std::constructible_from && + (!std::is_void_v) [[nodiscard]] - std::optional send(Payload&& payload) { + std::optional send(Arg&& payload) { if (connecting_port == nullptr) { return std::nullopt; } - return connecting_port->receive(std::forward(payload)); - } - void send(const Payload&& payload) - requires(std::is_same_v) - { - if (connecting_port != nullptr) { - connecting_port->receive(std::forward(payload)); - } + return connecting_port->receive(std::forward(payload)); } - [[nodiscard]] - std::optional send(const Payload& payload) - requires(!std::is_same_v) - { - if (connecting_port != nullptr) { - return connecting_port->receive(payload); + template + requires std::constructible_from && + std::is_void_v + void send(Arg&& payload) { + if (connecting_port == nullptr || !connecting_port->has_callback()) { + return; } - return std::nullopt; + + connecting_port->receive(std::forward(payload)); } protected: @@ -237,7 +314,7 @@ class SIMO_PUBLIC CallbackOutPort : public Port { template bool OutPort::connect(Port* other) { - if (other->get_type_id() != get_type_id()) { + if (other == nullptr) { return false; } auto* other_casted = boost::typeindex::runtime_cast*>(other); @@ -250,7 +327,7 @@ bool OutPort::connect(Port* other) { template bool InPort::connect(Port* other) { - if (other->get_type_id() != get_type_id()) { + if (other == nullptr) { return false; } auto* other_casted = boost::typeindex::runtime_cast*>(other); @@ -263,7 +340,7 @@ bool InPort::connect(Port* other) { template bool CallbackOutPort::connect(Port* other) { - if (other == nullptr || other->get_type_id() != get_type_id()) { + if (other == nullptr) { return false; } auto* other_casted = @@ -278,7 +355,7 @@ bool CallbackOutPort::connect(Port* other) { template bool CallbackInPort::connect(Port* other) { - if (other == nullptr || other->get_type_id() != get_type_id()) { + if (other == nullptr) { return false; } auto* other_casted = @@ -291,6 +368,38 @@ bool CallbackInPort::connect(Port* other) { return true; } +template +bool CallbackContractOutPort::connect( + Port* other) { + if (other == nullptr) { + return false; + } + auto* other_casted = boost::typeindex::runtime_cast< + CallbackContractInPort*>(other); + if (other_casted == nullptr) { + return false; + } + ContractInterface::connected_port(other_casted); + other_casted->connected_port(this); + return CallbackOutPort::connect(other_casted); +} + +template +bool CallbackContractInPort::connect( + Port* other) { + if (other == nullptr) { + return false; + } + auto* other_casted = boost::typeindex::runtime_cast< + CallbackContractOutPort*>(other); + if (other_casted == nullptr) { + return false; + } + ContractInterface::connected_port(other_casted); + other_casted->connected_port(this); + return CallbackInPort::connect(other_casted); +} + /// Port that can send and receive payloads on separate channels. /// It can be connected to a BidirectionalPortTyped (note /// the types are inverted). diff --git a/tests/port/PortTest.cc b/tests/port/PortTest.cc index 079f3a1..467efe6 100644 --- a/tests/port/PortTest.cc +++ b/tests/port/PortTest.cc @@ -143,6 +143,27 @@ BOOST_AUTO_TEST_CASE(CallbackPortsConnectionReferencePayload) { BOOST_CHECK_EQUAL(value, 1); } +BOOST_AUTO_TEST_CASE(CallbackContractPortsConnectionSuccess) { + Ports::CallbackContractOutPort out; + // No port connected returns true + BOOST_CHECK(out.verify_connected_port_contract().error() == + Ports::VERIFY_CONTRACT_ERROR::NO_CONNECTED_PORT); + Ports::CallbackContractInPort in([](int& ref) { ref = 1; }); + BOOST_CHECK_EQUAL(out.connect(&in), true); + // No contract set, returns true + BOOST_CHECK(out.verify_connected_port_contract().error() == + Ports::VERIFY_CONTRACT_ERROR::NO_CONNECTED_PORT_CONTRACT); + in.contract(10); + // A predicate to verify the contract is not set yet, so return true + BOOST_CHECK(out.verify_connected_port_contract().error() == + Ports::VERIFY_CONTRACT_ERROR::NO_PREDICATE); + out.connected_port_contract_predicate([](int v) { return v == 9; }); + // Predicate not satisfied + BOOST_CHECK_EQUAL(out.verify_connected_port_contract().value(), false); + in.contract(9); + BOOST_CHECK_EQUAL(out.verify_connected_port_contract().value(), true); +} + BOOST_AUTO_TEST_CASE(CallbackInPortMovesRvaluePayloadToCallback) { Ports::CallbackOutPort, bool> out; Ports::CallbackInPort, bool> in;