From 0837b5a466ba5c03a85a1128f67fb7b9876cea75 Mon Sep 17 00:00:00 2001 From: fusiled Date: Thu, 23 Jul 2026 17:15:17 -0500 Subject: [PATCH] Misc fixes Remove useless flake section Module: Add typed port getter in Module Module: Return unconnected ports with method SimoSim: Allow to print ports --- flake.nix | 30 +-------------- include/Simo/module/Module.h | 37 +++++++++++++++++- include/Simo/port/Port.h | 37 ++++++++++++++++++ src/SimoSim/SimoSim.cc | 25 ++++++++++++ src/module/Module.cc | 19 ++++++++++ tests/module/ModuleTest.cc | 61 ++++++++++++++++++++++++++++++ tests/port/PortTest.cc | 42 ++++++++++++++++++++ tests/statistics/StatisticsTest.cc | 2 + 8 files changed, 223 insertions(+), 30 deletions(-) diff --git a/flake.nix b/flake.nix index 7a8e25d..1965b5c 100644 --- a/flake.nix +++ b/flake.nix @@ -57,34 +57,6 @@ ctest --output-on-failure ''; }; - Simo = pkgs.clangStdenv.mkDerivation { - pname = "Simo"; - version = "0.0.1"; - src = ./.; - - nativeBuildInputs = with pkgs; [ - # Real build dependencies - cmake - ninja - doxygen - ]; - - buildInputs = with pkgs; [ - boost - glaze - ]; - - cmakeFlags = [ - "-DPORTABLE_BUILD=ON" - "-DENABLE_RELEASE_LTO=OFF" - "-DCMAKE_CXX_SCAN_FOR_MODULES=OFF" - ]; - - doCheck = true; - checkPhase = '' - ctest --output-on-failure - ''; - }; derivationAttributes = { default = pkgs.clangStdenv.mkDerivation SimoBaseAttributes; @@ -114,7 +86,7 @@ llvmPackages.llvm ]; shellHook = '' - export SHELL="${pkgs.bashInteractive}/bin/bash" + export SHELL="${pkgs.bashInteractive}/bin/bash" ''; }; } diff --git a/include/Simo/module/Module.h b/include/Simo/module/Module.h index 29efac6..cb5de6b 100644 --- a/include/Simo/module/Module.h +++ b/include/Simo/module/Module.h @@ -111,8 +111,43 @@ class SIMO_PUBLIC Module { /// Record a statistic in a StatMapper to dump statistics void record_statistics(Statistics::StatMapper& mapper); + /// Get a single port [[nodiscard]] Port* get_port(std::string_view); + /// Get a single port + /// + /// Return nullptr if the type does not match + template + [[nodiscard]] + T get_port(std::string_view name) + requires std::is_pointer_v + { + Port* port = get_port(name); + if (port == nullptr) { + return nullptr; + } + return boost::typeindex::runtime_cast(port); + } + + /// Get a single port + /// + /// Return nullptr if the type does not match + template + requires(!std::is_pointer_v) + [[nodiscard]] + T* get_port(std::string_view name) { + return get_port(name); + } + + struct PortWithFullName { + std::string full_name; + Port* port; + }; + + /// Return all the unconnected ports that a module exposes + std::vector get_unconnected_ports( + bool include_nested_components) const; + Time current_time() const; template @@ -200,4 +235,4 @@ class SIMO_PUBLIC Module { }; } // namespace Simo -#endif // SIMO_MODULE_HH \ No newline at end of file +#endif // SIMO_MODULE_HH diff --git a/include/Simo/port/Port.h b/include/Simo/port/Port.h index 4f24762..16b5708 100644 --- a/include/Simo/port/Port.h +++ b/include/Simo/port/Port.h @@ -40,9 +40,15 @@ class SIMO_PUBLIC Port { return boost::typeindex::type_id(); } + TypeId get_runtime_type() const { + return boost::typeindex::type_id_runtime(*this); + } + [[nodiscard]] virtual bool connect(Port* other) = 0; + virtual bool connected() const = 0; + [[nodiscard]] std::string_view name() const { return name_; } void name(const std::string_view name) { name_ = name; } @@ -81,6 +87,11 @@ class SIMO_PUBLIC OutPort : public Port { [[nodiscard]] bool connect(Port* other) override; + [[nodiscard]] + bool connected() const override { + return connecting_port != nullptr; + } + SEND_OUTCOME send(Payload&& payload) { storage = std::move(payload); switch (state_) { @@ -114,6 +125,7 @@ class SIMO_PUBLIC OutPort : public Port { protected: Payload storage; PORT_STATE state_ = PORT_STATE::EMPTY; + InPort* connecting_port = nullptr; }; /// Templated port that can received payloads from an OutPort of the same type @@ -128,6 +140,11 @@ class SIMO_PUBLIC InPort : public Port { [[nodiscard]] bool connect(Port* other) override; + [[nodiscard]] + bool connected() const override { + return connecting_port != nullptr; + } + Payload receive() { SIMO_ASSERT(connecting_port != nullptr); SIMO_ASSERT(connecting_port->state() != @@ -171,6 +188,11 @@ class SIMO_PUBLIC CallbackInPort : public Port { [[nodiscard]] bool connect(Port* other) override; + [[nodiscard]] + bool connected() const override { + return connecting_port != nullptr; + } + void callback(Callback callback) { callback_ = std::move(callback); } [[nodiscard]] @@ -198,6 +220,7 @@ class SIMO_PUBLIC CallbackInPort : public Port { } Callback callback_; + CallbackOutPort* connecting_port = nullptr; }; enum struct SIMO_PUBLIC VERIFY_CONTRACT_ERROR : std::uint8_t { @@ -285,6 +308,11 @@ class SIMO_PUBLIC CallbackOutPort : public Port { [[nodiscard]] bool connect(Port* other) override; + [[nodiscard]] + bool connected() const override { + return connecting_port != nullptr; + } + template requires std::constructible_from && (!std::is_void_v) @@ -321,6 +349,7 @@ bool OutPort::connect(Port* other) { if (other_casted == nullptr) { return false; } + connecting_port = other_casted; other_casted->connecting_port = this; return true; } @@ -335,6 +364,7 @@ bool InPort::connect(Port* other) { return false; } connecting_port = other_casted; + other_casted->connecting_port = this; return true; } @@ -350,6 +380,7 @@ bool CallbackOutPort::connect(Port* other) { return false; } connecting_port = other_casted; + other_casted->connecting_port = this; return true; } @@ -364,6 +395,7 @@ bool CallbackInPort::connect(Port* other) { if (other_casted == nullptr) { return false; } + connecting_port = other_casted; other_casted->connecting_port = this; return true; } @@ -409,6 +441,11 @@ class SIMO_PUBLIC BidirectionalPortTyped : public Port { BOOST_TYPE_INDEX_REGISTER_RUNTIME_CLASS(Port) bool connect(Port* other) override; + [[nodiscard]] + bool connected() const override { + return out_port.connected() && in_port.connected(); + } + /// Push a payload on the out port OutPort::SEND_OUTCOME send_out(OutPayload&& payload) { return out_port.send(std::move(payload)); diff --git a/src/SimoSim/SimoSim.cc b/src/SimoSim/SimoSim.cc index 3302cd5..0fe5a49 100644 --- a/src/SimoSim/SimoSim.cc +++ b/src/SimoSim/SimoSim.cc @@ -70,10 +70,29 @@ std::optional parse_module_port_name( }; } +void print_system_ports( + const std::unordered_map& module_map) { + bool ports_printed = false; + std::cout << "Ports exposed by the system:\n"; + for (const auto& [module_name, module_param_pair] : module_map) { + const auto& module = module_param_pair.module; + const auto ports = module->get_unconnected_ports(true); + ports_printed = !ports.empty(); + for (const auto& port : ports) { + std::cout << " " << port.full_name << " - " + << port.port->get_runtime_type().pretty_name() << "\n"; + } + } + if (!ports_printed) { + std::cout << " No ports\n"; + } +} + int main(const int argc, char* argv[]) { std::filesystem::path config_path; std::vector collection_search_paths; int verbosity = 0; + bool print_ports = false; CLI::App app{"Simulation with Simo"}; @@ -87,6 +106,8 @@ int main(const int argc, char* argv[]) { ->check(CLI::ExistingDirectory); app.add_flag("-v,--verbose", verbosity, "Increase verbosity level (e.g., -v, -vv, -vvv)"); + app.add_flag("--print-ports", print_ports, + "Print available ports before port binding phase"); CLI11_PARSE(app, argc, argv); @@ -200,6 +221,10 @@ int main(const int argc, char* argv[]) { return INITIALIZATION_FAILED; } + if (print_ports) { + print_system_ports(module_map); + } + for (const auto& [left_endpoint, right_endpoint] : cfg.connections) { const auto left = parse_module_port_name(left_endpoint); const auto right = parse_module_port_name(right_endpoint); diff --git a/src/module/Module.cc b/src/module/Module.cc index 4cb50c4..905ed22 100644 --- a/src/module/Module.cc +++ b/src/module/Module.cc @@ -68,6 +68,25 @@ Port* Module::get_port(const std::string_view name) { : nullptr; } +std::vector Module::get_unconnected_ports( + bool include_nested_components) const { + std::vector ret; + for (const auto& p : ports) { + if (!p.second->connected()) { + ret.emplace_back(name_of_child(p.first), p.second.get()); + } + } + if (!include_nested_components) { + return ret; + } + for (const auto& child : children) { + const auto child_ret = + child->get_unconnected_ports(include_nested_components); + ret.insert(ret.end(), child_ret.begin(), child_ret.end()); + } + return ret; +} + InitializationStatus Module::log_setup(const std::filesystem::path& out_file) { logger = {}; return logger.initialize(out_file); diff --git a/tests/module/ModuleTest.cc b/tests/module/ModuleTest.cc index 9461d4a..d1cfe13 100644 --- a/tests/module/ModuleTest.cc +++ b/tests/module/ModuleTest.cc @@ -122,4 +122,65 @@ BOOST_AUTO_TEST_CASE(ModuleChild) { BOOST_CHECK_EQUAL(child_status.success(), true); BOOST_CHECK_EQUAL(p_child.name(), "root/child"); } + +BOOST_AUTO_TEST_CASE(ModuleGetsPortAsRequestedType) { + Simo::Module module; + auto& port = + module.create_port>("callback"); + + auto* explicitly_typed_port = + module.get_port*>("callback"); + auto* inferred_pointer_port = + module.get_port>("callback"); + + BOOST_CHECK_EQUAL(explicitly_typed_port, &port); + BOOST_CHECK_EQUAL(inferred_pointer_port, &port); +} + +BOOST_AUTO_TEST_CASE(ModuleTypedGetPortReturnsNullForMissingOrWrongType) { + Simo::Module module; + module.create_port>("callback"); + + auto* missing_port = + module.get_port>("missing"); + auto* wrong_type_port = + module.get_port>("callback"); + + BOOST_CHECK_EQUAL(missing_port, nullptr); + BOOST_CHECK_EQUAL(wrong_type_port, nullptr); +} + +BOOST_AUTO_TEST_CASE(ModuleGetsUnconnectedPorts) { + Simo::Context context; + Simo::Module module; + Simo::Parameters parameters; + parameters.name("root"); + BOOST_REQUIRE(module.initialize(context, parameters).success()); + + auto& connected_out = + module.create_port>("connected_out"); + auto& connected_in = module.create_port>("connected_in"); + BOOST_REQUIRE(connected_out.connect(&connected_in)); + auto& root_unconnected = + module.create_port>("unconnected"); + + auto& child = module.create_child(); + Simo::Parameters child_parameters; + child_parameters.name(module.name_of_child("child")); + BOOST_REQUIRE(child.initialize(context, child_parameters).success()); + auto& child_unconnected = + child.create_port>("unconnected"); + + const auto root_ports = module.get_unconnected_ports(false); + BOOST_REQUIRE_EQUAL(root_ports.size(), 1); + BOOST_CHECK_EQUAL(root_ports.front().full_name, "root/unconnected"); + BOOST_CHECK_EQUAL(root_ports.front().port, &root_unconnected); + + const auto all_ports = module.get_unconnected_ports(true); + BOOST_REQUIRE_EQUAL(all_ports.size(), 2); + BOOST_CHECK_EQUAL(all_ports[0].full_name, "root/unconnected"); + BOOST_CHECK_EQUAL(all_ports[0].port, &root_unconnected); + BOOST_CHECK_EQUAL(all_ports[1].full_name, "root/child/unconnected"); + BOOST_CHECK_EQUAL(all_ports[1].port, &child_unconnected); +} } // namespace Simo::Tests diff --git a/tests/port/PortTest.cc b/tests/port/PortTest.cc index 467efe6..df57acd 100644 --- a/tests/port/PortTest.cc +++ b/tests/port/PortTest.cc @@ -28,6 +28,8 @@ class NamedPort final : public Simo::Port { [[nodiscard]] bool connect(Simo::Port* other) override { return other != nullptr; } + + [[nodiscard]] bool connected() const override { return false; } }; } // namespace @@ -45,6 +47,40 @@ BOOST_AUTO_TEST_CASE(PortNameGetterAndSetter) { BOOST_CHECK_EQUAL(left.connect(nullptr), false); } +BOOST_AUTO_TEST_CASE(PortsReportTheirConnectionState) { + Ports::OutPort out; + Ports::InPort in; + BOOST_CHECK(!out.connected()); + BOOST_CHECK(!in.connected()); + BOOST_REQUIRE(out.connect(&in)); + BOOST_CHECK(out.connected()); + BOOST_CHECK(in.connected()); + + Ports::CallbackOutPort callback_out; + Ports::CallbackInPort callback_in; + BOOST_CHECK(!callback_out.connected()); + BOOST_CHECK(!callback_in.connected()); + BOOST_REQUIRE(callback_in.connect(&callback_out)); + BOOST_CHECK(callback_out.connected()); + BOOST_CHECK(callback_in.connected()); + + Ports::CallbackContractOutPort contract_out; + Ports::CallbackContractInPort contract_in; + BOOST_CHECK(!contract_out.connected()); + BOOST_CHECK(!contract_in.connected()); + BOOST_REQUIRE(contract_out.connect(&contract_in)); + BOOST_CHECK(contract_out.connected()); + BOOST_CHECK(contract_in.connected()); + + Ports::BidirectionalPort bidirectional_left; + Ports::BidirectionalPort bidirectional_right; + BOOST_CHECK(!bidirectional_left.connected()); + BOOST_CHECK(!bidirectional_right.connected()); + BOOST_REQUIRE(bidirectional_left.connect(&bidirectional_right)); + BOOST_CHECK(bidirectional_left.connected()); + BOOST_CHECK(bidirectional_right.connected()); +} + BOOST_AUTO_TEST_CASE(CallbackInPortSendReturnsFalseWhenDisconnected) { Ports::CallbackOutPort out; @@ -185,4 +221,10 @@ BOOST_AUTO_TEST_CASE(CallbackInPortMovesRvaluePayloadToCallback) { BOOST_CHECK_EQUAL(received_value, 42); } +BOOST_AUTO_TEST_CASE(GetRuntimeType) { + Ports::CallbackOutPort, bool> port; + Port* casted_port = &port; + BOOST_CHECK_EQUAL(port.get_runtime_type(), casted_port->get_runtime_type()); +} + } // namespace Simo::Tests diff --git a/tests/statistics/StatisticsTest.cc b/tests/statistics/StatisticsTest.cc index a837cc3..de0aab9 100644 --- a/tests/statistics/StatisticsTest.cc +++ b/tests/statistics/StatisticsTest.cc @@ -37,6 +37,8 @@ class NamedPort final : public Simo::Port { [[nodiscard]] bool connect(Simo::Port* other) override { return other != nullptr; } + + [[nodiscard]] bool connected() const override { return false; } }; class StatisticRecordingModule final : public Simo::Module {