├── cmake ├── nsis-icon.ico ├── nsis-banner.bmp ├── nsis-welcome.bmp ├── enable-tests.cmake ├── uninstall.cmake ├── config.cmake.in ├── packaging.cmake └── cmake-boilerplate.cmake ├── doc └── img │ └── superflow-graph.png ├── loader ├── test │ ├── libs │ │ ├── libdependent_library.so │ │ └── libmissing_dependency.so │ ├── proxels │ │ ├── dummy_value_adapter.cpp │ │ ├── lib_path.h.in │ │ ├── fauxade.h │ │ ├── dummy.cpp │ │ ├── yummy.cpp │ │ ├── mummy.cpp │ │ ├── include │ │ │ └── testing │ │ │ │ └── dummy_value_adapter.h │ │ └── CMakeLists.txt │ ├── test_load_factories.cpp │ ├── test_rtld_now.cpp │ ├── CMakeLists.txt │ ├── README.md │ └── test_proxel_library.cpp ├── loader-config.cmake.in ├── CMakeLists.txt ├── include │ └── superflow │ │ └── loader │ │ ├── load_factories.h │ │ ├── proxel_library.h │ │ └── register_factory.h ├── src │ └── proxel_library.cpp └── README.md ├── test_package ├── src │ ├── superflowcurses_runtime_test.cpp │ ├── superflowcore_runtime_test.cpp │ ├── superflowyaml_runtime_test.cpp │ └── superflowloader_runtime_test.cpp ├── CMakeLists.txt ├── conanfile.py └── GenerateTestFromModule.cmake ├── .gitignore ├── core ├── CMakeLists.txt ├── test │ ├── pimpl_test.h │ ├── pimpl_test.cpp │ ├── crashing_proxel.h │ ├── test_pimpl.cpp │ ├── connectable_proxel.h │ ├── test_sleeper.cpp │ ├── threaded_proxel.h │ ├── test_multi_queue_getter.cpp │ ├── CMakeLists.txt │ ├── templated_testproxel.h │ ├── connectable_port.h │ ├── multi_connectable_port.h │ ├── test_metronome.cpp │ ├── test_proxel_status.cpp │ ├── test_proxel_timer.cpp │ ├── test_port_manager.cpp │ ├── test_interface_port.cpp │ ├── test_signal_waiter.cpp │ ├── test_throttle.cpp │ ├── test_graph_factory.cpp │ └── test_shared_mutexed.cpp ├── include │ └── superflow │ │ ├── connection_spec.h │ │ ├── proxel_config.h │ │ ├── utils │ │ ├── wait_for_signal.h │ │ ├── terminated_exception.h │ │ ├── pimpl_impl.h │ │ ├── sleeper.h │ │ ├── signal_waiter.h │ │ ├── metronome.h │ │ ├── blocker.h │ │ ├── pimpl_h.h │ │ ├── graphviz.h │ │ ├── proxel_timer.h │ │ ├── data_stream.h │ │ ├── throttle.h │ │ ├── mutexed.h │ │ └── shared_mutexed.h │ │ ├── factory.h │ │ ├── port_status.h │ │ ├── port.h │ │ ├── policy.h │ │ ├── queue_getter.h │ │ ├── port_manager.h │ │ ├── graph_factory.h │ │ ├── proxel_status.h │ │ ├── value.h │ │ ├── queue_set_getter.h │ │ ├── proxel.h │ │ ├── mapped_asset_manager.h │ │ ├── factory_map.h │ │ ├── consumer_port.h │ │ ├── callback_consumer_port.h │ │ ├── multi_queue_getter.h │ │ ├── connection_manager.h │ │ └── graph.h ├── src │ ├── wait_for_signal.cpp │ ├── sleeper.cpp │ ├── proxel.cpp │ ├── port_manager.cpp │ ├── metronome.cpp │ ├── proxel_timer.cpp │ ├── graphviz.cpp │ └── signal_waiter.cpp └── core-config.cmake.in ├── curses ├── CMakeLists.txt ├── curses-config.cmake.in ├── test │ ├── test_superflow_curses.cpp │ └── CMakeLists.txt ├── include │ └── superflow │ │ └── curses │ │ ├── window.h │ │ ├── proxel_window.h │ │ └── graph_gui.h └── src │ ├── window.cpp │ └── proxel_window.cpp ├── yaml ├── yaml-config.cmake.in ├── include │ └── superflow │ │ └── yaml │ │ ├── factory.h │ │ ├── yaml_property_list.h │ │ ├── yaml_string_pair.h │ │ └── yaml.h ├── test │ ├── CMakeLists.txt │ ├── yaml-test-proxel.cpp │ └── test_yaml_property_list.cpp └── CMakeLists.txt ├── LICENSE ├── .clang-format └── CMakeLists.txt /cmake/nsis-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFI-no/superflow/HEAD/cmake/nsis-icon.ico -------------------------------------------------------------------------------- /cmake/nsis-banner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFI-no/superflow/HEAD/cmake/nsis-banner.bmp -------------------------------------------------------------------------------- /cmake/nsis-welcome.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFI-no/superflow/HEAD/cmake/nsis-welcome.bmp -------------------------------------------------------------------------------- /doc/img/superflow-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFI-no/superflow/HEAD/doc/img/superflow-graph.png -------------------------------------------------------------------------------- /loader/test/libs/libdependent_library.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFI-no/superflow/HEAD/loader/test/libs/libdependent_library.so -------------------------------------------------------------------------------- /loader/test/libs/libmissing_dependency.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FFI-no/superflow/HEAD/loader/test/libs/libmissing_dependency.so -------------------------------------------------------------------------------- /test_package/src/superflowcurses_runtime_test.cpp: -------------------------------------------------------------------------------- 1 | #include "superflow/curses/graph_gui.h" 2 | 3 | int main() 4 | { 5 | flow::curses::GraphGUI gui{}; 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .devcontainer 2 | .idea 3 | .vs 4 | .vscode 5 | *.tag 6 | 3rd-party 7 | build 8 | cmake-build* 9 | CMakeSettings.json 10 | CMakeUserPresets.json 11 | html 12 | latex 13 | lib_path.h 14 | -------------------------------------------------------------------------------- /loader/test/proxels/dummy_value_adapter.cpp: -------------------------------------------------------------------------------- 1 | #include "testing/dummy_value_adapter.h" 2 | 3 | namespace flow::test 4 | { 5 | bool DummyValueAdapter::hasKey(const std::string& key) const 6 | { return key == key_; } 7 | } 8 | -------------------------------------------------------------------------------- /test_package/src/superflowcore_runtime_test.cpp: -------------------------------------------------------------------------------- 1 | #include "superflow/utils/metronome.h" 2 | #include 3 | 4 | int main() 5 | { 6 | flow::Metronome metronome{[](const auto) {}, std::chrono::microseconds{1}}; 7 | } 8 | -------------------------------------------------------------------------------- /test_package/src/superflowyaml_runtime_test.cpp: -------------------------------------------------------------------------------- 1 | #include "superflow/graph.h" 2 | #include "superflow/yaml/yaml.h" 3 | 4 | int main() 5 | { 6 | try 7 | { 8 | const auto graph = flow::yaml::createGraph("", flow::yaml::FactoryMap{}); 9 | } catch (...) 10 | {} 11 | } 12 | -------------------------------------------------------------------------------- /core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(core) 2 | init_module() 3 | 4 | find_package(Threads REQUIRED) 5 | 6 | add_library_boilerplate() 7 | 8 | target_link_libraries(${target_name} 9 | PUBLIC 10 | Threads::Threads 11 | ) 12 | 13 | if (BUILD_TESTS) 14 | add_subdirectory(test) 15 | endif () 16 | -------------------------------------------------------------------------------- /loader/test/proxels/lib_path.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // This file is generated by CMake. Changes will be overwritten. 3 | 4 | #include 5 | 6 | namespace flow::test::local 7 | { 8 | const std::string lib_path{"$"}; 9 | const std::string lib_dir{"$"}; 10 | } 11 | -------------------------------------------------------------------------------- /core/test/pimpl_test.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/utils/pimpl_h.h" 5 | 6 | namespace flow::test 7 | { 8 | class A 9 | { 10 | public: 11 | A(); 12 | 13 | private: 14 | class impl; 15 | 16 | flow::pimpl m_; 17 | }; 18 | } -------------------------------------------------------------------------------- /loader/test/proxels/fauxade.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | 6 | namespace flow::test 7 | { 8 | struct Fauxade 9 | { 10 | using Ptr = std::shared_ptr; 11 | int val = 0; 12 | explicit Fauxade(const int i) : val{i}{} 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /curses/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(curses) 2 | init_module() 3 | 4 | find_package(ncursescpp REQUIRED) 5 | 6 | add_library_boilerplate() 7 | 8 | target_link_libraries(${target_name} 9 | PUBLIC 10 | superflow::core 11 | PRIVATE 12 | ncursescpp::ncursescpp 13 | ) 14 | 15 | if (BUILD_TESTS) 16 | add_subdirectory(test) 17 | endif() -------------------------------------------------------------------------------- /core/include/superflow/connection_spec.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | 6 | namespace flow 7 | { 8 | struct ConnectionSpec 9 | { 10 | std::string lhs_name; 11 | std::string lhs_port; 12 | std::string rhs_name; 13 | std::string rhs_port; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /core/test/pimpl_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | 3 | #include "pimpl_test.h" 4 | #include "superflow/utils/pimpl_impl.h" 5 | 6 | template class flow::pimpl; 7 | 8 | namespace flow::test 9 | { 10 | class A::impl 11 | {}; 12 | 13 | A::A() 14 | : m_{} 15 | {} 16 | } -------------------------------------------------------------------------------- /core/include/superflow/proxel_config.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace flow 8 | { 9 | template 10 | struct ProxelConfig 11 | { 12 | std::string id; 13 | std::string type; 14 | PropertyList properties; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /core/src/wait_for_signal.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/utils/wait_for_signal.h" 3 | 4 | #include "superflow/utils/signal_waiter.h" 5 | 6 | namespace flow 7 | { 8 | void waitForSignal(const std::vector& signals) 9 | { 10 | const SignalWaiter waiter{signals}; 11 | waiter.getFuture().wait(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /core/core-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | message(STATUS "* Found @CMAKE_PROJECT_NAME@::@PROJECT_NAME@: " "${CMAKE_CURRENT_LIST_FILE}") 3 | include(CMakeFindDependencyMacro) 4 | find_dependency(Threads) 5 | 6 | set(@CMAKE_PROJECT_NAME@_@PROJECT_NAME@_FOUND TRUE) 7 | 8 | check_required_components(@PROJECT_NAME@) 9 | message(STATUS "* Loading @CMAKE_PROJECT_NAME@::@PROJECT_NAME@ complete") 10 | -------------------------------------------------------------------------------- /yaml/yaml-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | message(STATUS "* Found @CMAKE_PROJECT_NAME@::@PROJECT_NAME@: " "${CMAKE_CURRENT_LIST_FILE}") 3 | include(CMakeFindDependencyMacro) 4 | find_dependency(yaml-cpp) 5 | 6 | set(@CMAKE_PROJECT_NAME@_@PROJECT_NAME@_FOUND TRUE) 7 | 8 | check_required_components(@PROJECT_NAME@) 9 | message(STATUS "* Loading @CMAKE_PROJECT_NAME@::@PROJECT_NAME@ complete") 10 | -------------------------------------------------------------------------------- /curses/curses-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | message(STATUS "* Found @CMAKE_PROJECT_NAME@::@PROJECT_NAME@: " "${CMAKE_CURRENT_LIST_FILE}") 3 | include(CMakeFindDependencyMacro) 4 | 5 | find_dependency(ncursescpp) 6 | 7 | set(@CMAKE_PROJECT_NAME@_@PROJECT_NAME@_FOUND TRUE) 8 | 9 | check_required_components(@PROJECT_NAME@) 10 | message(STATUS "* Loading @CMAKE_PROJECT_NAME@::@PROJECT_NAME@ complete") 11 | -------------------------------------------------------------------------------- /test_package/src/superflowloader_runtime_test.cpp: -------------------------------------------------------------------------------- 1 | #include "superflow/loader/load_factories.h" 2 | #include "superflow/yaml/yaml_property_list.h" 3 | 4 | int main() 5 | { 6 | try 7 | { 8 | const std::vector libraries{{"path to library"}}; 9 | 10 | const auto factory_map = flow::load::loadFactories(libraries); 11 | } catch (...) 12 | {} 13 | } 14 | -------------------------------------------------------------------------------- /core/test/crashing_proxel.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/proxel.h" 5 | 6 | namespace flow 7 | { 8 | class CrashingProxel : public Proxel 9 | { 10 | public: 11 | void start() override 12 | { 13 | throw std::runtime_error("This proxel has crashed"); 14 | } 15 | 16 | void stop() noexcept override 17 | {} 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /curses/test/test_superflow_curses.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/curses/graph_gui.h" 3 | #include "superflow/graph.h" 4 | 5 | #include "gtest/gtest.h" 6 | 7 | using namespace flow; 8 | using namespace flow::curses; 9 | 10 | TEST(SuperFlowCurses, compiles) 11 | { 12 | Graph graph(std::map{}); 13 | GraphGUI gui; 14 | } 15 | -------------------------------------------------------------------------------- /loader/loader-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | message(STATUS "* Found @CMAKE_PROJECT_NAME@::@PROJECT_NAME@: " "${CMAKE_CURRENT_LIST_FILE}") 3 | include(CMakeFindDependencyMacro) 4 | find_dependency(Boost COMPONENTS system filesystem) 5 | 6 | set(@CMAKE_PROJECT_NAME@_@PROJECT_NAME@_FOUND TRUE) 7 | 8 | check_required_components(@PROJECT_NAME@) 9 | message(STATUS "* Loading @CMAKE_PROJECT_NAME@::@PROJECT_NAME@ complete") 10 | -------------------------------------------------------------------------------- /core/test/test_pimpl.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | #include "pimpl_test.h" 3 | 4 | #include "gtest/gtest.h" 5 | 6 | TEST(SuperFlowCurses, pimpl_empty_ctor) 7 | { 8 | // This works if explicit instantiation is done for the impl-class. 9 | // See pimpl_test.cpp#6. 10 | // There should be no linker errors and no 11 | // "undefined reference to 'pimpl::~pimpl()'" 12 | flow::test::A a; 13 | } -------------------------------------------------------------------------------- /yaml/include/superflow/yaml/factory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/yaml/yaml_property_list.h" 5 | 6 | #include "superflow/factory_map.h" 7 | #include "superflow/proxel_config.h" 8 | 9 | namespace flow::yaml 10 | { 11 | using Factory = flow::Factory; 12 | using FactoryMap = flow::FactoryMap; 13 | using ProxelConfig = flow::ProxelConfig; 14 | } 15 | -------------------------------------------------------------------------------- /core/include/superflow/utils/wait_for_signal.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace flow 8 | { 9 | /// \brief Make the current thread wait for the specified signal(s). 10 | /// This is thread-safe and can be used on multiple threads simultaneously. 11 | /// \param signals The signals to wait for (default is SIGINT). 12 | void waitForSignal(const std::vector& signals = {SIGINT}); 13 | } 14 | -------------------------------------------------------------------------------- /loader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(loader) 2 | init_module() 3 | 4 | find_package(Boost REQUIRED system filesystem) 5 | 6 | add_library_boilerplate() 7 | 8 | target_link_libraries(${target_name} 9 | PUBLIC 10 | Boost::headers 11 | Boost::filesystem 12 | Boost::system 13 | superflow::core 14 | INTERFACE 15 | ${CMAKE_DL_LIBS} 16 | ) 17 | 18 | if (MSVC) 19 | target_link_libraries(${target_name} INTERFACE Boost::dynamic_linking) 20 | endif() 21 | 22 | if (BUILD_TESTS) 23 | add_subdirectory(test) 24 | endif() 25 | -------------------------------------------------------------------------------- /core/include/superflow/factory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/proxel.h" 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | /// \brief Function for creating a new Proxel 11 | /// \param name[in] The name of the Proxel 12 | /// \param properties[in] configuration properties for the Proxel 13 | template 14 | using Factory = std::function; 17 | } 18 | -------------------------------------------------------------------------------- /cmake/enable-tests.cmake: -------------------------------------------------------------------------------- 1 | set(gtest_tag "v1.14.0") 2 | message(STATUS "* Fetch content GTest ${gtest_tag}") 3 | include(FetchContent) 4 | FetchContent_Declare( 5 | googletest 6 | GIT_REPOSITORY https://github.com/google/googletest.git 7 | GIT_TAG ${gtest_tag} 8 | ) 9 | 10 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 11 | set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) 12 | set(BUILD_GTEST ON CACHE BOOL "" FORCE) 13 | 14 | FetchContent_MakeAvailable(googletest) 15 | 16 | enable_testing() 17 | message(STATUS "* GTest fetched") 18 | -------------------------------------------------------------------------------- /core/src/sleeper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/utils/sleeper.h" 3 | #include 4 | 5 | namespace flow 6 | { 7 | 8 | Sleeper::Sleeper(Clock::duration period) 9 | : time_point_{Clock::now()} 10 | , period_(period) 11 | {} 12 | 13 | void Sleeper::sleepForRemainderOfPeriod() const { 14 | time_point_ += period_; 15 | std::this_thread::sleep_until(time_point_); 16 | } 17 | 18 | void Sleeper::setSleepPeriod(const Clock::duration period) { 19 | period_ = period; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/include/superflow/port_status.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace flow 8 | { 9 | /// \brief A container for statistics and status for a Port 10 | /// \see Port 11 | struct PortStatus 12 | { 13 | inline static constexpr size_t undefined = std::numeric_limits::max(); 14 | 15 | size_t num_connections; ///< Number of connections to the Port 16 | size_t num_transactions; ///< Number of transactions passed through the Port 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /core/include/superflow/utils/terminated_exception.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | 6 | namespace flow 7 | { 8 | struct TerminatedException : public std::runtime_error 9 | { 10 | TerminatedException() 11 | : std::runtime_error("LockQueue is terminated") 12 | {} 13 | 14 | explicit TerminatedException(const std::string& what_arg) 15 | : std::runtime_error(what_arg) 16 | {} 17 | 18 | explicit TerminatedException(const char* what_arg) 19 | : std::runtime_error(what_arg) 20 | {} 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(PackageTest CXX) 3 | 4 | set(name superflow) 5 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_BINARY_DIR}) 6 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 7 | 8 | enable_testing() 9 | find_package(${name} REQUIRED core curses loader yaml) 10 | 11 | include(GenerateTestFromModule) 12 | 13 | set(modules core curses loader yaml) 14 | foreach(module ${modules}) 15 | generate_test_from_module(${name} ${module}) 16 | endforeach() 17 | 18 | if(TARGET superflow::loader) 19 | target_link_libraries(superflow_loader_runtime_test superflow::yaml) 20 | endif() 21 | -------------------------------------------------------------------------------- /curses/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PARENT_PROJECT ${PROJECT_NAME}) 2 | set(target_test_name "${CMAKE_PROJECT_NAME}-${PARENT_PROJECT}-test") 3 | project(${target_test_name} CXX) 4 | message(STATUS "* Adding test executable '${target_test_name}'") 5 | 6 | add_executable(${target_test_name} 7 | "test_superflow_curses.cpp" 8 | ) 9 | 10 | target_link_libraries( 11 | ${target_test_name} 12 | PRIVATE GTest::gtest GTest::gtest_main 13 | PRIVATE ${CMAKE_PROJECT_NAME}::curses 14 | ) 15 | 16 | set_target_properties(${target_test_name} PROPERTIES 17 | CXX_STANDARD_REQUIRED ON 18 | CXX_STANDARD 17 19 | ) 20 | 21 | include(GoogleTest) 22 | gtest_discover_tests(${target_test_name}) 23 | -------------------------------------------------------------------------------- /curses/include/superflow/curses/window.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/utils/pimpl_h.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace nccpp 10 | { struct Color; } 11 | 12 | namespace flow::curses 13 | { 14 | class Window 15 | { 16 | public: 17 | Window( 18 | int x, 19 | int y, 20 | int width, 21 | int height 22 | ); 23 | 24 | void render( 25 | const std::string& name, 26 | const std::vector& lines, 27 | const nccpp::Color& color 28 | ); 29 | 30 | private: 31 | class impl; 32 | pimpl m_; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /yaml/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PARENT_PROJECT ${PROJECT_NAME}) 2 | project(${CMAKE_PROJECT_NAME}-${PARENT_PROJECT}-test CXX) 3 | 4 | message(STATUS "* Adding test executable '${PROJECT_NAME}'") 5 | add_executable(${PROJECT_NAME} 6 | "yaml-test-proxel.cpp" 7 | "test_yaml_property_list.cpp" 8 | ) 9 | 10 | target_link_libraries( 11 | ${PROJECT_NAME} 12 | PRIVATE 13 | GTest::gtest GTest::gtest_main 14 | ${CMAKE_PROJECT_NAME}::loader 15 | ${CMAKE_PROJECT_NAME}::yaml 16 | ) 17 | 18 | set_target_properties(${PROJECT_NAME} PROPERTIES 19 | CXX_STANDARD_REQUIRED ON 20 | CXX_STANDARD 17 21 | ENABLE_EXPORTS ON 22 | ) 23 | 24 | include(GoogleTest) 25 | gtest_discover_tests(${PROJECT_NAME}) 26 | -------------------------------------------------------------------------------- /yaml/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(yaml) 2 | init_module() 3 | 4 | find_package(yaml-cpp REQUIRED) 5 | 6 | add_library_boilerplate() 7 | 8 | option(SET_LOADER_ADAPTER "Set the LOADER_ADAPTER_xxx macros. Turn off if you are linking more than one adapter." ON) 9 | if (SET_LOADER_ADAPTER) 10 | target_compile_definitions(${target_name} 11 | INTERFACE 12 | LOADER_ADAPTER_HEADER="superflow/yaml/yaml_property_list.h" 13 | LOADER_ADAPTER_NAME=YAML 14 | LOADER_ADAPTER_TYPE=flow::yaml::YAMLPropertyList 15 | ) 16 | endif () 17 | 18 | target_link_libraries(${target_name} 19 | PUBLIC 20 | superflow::core 21 | yaml-cpp::yaml-cpp 22 | ) 23 | 24 | if (BUILD_TESTS) 25 | add_subdirectory(test) 26 | endif() 27 | -------------------------------------------------------------------------------- /core/include/superflow/utils/pimpl_impl.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | // https://herbsutter.com/gotw/_101/ 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | template 11 | template 12 | pimpl::pimpl(Args&& ...args) 13 | : m{std::make_unique(std::forward(args)...)} 14 | {} 15 | 16 | template 17 | pimpl::~pimpl() = default; 18 | 19 | template 20 | pimpl & pimpl::operator=(pimpl&&) noexcept = default; 21 | 22 | template 23 | T* pimpl::operator->() const 24 | { return m.get(); } 25 | 26 | template 27 | T& pimpl::operator*() const 28 | { return *m.get(); } 29 | } -------------------------------------------------------------------------------- /core/test/connectable_proxel.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/proxel.h" 5 | 6 | namespace flow 7 | { 8 | class ConnectableProxel : public Proxel 9 | { 10 | public: 11 | ConnectableProxel( 12 | const Port::Ptr& in_port, 13 | const Port::Ptr& out_port 14 | ) 15 | : in_port_{in_port} 16 | , out_port_{out_port} 17 | { 18 | registerPorts( 19 | { 20 | {"inport", in_port_}, 21 | {"outport", out_port_} 22 | } 23 | ); 24 | } 25 | 26 | void start() override 27 | {}; 28 | 29 | void stop() noexcept override 30 | {}; 31 | 32 | Port::Ptr in_port_; 33 | Port::Ptr out_port_; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /loader/test/test_load_factories.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "testing/dummy_value_adapter.h" 3 | 4 | #include "superflow/loader/load_factories.h" 5 | #include "proxels/lib_path.h" 6 | #include "proxels/fauxade.h" 7 | 8 | #include "gtest/gtest.h" 9 | 10 | using namespace flow; 11 | 12 | TEST(LoadFactories, loadFactories_from_vector_of_libraries) 13 | { 14 | const std::vector libraries{ 15 | {flow::test::local::lib_dir, "proxels"} 16 | }; 17 | 18 | flow::FactoryMap factories; 19 | 20 | ASSERT_NO_FATAL_FAILURE( 21 | factories = load::loadFactories(libraries) 22 | ); 23 | 24 | ASSERT_FALSE(factories.empty()); 25 | } 26 | -------------------------------------------------------------------------------- /cmake/uninstall.cmake: -------------------------------------------------------------------------------- 1 | # https://gist.github.com/royvandam/3033428 2 | 3 | set(MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt") 4 | 5 | if(NOT EXISTS ${MANIFEST}) 6 | message(FATAL_ERROR "Cannot find install manifest: '${MANIFEST}'") 7 | endif() 8 | 9 | file(STRINGS ${MANIFEST} files) 10 | foreach(file ${files}) 11 | if(EXISTS ${file}) 12 | message(STATUS "Removing file: '${file}'") 13 | 14 | exec_program( 15 | ${CMAKE_COMMAND} ARGS "-E remove ${file}" 16 | OUTPUT_VARIABLE stdout 17 | RETURN_VALUE result 18 | ) 19 | 20 | if(NOT "${result}" STREQUAL 0) 21 | message(FATAL_ERROR "Failed to remove file: '${file}'.") 22 | endif() 23 | else() 24 | MESSAGE(STATUS "File '${file}' does not exist.") 25 | endif() 26 | endforeach(file) 27 | -------------------------------------------------------------------------------- /test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from conan import ConanFile 4 | from conan.tools.cmake import CMake, cmake_layout 5 | from conan.tools.build import can_run 6 | 7 | 8 | class superflowTestConan(ConanFile): 9 | settings = "os", "compiler", "build_type", "arch" 10 | generators = "CMakeDeps", "CMakeToolchain" 11 | 12 | def requirements(self): 13 | self.requires(self.tested_reference_str) 14 | 15 | def build(self): 16 | cmake = CMake(self) 17 | cmake.configure() 18 | cmake.build() 19 | 20 | def layout(self): 21 | cmake_layout(self) 22 | 23 | def test(self): 24 | if can_run(self): 25 | cmake = CMake(self) 26 | cmake.test(env="conanrun") 27 | #cmd = os.path.join(self.cpp.build.bindir, "example") 28 | #self.run(cmd, env="conanrun") 29 | -------------------------------------------------------------------------------- /loader/test/proxels/dummy.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/proxel.h" 3 | #include "testing/dummy_value_adapter.h" 4 | 5 | namespace flow::test 6 | { 7 | class Dummy : public flow::Proxel 8 | { 9 | public: 10 | Dummy() 11 | { setState(State::Paused); } 12 | 13 | void start() override 14 | { setState(State::Running); } 15 | 16 | void stop() noexcept override 17 | { setState(State::Unavailable); } 18 | 19 | ~Dummy() override = default; 20 | 21 | template 22 | static flow::Proxel::Ptr static_create(const PropertyList&); 23 | }; 24 | 25 | template 26 | flow::Proxel::Ptr Dummy::static_create(const PropertyList&) 27 | { 28 | return std::make_shared(); 29 | } 30 | 31 | REGISTER_DUMMY_PROXEL_FACTORY(Dummy, Dummy::static_create) 32 | } 33 | -------------------------------------------------------------------------------- /test_package/GenerateTestFromModule.cmake: -------------------------------------------------------------------------------- 1 | macro(generate_test_from_module namespace module_name) 2 | if(TARGET ${namespace}::${module_name}) 3 | message(STATUS "generate_test_from_module(${namespace} ${module_name})") 4 | add_executable(${namespace}_${module_name}_runtime_test src/${namespace}${module_name}_runtime_test.cpp) 5 | target_link_libraries(${namespace}_${module_name}_runtime_test ${namespace}::${module_name}) 6 | set_target_properties(${namespace}_${module_name}_runtime_test PROPERTIES 7 | CXX_STANDARD_REQUIRED ON 8 | CXX_STANDARD 17 9 | BUILD_RPATH $ORIGIN 10 | INSTALL_RPATH $ORIGIN 11 | ) 12 | add_test(${namespace}_${module_name}_runtime_test ${namespace}_${module_name}_runtime_test) 13 | else() 14 | message(WARNING "generate_test_from_module: nonexisting target ${namespace}::${module_name}") 15 | endif() 16 | endmacro() 17 | -------------------------------------------------------------------------------- /loader/test/proxels/yummy.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/proxel.h" 3 | #include "superflow/loader/register_factory.h" 4 | #include "superflow/value.h" 5 | 6 | namespace flow::test 7 | { 8 | class Yummy : public flow::Proxel 9 | { 10 | public: 11 | Yummy(int value) 12 | { 13 | setState(State::AwaitingInput); 14 | setStatusInfo(std::to_string(value)); 15 | } 16 | 17 | void start() override 18 | { setState(State::Running); } 19 | 20 | void stop() noexcept override 21 | { setState(State::Unavailable); } 22 | 23 | ~Yummy() override = default; 24 | }; 25 | 26 | namespace 27 | { 28 | template 29 | flow::Proxel::Ptr freeFunctionCreate(const PropertyList& property_list) 30 | { 31 | int value = flow::value(property_list, "int key"); 32 | return std::make_shared(value); 33 | } 34 | 35 | REGISTER_PROXEL_FACTORY(Yummy, freeFunctionCreate) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/test/test_sleeper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/utils/sleeper.h" 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | TEST(Sleeper, check) 11 | { 12 | using namespace std::chrono_literals; 13 | 14 | 15 | Sleeper sleeper(10ms); 16 | const auto begin = Sleeper::Clock::now(); 17 | for (int i =0; i<10; ++i) 18 | { 19 | if (i > 4) 20 | { 21 | sleeper.setSleepPeriod(5ms); 22 | } 23 | sleeper.sleepForRemainderOfPeriod(); 24 | 25 | } 26 | const auto end = Sleeper::Clock::now(); 27 | const auto expected_duration = 5*(10ms + 5ms); 28 | const auto actual_duration = std::chrono::duration_cast(end - begin); 29 | //std::cout << "expected_duration: " << expected_duration.count() << std::endl; 30 | //std::cout << "actual_duration: " << actual_duration.count() << std::endl; 31 | ASSERT_TRUE(actual_duration == expected_duration);} 32 | } 33 | -------------------------------------------------------------------------------- /yaml/test/yaml-test-proxel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/loader/register_factory.h" 3 | #include "superflow/proxel.h" 4 | #include "superflow/value.h" 5 | 6 | namespace flow::test 7 | { 8 | class YamlTestProxel : public flow::Proxel 9 | { 10 | public: 11 | YamlTestProxel(int value) 12 | { 13 | setState(State::AwaitingInput); 14 | setStatusInfo(std::to_string(value)); 15 | } 16 | 17 | void start() override 18 | { setState(State::Running); } 19 | 20 | void stop() noexcept override 21 | { setState(State::Unavailable); } 22 | 23 | ~YamlTestProxel() override = default; 24 | }; 25 | 26 | namespace 27 | { 28 | template 29 | flow::Proxel::Ptr createYamlTestProxel(const PropertyList& property_list) 30 | { 31 | int value = flow::value(property_list, "int"); 32 | return std::make_shared(value); 33 | } 34 | 35 | REGISTER_PROXEL_FACTORY(YamlTestProxel, createYamlTestProxel) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /loader/test/proxels/mummy.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/proxel.h" 3 | #include "superflow/loader/register_factory.h" 4 | #include "fauxade.h" 5 | 6 | namespace flow::test 7 | { 8 | class Mummy : public flow::Proxel 9 | { 10 | public: 11 | explicit Mummy(int i) 12 | { 13 | setState(State::Paused); 14 | setStatusInfo(std::to_string(i)); 15 | } 16 | 17 | void start() override 18 | { setState(State::Running); } 19 | 20 | void stop() noexcept override 21 | { setState(State::Unavailable); } 22 | 23 | ~Mummy() override = default; 24 | 25 | template 26 | static flow::Proxel::Ptr createWithArgs(const PropertyList&, const Fauxade::Ptr&); 27 | }; 28 | 29 | template 30 | flow::Proxel::Ptr Mummy::createWithArgs(const PropertyList&, const Fauxade::Ptr& f_ptr) 31 | { 32 | return std::make_shared(f_ptr->val); 33 | } 34 | 35 | REGISTER_PROXEL_FACTORY(Mummy, Mummy::createWithArgs) 36 | } 37 | -------------------------------------------------------------------------------- /core/test/threaded_proxel.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/proxel.h" 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | class ThreadedProxel : public Proxel 11 | { 12 | public: 13 | ThreadedProxel() 14 | : thread_id_{} 15 | , start_was_called_{false} 16 | , stop_was_called_{false} 17 | {} 18 | 19 | using Ptr = std::shared_ptr; 20 | 21 | void start() override 22 | { 23 | start_was_called_ = true; 24 | thread_id_ = std::this_thread::get_id(); 25 | } 26 | 27 | void stop() noexcept override 28 | { 29 | stop_was_called_ = true; 30 | } 31 | 32 | std::thread::id getThreadId() const 33 | { 34 | return thread_id_; 35 | } 36 | 37 | bool startWasCalled() const 38 | { 39 | return start_was_called_; 40 | } 41 | 42 | bool stopWasCalled() const 43 | { 44 | return stop_was_called_; 45 | } 46 | 47 | private: 48 | std::thread::id thread_id_; 49 | bool start_was_called_; 50 | bool stop_was_called_; 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /loader/test/test_rtld_now.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "testing/dummy_value_adapter.h" 3 | 4 | #include "superflow/loader/proxel_library.h" 5 | #include "proxels/lib_path.h" 6 | #include "proxels/fauxade.h" 7 | 8 | #include "gtest/gtest.h" 9 | 10 | using namespace flow; 11 | 12 | TEST(ProxelLibrary, rtld_now_missing_dependency) 13 | { 14 | /// libdependent_library.so depends on libmissing_dependency.so, but libmissing_dependency.so is not on the linker path. 15 | /// rtld_now should cause the loading of libdependent_library.so to fail. 16 | EXPECT_THROW( 17 | std::ignore = load::ProxelLibrary(flow::test::local::lib_dir, "dependent_library"), 18 | std::invalid_argument 19 | ); 20 | } 21 | 22 | TEST(ProxelLibrary, rtld_now_dependencies_resolved) 23 | { 24 | /// libmissing_dependency.so has no dependencies that are not on the default linker path. 25 | /// It should thus load just fine. 26 | EXPECT_NO_THROW( 27 | std::ignore = load::ProxelLibrary(flow::test::local::lib_dir, "missing_dependency") 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /core/src/proxel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/proxel.h" 3 | 4 | namespace flow 5 | { 6 | const Port::Ptr& Proxel::getPort(const std::string& name) const 7 | { 8 | return port_manager_.get(name); 9 | } 10 | 11 | const PortManager::PortMap& Proxel::getPorts() const 12 | { 13 | return port_manager_.getPorts(); 14 | } 15 | 16 | ProxelStatus Proxel::getStatus() const 17 | { 18 | return { 19 | getState(), 20 | getStatusInfo(), 21 | port_manager_.getStatus() 22 | }; 23 | } 24 | 25 | void Proxel::setState(const State state) const 26 | { 27 | state_ = state; 28 | } 29 | 30 | void Proxel::setStatusInfo(const std::string& status_info) const 31 | { 32 | status_info_.store(status_info); 33 | } 34 | 35 | Proxel::State Proxel::getState() const 36 | { 37 | return state_; 38 | } 39 | 40 | std::string Proxel::getStatusInfo() const 41 | { 42 | return status_info_.load(); 43 | } 44 | 45 | void Proxel::registerPorts(PortManager::PortMap&& ports) 46 | { 47 | port_manager_ = PortManager{std::move(ports)}; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Norwegian Defence Research Establishment (FFI) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /core/include/superflow/port.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/port_status.h" 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | /// \brief Interface for interconnection between two entities exchanging data. 11 | class Port 12 | { 13 | public: 14 | using Ptr = std::shared_ptr; 15 | 16 | virtual ~Port() = default; 17 | 18 | /** 19 | * \brief Attempts to connect ports. Does nothing if already connected 20 | * to ptr. Throws if already connected and multiple connections is 21 | * unsupported. 22 | * \throw Exception if connection fails 23 | */ 24 | virtual void connect(const Port::Ptr& ptr) = 0; 25 | 26 | /** 27 | * \brief disconnects port(s) if connected 28 | * otherwise does nothing 29 | */ 30 | virtual void disconnect() noexcept = 0; 31 | 32 | /** 33 | * \brief Disconnects port if connected 34 | * otherwise does nothing 35 | */ 36 | virtual void disconnect(const Port::Ptr& ptr) noexcept = 0; 37 | 38 | [[nodiscard]] virtual bool isConnected() const = 0; 39 | 40 | [[nodiscard]] virtual PortStatus getStatus() const = 0; 41 | }; 42 | } -------------------------------------------------------------------------------- /loader/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PARENT_PROJECT ${PROJECT_NAME}) 2 | set(target_test_name "${CMAKE_PROJECT_NAME}-${PARENT_PROJECT}-test") 3 | project(${target_test_name} CXX) 4 | message(STATUS "* Adding test executable '${target_test_name}'") 5 | 6 | add_subdirectory(proxels) 7 | 8 | 9 | add_executable(${target_test_name} 10 | "test_load_factories.cpp" 11 | "test_proxel_library.cpp" 12 | "$<$:test_rtld_now.cpp>" 13 | ) 14 | 15 | target_link_libraries( 16 | ${target_test_name} 17 | PRIVATE 18 | GTest::gtest GTest::gtest_main 19 | ${CMAKE_PROJECT_NAME}::core 20 | ${CMAKE_PROJECT_NAME}::loader 21 | superflow::dummy-adapter 22 | ) 23 | 24 | set_target_properties(${target_test_name} PROPERTIES 25 | CXX_STANDARD_REQUIRED ON 26 | CXX_STANDARD 17 27 | ) 28 | 29 | file(GENERATE 30 | OUTPUT "$/libdependent_library.so" 31 | INPUT "${CMAKE_CURRENT_LIST_DIR}/libs/libdependent_library.so" 32 | ) 33 | 34 | file(GENERATE 35 | OUTPUT "$/libmissing_dependency.so" 36 | INPUT "${CMAKE_CURRENT_LIST_DIR}/libs/libmissing_dependency.so" 37 | ) 38 | 39 | include(GoogleTest) 40 | gtest_discover_tests(${target_test_name}) 41 | -------------------------------------------------------------------------------- /curses/include/superflow/curses/proxel_window.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/curses/window.h" 5 | #include "superflow/proxel_status.h" 6 | 7 | namespace flow::curses 8 | { 9 | class ProxelWindow 10 | { 11 | public: 12 | ProxelWindow(); 13 | 14 | ProxelWindow( 15 | int x, 16 | int y, 17 | int width, 18 | size_t max_ports_shown = 0 19 | ); 20 | 21 | void renderStatus( 22 | const std::string& name, 23 | const ProxelStatus& status 24 | ); 25 | 26 | static int getHeight(); 27 | 28 | static constexpr int port_window_width = 10; 29 | static constexpr int port_window_height = 2; 30 | 31 | private: 32 | static constexpr int inner_height = 6; 33 | 34 | int x_; 35 | int y_; 36 | int width_; 37 | size_t max_ports_shown_; 38 | 39 | Window window_; 40 | std::map port_windows_; 41 | 42 | static std::vector getLines(const std::string& str, size_t max_line_length); 43 | 44 | static std::vector split(const std::string& str, char sep); 45 | 46 | static std::vector split(const std::string& str, size_t chunk_size); 47 | 48 | static nccpp::Color getStateColor(ProxelStatus::State state); 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /core/src/port_manager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/port_manager.h" 3 | 4 | #include 5 | 6 | namespace flow 7 | { 8 | PortManager::PortManager(const PortMap& ports) 9 | : ports_{ports} 10 | {} 11 | 12 | PortManager::PortManager(PortMap&& ports) 13 | : ports_{std::move(ports)} 14 | {} 15 | 16 | PortManager::~PortManager() 17 | { 18 | for (auto& kv : ports_) 19 | { 20 | Port::Ptr& port = kv.second; 21 | 22 | if (port == nullptr) 23 | { 24 | continue; 25 | } 26 | 27 | port->disconnect(); 28 | } 29 | } 30 | 31 | const Port::Ptr& PortManager::get(const std::string& name) const 32 | { 33 | try 34 | { return ports_.at(name); } 35 | catch (...) 36 | { throw std::invalid_argument(std::string("port '" + name + "' does not exist")); } 37 | } 38 | 39 | const PortManager::PortMap& PortManager::getPorts() const 40 | { 41 | return ports_; 42 | } 43 | 44 | std::map PortManager::getStatus() const 45 | { 46 | std::map statuses; 47 | 48 | for (const auto& [port_name, port] : ports_) 49 | { 50 | if (port == nullptr) 51 | { continue; } 52 | 53 | statuses[port_name] = port->getStatus(); 54 | } 55 | 56 | return statuses; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/include/superflow/policy.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | namespace flow 5 | { 6 | enum class GetMode 7 | { 8 | Blocking, ///< Attempts to retreive data from the buffer is blocking, so that the consumer will 9 | /// wait until new data is added to the buffer. The wait will be aborted only if the 10 | /// consumer is deactivated. 11 | Latched, ///< If buffer is empty, latched mode acts like Blocking. Else, the first data in the 12 | /// buffer will be read. With buffer size 1 you will always get newest data available. 13 | ReadyOnly, ///< When connected to multiple producers one would usually wait for all of them to 14 | /// produce data. This mode fetches only from ready producers, i.e. not necessary all 15 | AtLeastOneNew ///< Similar to Latched, but blocks until at least one of the producers have new data. 16 | }; 17 | 18 | enum class ConnectPolicy 19 | { 20 | Single, ///< Only allow one ProducerPort to connect. 21 | Multi ///< Allow multiple ProducerPort to connect. 22 | }; 23 | 24 | enum class LeakPolicy 25 | { 26 | Leaky, ///< Oldest data is dropped when pushing to a full buffer 27 | PushBlocking ///< Push blocks if buffer is full 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /loader/test/README.md: -------------------------------------------------------------------------------- 1 | How the dummy so-files for testing rtld_now were created: (main is not required) 2 | 3 | ```bash 4 | g++ -c -Wall -fpic missing_dependency.cpp 5 | g++ -shared -o libmissing_dependency.so missing_dependency.o 6 | 7 | g++ -c -Wall -fpic dependent_library.cpp 8 | g++ -L. -shared -o libdependent_library.so dependent_library.o -lmissing_dependency 9 | 10 | LD_LIBRARY_PATH=${PWD} g++ -L. -Wall -o main main.cpp -ldependent_library 11 | LD_LIBRARY_PATH=${PWD} ./main 12 | 13 | cp libmissing_dependency.so superflow/loader/test/libs/ 14 | cp libdependent_library.so superflow/loader/test/libs/ 15 | ``` 16 | 17 | **missing_dependency.hpp** 18 | ```cpp 19 | #pragma once 20 | 21 | void function_in_missing_dependency(); 22 | ``` 23 | 24 | **missing_dependency.cpp** 25 | ```cpp 26 | #include "missing_dependency.hpp" 27 | #include 28 | 29 | void function_in_missing_dependency() 30 | { std::cout << "Hello, I am a function in a missing shared library" << std::endl; } 31 | ``` 32 | 33 | **dependent_library.hpp** 34 | ```cpp 35 | #pragma once 36 | 37 | void dependent_function(); 38 | ``` 39 | 40 | **dependent_library.cpp** 41 | ```cpp 42 | #include "dependent_library.hpp" 43 | #include "missing_dependency.hpp" 44 | 45 | void dependent_function() 46 | { function_in_missing_dependency(); } 47 | ``` 48 | 49 | **main.cpp** 50 | ```cpp 51 | #include "dependent_library.hpp" 52 | 53 | int main() 54 | { dependent_function(); } 55 | ``` 56 | -------------------------------------------------------------------------------- /loader/include/superflow/loader/load_factories.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "superflow/loader/proxel_library.h" 4 | 5 | namespace flow::load 6 | { 7 | /// \brief Collect factories across multiple ProxelLibrary%s and concatenate into a single FactoryMap 8 | /// \see ProxelLibrary::loadFactories 9 | template 10 | FactoryMap loadFactories( 11 | const std::vector& libraries, 12 | Args... args 13 | ) 14 | { 15 | FactoryMap factories; 16 | 17 | for (const auto& library : libraries) 18 | { 19 | factories += library.loadFactories(args...); 20 | } 21 | 22 | return factories; 23 | } 24 | 25 | // It is disallowed to construct the vector as a temporary within the function argument list, 26 | // as this would cause the library to be unloaded immediately after return. 27 | // The std::is_same is there to make the static_assert dependent on the template parameter, 28 | // otherwise it would cause a compilation error even when the overload is not called. 29 | template 30 | FactoryMap loadFactories( 31 | const std::vector&&, 32 | Args... 33 | ) 34 | { 35 | static_assert( 36 | !std::is_same::value, // always false 37 | "No temporary allowed!" 38 | "You must keep the libraries in scope, so they won't be unloaded while you attempt to use them."); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/test/test_multi_queue_getter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/multi_queue_getter.h" 3 | 4 | #include "gtest/gtest.h" 5 | 6 | using namespace flow; 7 | 8 | TEST(MultiQueueGetter, LatchedPopsQueuesWithMultipleElements) 9 | { 10 | MultiLockQueue multi_queue(2, {0, 1}); 11 | MultiQueueGetter getter; 12 | 13 | // push elements `42` and `13` into queue 0 14 | multi_queue.push(0, 42); 15 | multi_queue.push(0, 13); 16 | 17 | // only push element `42` into queue 1 18 | multi_queue.push(1, 42); 19 | 20 | { 21 | std::vector values; 22 | getter.get(multi_queue, values); 23 | 24 | // expect both to be 42 25 | ASSERT_EQ(values[0], 42); 26 | ASSERT_EQ(values[1], 42); 27 | } 28 | 29 | { 30 | std::vector values; 31 | getter.get(multi_queue, values); 32 | 33 | // expect one value (the one from queue 0) to be 13, and the other to be 42 34 | // QueueGetter does not guarantee the order, so we don't know which is which 35 | ASSERT_EQ(std::max(values[0], values[1]), 42); 36 | ASSERT_EQ(std::min(values[0], values[1]), 13); 37 | } 38 | 39 | { 40 | std::vector values; 41 | getter.get(multi_queue, values); 42 | 43 | // still expect one value (the one from queue 0) to be 13, and the other to be 42 44 | ASSERT_EQ(std::max(values[0], values[1]), 42); 45 | ASSERT_EQ(std::min(values[0], values[1]), 13); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/include/superflow/queue_getter.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/policy.h" 5 | #include "superflow/utils/lock_queue.h" 6 | #include 7 | 8 | namespace flow 9 | { 10 | template 11 | struct QueueGetter 12 | { 13 | static_assert(M == GetMode::Blocking || M == GetMode::Latched, "Selected GetMode is not available for this Port"); 14 | }; 15 | 16 | template 17 | struct QueueGetter 18 | { 19 | static std::optional get(LockQueue& queue) 20 | { 21 | try 22 | { 23 | return queue.pop(); 24 | } 25 | catch (const TerminatedException&) 26 | { return std::nullopt; } 27 | } 28 | 29 | static bool hasNext(const LockQueue& queue) 30 | { 31 | return !queue.isEmpty(); 32 | } 33 | 34 | void clear() 35 | { /* noop */ } 36 | }; 37 | 38 | template 39 | struct QueueGetter 40 | { 41 | std::optional get(LockQueue& queue) 42 | { 43 | try 44 | { 45 | if (!opt.has_value() || !queue.isEmpty()) 46 | { opt = queue.pop(); } 47 | 48 | return opt; 49 | } 50 | catch (const TerminatedException&) 51 | { return std::nullopt; } 52 | } 53 | 54 | bool hasNext(const LockQueue& queue) const 55 | { return opt.has_value() || !queue.isEmpty(); } 56 | 57 | void clear() 58 | { 59 | opt = std::nullopt; 60 | } 61 | 62 | private: 63 | std::optional opt; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /core/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PARENT_PROJECT ${PROJECT_NAME}) 2 | set(target_test_name "${CMAKE_PROJECT_NAME}-${PARENT_PROJECT}-test") 3 | project(${target_test_name} CXX) 4 | message(STATUS "* Adding test executable '${target_test_name}'") 5 | 6 | add_executable(${target_test_name} 7 | "connectable_port.h" 8 | "connectable_proxel.h" 9 | "crashing_proxel.h" 10 | "multi_connectable_port.h" 11 | "pimpl_test.cpp" 12 | "templated_testproxel.h" 13 | "test_block_lock_queue.cpp" 14 | "test_buffered_consumer_port.cpp" 15 | "test_callback_consumer_port.cpp" 16 | "test_connection_manager.cpp" 17 | "test_graph_factory.cpp" 18 | "test_graph.cpp" 19 | "test_interface_port.cpp" 20 | "test_lock_queue.cpp" 21 | "test_multi_lock_queue.cpp" 22 | "test_pimpl.cpp" 23 | "test_requester_responder_port.cpp" 24 | "test_metronome.cpp" 25 | "test_multi_consumer_port.cpp" 26 | "test_multi_queue_getter.cpp" 27 | "test_multi_requester_port.cpp" 28 | "test_mutexed.cpp" 29 | "test_port_manager.cpp" 30 | "test_proxel_status.cpp" 31 | "test_producer_consumer_port.cpp" 32 | "test_proxel_timer.cpp" 33 | "test_shared_mutexed.cpp" 34 | "test_signal_waiter.cpp" 35 | "test_sleeper.cpp" 36 | "test_throttle.cpp" 37 | "threaded_proxel.h" 38 | ) 39 | 40 | target_link_libraries( 41 | ${target_test_name} 42 | PRIVATE GTest::gtest GTest::gtest_main 43 | PRIVATE ${CMAKE_PROJECT_NAME}::core 44 | ) 45 | 46 | set_target_properties(${target_test_name} PROPERTIES 47 | CXX_STANDARD_REQUIRED ON 48 | CXX_STANDARD 17 49 | ) 50 | 51 | include(GoogleTest) 52 | gtest_discover_tests(${target_test_name}) 53 | -------------------------------------------------------------------------------- /core/test/templated_testproxel.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/buffered_consumer_port.h" 5 | #include "superflow/policy.h" 6 | #include "superflow/port_manager.h" 7 | #include "superflow/proxel.h" 8 | #include "superflow/producer_port.h" 9 | #include "superflow/value.h" 10 | 11 | #include 12 | 13 | namespace flow 14 | { 15 | template 16 | class TemplatedProxel : public Proxel 17 | { 18 | public: 19 | explicit TemplatedProxel(T init_value = {}) 20 | : value_{std::move(init_value)} 21 | , out_port_{std::make_shared()} 22 | , in_port_{std::make_shared()} 23 | { 24 | registerPorts( 25 | { 26 | {"outport", out_port_}, 27 | {"inport", in_port_} 28 | }); 29 | } 30 | 31 | void start() override 32 | { out_port_->send(value_); } 33 | 34 | void stop() noexcept override 35 | {} 36 | 37 | T getValue() 38 | { 39 | (*in_port_) >> value_; 40 | return value_; 41 | } 42 | 43 | T getStoredValue() const 44 | { return value_; } 45 | 46 | template 47 | static flow::Proxel::Ptr create( 48 | const PropertyList& properties 49 | ) 50 | { 51 | return std::make_shared>( 52 | value(properties, "init_value") 53 | ); 54 | } 55 | 56 | private: 57 | using InPort = BufferedConsumerPort; 58 | using OutPort = ProducerPort; 59 | 60 | T value_; 61 | 62 | typename OutPort::Ptr out_port_; 63 | typename InPort::Ptr in_port_; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /yaml/test/test_yaml_property_list.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "gtest/gtest.h" 3 | #include "superflow/loader/proxel_library.h" 4 | #include "superflow/yaml/yaml_property_list.h" 5 | 6 | #include "boost/dll/runtime_symbol_info.hpp" // for program_location() 7 | #include 8 | 9 | 10 | namespace dll = boost::dll; 11 | 12 | TEST(SuperflowYaml, can_load_load_test_libary) 13 | { 14 | ASSERT_NO_FATAL_FAILURE(flow::load::ProxelLibrary library{dll::program_location()}); 15 | } 16 | 17 | TEST(SuperflowYaml, can_load_adapter_name) 18 | { 19 | const flow::load::ProxelLibrary library{dll::program_location()}; 20 | 21 | ASSERT_NO_THROW( 22 | std::ignore = library.loadFactories() 23 | ); 24 | } 25 | 26 | TEST(SuperflowYaml, can_load_factories_and_create_proxel) 27 | { 28 | const flow::load::ProxelLibrary library{dll::program_location()}; 29 | 30 | const auto factories = library.loadFactories(); 31 | 32 | ASSERT_FALSE(factories.empty()); 33 | 34 | const auto& proxel_factory = factories.get("YamlTestProxel"); 35 | 36 | constexpr int the_number{42}; 37 | const auto load_yaml_properties = [the_number] 38 | { 39 | YAML::Node node; 40 | node["int"] = the_number; 41 | return flow::yaml::YAMLPropertyList(node); 42 | }; 43 | 44 | const auto properties = load_yaml_properties(); 45 | const auto proxel = std::invoke(proxel_factory, properties); 46 | 47 | EXPECT_EQ(flow::ProxelStatus::State::AwaitingInput, proxel->getStatus().state); 48 | ASSERT_EQ(std::to_string(the_number), proxel->getStatus().info); 49 | } 50 | -------------------------------------------------------------------------------- /loader/test/proxels/include/testing/dummy_value_adapter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "superflow/loader/register_factory.h" 4 | #include "boost/preprocessor/stringize.hpp" 5 | 6 | // By using REGISTER_PROXEL_FACTORY_SECTIONED, 7 | // you can register several adapter types at once, and then choose one of the adapters when 8 | // loading the library. 9 | // 10 | // Preferably, a helper macro is wrapping REGISTER_PROXEL_FACTORY_SECTIONED as in this example. 11 | // If the Adapter authors provide similar macros, you could go on like so: 12 | // 13 | // #include "my/json_property_list.h" 14 | // REGISTER_JSON_PROXEL_FACTORY(Dummy, Dummy::static_create) 15 | // 16 | // #include "superflow/yaml/yaml_property_list.h" 17 | // REGISTER_YAML_PROXEL_FACTORY(Dummy, Dummy::static_create) 18 | // 19 | // #include "testing/dummy_value_adapter.h" 20 | // REGISTER_DUMMY_PROXEL_FACTORY(Dummy, Dummy::static_create) 21 | 22 | #define REGISTER_DUMMY_PROXEL_FACTORY(ProxelName, Factory) \ 23 | REGISTER_PROXEL_FACTORY_SECTIONED(ProxelName, Factory, DUMMY_ADAPTER_NAME) 24 | 25 | namespace flow::test 26 | { 27 | class DummyValueAdapter 28 | { 29 | public: 30 | template 31 | R convertValue(const std::string&) const 32 | { return R{}; } 33 | 34 | [[nodiscard]] bool hasKey(const std::string& key) const; 35 | 36 | static constexpr const char* adapter_name{BOOST_PP_STRINGIZE(DUMMY_ADAPTER_NAME)}; 37 | 38 | std::string key_{"dummy_key"}; 39 | int int_value{21}; 40 | }; 41 | 42 | template<> 43 | inline int DummyValueAdapter::convertValue(const std::string& key) const 44 | { 45 | return key == "int key" ? int_value : -1; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /yaml/include/superflow/yaml/yaml_property_list.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "yaml-cpp/yaml.h" 5 | 6 | #include 7 | 8 | namespace flow::yaml 9 | { 10 | class YAMLPropertyList 11 | { 12 | public: 13 | YAMLPropertyList() = default; 14 | 15 | explicit YAMLPropertyList(const YAML::Node& parent); 16 | 17 | bool hasKey(const std::string& key) const; 18 | 19 | template 20 | T convertValue(const std::string& key) const; 21 | 22 | static constexpr const char* adapter_name{"YAML"}; 23 | 24 | private: 25 | YAML::Node parent_; 26 | }; 27 | 28 | // ----- Implementation ----- 29 | inline YAMLPropertyList::YAMLPropertyList(const YAML::Node& parent) 30 | : parent_{parent} 31 | { 32 | if (!parent.IsMap()) 33 | { 34 | throw std::invalid_argument("Input node to YAMLPropertyList must be a YAML::Map."); 35 | } 36 | } 37 | 38 | inline bool YAMLPropertyList::hasKey(const std::string& key) const 39 | { 40 | return bool(parent_[key]); 41 | } 42 | 43 | template 44 | T YAMLPropertyList::convertValue(const std::string& key) const 45 | { 46 | const auto& node = parent_[key]; 47 | if (!node) 48 | { 49 | throw std::runtime_error({"Could not find key \"" + key + "\" in property list"}); 50 | } 51 | 52 | try 53 | { 54 | return node.as(); 55 | } 56 | catch (const YAML::TypedBadConversion&) 57 | { 58 | throw std::runtime_error({"Type mismatch for key: \"" + key + "\""}); 59 | } 60 | catch (const YAML::BadConversion&) 61 | { 62 | throw std::runtime_error({"Failed to parse key: \"" + key + "\""}); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/include/superflow/utils/sleeper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | 6 | namespace flow 7 | { 8 | /// \brief Being the sister of Metronome and Throttle, Sleeper is the third way of rate control. 9 | /// Typically intended to be used within a for-loop in order to stall execution. 10 | /// Sleeper will sleep until another `period` has passed since the previous call to `sleepForRemainderOfPeriod`, thus a steady rate. 11 | /// You can also adjust the sleep period with ´setNewSleepPeriod(Clock::duration period);` 12 | /// 13 | /// ```cpp 14 | /// using namespace std::chrono_literals; 15 | /// const Sleeper rate_limiter(10ms); 16 | /// 17 | /// for (const auto& data : *latched_port_) 18 | /// { 19 | /// do_work(); 20 | /// rate_limiter.sleepForRemainderOfPeriod(); // Sleep for the rest of the period 21 | /// 22 | /// if(some condition) 23 | /// { 24 | /// rate_limiter.setNewSleepPeriod(xms); 25 | /// } 26 | /// } 27 | /// ``` 28 | /// 29 | /// 30 | /// If execution time of `do_work()` is less than 10ms, it will not be called again until 10ms has passed since 31 | /// the previous call. If execution time is > 10ms, it will be called again immediately as sleepForRemainderOfPeriod time is already due. 32 | /// 33 | /// \see Metronome, Throttle 34 | class Sleeper 35 | { 36 | public: 37 | using Clock = std::chrono::steady_clock; 38 | 39 | explicit Sleeper(Clock::duration period); 40 | 41 | void sleepForRemainderOfPeriod() const; 42 | void setSleepPeriod(const Clock::duration period); 43 | 44 | private: 45 | mutable Clock::time_point time_point_; 46 | Clock::duration period_; 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /core/include/superflow/port_manager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/port.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace flow 10 | { 11 | /// \brief Container for managing Ports within a Proxel 12 | /// \see Port, Proxel 13 | class PortManager 14 | { 15 | public: 16 | /// \brief A map mapping port names to port pointers 17 | using PortMap = std::map; 18 | 19 | /// \brief Create a new PortManager to manage a PortMap 20 | /// \param ports an existing PortMap 21 | explicit PortManager(const PortMap& ports); 22 | 23 | /// \brief Create a PortManager and let the manager own all Ports 24 | /// \param ports an existing PortMap 25 | explicit PortManager(PortMap&& ports); 26 | 27 | ~PortManager(); 28 | 29 | PortManager() = default; 30 | 31 | PortManager(const PortManager&) = default; 32 | 33 | PortManager(PortManager&&) = default; 34 | 35 | PortManager& operator=(const PortManager&) = default; 36 | 37 | PortManager& operator=(PortManager&&) = default; 38 | 39 | /// \brief Get the port with the given name 40 | /// \param name The name of the Port 41 | /// \return A pointer to the Port 42 | [[nodiscard]] const Port::Ptr& get(const std::string& name) const; 43 | 44 | /// \brief Request access to the PortMap managed by the PortManager. 45 | /// \return The PortMap 46 | [[nodiscard]] const PortMap& getPorts() const; 47 | 48 | /// \brief Get the status of all Ports 49 | /// \return A map from port name to port status 50 | /// \see PortStatus 51 | [[nodiscard]] std::map getStatus() const; 52 | 53 | private: 54 | PortMap ports_; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /core/test/connectable_port.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/port.h" 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | template 11 | class ConnectablePort : 12 | public Port, 13 | public std::enable_shared_from_this> 14 | { 15 | public: 16 | using Ptr = std::shared_ptr; 17 | 18 | std::shared_ptr connection_; 19 | size_t num_connections = PortStatus::undefined; 20 | size_t num_transactions = PortStatus::undefined; 21 | bool did_get_disconnect = false; 22 | 23 | void connect(const Port::Ptr& ptr) override 24 | { 25 | auto connection = std::dynamic_pointer_cast(ptr); 26 | 27 | if (connection == nullptr) 28 | { 29 | throw std::invalid_argument("Mismatch between port types"); 30 | } 31 | 32 | connection_ = std::move(connection); 33 | connection_->connection_ = this->shared_from_this(); 34 | } 35 | 36 | void disconnect() noexcept override 37 | { 38 | did_get_disconnect = true; 39 | 40 | if (connection_ == nullptr) 41 | { 42 | return; 43 | } 44 | 45 | connection_->connection_ = nullptr; 46 | connection_ = nullptr; 47 | } 48 | 49 | void disconnect(const Port::Ptr& ptr) noexcept override 50 | { 51 | if (connection_ != ptr) 52 | { 53 | return; 54 | } 55 | 56 | disconnect(); 57 | } 58 | 59 | bool isConnected() const override 60 | { 61 | return connection_ != nullptr; 62 | } 63 | 64 | PortStatus getStatus() const override 65 | { 66 | return { 67 | num_connections, 68 | num_transactions 69 | }; 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /core/src/metronome.cpp: -------------------------------------------------------------------------------- 1 | #include "superflow/utils/metronome.h" 2 | 3 | namespace flow 4 | { 5 | using Clock = std::chrono::steady_clock; 6 | 7 | Metronome::Metronome( 8 | const Functional& func, 9 | const Duration period 10 | ) 11 | : has_stopped_{false} 12 | { 13 | const auto first_time_point = Clock::now(); 14 | 15 | worker_ = std::async( 16 | std::launch::async, 17 | [this, func, period, first_time_point]() 18 | { 19 | auto time_point = first_time_point; 20 | 21 | while (!has_stopped_) 22 | { 23 | time_point += period; 24 | 25 | { 26 | std::unique_lock lock{mutex_}; 27 | const bool has_stopped = cv_.wait_until(lock, time_point, 28 | [this]{ return has_stopped_; } 29 | ); 30 | if (has_stopped) 31 | { break; } 32 | } 33 | 34 | const auto duration_since_begin = Clock::now() - first_time_point; 35 | 36 | func(duration_since_begin); 37 | } 38 | } 39 | ); 40 | } 41 | 42 | Metronome::~Metronome() 43 | { 44 | stop(); 45 | } 46 | 47 | void Metronome::get() 48 | { 49 | if (worker_.valid()) 50 | { 51 | worker_.get(); 52 | } 53 | else 54 | { 55 | throw std::runtime_error{"Cannot call get() on Metronome with invalid state"}; 56 | } 57 | } 58 | 59 | void Metronome::check() 60 | { 61 | const auto state = worker_.wait_for(std::chrono::seconds{0}); 62 | 63 | if (state != std::future_status::timeout) 64 | { 65 | get(); 66 | } 67 | } 68 | 69 | void Metronome::stop() noexcept 70 | { 71 | { 72 | std::scoped_lock lock{mutex_}; 73 | 74 | if (has_stopped_) 75 | { return; } 76 | 77 | has_stopped_ = true; 78 | } 79 | 80 | cv_.notify_one(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /core/include/superflow/graph_factory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/connection_spec.h" 5 | #include "superflow/factory_map.h" 6 | #include "superflow/graph.h" 7 | #include "superflow/proxel_config.h" 8 | 9 | namespace flow 10 | { 11 | template 12 | inline std::map createProxelsFromConfig( 13 | const FactoryMap& factory_map, 14 | const std::vector>& proxel_configurations) 15 | { 16 | std::map proxels{}; 17 | 18 | for (const auto& config : proxel_configurations) 19 | { 20 | if (proxels.count(config.id) > 0) 21 | { throw std::invalid_argument("Proxel with id '"+ config.id +"' is defined more than once."); } 22 | 23 | const auto& factory = factory_map.get(config.type); 24 | try 25 | { 26 | proxels.emplace(config.id, factory(config.properties)); 27 | } 28 | catch(const std::exception& e) 29 | { 30 | throw std::runtime_error("Failed to create proxel '" + config.id + "': " + e.what()); 31 | } 32 | } 33 | 34 | return proxels; 35 | } 36 | 37 | template 38 | inline Graph createGraph( 39 | const FactoryMap& factory_map, 40 | const std::vector>& proxel_configurations, 41 | const std::vector& connections 42 | ) 43 | { 44 | Graph graph{ 45 | createProxelsFromConfig(factory_map, proxel_configurations) 46 | }; 47 | 48 | for (const auto& connection : connections) 49 | { graph.connect(connection.lhs_name, connection.lhs_port, connection.rhs_name, connection.rhs_port); } 50 | 51 | return graph; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /core/include/superflow/utils/signal_waiter.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace flow 11 | { 12 | /// \brief RAII-wrapper for thread-safe listening to one or more signals. 13 | /// If you are using std::signal() or plain C signal() in your program, this class might not work as expected. 14 | /// Use SignalWaiter::hasGottenSignal() at any time to check if one of the signals have been raised. 15 | /// Use e.g. SignalWaiter::getFuture().wait() to wait until a signal has been received. 16 | class SignalWaiter 17 | { 18 | public: 19 | /// \brief Creates a SignalWaiter that listens to all `signals`. 20 | /// The signal handlers are guaranteed to have been installed once the CTOR returns. 21 | explicit SignalWaiter( 22 | const std::vector& signals = {SIGINT} 23 | ); 24 | 25 | /// \brief Immediately stops listening to signals, as well as resolving all waiting futures 26 | ~SignalWaiter(); 27 | 28 | /// \brief Returns whether any of the `signals` have been received 29 | [[nodiscard]] bool hasGottenSignal() const; 30 | 31 | /// \brief Returns a future that is resolved as soon as one of the `signals` have been received, or the waiter 32 | /// has been destroyed. 33 | [[nodiscard]] std::shared_future getFuture() const; 34 | 35 | private: 36 | std::mutex mutex_; 37 | std::condition_variable cv_; 38 | 39 | bool is_waiting_; 40 | bool got_signal_; 41 | std::shared_ptr> handler_; 42 | std::shared_future worker_; 43 | 44 | void handleSignal(); 45 | 46 | void await(const std::vector& signals); 47 | }; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /yaml/include/superflow/yaml/yaml_string_pair.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "yaml-cpp/node/convert.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace YAML 10 | { 11 | template<> 12 | struct convert> 13 | { 14 | static Node encode(const std::pair& rhs) 15 | { 16 | Node node(NodeType::Sequence); 17 | node.push_back(rhs.first); 18 | node.push_back(rhs.second); 19 | return node; 20 | } 21 | 22 | static bool decode(const Node& node, std::pair& rhs) 23 | { 24 | if (node.IsMap() && node.size() == 1) 25 | { 26 | rhs.first = node.begin()->first.as(); 27 | rhs.second = node.begin()->second.as(); 28 | return true; 29 | } 30 | 31 | return false; 32 | } 33 | }; 34 | 35 | template<> 36 | struct convert>> 37 | { 38 | static Node encode(const std::pair>& pair) 39 | { 40 | Node node(NodeType::Sequence); 41 | 42 | node.push_back(pair.first); 43 | node.push_back(pair.second); 44 | 45 | return node; 46 | } 47 | 48 | static bool decode(const Node& node, std::pair>& rhs) 49 | { 50 | if (node.IsMap() && node.size() == 1) 51 | { 52 | rhs.first = node.begin()->first.as(); 53 | 54 | const auto& second = node.begin()->second; 55 | rhs.second = second.IsSequence() 56 | ? second.as>() 57 | : std::vector{second.as()}; 58 | 59 | return true; 60 | } 61 | 62 | return false; 63 | } 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /core/include/superflow/proxel_status.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/port_status.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace flow 11 | { 12 | struct ProxelStatus 13 | { 14 | enum class State 15 | { 16 | AwaitingInput, 17 | AwaitingRequest, 18 | AwaitingResponse, 19 | Crashed, 20 | NotConnected, 21 | Paused, 22 | Running, 23 | Unavailable, 24 | Undefined, 25 | Warning, 26 | }; 27 | 28 | State state; 29 | std::string info; 30 | std::map ports; 31 | }; 32 | 33 | /// A map with the latest ProxelStatuses, as key/value pairs such as { proxel_name: ProxelStatus } 34 | using ProxelStatusMap = std::map; 35 | 36 | /// \brief Write enum ProxelStatus as string to a std::ostream 37 | inline std::ostream& operator<<(std::ostream& os, const ProxelStatus::State& state) 38 | { 39 | switch (state) 40 | { 41 | case ProxelStatus::State::AwaitingInput: 42 | return os << "NO INPUT"; 43 | case ProxelStatus::State::AwaitingRequest: 44 | return os << "NO REQUEST"; 45 | case ProxelStatus::State::AwaitingResponse: 46 | return os << "NO RESPONSE"; 47 | case ProxelStatus::State::Crashed: 48 | return os << "CRASHED"; 49 | case ProxelStatus::State::NotConnected: 50 | return os << "NOT CONNECTED"; 51 | case ProxelStatus::State::Paused: 52 | return os << "PAUSED"; 53 | case ProxelStatus::State::Running: 54 | return os << "RUNNING"; 55 | case ProxelStatus::State::Unavailable: 56 | return os << "UNAVAILABLE"; 57 | case ProxelStatus::State::Warning: 58 | return os << "WARNING"; 59 | default: 60 | return os << "UNDEFINED"; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/proxel_timer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/utils/proxel_timer.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace flow 8 | { 9 | void ProxelTimer::start() 10 | { 11 | start_ = Clock::now(); 12 | std::call_once(first_flag_, [this]() 13 | { first_time_point_ = start_; }); 14 | } 15 | 16 | double ProxelTimer::stop() 17 | { 18 | if (Duration(first_time_point_.time_since_epoch()).count() == 0.) 19 | { throw std::runtime_error("ProxelTimer::stop() has been called before ProxelTimer::start()"); } 20 | 21 | const auto now = Clock::now(); 22 | 23 | const double processing_time = Duration{now - start_}.count(); 24 | summed_processing_time_ += processing_time; 25 | 26 | ++run_counter_; 27 | mean_processing_time_ = summed_processing_time_ / static_cast(run_counter_); 28 | 29 | const auto uptime = Duration{now - first_time_point_}.count(); 30 | mean_busyness_time_ = summed_processing_time_ / uptime; 31 | 32 | return processing_time; 33 | } 34 | 35 | double ProxelTimer::peek() const 36 | { 37 | const auto now = Clock::now(); 38 | const double processing_time = Duration{now - start_}.count(); 39 | return processing_time; 40 | } 41 | 42 | double ProxelTimer::getAverageProcessingTime() const 43 | { 44 | return mean_processing_time_; 45 | } 46 | 47 | double ProxelTimer::getAverageBusyness() const 48 | { 49 | return mean_busyness_time_; 50 | } 51 | 52 | unsigned long long ProxelTimer::getRunCount() const 53 | { 54 | return run_counter_; 55 | } 56 | 57 | std::string ProxelTimer::getStatusInfo() const 58 | { 59 | std::ostringstream ss; 60 | ss << std::fixed << std::setprecision(3) 61 | << "time: " << getAverageProcessingTime() << "s" 62 | << "\n" 63 | << "busy: " << getAverageBusyness(); 64 | 65 | return ss.str(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /curses/include/superflow/curses/graph_gui.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/curses/proxel_window.h" 5 | #include "superflow/graph.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace flow::curses 12 | { 13 | class GraphGUI 14 | { 15 | public: 16 | explicit GraphGUI(size_t max_ports_shown = 0); 17 | 18 | void spin( 19 | const Graph& graph, 20 | const std::unordered_set& blacklisted_proxels = {} 21 | ); 22 | 23 | void spinOnce( 24 | const Graph& graph, 25 | const std::unordered_set& blacklisted_proxels = {} 26 | ); 27 | 28 | private: 29 | using ProxelSet = std::map; 30 | using StatusSet = std::map; 31 | using WindowSet = std::map; 32 | 33 | static constexpr int window_h_padding = 2; 34 | static constexpr int window_v_padding = 4; 35 | 36 | ProxelSet last_proxels_; 37 | WindowSet windows_; 38 | 39 | size_t max_ports_shown_; 40 | int width_ = -1; 41 | int height_ = -1; 42 | int minimum_window_width_ = 0; 43 | 44 | [[nodiscard]] bool guiSizeHasChanged() const; 45 | 46 | [[nodiscard]] bool proxelSetHasChanged(const ProxelSet& proxels) const; 47 | 48 | static WindowSet createWindows( 49 | const ProxelSet& proxels, 50 | int width, 51 | int height, 52 | int minimum_window_width, 53 | size_t max_ports_shown 54 | ); 55 | 56 | [[nodiscard]] size_t getMaxNumPorts(const StatusSet& statuses) const; 57 | 58 | static int getMinimumWindowWidth(int max_num_ports); 59 | 60 | static int getWindowWidth(int num_cols, int width, int height); 61 | 62 | static int getNumCols( 63 | int num_proxels, 64 | int width, 65 | int height, 66 | int minimum_window_width 67 | ); 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /core/include/superflow/value.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | 6 | namespace flow 7 | { 8 | /// \brief Extract a value from a PropertyList. 9 | /// 10 | /// A PropertyList is a templated type that must support extraction of values by referring to their names. 11 | /// It is required that a valid PropertyList defines the following functions: 12 | ///
    13 | ///
  • `bool hasKey(const std::string& key)`, tells whether the given key exists or not.
  • 14 | ///
  • `T convertValue(const std::string& key)`, retrieves a value from the list.
  • 15 | ///
16 | /// \tparam T The value type 17 | /// \tparam PropertyList The concrete type of PropertyList 18 | /// \param properties the property list 19 | /// \param key The identifier of the value 20 | /// \return The value. 21 | /// \note May throw if the key does not exist (depends on the implementation of PropertyList) 22 | template 23 | T value(const PropertyList& properties, const std::string& key) 24 | { 25 | return properties.template convertValue(key); 26 | } 27 | 28 | /// Same as above, but will return default_value if the 'key' does not exist. 29 | template 30 | T value(const PropertyList& properties, const std::string& key, const T& default_value) 31 | { 32 | return properties.hasKey(key) 33 | ? properties.template convertValue(key) 34 | : default_value; 35 | } 36 | 37 | /// Same as above, but will return a default value constructed by 'default_args' if 'key' does not exist. 38 | template 39 | T value(const PropertyList& properties, const std::string& key, Args... default_args) 40 | { 41 | return properties.hasKey(key) 42 | ? properties.template convertValue(key) 43 | : T{default_args...}; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/include/superflow/queue_set_getter.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/policy.h" 5 | #include "superflow/queue_getter.h" 6 | #include "superflow/utils/lock_queue.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace flow 12 | { 13 | template 14 | struct QueueSetGetter 15 | { 16 | using Queue = LockQueue; 17 | using QueueSet = std::vector>; 18 | 19 | static void get(const QueueSet& queues, std::vector& ts) 20 | { 21 | ts.resize(queues.size()); 22 | 23 | for (size_t i = 0; i < queues.size(); ++i) 24 | { 25 | QueueGetter::get(*queues[i], ts[i]); 26 | } 27 | } 28 | 29 | static bool hasNext(const QueueSet& queues) 30 | { 31 | for (const auto& queue : queues) 32 | { 33 | if (queue->getQueueSize() == 0) 34 | { 35 | return false; 36 | } 37 | } 38 | 39 | return true; 40 | } 41 | }; 42 | 43 | template 44 | struct QueueSetGetter 45 | { 46 | using Queue = LockQueue; 47 | using QueueSet = std::vector>; 48 | 49 | static void get(const QueueSet& queues, std::vector& ts) 50 | { 51 | QueueSet ready_queues; 52 | 53 | for (const auto& queue : queues) 54 | { 55 | if (queue->getQueueSize() > 0) 56 | { 57 | ready_queues.push_back(queue); 58 | } 59 | } 60 | 61 | ts.resize(ready_queues.size()); 62 | 63 | for (size_t i = 0; i < ready_queues.size(); ++i) 64 | { 65 | QueueGetter::get(*ready_queues[i], ts[i]); 66 | } 67 | } 68 | 69 | static bool hasNext(const QueueSet& queues) 70 | { 71 | for (const auto& queue : queues) 72 | { 73 | if (queue->getQueueSize() > 0) 74 | { 75 | return true; 76 | } 77 | } 78 | 79 | return false; 80 | } 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /core/include/superflow/utils/metronome.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace flow 9 | { 10 | /// \brief Calls a provided function on a separate thread at a given 11 | /// interval, omitting the first (immediate) call. Will continue to 12 | /// do so forever, until `stop()` or dtor is called. Useful for instance for 13 | /// printing error messages for stalling tasks. 14 | /// 15 | /// ```cpp 16 | /// Metronome repeater{ 17 | /// [](const auto& duration) 18 | /// { 19 | /// std::cerr 20 | /// << "my_func has been stalling for " << duration.count() << "s" 21 | /// << std::endl; 22 | /// }, 23 | /// std::chrono::seconds{2} 24 | /// }; 25 | /// 26 | /// my_func(); // we expect that this might stall 27 | /// repeater.stop(); 28 | /// 29 | /// // `repeater` will print the above message every 2 seconds until 30 | /// // `my_func()` has returned and `stop()` has been called. 31 | /// ``` 32 | /// 33 | /// If `my_func()` throws an exception, the repeater will stop, 34 | /// and a subsequent call to `check()` will rethrow the exception. 35 | /// 36 | /// \see Sleeper, Throttle 37 | class Metronome 38 | { 39 | public: 40 | using Duration = std::chrono::steady_clock::duration; 41 | using Functional = std::function; 42 | 43 | Metronome( 44 | const Functional& func, 45 | Duration period 46 | ); 47 | 48 | ~Metronome(); 49 | 50 | /// Wait for the worker to finish, and also receive any exceptions 51 | /// thrown in the worker thread (inside `func`). 52 | void get(); 53 | 54 | /// Method for testing if `func` has thrown an exception. 55 | /// Re-throws the exception thrown by `func`, if it has thrown. 56 | /// Otherwise returns immediately. 57 | void check(); 58 | 59 | /// Stop calling the provided `func` (and unblock dtor) 60 | void stop() noexcept; 61 | 62 | private: 63 | bool has_stopped_; 64 | std::mutex mutex_; 65 | std::condition_variable cv_; 66 | std::future worker_; 67 | }; 68 | } -------------------------------------------------------------------------------- /core/test/multi_connectable_port.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/port.h" 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | template 11 | class MultiConnectablePort : 12 | public Port, 13 | public std::enable_shared_from_this> 14 | { 15 | public: 16 | using Ptr = std::shared_ptr; 17 | std::unordered_set> connections_; 18 | bool did_get_disconnect = false; 19 | 20 | void connect(const Port::Ptr& ptr) override 21 | { 22 | auto connection = std::dynamic_pointer_cast(ptr); 23 | 24 | if (connection == nullptr) 25 | { 26 | throw std::invalid_argument("Mismatch between port types"); 27 | } 28 | 29 | connections_.insert(connection); 30 | connection->connections_.insert(this->shared_from_this()); 31 | } 32 | 33 | void disconnect() noexcept override 34 | { 35 | did_get_disconnect = true; 36 | 37 | for (const auto& connection : connections_) 38 | { 39 | connection->connections_.erase(this->shared_from_this()); 40 | } 41 | 42 | connections_.clear(); 43 | } 44 | 45 | void disconnect(const Port::Ptr& ptr) noexcept override 46 | { 47 | did_get_disconnect = true; 48 | 49 | auto connection = std::dynamic_pointer_cast(ptr); 50 | 51 | if (connection == nullptr) 52 | { 53 | return; 54 | } 55 | 56 | auto it = connections_.find(connection); 57 | 58 | if (it == connections_.end()) 59 | { 60 | return; 61 | } 62 | 63 | (*it)->connections_.erase(this->shared_from_this()); 64 | connections_.erase(it); 65 | } 66 | 67 | bool isConnected() const override 68 | { 69 | return !connections_.empty(); 70 | } 71 | 72 | size_t getNumConnections() const 73 | { 74 | return connections_.size(); 75 | } 76 | 77 | PortStatus getStatus() const override 78 | { 79 | return { 80 | getNumConnections(), 81 | 0 82 | }; 83 | } 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /loader/test/proxels/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR) 2 | 3 | project(proxels CXX) 4 | message(STATUS "* Adding test library 'proxels'") 5 | 6 | if (MSVC) 7 | set(CMAKE_DEBUG_POSTFIX "") 8 | foreach(config ${CMAKE_CONFIGURATION_TYPES}) 9 | string(TOUPPER ${config} CONFIG) 10 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFIG} ${CMAKE_CURRENT_BINARY_DIR}) 11 | endforeach() 12 | endif() 13 | 14 | add_library(dummy-adapter STATIC) 15 | add_library(superflow::dummy-adapter ALIAS dummy-adapter) 16 | target_sources(dummy-adapter 17 | PRIVATE dummy_value_adapter.cpp 18 | PUBLIC FILE_SET public_headers 19 | TYPE HEADERS 20 | BASE_DIRS include 21 | FILES 22 | include/testing/dummy_value_adapter.h 23 | ) 24 | target_link_libraries(dummy-adapter 25 | PUBLIC superflow::loader 26 | ) 27 | 28 | 29 | set_target_properties(dummy-adapter PROPERTIES 30 | CXX_STANDARD_REQUIRED ON 31 | CXX_STANDARD 17 32 | ) 33 | 34 | set(dummy_adapter_name "DUMMY") 35 | target_compile_definitions(dummy-adapter 36 | PUBLIC 37 | DUMMY_ADAPTER_NAME=${dummy_adapter_name} 38 | ) 39 | 40 | option(SET_LOADER_ADAPTER "Set the LOADER_ADAPTER_xxx macros. Turn off if you are linking more than one adapter." ON) 41 | if (SET_LOADER_ADAPTER) 42 | target_compile_definitions(dummy-adapter 43 | INTERFACE 44 | LOADER_ADAPTER_HEADER="testing/dummy_value_adapter.h" 45 | LOADER_ADAPTER_NAME=${dummy_adapter_name} 46 | LOADER_ADAPTER_TYPE=flow::test::DummyValueAdapter 47 | ) 48 | endif () 49 | 50 | add_library(proxels SHARED 51 | dummy.cpp 52 | mummy.cpp 53 | yummy.cpp 54 | ) 55 | 56 | target_link_libraries(proxels 57 | PRIVATE 58 | superflow::core 59 | superflow::dummy-adapter 60 | ) 61 | 62 | set_target_properties(proxels PROPERTIES 63 | CXX_STANDARD_REQUIRED ON 64 | CXX_STANDARD 17 65 | DEBUG_POSTFIX "" 66 | ) 67 | 68 | configure_file( 69 | "${CMAKE_CURRENT_LIST_DIR}/lib_path.h.in" 70 | "${CMAKE_CURRENT_LIST_DIR}/lib_path.h" 71 | ) 72 | 73 | file(GENERATE 74 | OUTPUT "${CMAKE_CURRENT_LIST_DIR}/lib_path.h" 75 | INPUT "${CMAKE_CURRENT_LIST_DIR}/lib_path.h" 76 | ) 77 | 78 | -------------------------------------------------------------------------------- /core/include/superflow/utils/blocker.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace flow 12 | { 13 | /// \brief Class for controlling flow when doing multi threaded testing. Blocker::block() blocks the current thread until 14 | /// Blocker::release() is called in another thread. This can be used to make the main thread wait for some computation in 15 | /// another thread. 16 | struct Blocker 17 | { 18 | /// \brief Block the current thread until another thread calls release() 19 | /// \return return false if the thread was already released so no blocking was necessary, 20 | /// or true if an actual block was experienced. 21 | bool block() 22 | { 23 | if (is_released_) 24 | { return false; } // did not wait 25 | 26 | std::unique_lock lock{mutex_}; 27 | cv_.wait(lock, [this] 28 | { return static_cast(is_released_); } 29 | ); 30 | 31 | return true; 32 | } 33 | 34 | /// \brief Unblock the thread(s) that has called block() 35 | void release() 36 | { 37 | is_released_ = true; 38 | cv_.notify_all(); 39 | } 40 | 41 | /// \brief Resets the Blocker. Further calls to block() will block until someone again calls release() 42 | void rearm() 43 | { is_released_ = false; } 44 | 45 | bool isReleased() const 46 | { return is_released_; } 47 | 48 | private: 49 | mutable std::mutex mutex_{}; 50 | mutable std::condition_variable cv_; 51 | std::atomic_bool is_released_{false}; 52 | }; 53 | 54 | /// \brief Function that calls Blocker::release() whenever the thread exits. Useful when testing uncontrolled exits from 55 | /// the thread, e.g exceptions. 56 | inline void unblockOnThreadExit(Blocker& blocker) 57 | { 58 | class Unblocker 59 | { 60 | public: 61 | explicit Unblocker(Blocker& blocker) : blocker_{blocker} 62 | {} 63 | 64 | ~Unblocker() 65 | { 66 | blocker_.release(); 67 | } 68 | 69 | private: 70 | Blocker& blocker_; 71 | }; 72 | 73 | thread_local Unblocker unblocker(blocker); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Generated from CLion C/C++ Code Style settings 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: None 6 | AlignOperands: Align 7 | AllowAllArgumentsOnNextLine: false 8 | AllowAllConstructorInitializersOnNextLine: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: Always 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Always 14 | AllowShortLambdasOnASingleLine: All 15 | AllowShortLoopsOnASingleLine: true 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakTemplateDeclarations: Yes 18 | BreakBeforeBraces: Custom 19 | BraceWrapping: 20 | AfterCaseLabel: false 21 | AfterClass: true 22 | AfterControlStatement: Always 23 | AfterEnum: true 24 | AfterFunction: true 25 | AfterNamespace: true 26 | AfterUnion: true 27 | BeforeCatch: false 28 | BeforeElse: true 29 | IndentBraces: false 30 | SplitEmptyFunction: false 31 | SplitEmptyRecord: true 32 | BreakBeforeBinaryOperators: None 33 | BreakBeforeTernaryOperators: true 34 | BreakConstructorInitializers: BeforeComma 35 | BreakInheritanceList: BeforeColon 36 | ColumnLimit: 0 37 | CompactNamespaces: false 38 | ContinuationIndentWidth: 2 39 | IndentCaseLabels: true 40 | IndentPPDirectives: None 41 | IndentWidth: 2 42 | KeepEmptyLinesAtTheStartOfBlocks: true 43 | MaxEmptyLinesToKeep: 2 44 | NamespaceIndentation: None 45 | ObjCSpaceAfterProperty: false 46 | ObjCSpaceBeforeProtocolList: true 47 | PointerAlignment: Left 48 | ReflowComments: false 49 | SpaceAfterCStyleCast: true 50 | SpaceAfterLogicalNot: false 51 | SpaceAfterTemplateKeyword: false 52 | SpaceBeforeAssignmentOperators: true 53 | SpaceBeforeCpp11BracedList: false 54 | SpaceBeforeCtorInitializerColon: true 55 | SpaceBeforeInheritanceColon: true 56 | SpaceBeforeParens: ControlStatements 57 | SpaceBeforeRangeBasedForLoopColon: false 58 | SpaceInEmptyParentheses: false 59 | SpacesBeforeTrailingComments: 0 60 | SpacesInAngles: false 61 | SpacesInCStyleCastParentheses: false 62 | SpacesInContainerLiterals: false 63 | SpacesInParentheses: false 64 | SpacesInSquareBrackets: false 65 | TabWidth: 2 66 | UseTab: Never 67 | -------------------------------------------------------------------------------- /loader/src/proxel_library.cpp: -------------------------------------------------------------------------------- 1 | #include "superflow/loader/proxel_library.h" 2 | #include "boost/dll/library_info.hpp" 3 | 4 | namespace flow::load 5 | { 6 | 7 | ProxelLibrary::ProxelLibrary(const boost::dll::fs::path& path_to_directory, const std::string& library_name) 8 | : ProxelLibrary{ 9 | path_to_directory / library_name, 10 | boost::dll::load_mode::append_decorations | boost::dll::load_mode::rtld_now} 11 | {} 12 | 13 | ProxelLibrary::ProxelLibrary(const boost::dll::fs::path& full_path) 14 | : ProxelLibrary{ 15 | full_path, 16 | boost::dll::load_mode::rtld_now} 17 | {} 18 | 19 | ProxelLibrary::ProxelLibrary(const boost::dll::fs::path& path, boost::dll::load_mode::type load_mode) 20 | try 21 | : library_{ 22 | path, 23 | load_mode} 24 | {} 25 | catch (boost::system::system_error& e) 26 | { 27 | const auto error_code = e.code(); 28 | const auto error_code_value = std::to_string(error_code.value()); 29 | const std::string error_code_category_name{error_code.category().name()}; 30 | const auto error_code_message = error_code.message(); 31 | const std::string what = 32 | "Exception occurred in constructor 'ProxelLibrary(\"" + path.string() + "\")':\n " 33 | + "error_code " + error_code_value + " (" + error_code_category_name + ") message: " + error_code_message + ". What:\n " 34 | + e.what(); 35 | 36 | throw std::invalid_argument(what); 37 | } 38 | catch (std::ios_base::failure& e) 39 | { 40 | throw std::invalid_argument( 41 | "Exception occurred in constructor 'ProxelLibrary(\"" + path.string() + "\")', probably due to non-existing path.\n " + 42 | e.what()); 43 | } 44 | 45 | std::vector ProxelLibrary::getSectionSymbols(const std::string& adapter_name) const 46 | { 47 | boost::dll::library_info info(library_.location()); 48 | std::vector symbols = info.symbols(adapter_name); 49 | if (symbols.empty()) 50 | { 51 | throw std::invalid_argument( 52 | "no section '" + adapter_name + "' found in library, or no proxel factories in in it." 53 | ); 54 | } 55 | return symbols; 56 | } 57 | 58 | std::string ProxelLibrary::extractFactoryName(const std::string& symbol, const std::string& adapter_name) 59 | { 60 | return symbol.substr(adapter_name.size() + 1); 61 | } 62 | } -------------------------------------------------------------------------------- /cmake/config.cmake.in: -------------------------------------------------------------------------------- 1 | # This file is autogenerated at the call of `make` or `make install` of superflow. 2 | # Use it when you want to `find_package(superflow CONFIG)`. 3 | # 4 | # This file will set the following variables for you to use: 5 | # superflow_FOUND 6 | # superflow_VERSION 7 | # superflow_LIBS 8 | # superflow_LIBRARIES 9 | # 10 | # For each module that was enabled at the call of `make install`, a variable 11 | # superflow__FOUND will be set to TRUE, so that you may use 12 | # find_package(superflow COMPONENTS ). 13 | # 14 | # If COMPONENTS is specified, 15 | # superflow_LIBS will be a list of the targets corresponding to the requested components. 16 | # Else, 17 | # superflow_LIBS will be a list of all compiled superflow targets. 18 | # In any case, 19 | # superflow::core will be included in superflow_LIBS. 20 | # 21 | # superflow_LIBRARIES is exactly the same as superflow_LIBS. 22 | # 23 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 24 | 25 | @PACKAGE_INIT@ 26 | include("${CMAKE_CURRENT_LIST_DIR}/superflow-targets.cmake") 27 | 28 | message(STATUS "* Loading @PROJECT_NAME@ v${@PROJECT_NAME@_VERSION}: " "${CMAKE_CURRENT_LIST_FILE}") 29 | 30 | list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}) 31 | include(CMakeFindDependencyMacro) 32 | 33 | if(superflow_FIND_COMPONENTS) 34 | set(requested_components core;${superflow_FIND_COMPONENTS}) 35 | #string(REGEX REPLACE "([a-z]+)(;|$)" "superflow-\\1\\2" requested_components "${requested_components}") 36 | else() 37 | set(requested_components @built_components@) 38 | string(REGEX REPLACE "superflow-([a-z]+)(;|$)" "\\1\\2" requested_components "${requested_components}") 39 | endif() 40 | list(REMOVE_DUPLICATES requested_components) 41 | 42 | string(REPLACE ";" " " spaced_components "${requested_components}") 43 | message(STATUS "* Requested superflow components: ${spaced_components}") 44 | 45 | foreach(comp IN LISTS requested_components) 46 | find_dependency(superflow-${comp}) 47 | list(APPEND superflow_LIBS "superflow::${comp}") 48 | endforeach() 49 | 50 | set(superflow_LIBRARIES ${superflow_LIBS}) 51 | 52 | check_required_components(superflow) 53 | message(STATUS "* Found @PROJECT_NAME@ v${@PROJECT_NAME@_VERSION} ${spaced_components}") 54 | -------------------------------------------------------------------------------- /core/test/test_metronome.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | #include "superflow/utils/metronome.h" 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include 7 | 8 | using namespace std::chrono_literals; 9 | 10 | TEST(Metronome, check) 11 | { 12 | { 13 | std::promise promise; 14 | std::atomic_bool promise_isset{false}; 15 | 16 | flow::Metronome non_crashing_repeater{ 17 | [&promise,&promise_isset](const auto) 18 | { 19 | if (promise_isset) 20 | { return ; } 21 | 22 | promise.set_value(); 23 | promise_isset = true; 24 | }, 25 | 1us 26 | }; 27 | 28 | const auto future_status = promise.get_future().wait_for(1s); 29 | ASSERT_NE(future_status, std::future_status::timeout); 30 | 31 | EXPECT_NO_THROW(non_crashing_repeater.check()); 32 | } 33 | 34 | { 35 | std::promise promise; 36 | flow::Metronome crashing_repeater{ 37 | [&promise](const auto) 38 | { 39 | promise.set_value(); 40 | throw std::runtime_error{"error"}; 41 | }, 42 | std::chrono::microseconds{1} 43 | }; 44 | 45 | const auto future_status = promise.get_future().wait_for(1s); 46 | ASSERT_NE(future_status, std::future_status::timeout); 47 | std::this_thread::sleep_for(10ms); 48 | 49 | EXPECT_THROW(crashing_repeater.check(), std::runtime_error); 50 | } 51 | } 52 | 53 | TEST(Metronome, get) 54 | { 55 | { 56 | std::promise promise; 57 | 58 | flow::Metronome metronome{ 59 | [&promise](const auto) 60 | { 61 | static bool once = std::invoke([&promise]{ promise.set_value(); return true; }); 62 | }, 63 | std::chrono::microseconds{1} 64 | }; 65 | 66 | const auto future_status = promise.get_future().wait_for(1s); 67 | ASSERT_NE(future_status, std::future_status::timeout); 68 | 69 | metronome.stop(); 70 | EXPECT_NO_THROW(metronome.get()); 71 | EXPECT_THROW(metronome.get(), std::runtime_error); 72 | } 73 | } 74 | 75 | TEST(Metronome, stop) 76 | { 77 | { 78 | flow::Metronome metronome{ 79 | [](auto){}, 80 | 1us 81 | }; 82 | 83 | metronome.stop(); 84 | EXPECT_NO_THROW(metronome.get()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /core/include/superflow/utils/pimpl_h.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | // https://herbsutter.com/gotw/_101/ 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | /// \brief Helper class for the pimpl pattern. 11 | /// The code is taken from Herb Sutter's 12 | /// GotW#101, with slight modifications. 13 | /// Read GotW#100for good practices and more about the Pimpl Idiom. 14 | ///
The \b pimpl_h.h file should be included from the header file of the class owning the pimpl object. 15 | ///
The \b pimpl_impl.h sould be included from the source file (cpp). Remember the explicit template instantiation! 16 | /// 17 | /// \b my_class.h 18 | /// \code{.cpp} 19 | /// #pragma once 20 | /// #include "superflow/utils/pimpl_h.h" 21 | /// class MyClass 22 | /// { 23 | /// // ... 24 | /// private: 25 | /// class impl; // forward declare 26 | /// pimpl m_; // instead of std::unique_ptr 27 | /// // ... 28 | /// }; 29 | /// \endcode 30 | /// \b my_class.cpp 31 | /// \code{.cpp} 32 | /// #include "mylib/my_class.h" 33 | /// #include "superflow/utils/pimpl_impl.h" 34 | /// 35 | /// // Easy to forget, but strictly required for the code to compile 36 | /// template class flow::pimpl; 37 | /// 38 | /// // The impl. 39 | /// class MyClass::impl 40 | /// { 41 | /// impl(impl ctor arguments); 42 | /// void func(); 43 | /// }; 44 | /// 45 | /// MyClass::MyClass(ctor args) 46 | /// : m_{impl ctor arguments} // instead of std::make_unique 47 | /// { 48 | /// m_->func(); // Access the impl 49 | /// } 50 | /// \endcode 51 | /// \tparam T The (typically private inner) class to be pimp'ed. 52 | /// \see https://herbsutter.com/gotw/_100/ 53 | /// \see https://herbsutter.com/gotw/_101/ 54 | /// \see https://stackoverflow.com/questions/8595471/does-the-gotw-101-solution-actually-solve-anything 55 | template 56 | class pimpl 57 | { 58 | private: 59 | std::unique_ptr m; 60 | public: 61 | 62 | template 63 | pimpl(Args&& ...); 64 | 65 | ~pimpl(); 66 | 67 | pimpl& operator=(pimpl&&) noexcept; 68 | 69 | T* operator->() const; 70 | 71 | T& operator*() const; 72 | }; 73 | } -------------------------------------------------------------------------------- /core/test/test_proxel_status.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/proxel.h" 3 | 4 | #include "gtest/gtest.h" 5 | 6 | using namespace flow; 7 | 8 | class MyProxel : public Proxel 9 | { 10 | public: 11 | MyProxel() = default; 12 | 13 | explicit MyProxel(const State state) 14 | { 15 | setState(state); 16 | }; 17 | 18 | explicit MyProxel(const std::string& status_info) 19 | { 20 | setStatusInfo(status_info); 21 | }; 22 | 23 | ~MyProxel() override = default; 24 | 25 | void start() override {}; 26 | 27 | void stop() noexcept override {}; 28 | 29 | void pushState(const State state) 30 | { 31 | setState(state); 32 | } 33 | 34 | void pushStatusInfo(const std::string& status_info) 35 | { 36 | setStatusInfo(status_info); 37 | } 38 | }; 39 | 40 | TEST(ProxelStatus, default_state_is_undefined) 41 | { 42 | MyProxel proxel; 43 | ASSERT_EQ(proxel.getStatus().state, ProxelStatus::State::Undefined); 44 | } 45 | 46 | TEST(ProxelStatus, setState_works) 47 | { 48 | MyProxel proxel; 49 | 50 | proxel.pushState(ProxelStatus::State::Running); 51 | ASSERT_EQ(proxel.getStatus().state, ProxelStatus::State::Running); 52 | } 53 | 54 | TEST(ProxelStatus, setState_works_from_ctor) 55 | { 56 | MyProxel proxel{ProxelStatus::State::Paused}; 57 | ASSERT_EQ(proxel.getStatus().state, ProxelStatus::State::Paused); 58 | 59 | proxel.pushState(ProxelStatus::State::Running); 60 | ASSERT_EQ(proxel.getStatus().state, ProxelStatus::State::Running); 61 | } 62 | 63 | TEST(ProxelStatus, default_status_info_is_empty) 64 | { 65 | MyProxel proxel; 66 | ASSERT_EQ(proxel.getStatus().info, ""); 67 | } 68 | 69 | TEST(ProxelStatus, setStatusInfo_works) 70 | { 71 | MyProxel proxel; 72 | 73 | const std::string info = "hallo"; 74 | proxel.pushStatusInfo(info); 75 | ASSERT_EQ(proxel.getStatus().info, info); 76 | } 77 | 78 | TEST(ProxelStatus, setStatusInfo_works_from_ctor) 79 | { 80 | const std::string orig_info = "hallo"; 81 | MyProxel proxel{orig_info}; 82 | ASSERT_EQ(proxel.getStatus().info, orig_info); 83 | 84 | proxel.pushState(ProxelStatus::State::Running); 85 | const std::string other_info = "heihei"; 86 | proxel.pushStatusInfo(other_info); 87 | ASSERT_EQ(proxel.getStatus().info, other_info); 88 | } 89 | -------------------------------------------------------------------------------- /core/include/superflow/utils/graphviz.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "superflow/connection_spec.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template<> 11 | struct std::hash 12 | { 13 | std::size_t operator()(const flow::ConnectionSpec& c) const noexcept 14 | { 15 | std::size_t h1 = std::hash{}(c.lhs_name); 16 | std::size_t h2 = std::hash{}(c.lhs_port); 17 | std::size_t h3 = std::hash{}(c.rhs_name); 18 | std::size_t h4 = std::hash{}(c.rhs_port); 19 | return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3); 20 | } 21 | }; 22 | 23 | template<> 24 | struct std::equal_to 25 | { 26 | bool operator()( 27 | const flow::ConnectionSpec& lhs, 28 | const flow::ConnectionSpec& rhs 29 | ) const 30 | { 31 | return 32 | lhs.lhs_name == rhs.lhs_name && 33 | lhs.lhs_port == rhs.lhs_port && 34 | lhs.rhs_name == rhs.rhs_name && 35 | lhs.rhs_port == rhs.rhs_port; 36 | } 37 | }; 38 | 39 | namespace flow 40 | { 41 | /// \brief Parse ConnectionSpec%s to create graphviz source code for rendering with dot 42 | /// 43 | /// \code{.cpp} 44 | /// std::ofstream{"./superflow.gv"} << flow::yaml::generateDOTFile(config_file_path) << std::endl; 45 | /// \endcode 46 | /// 47 | /// \code{.sh} 48 | /// dot -Tsvg -o graph.svg superflow.gv 49 | /// \endcode 50 | class GraphViz 51 | { 52 | public: 53 | /// Build the internal data structure parsed from the ConnectionSpec%s. 54 | /// 55 | /// We also support dummy ConnectionSpec%s like {proxel_name,{},{},{}}, 56 | /// so that unconnected proxels can also be rendered. 57 | /// \param connections 58 | GraphViz( 59 | const std::vector& connections 60 | ); 61 | 62 | /// Render the DOT-file source code 63 | std::string employ() const; 64 | 65 | private: 66 | using AdjacencyList = std::unordered_set; 67 | 68 | struct ProxelMeta 69 | { 70 | AdjacencyList adjacency_list; 71 | std::set lhs_ports; 72 | std::set rhs_ports; 73 | }; 74 | 75 | std::map node_list; 76 | 77 | void insert(const ConnectionSpec& connection); 78 | std::string getNodeDefinitions() const; 79 | std::string getNodeConnections() const; 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /core/include/superflow/proxel.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/port.h" 5 | #include "superflow/port_manager.h" 6 | #include "superflow/proxel_status.h" 7 | #include "superflow/utils/mutexed.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace flow 13 | { 14 | /// \brief Abstract class for Processing Element 15 | /// 16 | /// A Proxel, short for processing element, is an isolated "black box" 17 | /// responsible for doing some kind of data manipulation. 18 | /// Proxel is in fact just an interface defining a handful of required methods. 19 | /// Besides that, a class extending the Proxel interface may do whatever 20 | /// the developer decides. The recommended way to create a Proxel is to develop 21 | /// a processing algorithms independently as an external library, 22 | /// and then embed the algorithm into Superflow by wrapping it in a Proxel. 23 | class Proxel 24 | { 25 | public: 26 | using Ptr = std::shared_ptr; 27 | using ConstPtr = std::shared_ptr; 28 | 29 | virtual ~Proxel() = default; 30 | 31 | /// \brief Method is expected to prepare the Proxel for processing, 32 | /// and make it listen to it's input ports. 33 | virtual void start() = 0; 34 | 35 | /// \brief Method is expected to make the Proxel stop processing and 36 | /// thereby stop producing outputs. If the Proxel is designed to have 37 | /// start() called by an external thread, stop() is expected to make that 38 | /// thread return. 39 | virtual void stop() noexcept = 0; 40 | 41 | /// \brief Request a pointer to a Proxel's port by a given name. 42 | /// \param name The unique name of the port 43 | /// \return 44 | const Port::Ptr& getPort(const std::string& name) const; 45 | 46 | const PortManager::PortMap& getPorts() const; 47 | 48 | /// \brief Request the current status of the Proxel. 49 | /// \return 50 | ProxelStatus getStatus() const; 51 | 52 | protected: 53 | using State = ProxelStatus::State; 54 | 55 | void setState(State state) const; 56 | 57 | void setStatusInfo(const std::string& status_info) const; 58 | 59 | void registerPorts(PortManager::PortMap&& ports); 60 | 61 | private: 62 | mutable std::atomic state_ = State::Undefined; 63 | mutable Mutexed status_info_; 64 | PortManager port_manager_; 65 | 66 | State getState() const; 67 | 68 | std::string getStatusInfo() const; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /core/include/superflow/mapped_asset_manager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace flow 10 | { 11 | template 12 | class MappedAssetManager 13 | { 14 | public: 15 | void put(const K& key, const V& value); 16 | 17 | V get(const K& key) const; 18 | 19 | bool has(const K& key) const; 20 | 21 | void erase(const K& key); 22 | 23 | void clear(); 24 | 25 | std::vector getAll() const; 26 | 27 | private: 28 | mutable std::mutex mutex_; 29 | std::map values_; 30 | }; 31 | 32 | // ----- Implementation ----- 33 | template 34 | void MappedAssetManager::put(const K& key, const V& value) 35 | { 36 | std::lock_guard lock{mutex_}; 37 | 38 | if (values_.find(key) != values_.end()) 39 | { 40 | return; 41 | } 42 | 43 | values_[key] = value; 44 | } 45 | 46 | template 47 | V MappedAssetManager::get(const K& key) const 48 | { 49 | V v; 50 | 51 | { 52 | std::lock_guard lock{mutex_}; 53 | 54 | auto it = values_.find(key); 55 | 56 | if (it == values_.end()) 57 | { 58 | throw std::invalid_argument("Attempted accessing non-existing element"); 59 | } 60 | 61 | v = it->second; 62 | } 63 | 64 | return v; 65 | } 66 | 67 | template 68 | bool MappedAssetManager::has(const K& key) const 69 | { 70 | std::lock_guard lock{mutex_}; 71 | 72 | return values_.find(key) != values_.end(); 73 | } 74 | 75 | template 76 | void MappedAssetManager::erase(const K& key) 77 | { 78 | std::lock_guard lock{mutex_}; 79 | 80 | auto it = values_.find(key); 81 | 82 | if (it == values_.end()) 83 | { 84 | return; 85 | } 86 | 87 | values_.erase(it); 88 | } 89 | 90 | template 91 | void MappedAssetManager::clear() 92 | { 93 | std::lock_guard lock{mutex_}; 94 | 95 | values_.clear(); 96 | } 97 | 98 | template 99 | std::vector MappedAssetManager::getAll() const 100 | { 101 | std::lock_guard lock{mutex_}; 102 | 103 | std::vector values; 104 | 105 | for (const auto& kv : values_) 106 | { 107 | values.push_back(kv.second); 108 | } 109 | 110 | return values; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /core/test/test_proxel_timer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/utils/proxel_timer.h" 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include 7 | #include 8 | 9 | using namespace flow; 10 | 11 | TEST(ProxelTimer, stop_before_start_throws) 12 | { 13 | ProxelTimer timer; 14 | ASSERT_THROW(timer.stop(), std::runtime_error); 15 | } 16 | 17 | TEST(ProxelTimer, start_before_stop_doesnt_throw) 18 | { 19 | ProxelTimer timer; 20 | timer.start(); 21 | ASSERT_NO_THROW(timer.stop()); 22 | } 23 | 24 | TEST(ProxelTimer, peek) 25 | { 26 | ProxelTimer timer; 27 | timer.start(); 28 | double elapsed_time; 29 | ASSERT_NO_THROW(elapsed_time = timer.peek()); 30 | ASSERT_GT(elapsed_time, 0.); 31 | } 32 | 33 | TEST(ProxelTimer, get_run_count) 34 | { 35 | using namespace std::chrono_literals; 36 | { 37 | ProxelTimer timer; 38 | timer.start(); 39 | timer.stop(); 40 | timer.start(); 41 | timer.stop(); 42 | 43 | EXPECT_EQ(2, timer.getRunCount()); 44 | } 45 | } 46 | 47 | TEST(ProxelTimer, get_busyness) 48 | { 49 | using namespace std::chrono_literals; 50 | { 51 | ProxelTimer timer; 52 | timer.start(); 53 | timer.stop(); 54 | 55 | EXPECT_EQ(1, timer.getAverageBusyness()); 56 | } 57 | 58 | { 59 | ProxelTimer timer; 60 | 61 | timer.start(); 62 | std::this_thread::sleep_for(100us); 63 | timer.stop(); 64 | 65 | std::this_thread::sleep_for(10us); 66 | 67 | timer.start(); 68 | std::this_thread::sleep_for(100us); 69 | timer.stop(); 70 | 71 | EXPECT_LE(0.1, timer.getAverageBusyness()); 72 | } 73 | } 74 | 75 | TEST(ProxelTimer, get_average_processing_time) 76 | { 77 | using namespace std::chrono_literals; 78 | 79 | { 80 | ProxelTimer timer; 81 | timer.start(); 82 | std::this_thread::sleep_for(1ms); 83 | const auto total = timer.stop(); 84 | const auto average = timer.getAverageProcessingTime(); 85 | 86 | EXPECT_EQ(total, average); 87 | } 88 | { 89 | ProxelTimer timer; 90 | timer.start(); 91 | std::this_thread::sleep_for(1ms); 92 | auto total = timer.stop(); 93 | timer.start(); 94 | total += timer.stop(); 95 | 96 | const auto average = timer.getAverageProcessingTime(); 97 | EXPECT_EQ(total/timer.getRunCount(), average); 98 | } 99 | } 100 | 101 | TEST(ProxelTimer, get_status_info) 102 | { 103 | ProxelTimer timer; 104 | std::string info; 105 | EXPECT_NO_FATAL_FAILURE(info = timer.getStatusInfo()); 106 | EXPECT_EQ(info.back(), '0'); 107 | } 108 | -------------------------------------------------------------------------------- /core/include/superflow/utils/proxel_timer.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/proxel_status.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace flow 12 | { 13 | /// \brief Utility class for measuring the workload of a Proxel. 14 | /// 15 | /// Typical usage: 16 | /// \code{.cpp} 17 | /// flow::ProxelTimer proxel_timer_; 18 | /// // ... 19 | /// for (const auto& data: *input_port_) 20 | /// { 21 | /// setState(State::Running); 22 | /// proxel_timer_.start(); 23 | /// // work ... 24 | /// proxel_timer_.stop(); 25 | /// setStatusInfo(proxel_timer_.getStatusInfo()); 26 | /// } 27 | /// \endcode 28 | class ProxelTimer 29 | { 30 | public: 31 | /// \brief Start the timer 32 | void start(); 33 | 34 | /// \brief Stop the timer 35 | /// \return elapsed time since start (in seconds) 36 | double stop(); 37 | 38 | /// \brief Read time since start without stopping the timer 39 | /// \return elapsed time since start (in seconds) 40 | [[nodiscard]] double peek() const; 41 | 42 | /// \brief Get time from start to stop, averaged over all starts and stops. 43 | /// Measured in seconds. 44 | /// \return average processing time (in seconds) 45 | [[nodiscard]] double getAverageProcessingTime() const; 46 | 47 | /// \brief Get the ratio of processing vs idle state. 48 | /// 49 | /// A value of 1 is max busyness and means no idle time. 50 | /// A value of 0 means no processing at all, only idle time. 51 | /// 52 | /// Computed as summed processing time divided by time since start was called for the first time.
53 | /// \f[ Busyness = \frac{\sum_{i} {stop_i-start_i}}{stop_i-start_0} \f] 54 | /// \return average busy ratio 55 | [[nodiscard]] double getAverageBusyness() const; 56 | 57 | /// \brief Read how many times the timer has been stopped. 58 | /// \return run count 59 | [[nodiscard]] unsigned long long getRunCount() const; 60 | 61 | /// \brief Create a formatted string containing average processing time and average busyness. 62 | /// \return The formatted string. 63 | [[nodiscard]] std::string getStatusInfo() const; 64 | 65 | private: 66 | using Clock = std::chrono::high_resolution_clock; 67 | using Duration = std::chrono::duration; 68 | 69 | std::once_flag first_flag_; 70 | Clock::time_point first_time_point_; 71 | 72 | std::atomic run_counter_{0}; 73 | double summed_processing_time_{0}; 74 | 75 | std::atomic mean_processing_time_{0}; 76 | std::atomic mean_busyness_time_{0}; 77 | 78 | Clock::time_point start_; 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /core/test/test_port_manager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/port_manager.h" 3 | #include "superflow/requester_port.h" 4 | 5 | #include "connectable_port.h" 6 | 7 | #include "gtest/gtest.h" 8 | 9 | using namespace flow; 10 | 11 | TEST(PortManager, throwsIfPortDoesNotExistWhenEmpty) 12 | { 13 | PortManager manager; 14 | Port::Ptr result; 15 | ASSERT_THROW(result = manager.get("does not exist"), std::invalid_argument); 16 | } 17 | 18 | TEST(PortManager, throwsIfPortDoesNotExistWhenNonEmpty) 19 | { 20 | PortManager manager{{ 21 | {"foo", nullptr} 22 | }}; 23 | 24 | Port::Ptr result; 25 | ASSERT_THROW(result = manager.get("does not exist"), std::invalid_argument); 26 | } 27 | 28 | TEST(PortManager, returnsCorrectPort) 29 | { 30 | Port::Ptr some_port = std::make_shared>(); 31 | Port::Ptr some_other_port = std::make_shared>(); 32 | 33 | PortManager manager{{ 34 | {"foo", some_port}, 35 | {"bar", some_other_port}, 36 | {"baz", std::make_shared>()} 37 | }}; 38 | 39 | Port::Ptr result; 40 | ASSERT_THROW(result = manager.get("does not exist"), std::invalid_argument); 41 | ASSERT_FALSE(some_port == some_other_port); 42 | ASSERT_EQ(manager.get("foo"), some_port); 43 | ASSERT_EQ(manager.get("bar"), some_other_port); 44 | } 45 | 46 | TEST(PortManager, dtorDisconnectsPorts) 47 | { 48 | constexpr size_t num_ports = 10; 49 | std::map>> ports; 50 | 51 | for (size_t i = 0; i < num_ports; ++i) 52 | { 53 | std::ostringstream ss; 54 | ss << "port_" << i; 55 | 56 | const std::string port_id = ss.str(); 57 | 58 | ports[port_id] = std::make_shared>(); 59 | ASSERT_FALSE(ports[port_id]->did_get_disconnect); 60 | } 61 | 62 | { 63 | PortManager manager{{ports.begin(), ports.end()}}; 64 | 65 | for (const auto& kv : ports) 66 | { 67 | ASSERT_FALSE(kv.second->did_get_disconnect); 68 | } 69 | } 70 | 71 | for (const auto& kv : ports) 72 | { 73 | ASSERT_TRUE(kv.second->did_get_disconnect); 74 | } 75 | } 76 | 77 | TEST(PortManager, getStatusHandlesNullptr) 78 | { 79 | PortManager manager{{{}}}; 80 | ASSERT_EQ(manager.getPorts().size(), 1); 81 | 82 | const auto& port_ptr = manager.get(""); 83 | ASSERT_EQ(port_ptr, nullptr); 84 | 85 | ASSERT_NO_FATAL_FAILURE(std::ignore = manager.getStatus()); 86 | 87 | const auto statuses = manager.getStatus(); 88 | ASSERT_EQ(statuses.size(), 0); 89 | } 90 | 91 | -------------------------------------------------------------------------------- /core/include/superflow/factory_map.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/factory.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace flow 10 | { 11 | /// \brief Container for mapping a Proxel type to its respective Factory 12 | /// \tparam PropertyList 13 | template 14 | class FactoryMap 15 | { 16 | public: 17 | FactoryMap() = default; 18 | 19 | FactoryMap(FactoryMap&&) noexcept = default; 20 | 21 | FactoryMap(const FactoryMap&) = default; 22 | 23 | FactoryMap& operator=(FactoryMap&&) noexcept = default; 24 | 25 | FactoryMap& operator=(const FactoryMap&) = default; 26 | 27 | /// \brief Create a new FactoryMap 28 | /// \param factories mapping between proxel type and factory. 29 | explicit FactoryMap(const std::map>& factories); 30 | 31 | explicit FactoryMap(std::map>&& factories); 32 | 33 | /// \brief Get Factory for the given Proxel type. 34 | /// \param type The name of the Proxel type 35 | /// \return The Proxel type's Factory 36 | const Factory& get(const std::string& type) const; 37 | 38 | /// \brief Concatenate two FactoryMaps 39 | /// \param other the FactoryMap to combine with the current 40 | /// \return A new, concatenated FactoryMap 41 | FactoryMap operator+(const FactoryMap& other) const; 42 | void operator+=(const FactoryMap& other); 43 | 44 | [[nodiscard]] bool empty() const { return factories_.empty(); } 45 | 46 | private: 47 | std::map> factories_; 48 | }; 49 | 50 | // ----- Implementation ----- 51 | 52 | template 53 | FactoryMap::FactoryMap(const std::map>& factories) 54 | : factories_{factories} 55 | {} 56 | 57 | template 58 | FactoryMap::FactoryMap(std::map>&& factories) 59 | : factories_{std::move(factories)} 60 | {} 61 | 62 | template 63 | const Factory& FactoryMap::get(const std::string& type) const 64 | { 65 | try 66 | { return factories_.at(type); } 67 | catch (std::exception&) 68 | { throw std::invalid_argument({"FactoryMap failed to load factory '" + type + "'"}); } 69 | } 70 | 71 | template 72 | FactoryMap FactoryMap::operator+(const FactoryMap& other) const 73 | { 74 | std::map> sum{factories_.begin(), factories_.end()}; 75 | 76 | sum.insert(other.factories_.begin(), other.factories_.end()); 77 | 78 | return FactoryMap{std::move(sum)}; 79 | } 80 | 81 | template 82 | void FactoryMap::operator+=(const FactoryMap& other) 83 | { 84 | factories_.insert(other.factories_.begin(), other.factories_.end()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /core/include/superflow/consumer_port.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/port.h" 5 | 6 | #include 7 | 8 | namespace flow 9 | { 10 | namespace detail 11 | { 12 | template 13 | class Consumer 14 | { 15 | public: 16 | using Ptr = std::shared_ptr; 17 | using Type = T; 18 | 19 | virtual ~Consumer() = default; 20 | 21 | /// \brief Function to be called by ProducerPort in order to send data to the ConsumerPort 22 | /// \param data The data sent by ProducerPort 23 | /// \param port Pointer to the ProducerPort sending data 24 | virtual void receive(const T& data, const Port::Ptr& port) = 0; 25 | }; 26 | 27 | template 28 | class ConsumerVariant 29 | : protected virtual Consumer 30 | , public Consumer 31 | { 32 | public: 33 | static_assert( 34 | std::is_convertible_v || std::is_constructible_v, 35 | "Cannot create a ConsumerVariant with the Variant and Base types specified in your ConsumerPort because const Variant& is not convertible to const Base&." 36 | ); 37 | 38 | void receive(const Variant& data, const Port::Ptr& port) final 39 | { 40 | using BaseConsumer = Consumer; 41 | if constexpr (std::is_convertible_v) 42 | { 43 | static_cast(*this).receive(static_cast(data), port); 44 | } 45 | else 46 | { 47 | // Since const Variant& is not convertible to const Base&, Base must be 48 | // constructible from const Variant&. This is less efficient than casting refs. 49 | 50 | static_cast(*this).receive(static_cast(data), port); 51 | } 52 | } 53 | }; 54 | } 55 | 56 | /// \brief Interface for input ports able to connect with ProducerPort. 57 | /// \tparam T The base type (e.g. "output type") of data of the consumer. 58 | /// \tparam Variants... Optional upstream variant types of T which are also to be accepted by 59 | /// the consumer. Each such `Variants` must be const ref convertible to `T`, that is 60 | /// `const Variant&` must [be convertible](https://en.cppreference.com/w/cpp/types/is_convertible) 61 | /// to `const T&`. This is also useful for allowing upcasts from a derived class 62 | /// to a parent baseclass. 63 | /// \see ProducerPort 64 | /// \see BufferedConsumerPort 65 | /// \see CallbackConsumerPort 66 | /// \see MultiConsumerPort 67 | template 68 | class ConsumerPort : 69 | public Port, 70 | public virtual detail::Consumer, 71 | public detail::ConsumerVariant... 72 | { 73 | public: 74 | using Ptr = std::shared_ptr; 75 | }; 76 | 77 | /// \brief Partial template specialization for the case when `T` is a [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant). 78 | /// For further details, \see ConsumerPort. 79 | template 80 | class ConsumerPort> : 81 | public ConsumerPort, Variants...> 82 | {}; 83 | } 84 | -------------------------------------------------------------------------------- /core/test/test_interface_port.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/interface_port.h" 3 | 4 | #include "gtest/gtest.h" 5 | 6 | using namespace flow; 7 | 8 | class MyInterface 9 | { 10 | public: 11 | virtual int foo(int bar) const = 0; 12 | }; 13 | 14 | class MyHost : public MyInterface 15 | { 16 | public: 17 | MyHost() 18 | : port{std::make_shared::Host>(*this)} 19 | {} 20 | 21 | int foo(const int bar) const override 22 | { 23 | return 2*bar; 24 | } 25 | 26 | InterfacePort::Host::Ptr port; 27 | }; 28 | 29 | TEST(InterfacePort, happy_path) 30 | { 31 | using Client = InterfacePort::Client; 32 | 33 | MyHost host; 34 | const auto client = std::make_shared(); 35 | 36 | client->connect(host.port); 37 | 38 | ASSERT_EQ(2, client->get().foo(1)); 39 | ASSERT_EQ(42, client->get().foo(21)); 40 | } 41 | 42 | TEST(InterfacePort, isConnected) 43 | { 44 | using Client = InterfacePort::Client; 45 | 46 | MyHost host; 47 | const auto client = std::make_shared(); 48 | 49 | EXPECT_FALSE(host.port->isConnected()); 50 | EXPECT_FALSE(client->isConnected()); 51 | 52 | client->connect(host.port); 53 | 54 | EXPECT_TRUE(host.port->isConnected()); 55 | EXPECT_TRUE(client->isConnected()); 56 | } 57 | 58 | TEST(InterfacePort, getThrowsIfNotConnected) 59 | { 60 | using Client = InterfacePort::Client; 61 | 62 | MyHost host; 63 | const auto client = std::make_shared(); 64 | 65 | ASSERT_FALSE(host.port->isConnected()); 66 | ASSERT_FALSE(client->isConnected()); 67 | 68 | EXPECT_THROW(host.port->get(), std::runtime_error); 69 | EXPECT_THROW(client->get(), std::runtime_error); 70 | 71 | client->connect(host.port); 72 | 73 | EXPECT_NO_THROW(host.port->get()); 74 | EXPECT_NO_THROW(client->get()); 75 | } 76 | 77 | TEST(InterfacePort, num_transactions) 78 | { 79 | using Client = InterfacePort::Client; 80 | 81 | MyHost my_host; 82 | const auto& host = my_host.port; 83 | const auto client = std::make_shared(); 84 | 85 | ASSERT_NO_FATAL_FAILURE(client->connect(host)); 86 | ASSERT_EQ(0, host->getStatus().num_transactions); 87 | ASSERT_EQ(0, client->getStatus().num_transactions); 88 | 89 | ASSERT_EQ(2, client->get().foo(1)); 90 | 91 | EXPECT_EQ(1, host->getStatus().num_transactions); 92 | EXPECT_EQ(1, client->getStatus().num_transactions); 93 | 94 | ASSERT_NO_FATAL_FAILURE( client->get()); 95 | 96 | EXPECT_EQ(2, host->getStatus().num_transactions); 97 | EXPECT_EQ(2, client->getStatus().num_transactions); 98 | 99 | client->disconnect(); 100 | EXPECT_THROW(client->get(), std::runtime_error); 101 | EXPECT_EQ(3, client->getStatus().num_transactions); 102 | EXPECT_EQ(2, host->getStatus().num_transactions); 103 | 104 | EXPECT_THROW(host->get(), std::runtime_error); 105 | EXPECT_EQ(3, client->getStatus().num_transactions); 106 | EXPECT_EQ(3, host->getStatus().num_transactions); 107 | } -------------------------------------------------------------------------------- /core/src/graphviz.cpp: -------------------------------------------------------------------------------- 1 | #include "superflow/utils/graphviz.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace flow 9 | { 10 | namespace 11 | { 12 | std::string portFormatting(const std::string& port_name) 13 | { 14 | return "<" + port_name + "> " + port_name; 15 | } 16 | 17 | std::string strToUpper(std::string str) 18 | { 19 | std::transform( 20 | str.begin(), str.end(), str.begin(), 21 | [](unsigned char c) 22 | { return std::toupper(c); } 23 | ); 24 | return str; 25 | } 26 | 27 | std::string join(const std::set& data) 28 | { 29 | if (data.empty()) 30 | { return {}; } 31 | 32 | return std::accumulate( 33 | std::next(data.begin()), data.end(), 34 | portFormatting(*(data.begin())), 35 | [](std::string a, const std::string& b) 36 | { return std::move(a) + "| " + portFormatting(b); } 37 | ); 38 | } 39 | } 40 | 41 | GraphViz::GraphViz( 42 | const std::vector& connections 43 | ) 44 | { 45 | for (const auto& connection : connections) 46 | { 47 | insert(connection); 48 | } 49 | } 50 | 51 | void GraphViz::insert(const ConnectionSpec& connection) 52 | { 53 | if (connection.lhs_name.empty()) 54 | { throw std::invalid_argument("ConnectionSpec must at least have lhs_name."); } 55 | 56 | if (connection.lhs_port.empty() || connection.rhs_name.empty() || connection.rhs_port.empty()) 57 | { 58 | node_list[connection.lhs_name]; 59 | return; 60 | } 61 | 62 | node_list[connection.lhs_name].adjacency_list.insert(connection); 63 | node_list[connection.lhs_name].lhs_ports.insert(connection.lhs_port); 64 | node_list[connection.rhs_name].rhs_ports.insert(connection.rhs_port); 65 | } 66 | 67 | std::string GraphViz::getNodeDefinitions() const 68 | { 69 | std::ostringstream os; 70 | 71 | for (const auto& [node_name, node]: node_list) 72 | { 73 | std::string out = join(node.lhs_ports); 74 | std::string in = join(node.rhs_ports); 75 | os << " " << node_name 76 | << " [label=\"{" 77 | << "{ " << in << "} | " 78 | << strToUpper(node_name) << " | " 79 | << "{ " << out << "} " 80 | << "}\"]\n"; 81 | } 82 | 83 | return os.str(); 84 | } 85 | 86 | std::string GraphViz::getNodeConnections() const 87 | { 88 | std::ostringstream os; 89 | 90 | for (const auto& [node_name, node]: node_list) 91 | { 92 | for (const auto& conn: node.adjacency_list) 93 | { 94 | if (conn.lhs_port.empty() || conn.rhs_name.empty() || conn.rhs_port.empty()) 95 | { continue; } 96 | 97 | os << " " << conn.lhs_name << ":" << conn.lhs_port << " -> " << conn.rhs_name << ":" << conn.rhs_port << "\n"; 98 | } 99 | } 100 | return os.str(); 101 | } 102 | 103 | std::string GraphViz::employ() const 104 | { 105 | std::ostringstream gv; 106 | gv << "digraph superflow {\n"; 107 | gv << " rankdir=\"LR\";\n"; 108 | gv << " node [shape=Mrecord];\n"; 109 | gv << getNodeDefinitions(); 110 | gv << "\n"; 111 | gv << getNodeConnections(); 112 | gv << "}\n"; 113 | 114 | return gv.str(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /core/include/superflow/utils/data_stream.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace flow 9 | { 10 | /// \brief Interface for classes that will continuosly produce data. 11 | /// Defines a convenient extract stream operator for those classes, 12 | /// facilitating statements like `for(T item; stream >> item;){}` 13 | /// \tparam T 14 | template 15 | class DataStream 16 | { 17 | public: 18 | virtual ~DataStream() = default; 19 | 20 | /// \brief Request the next item from the stream. 21 | /// \param item The element that will contain the newly retreived data. 22 | /// \return Should return true if item contains valid data. 23 | virtual std::optional getNext() = 0; 24 | 25 | /// \brief Tells whether the stream is valid, i.e. is alive and produces valid data. 26 | /// \return Should return true if the stream is valid. 27 | virtual operator bool() const = 0; 28 | 29 | class Iterator 30 | { 31 | public: 32 | using iterator_category = std::input_iterator_tag; 33 | using value_type = T; 34 | using difference_type = std::ptrdiff_t; 35 | using pointer = T*; 36 | using reference = T&; 37 | 38 | explicit Iterator(DataStream& stream) //NOLINT 39 | : Iterator{stream, !stream} 40 | {} 41 | 42 | Iterator(DataStream& stream, const bool is_end) 43 | : stream_{stream} 44 | , is_end_{is_end} 45 | { 46 | ++*this; 47 | } 48 | 49 | Iterator& operator++() 50 | { 51 | if (!is_end_) 52 | { 53 | t_ = stream_.getNext(); 54 | is_end_ = not t_.has_value(); 55 | } 56 | 57 | return *this; 58 | } 59 | 60 | Iterator operator++(int) 61 | { 62 | auto retval = *this; 63 | 64 | ++(*this); 65 | 66 | return retval; 67 | } 68 | 69 | bool operator==(const Iterator& other) const 70 | { 71 | if (is_end_ || !stream_) 72 | { return other.is_end_ || !other.stream_; } 73 | else 74 | { return !other.is_end_ && other.stream_; } 75 | } 76 | 77 | bool operator!=(const Iterator& other) const 78 | { 79 | return !(*this == other); 80 | } 81 | 82 | constexpr const T& operator*() const & { return *t_; } 83 | 84 | T& operator*() & { return *t_; } 85 | 86 | T&& operator*() && { return std::move(*t_); } 87 | 88 | private: 89 | DataStream& stream_; 90 | bool is_end_; 91 | std::optional t_; 92 | }; 93 | 94 | Iterator begin() 95 | { 96 | return Iterator{*this}; 97 | } 98 | 99 | Iterator end() 100 | { 101 | return {*this, true}; 102 | } 103 | }; 104 | 105 | /// \brief Extract operator for DataStream 106 | /// \tparam T the type of data in the stream. 107 | /// \param stream the stream. 108 | /// \param item element containing the received data 109 | /// \return the stream. 110 | template 111 | inline DataStream& operator>>(DataStream& stream, T& item) 112 | { 113 | if (auto data = stream.getNext()) 114 | { item = std::move(*data); } 115 | 116 | return stream; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /core/src/signal_waiter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/utils/signal_waiter.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace flow 8 | { 9 | namespace 10 | { 11 | using Handler = std::shared_ptr>; 12 | 13 | std::mutex handler_mutex; 14 | std::map> handlers; 15 | 16 | void register_handler(int signal, const Handler& handler); 17 | 18 | void unregister_handler(int signal, const Handler& handler); 19 | 20 | void handle_signal(int signal); 21 | } 22 | 23 | SignalWaiter::SignalWaiter( 24 | const std::vector& signals 25 | ) 26 | : is_waiting_{true} 27 | , got_signal_{false} 28 | , handler_{std::make_shared>([this](){handleSignal();})} 29 | { 30 | for (const auto signal : signals) 31 | { 32 | register_handler(signal, handler_); 33 | } 34 | 35 | worker_ = std::async( 36 | std::launch::async, 37 | [this, signals]() 38 | { 39 | await(signals); 40 | } 41 | ); 42 | } 43 | 44 | SignalWaiter::~SignalWaiter() 45 | { 46 | is_waiting_ = false; 47 | cv_.notify_one(); 48 | worker_.wait(); 49 | } 50 | 51 | bool SignalWaiter::hasGottenSignal() const 52 | { 53 | return got_signal_; 54 | } 55 | 56 | std::shared_future SignalWaiter::getFuture() const 57 | { 58 | return worker_; 59 | } 60 | 61 | void SignalWaiter::handleSignal() 62 | { 63 | got_signal_ = true; 64 | cv_.notify_one(); 65 | } 66 | 67 | void SignalWaiter::await(const std::vector& signals) 68 | { 69 | std::unique_lock lock{mutex_}; 70 | cv_.wait( 71 | lock, 72 | [this]() 73 | { 74 | return !is_waiting_ || got_signal_; 75 | } 76 | ); 77 | 78 | for (const auto signal : signals) 79 | { 80 | unregister_handler(signal, handler_); 81 | } 82 | } 83 | 84 | namespace 85 | { 86 | void register_handler( 87 | const int signal, 88 | const Handler& handler 89 | ) 90 | { 91 | std::lock_guard lock{handler_mutex}; 92 | 93 | auto it = handlers.find(signal); 94 | 95 | if (it == handlers.end()) 96 | { 97 | it = handlers.insert({signal, {}}).first; 98 | std::signal(signal, handle_signal); 99 | } 100 | 101 | it->second.insert(handler); 102 | } 103 | 104 | void unregister_handler( 105 | const int signal, 106 | const Handler& handler 107 | ) 108 | { 109 | std::lock_guard lock{handler_mutex}; 110 | 111 | const auto it = handlers.find(signal); 112 | 113 | if (it == handlers.end()) 114 | { 115 | return; 116 | } 117 | 118 | it->second.erase(handler); 119 | 120 | if (it->second.empty()) 121 | { 122 | // no handlers are registered with this signal 123 | // so set it back to default 124 | 125 | std::signal(signal, SIG_DFL); 126 | handlers.erase(it); 127 | } 128 | } 129 | 130 | void handle_signal(const int signal) 131 | { 132 | std::lock_guard lock{handler_mutex}; 133 | 134 | const auto it = handlers.find(signal); 135 | 136 | if (it == handlers.end()) 137 | { 138 | return; 139 | } 140 | 141 | for (const auto& handler : it->second) 142 | { 143 | (*handler)(); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /core/test/test_signal_waiter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/utils/signal_waiter.h" 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include 7 | #include 8 | 9 | using namespace flow; 10 | 11 | TEST(SignalWaiter, waits) 12 | { 13 | SignalWaiter waiter; 14 | 15 | ASSERT_FALSE(waiter.hasGottenSignal()); 16 | 17 | std::raise(SIGINT); 18 | 19 | ASSERT_TRUE(waiter.hasGottenSignal()); 20 | } 21 | 22 | TEST(SignalWaiter, stopsWaitingOnDTOR) 23 | { 24 | std::shared_future waiter_future; 25 | { 26 | SignalWaiter waiter; 27 | ASSERT_FALSE(waiter.hasGottenSignal()); 28 | waiter_future = waiter.getFuture(); 29 | } 30 | waiter_future.get(); 31 | } 32 | 33 | namespace 34 | { 35 | constexpr size_t num_workers = 50; 36 | 37 | auto createSignalWaiterWorkers(int signal) 38 | { 39 | std::vector> workers; 40 | std::vector workers_done(num_workers, false); 41 | std::vector> workers_has_started(num_workers); 42 | std::vector> workers_has_ended(num_workers); 43 | 44 | for (size_t i = 0; i < num_workers; ++i) 45 | { 46 | workers.push_back( 47 | std::async( 48 | std::launch::async, 49 | [&worker_done = workers_done[i], 50 | &worker_has_started = workers_has_started[i], 51 | &worker_has_ended = workers_has_ended[i], 52 | signal]() 53 | { 54 | SignalWaiter waiter{{signal}}; 55 | worker_has_started.set_value(); 56 | waiter.getFuture().wait(); 57 | 58 | worker_done = true; 59 | worker_has_ended.set_value(); 60 | } 61 | ) 62 | ); 63 | } 64 | 65 | return std::make_tuple(std::move(workers), 66 | std::move(workers_done), 67 | std::move(workers_has_started), 68 | std::move(workers_has_ended)); 69 | } 70 | 71 | bool all(const std::vector& values) 72 | { 73 | return std::all_of( 74 | values.begin(), 75 | values.end(), 76 | [](uint8_t status) 77 | { return status; } 78 | ); 79 | } 80 | 81 | void waitFor(std::vector>& promises_to_wait_for) 82 | { 83 | for (auto& promise: promises_to_wait_for) 84 | { 85 | promise.get_future().wait(); 86 | } 87 | } 88 | } 89 | 90 | TEST(SignalWaiter, multiThreaded) 91 | { 92 | auto [sigint_workers, 93 | sigint_workers_done, 94 | sigint_workers_has_started, 95 | sigint_workers_has_ended] = createSignalWaiterWorkers(SIGINT); 96 | 97 | auto [sigterm_workers, 98 | sigterm_workers_done, 99 | sigterm_workers_has_started, 100 | sigterm_workers_has_ended] = createSignalWaiterWorkers(SIGTERM); 101 | 102 | waitFor(sigint_workers_has_started); 103 | ASSERT_FALSE(all(sigint_workers_done)); 104 | std::raise(SIGINT); 105 | waitFor(sigint_workers_has_ended); 106 | ASSERT_TRUE(all(sigint_workers_done)); 107 | 108 | waitFor(sigterm_workers_has_started); 109 | ASSERT_FALSE(all(sigterm_workers_done)); 110 | std::raise(SIGTERM); 111 | waitFor(sigterm_workers_has_ended); 112 | ASSERT_TRUE(all(sigterm_workers_done)); 113 | } 114 | 115 | -------------------------------------------------------------------------------- /loader/include/superflow/loader/proxel_library.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/factory_map.h" 5 | #include "boost/dll/shared_library.hpp" 6 | #include 7 | 8 | namespace flow::load 9 | { 10 | /// This class will hold a shared library that contains proxels and proxel factories, 11 | /// which can be dynamically loaded using the flow::load module. 12 | /// \note An object of this class must not go out of scope as long as its proxels are in use. 13 | /// That will cause the shared library to be unloaded, and your application to crash! 14 | class ProxelLibrary 15 | { 16 | public: 17 | /// Load a proxel library from a path, and the library name. 18 | /// The prefix and suffix of the library file will be computed based on platform (more portable code) 19 | /// E.g.: library name: myproxels 20 | /// Linux result: libmyproxels.so 21 | /// Windows result: myproxels.dll 22 | ProxelLibrary( 23 | const boost::dll::fs::path& path_to_directory, 24 | const std::string& library_name 25 | ); 26 | 27 | /// Load a proxel library by using the full path to the exact library file. 28 | ProxelLibrary( 29 | const boost::dll::fs::path& full_path 30 | ); 31 | 32 | /// \brief Load the factories registered with type PropertyList 33 | /// \tparam PropertyList Some class compatible with flow::value 34 | /// \tparam Args any optional arguments to the factories (see unit tests for examples) 35 | /// \param args any optional arguments to the factories (see unit tests for examples) 36 | /// \return the factories 37 | /// \throws std::invalid_argument if there are no string PropertyList::adapter_name, 38 | /// or there are no proxel factories registered to that adapter name in the library. 39 | template 40 | FactoryMap loadFactories( 41 | Args... args 42 | ) const; 43 | 44 | private: 45 | /// Holds the actual shared library 46 | boost::dll::shared_library library_; 47 | 48 | /// Exception handling are covered here 49 | ProxelLibrary( 50 | const boost::dll::fs::path& path, 51 | boost::dll::load_mode::type load_mode 52 | ); 53 | 54 | [[nodiscard]] std::vector getSectionSymbols(const std::string& adapter_name) const; 55 | 56 | /// All factory-symbols are prefixed with the adapter_name + underscore 57 | [[nodiscard]] static std::string extractFactoryName(const std::string& symbol, const std::string& adapter_name); 58 | }; 59 | 60 | // -- ProxelLibrary::loadFactories implementation -- // 61 | template 62 | FactoryMap ProxelLibrary::loadFactories(Args... args) const 63 | { 64 | typename std::map> factories; 65 | 66 | const auto symbols = getSectionSymbols(PropertyList::adapter_name); 67 | for (const auto& symbol: symbols) 68 | { 69 | auto factory = library_.get_alias < flow::Proxel::Ptr(PropertyList const&, Args...)>(symbol); 70 | factories.emplace( 71 | extractFactoryName(symbol, PropertyList::adapter_name), 72 | [factory = std::move(factory), args...](PropertyList const& adapter) 73 | { 74 | return std::invoke(factory, adapter, args...); 75 | } 76 | ); 77 | } 78 | return FactoryMap{std::move(factories)}; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /core/include/superflow/callback_consumer_port.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/connection_manager.h" 5 | #include "superflow/consumer_port.h" 6 | #include "superflow/policy.h" 7 | 8 | #include 9 | 10 | namespace flow 11 | { 12 | /// \brief This port calls a function every time data is received. 13 | /// \tparam T The type of data to be exchanged between ports. 14 | /// \tparam P ConnectPolicy 15 | /// \tparam Variants... Optionally supported input variant types. \see ConsumerPort 16 | template< 17 | typename T, 18 | ConnectPolicy P = ConnectPolicy::Single, 19 | typename... Variants 20 | > 21 | class CallbackConsumerPort : 22 | public ConsumerPort, 23 | public std::enable_shared_from_this 24 | { 25 | public: 26 | using Ptr = std::shared_ptr; 27 | using Callback = std::function; 28 | 29 | explicit CallbackConsumerPort(const Callback&); 30 | 31 | void receive(const T&, const Port::Ptr&) override; 32 | 33 | void connect(const Port::Ptr& ptr) override; 34 | 35 | void disconnect() noexcept override; 36 | 37 | void disconnect(const Port::Ptr& ptr) noexcept override; 38 | 39 | bool isConnected() const override; 40 | 41 | PortStatus getStatus() const override; 42 | 43 | private: 44 | size_t num_transactions_ = 0; 45 | 46 | Callback callback_; 47 | ConnectionManager

connection_manager_; 48 | }; 49 | 50 | // --- Implementations --- // 51 | template< 52 | typename T, 53 | ConnectPolicy P, 54 | typename... Variants 55 | > 56 | CallbackConsumerPort::CallbackConsumerPort(const Callback& callback) 57 | : callback_{callback} 58 | {} 59 | 60 | template< 61 | typename T, 62 | ConnectPolicy P, 63 | typename... Variants 64 | > 65 | inline void CallbackConsumerPort::receive(const T& t, const Port::Ptr&) 66 | { 67 | callback_(t); 68 | ++num_transactions_; 69 | } 70 | 71 | template< 72 | typename T, 73 | ConnectPolicy P, 74 | typename... Variants 75 | > 76 | void CallbackConsumerPort::connect(const Port::Ptr& ptr) 77 | { 78 | connection_manager_.connect(shared_from_this(), ptr); 79 | } 80 | 81 | template< 82 | typename T, 83 | ConnectPolicy P, 84 | typename... Variants 85 | > 86 | void CallbackConsumerPort::disconnect() noexcept 87 | { 88 | connection_manager_.disconnect(shared_from_this()); 89 | } 90 | 91 | template< 92 | typename T, 93 | ConnectPolicy P, 94 | typename... Variants 95 | > 96 | void CallbackConsumerPort::disconnect(const Port::Ptr& ptr) noexcept 97 | { 98 | connection_manager_.disconnect(shared_from_this(), ptr); 99 | } 100 | 101 | template< 102 | typename T, 103 | ConnectPolicy P, 104 | typename... Variants 105 | > 106 | bool CallbackConsumerPort::isConnected() const 107 | { 108 | return connection_manager_.isConnected(); 109 | } 110 | 111 | template< 112 | typename T, 113 | ConnectPolicy P, 114 | typename... Variants 115 | > 116 | PortStatus CallbackConsumerPort::getStatus() const 117 | { 118 | return { 119 | connection_manager_.getNumConnections(), 120 | num_transactions_ 121 | }; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /core/include/superflow/multi_queue_getter.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/policy.h" 5 | 6 | #include "superflow/utils/multi_lock_queue.h" 7 | 8 | #include 9 | 10 | namespace flow 11 | { 12 | template 13 | class MultiQueueGetter 14 | {}; 15 | 16 | template 17 | class MultiQueueGetter 18 | { 19 | public: 20 | void get(MultiLockQueue& multi_queue, std::vector& items) 21 | { 22 | auto item_map = multi_queue.popAll(); 23 | 24 | items.resize(item_map.size()); 25 | 26 | size_t i = 0; 27 | 28 | for (auto& kv : item_map) 29 | { 30 | items[i++] = std::move(kv.second); 31 | } 32 | } 33 | 34 | bool hasNext(const MultiLockQueue& multi_queue) const 35 | { 36 | return multi_queue.hasAll(); 37 | } 38 | }; 39 | 40 | template 41 | class MultiQueueGetter 42 | { 43 | public: 44 | void get(MultiLockQueue& multi_queue, std::vector& items) 45 | { 46 | if (last_items_.empty()) 47 | { 48 | last_items_ = multi_queue.popAll(); 49 | } else 50 | { 51 | for (auto& kv : multi_queue.popReady()) 52 | { 53 | last_items_[kv.first] = std::move(kv.second); 54 | } 55 | } 56 | 57 | items.resize(last_items_.size()); 58 | 59 | size_t i = 0; 60 | 61 | for (const auto& kv : last_items_) 62 | { 63 | items[i++] = kv.second; 64 | } 65 | } 66 | 67 | bool hasNext(const MultiLockQueue& multi_queue) const 68 | { 69 | if (last_items_.empty()) 70 | { 71 | return multi_queue.hasAll(); 72 | } 73 | 74 | return true; 75 | } 76 | 77 | private: 78 | std::map last_items_; 79 | }; 80 | 81 | template 82 | class MultiQueueGetter 83 | { 84 | public: 85 | void get(MultiLockQueue& multi_queue, std::vector& items) 86 | { 87 | auto item_map = multi_queue.popReady(); 88 | items.resize(item_map.size()); 89 | 90 | size_t i = 0; 91 | 92 | for (auto& kv : item_map) 93 | { 94 | items[i++] = std::move(kv.second); 95 | } 96 | } 97 | 98 | bool hasNext(const MultiLockQueue& multi_queue) const 99 | { 100 | return true; 101 | } 102 | 103 | private: 104 | std::map last_items_; 105 | }; 106 | 107 | template 108 | class MultiQueueGetter 109 | { 110 | public: 111 | void get(MultiLockQueue& multi_queue, std::vector& items) 112 | { 113 | if (last_items_.empty()) 114 | { 115 | last_items_ = multi_queue.popAll(); 116 | } else 117 | { 118 | for (auto& kv : multi_queue.popAtLeastOne()) 119 | { 120 | last_items_[kv.first] = std::move(kv.second); 121 | } 122 | } 123 | 124 | items.resize(last_items_.size()); 125 | 126 | size_t i = 0; 127 | 128 | for (const auto& kv : last_items_) 129 | { 130 | items[i++] = kv.second; 131 | } 132 | } 133 | 134 | bool hasNext(const MultiLockQueue& multi_queue) const 135 | { 136 | if (last_items_.empty()) 137 | { 138 | return multi_queue.hasAll(); 139 | } 140 | 141 | return multi_queue.hasAny(); 142 | } 143 | 144 | private: 145 | std::map last_items_; 146 | }; 147 | } 148 | -------------------------------------------------------------------------------- /core/include/superflow/utils/throttle.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace flow 11 | { 12 | /// \brief This is a utility for limiting the rate of which a function is called. 13 | /// 14 | /// The target function will be called periodically at a requested rate, using an 15 | /// internal timer. 16 | /// If new data is provided repeatedly while the throttler is stalling, only the latest data 17 | /// will be buffered. When the timer expires, that last data will be sent. 18 | /// If the timer expires without any new data being available, nothing happens. 19 | /// If new data is provided after the timer expires, it is sent immediately 20 | /// and the timer starts again. 21 | /// \tparam T the type of data sent. 22 | /// 23 | /// \see Metronome, Sleeper 24 | template 25 | class Throttle 26 | { 27 | 28 | public: 29 | /// Signature of the target function 30 | using Callback = std::function; 31 | 32 | using Duration = std::chrono::steady_clock::duration; 33 | 34 | /// \brief Create a new Throttle. 35 | /// If the callback throws an exception, it will be caught and propagated 36 | /// to the next caller of push. 37 | /// \param cb the function to be called if data is available when the timer expires. 38 | /// \param delay the rate at which the given function is called 39 | Throttle(Callback cb, const Duration& delay); 40 | 41 | ~Throttle(); 42 | 43 | /// \brief Push new data to the Throttle. 44 | /// \param data to be sent to the target function. 45 | /// \throws any exception that may occur in the provided callback function. 46 | /// If an exception has been raised in the previous run of the worker thread, this function 47 | /// will propagate the exception to the current caller. 48 | template 49 | void push(U&& data); 50 | 51 | private: 52 | bool stopped_ = false; 53 | bool new_data_ = false; 54 | std::mutex mu_; 55 | std::condition_variable cv_; 56 | T data_; 57 | Callback callback_; 58 | Duration delay_; 59 | std::future runner_; 60 | 61 | void run(); 62 | }; 63 | 64 | // --- Implementation --- // 65 | 66 | template 67 | Throttle::Throttle(Callback cb, const Duration& delay) 68 | : callback_{std::move(cb)} 69 | , delay_{delay} 70 | , runner_{std::async(std::launch::async, [this]{ run(); })} 71 | {} 72 | 73 | template 74 | Throttle::~Throttle() 75 | { 76 | stopped_ = true; 77 | cv_.notify_all(); 78 | } 79 | 80 | template 81 | void Throttle::run() 82 | { 83 | while (!stopped_) 84 | { 85 | std::unique_lock lock(mu_); 86 | cv_.wait(lock, [this]{ return (stopped_ || new_data_); }); 87 | 88 | if (stopped_) 89 | { break; } 90 | 91 | try 92 | { callback_(std::move(data_)); } 93 | catch (...) 94 | { 95 | stopped_ = true; 96 | throw; 97 | } 98 | new_data_ = false; 99 | cv_.wait_for(lock, delay_, [this]{return stopped_;}); 100 | } 101 | } 102 | 103 | template 104 | template 105 | void Throttle::push(U&& data) 106 | { 107 | if (stopped_) 108 | { runner_.get(); } 109 | { 110 | std::scoped_lock lock(mu_); 111 | data_ = std::forward(data); 112 | new_data_ = true; 113 | } 114 | cv_.notify_one(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /loader/include/superflow/loader/register_factory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | /// \file register-factory.h 3 | /// \brief Macros for creating proxel plugin libraries. 4 | /// 5 | /// See #REGISTER_PROXEL_FACTORY(ProxelName, Factory). 6 | 7 | /// \def REGISTER_PROXEL_FACTORY(ProxelName, Factory) 8 | /// Register the symbol of the Factory function \a Factory to the alias \a ProxelName. 9 | /// 10 | /// The macro \a LOADER_ADAPTER_NAME must be defined and will be used as the 11 | /// shared library section name. 12 | /// 13 | /// The macro \a LOADER_ADAPTER_TYPE must be defined and set to a 14 | /// valid implementation of ValueAdapter when compiling the proxel plugin library. 15 | /// 16 | /// The macro \a LOADER_ADAPTER_HEADER may be defined and set to a 17 | /// header file containing the definition of LOADER_ADAPTER_TYPE. 18 | /// 19 | /// \note The library flow::yaml will define the macros in its cmake INTERFACE_COMPILE_DEFINITIONS, 20 | /// so you should not have to worry about them. 21 | /// Thus, the following example is contrived, but illustrates the mechanism: 22 | /// 23 | /// \code{.cpp} 24 | /// #define LOADER_ADAPTER_NAME YAML 25 | /// #define LOADER_ADAPTER_TYPE flow::yaml::YAMLPropertyList 26 | /// #define LOADER_ADAPTER_HEADER "superflow/yaml/yaml_property_list.h" 27 | /// // ... 28 | /// REGISTER_PROXEL_FACTORY(MyProxel, createMyProxel) 29 | /// 30 | /// \param ProxelName The class name of the Proxel you want to register a factory for. 31 | /// \param Factory The flow::Factory function that can create a \a ProxelName wrapped in a Proxel::Ptr. 32 | 33 | /// \def LOADER_ADAPTER_TYPE 34 | /// The actual type of PropertyList, e.g. flow::yaml::YAMLPropertyList 35 | 36 | #pragma once 37 | 38 | #include "boost/dll/alias.hpp" 39 | #include "boost/preprocessor/stringize.hpp" 40 | 41 | #ifdef LOADER_ADAPTER_HEADER 42 | #include LOADER_ADAPTER_HEADER 43 | #endif 44 | 45 | /// Helper macro 46 | #define ALIAS(SECTION, NAME) \ 47 | BOOST_PP_CAT(BOOST_PP_CAT(SECTION, _), NAME) 48 | 49 | /// \brief Register the symbol of the Factory function \a Factory to the alias \a ProxelName 50 | /// within the shared library section \a Section. 51 | /// Helper macros like REGISTER_PROXEL_FACTORY, or macros for specific adapters like 52 | /// REGISTER_YAML_PROXEL_FACTORY (defined elsewhere) 53 | /// will call this macro with a predefined section name. 54 | /// Typically not used directly, but an example would be 55 | /// 56 | /// \code{.cpp} 57 | /// REGISTER_PROXEL_FACTORY_SECTIONED(YAML_MyProxel, createMyProxel, YAML) 58 | /// 59 | #define REGISTER_PROXEL_FACTORY_SECTIONED(ProxelName, Factory, Section) \ 60 | BOOST_DLL_ALIAS_SECTIONED( \ 61 | Factory, \ 62 | ALIAS(Section, ProxelName), \ 63 | Section \ 64 | ) 65 | 66 | #ifdef LOADER_IGNORE 67 | #define REGISTER_PROXEL_FACTORY(ProxelName, Factory) 68 | #elif defined(LOADER_ADAPTER_NAME) && defined(LOADER_ADAPTER_TYPE) 69 | #define REGISTER_PROXEL_FACTORY(ProxelName, Factory) \ 70 | REGISTER_PROXEL_FACTORY_SECTIONED(ProxelName, Factory, LOADER_ADAPTER_NAME) 71 | #else 72 | #define REGISTER_PROXEL_FACTORY(ProxelName, Factory) \ 73 | _Pragma(BOOST_PP_STRINGIZE(message \ 74 | "LOADER_ADAPTER_NAME and LOADER_ADAPTER_TYPE are not defined. "\ 75 | "REGISTER_PROXEL_FACTORY(" BOOST_PP_STRINGIZE(ProxelName) ", " BOOST_PP_STRINGIZE(Factory) ") has no effect."\ 76 | )) 77 | #endif 78 | -------------------------------------------------------------------------------- /core/test/test_throttle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/utils/blocker.h" 3 | #include "superflow/utils/throttle.h" 4 | 5 | #include "gtest/gtest.h" 6 | 7 | #include 8 | #include 9 | 10 | using namespace flow; 11 | 12 | TEST(Throttle, throttling) 13 | { 14 | std::vector result; 15 | Blocker blocker; 16 | 17 | const auto publish_fn = [&result, &blocker](std::string&& s) 18 | { 19 | result.push_back(s); 20 | blocker.release(); 21 | }; 22 | 23 | constexpr auto publish_rate = std::chrono::milliseconds(100); 24 | 25 | { 26 | Throttle th(publish_fn, publish_rate); 27 | 28 | th.push("first"); // Push the first data. 29 | 30 | EXPECT_TRUE(blocker.block()); // Wait to ensure throttle has pushed. 31 | blocker.rearm(); // block can be called again 32 | // As soon at it has published, 33 | // do a double push so that skip1 should be overwritten before next publish 34 | th.push("skip1"); 35 | th.push("second"); 36 | 37 | EXPECT_TRUE(blocker.block()); // Wait to ensure throttle has pushed again 38 | blocker.rearm(); // block can be called again 39 | 40 | th.push("skip2"); 41 | th.push("last"); 42 | 43 | blocker.block(); 44 | ASSERT_EQ(result.back(), "last"); 45 | } 46 | 47 | ASSERT_EQ(result.size(), 3); 48 | ASSERT_EQ(result[0], "first"); 49 | ASSERT_EQ(result[1], "second"); 50 | ASSERT_EQ(result[2], "last"); 51 | } 52 | 53 | inline const void* addr(const std::string& str) 54 | { return static_cast(str.c_str()); } 55 | 56 | 57 | TEST(Throttle, move_data) 58 | { 59 | const std::string contents(30, 'x'); 60 | std::string str = contents; 61 | const auto str_address = addr(str); 62 | 63 | ASSERT_NE(addr(contents), str_address); 64 | ASSERT_EQ(contents, str); // Can compare contents even if str is moved. 65 | 66 | std::string result; 67 | bool published = false; 68 | Blocker blocker; 69 | const auto publish_fn = [&result, &published, &blocker](auto&& s) 70 | { 71 | published = true; 72 | result = std::forward(s); 73 | blocker.release(); 74 | }; 75 | constexpr auto publish_rate = std::chrono::microseconds(1); 76 | 77 | Throttle th(publish_fn, publish_rate); 78 | 79 | th.push(std::move(str)); 80 | blocker.block(); 81 | EXPECT_EQ(published, true); 82 | EXPECT_EQ(contents, result); 83 | EXPECT_EQ(str_address, addr(result)); 84 | } 85 | 86 | TEST(Throttle, copy_data) 87 | { 88 | const std::string str(30, 'x'); 89 | const auto str_address = addr(str); 90 | 91 | std::string result; 92 | bool published = false; 93 | Blocker blocker; 94 | const auto publish_fn = [&result, &published, &blocker](auto&& s) 95 | { 96 | published = true; 97 | result = std::forward(s); 98 | blocker.release(); 99 | }; 100 | constexpr auto publish_rate = std::chrono::microseconds(1); 101 | 102 | Throttle th(publish_fn, publish_rate); 103 | 104 | th.push(str); 105 | blocker.block(); 106 | 107 | ASSERT_EQ(published, true); 108 | EXPECT_EQ(str, result); 109 | EXPECT_NE(str_address, addr(result)); 110 | } 111 | 112 | TEST(Throttle, exception) 113 | { 114 | Blocker blocker; 115 | const auto publish_fn = [&blocker](int) 116 | { 117 | unblockOnThreadExit(blocker); 118 | throw std::runtime_error("jalla jalla"); 119 | }; 120 | const auto publish_rate = std::chrono::microseconds(1); 121 | 122 | Throttle th(publish_fn, publish_rate); 123 | EXPECT_NO_THROW(th.push(42)); 124 | 125 | blocker.block(); 126 | EXPECT_THROW(th.push(42), std::runtime_error); 127 | } 128 | -------------------------------------------------------------------------------- /cmake/packaging.cmake: -------------------------------------------------------------------------------- 1 | macro(set_backslash _dst_var _string) 2 | string(REPLACE "/" "\\" ${_dst_var} ${_string}) 3 | endmacro() 4 | 5 | macro(unset_provides_conflicts_replaces _component) 6 | string(TOUPPER ${_component} _COMPONENT) 7 | set(CPACK_DEBIAN_${_COMPONENT}_PACKAGE_PROVIDES "") 8 | set(CPACK_DEBIAN_${_COMPONENT}_PACKAGE_CONFLICTS "") 9 | set(CPACK_DEBIAN_${_COMPONENT}_PACKAGE_REPLACES "") 10 | endmacro() 11 | 12 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) 13 | list(REMOVE_DUPLICATES CMAKE_MODULE_PATH) 14 | 15 | set(CPACK_PACKAGE_CONTACT "Ragnar Smestad ") 16 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "An efficient processing framework for modern C++") 17 | set(CPACK_PACKAGE_DESCRIPTION 18 | "The Superflow is the informational space between universes. 19 | It also serves as the place where dreams and ideas come from, and from where telepathy operates. (...). 20 | The laws of physics do not apply in the Superflow.") 21 | set(CPACK_PACKAGE_INSTALL_DIRECTORY "superflow") 22 | set(CPACK_PACKAGE_VENDOR "FFI (Forsvarets forskningsinstitutt)") 23 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") 24 | set(CPACK_VERBATIM_VARIABLES TRUE) 25 | 26 | set(CPACK_DEBIAN_PACKAGE_PROVIDES "superflow-core,superflow-curses,superflow-loader,superflow-yaml") 27 | set(CPACK_DEBIAN_PACKAGE_CONFLICTS "superflow-core,superflow-curses,superflow-loader,superflow-yaml") 28 | set(CPACK_DEBIAN_PACKAGE_REPLACES "superflow-core,superflow-curses,superflow-loader,superflow-yaml") 29 | unset_provides_conflicts_replaces("CORE") 30 | unset_provides_conflicts_replaces("CURSES") 31 | unset_provides_conflicts_replaces("LOADER") 32 | unset_provides_conflicts_replaces("YAML") 33 | 34 | set(CPACK_DEBIAN_CORE_PACKAGE_DEPENDS "build-essential") 35 | set(CPACK_DEBIAN_CURSES_PACKAGE_DEPENDS "libncurses-dev") 36 | set(CPACK_DEBIAN_LOADER_PACKAGE_DEPENDS "libboost-filesystem-dev") 37 | set(CPACK_DEBIAN_YAML_PACKAGE_DEPENDS "libyaml-cpp-dev") 38 | 39 | set(CPACK_DEB_COMPONENT_INSTALL ON) 40 | set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON) 41 | 42 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) 43 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "build-essential,libboost-filesystem-dev,libncurses-dev,libyaml-cpp-dev") 44 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/ffi-no/superflow") 45 | set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") 46 | set(CPACK_DEBIAN_PACKAGE_VERSION "${${PROJECT_NAME}_VERSION}") 47 | set(CPACK_DEBIAN_PACKAGE_RELEASE "0") 48 | set(CPACK_DEBIAN_PACKAGE_SECTION "libs") 49 | set(CPACK_DEBIAN_PACKAGE_DESCRIPTION 50 | "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}. 51 | ${CPACK_PACKAGE_DESCRIPTION}" 52 | ) 53 | 54 | set(CPACK_NSIS_CONTACT "Ragnar Smestad ") 55 | set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) 56 | set(CPACK_NSIS_INSTALL_ROOT "C:\\local") 57 | set(CPACK_NSIS_PACKAGE_NAME "superflow") 58 | set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/cmake/nsis-icon.ico") 59 | set(CPACK_NSIS_MUI_UNIICON "${CMAKE_SOURCE_DIR}/cmake/nsis-icon.ico") 60 | set_backslash(CPACK_NSIS_MUI_HEADERIMAGE_BITMAP "${CMAKE_SOURCE_DIR}/cmake/nsis-banner.bmp") 61 | set_backslash(CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP "${CMAKE_SOURCE_DIR}/cmake/nsis-welcome.bmp") 62 | 63 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 64 | set(CPACK_GENERATOR "NSIS64") 65 | else() 66 | set(CPACK_GENERATOR "DEB") 67 | endif() 68 | 69 | #set(CPACK_COMPONENTS_GROUPING "ONE_PER_GROUP") 70 | #set(CPACK_COMPONENTS_GROUPING "ALL_COMPONENTS_IN_ONE") 71 | #set(CPACK_COMPONENTS_GROUPING "IGNORE") 72 | 73 | include(CPack) 74 | 75 | cpack_add_component(core DISPLAY_NAME core DESCRIPTION "The flow::core library" GROUP dev) 76 | cpack_add_component(curses DISPLAY_NAME curses DESCRIPTION "The flow::curses library" GROUP dev DEPENDS core) 77 | cpack_add_component(loader DISPLAY_NAME loader DESCRIPTION "The flow::loader library" GROUP dev DEPENDS core) 78 | cpack_add_component(yaml DISPLAY_NAME yaml DESCRIPTION "The flow::yaml library" GROUP dev DEPENDS core) 79 | -------------------------------------------------------------------------------- /curses/src/window.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/curses/window.h" 3 | #include "superflow/utils/pimpl_impl.h" 4 | #include "ncursescpp/ncursescpp.hpp" 5 | #include 6 | #include 7 | 8 | // https://stackoverflow.com/a/2351155 9 | // You can use explicit instantiation to create an instantiation of 10 | // a templated class or function without actually using it in your code. 11 | // Because this is useful when you are creating library files that 12 | // use templates for distribution, uninstantiated template definitions 13 | // are not put into object files. 14 | template class flow::pimpl; 15 | 16 | namespace flow::curses 17 | { 18 | class Window::impl 19 | { 20 | public: 21 | impl(int x, int y, int width, int height); 22 | 23 | static constexpr int content_col_offset = 1; 24 | static constexpr int header_col_offset = 2; 25 | 26 | int width_; 27 | int height_; 28 | nccpp::Window window_; 29 | 30 | void setColor(const nccpp::Color& color); 31 | 32 | void renderBox(int background_color); 33 | 34 | void renderName(const std::string& name); 35 | 36 | void renderLine(const std::string& line, int row_offset); 37 | 38 | [[nodiscard]] std::string shortenString( 39 | const std::string& str, 40 | int col_offset, 41 | bool pad = false 42 | ) const; 43 | }; 44 | 45 | Window::Window(const int x, const int y, const int width, const int height) 46 | : m_{x, y, width, height} 47 | {} 48 | 49 | void Window::render( 50 | const std::string& name, 51 | const std::vector& lines, 52 | const nccpp::Color& color 53 | ) 54 | { 55 | m_->setColor(color); 56 | m_->renderBox(color.background); 57 | m_->renderName(name); 58 | 59 | for (size_t i = 0; i < lines.size(); ++i) 60 | { 61 | const int row_offset = 1 + static_cast(i); 62 | 63 | m_->renderLine(lines[i], row_offset); 64 | } 65 | 66 | for (auto i = static_cast(lines.size()); i < m_->height_; ++i) 67 | { 68 | const int row_offset = 1 + i; 69 | 70 | m_->renderLine("", row_offset); 71 | } 72 | 73 | m_->window_.refresh(); 74 | } 75 | 76 | Window::impl::impl(const int x, const int y, const int width, const int height) 77 | : width_{width} 78 | , height_{height} 79 | , window_{height + 2, width, y, x} 80 | {} 81 | 82 | void Window::impl::setColor(const nccpp::Color& color) 83 | { 84 | const auto attr = static_cast(nccpp::ncurses().color_to_attr(color)); 85 | 86 | window_.bkgd(attr); 87 | window_.attron(attr); 88 | } 89 | 90 | void Window::impl::renderBox(const int) 91 | { 92 | window_.box('|', '-'); 93 | } 94 | 95 | void Window::impl::renderName(const std::string& name) 96 | { 97 | const std::string header = shortenString(name, header_col_offset); 98 | 99 | window_.mvprintw(0, header_col_offset, header.c_str()); 100 | } 101 | 102 | void Window::impl::renderLine(const std::string& line, int row_offset) 103 | { 104 | const std::string content = shortenString(line, content_col_offset, true); 105 | 106 | window_.mvprintw(row_offset, content_col_offset, content.c_str()); 107 | } 108 | 109 | std::string Window::impl::shortenString( 110 | const std::string& str, 111 | const int col_offset, 112 | const bool pad) const 113 | { 114 | const auto width = static_cast(width_ - 2 * col_offset); 115 | std::ostringstream ss; 116 | 117 | if (str.length() <= width) 118 | { 119 | if (!pad || str.length() == width) 120 | { 121 | return str; 122 | } 123 | 124 | ss << str 125 | << std::setfill(' ') << std::setw(static_cast(width - str.length())) << ""; 126 | } else 127 | { 128 | const auto first_half_width = static_cast(width / 2); 129 | const auto last_half_width = width - first_half_width; 130 | 131 | ss << str.substr(0, first_half_width) 132 | << str.substr(str.length() - last_half_width, last_half_width); 133 | } 134 | 135 | return ss.str(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.2) 2 | project(superflow VERSION 4.0.1) 3 | message(STATUS "* Generating '${PROJECT_NAME}' v${${PROJECT_NAME}_VERSION}") 4 | 5 | set(CMAKE_DEBUG_POSTFIX "d") 6 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 8 | 9 | string(TOLOWER ${CMAKE_PROJECT_NAME} package_name) 10 | set(config_install_dir "share/cmake/${package_name}/") 11 | set(namespace "${package_name}::") 12 | set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/cmake/config.cmake.in") 13 | set(project_config_out "${CMAKE_BINARY_DIR}/${package_name}-config.cmake") 14 | set(targets_export_name "${package_name}-targets") 15 | set(version_config_out "${CMAKE_BINARY_DIR}/${package_name}-config-version.cmake") 16 | 17 | option(BUILD_TESTS "Whether or not to build the tests" OFF) 18 | option(BUILD_all "build superflow with all submodules" ON) 19 | option(BUILD_curses "build submodule curses" OFF) 20 | option(BUILD_loader "build submodule loader" OFF) 21 | option(BUILD_yaml "build submodule yaml" OFF) 22 | 23 | if (BUILD_TESTS) 24 | include(cmake/enable-tests.cmake) 25 | endif() 26 | 27 | include(${CMAKE_BINARY_DIR}/conan_paths.cmake OPTIONAL) 28 | include(${CMAKE_CURRENT_SOURCE_DIR}/build/conan_paths.cmake OPTIONAL) 29 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_BINARY_DIR}) 30 | 31 | include(CMakePackageConfigHelpers) 32 | include(cmake/cmake-boilerplate.cmake) 33 | 34 | add_subdirectory(core) 35 | 36 | if (NOT MSVC AND (BUILD_curses OR BUILD_all)) 37 | add_subdirectory(curses) 38 | endif() 39 | if (BUILD_loader OR BUILD_all) 40 | add_subdirectory(loader) 41 | endif() 42 | if (BUILD_yaml OR BUILD_all) 43 | add_subdirectory(yaml) 44 | endif() 45 | 46 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 47 | 48 | configure_package_config_file( 49 | ${project_config_in} 50 | ${project_config_out} 51 | INSTALL_DESTINATION ${config_install_dir} 52 | PATH_VARS built_components 53 | NO_SET_AND_CHECK_MACRO 54 | ) 55 | 56 | write_basic_package_version_file( 57 | ${version_config_out} 58 | COMPATIBILITY SameMajorVersion 59 | ) 60 | 61 | install(FILES 62 | ${project_config_out} 63 | ${version_config_out} 64 | DESTINATION ${config_install_dir} 65 | COMPONENT core 66 | ) 67 | 68 | install(FILES 69 | ${CMAKE_SOURCE_DIR}/LICENSE 70 | DESTINATION "${CMAKE_INSTALL_DOCDIR}" 71 | COMPONENT core 72 | ) 73 | 74 | install(EXPORT ${targets_export_name} 75 | NAMESPACE ${namespace} 76 | DESTINATION ${config_install_dir} 77 | COMPONENT core 78 | ) 79 | 80 | export( 81 | TARGETS ${built_components} 82 | NAMESPACE ${namespace} 83 | FILE ${CMAKE_BINARY_DIR}/${targets_export_name}.cmake 84 | ) 85 | 86 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 87 | # Create target 'doxygen' 88 | find_package(Doxygen) 89 | if (Doxygen_FOUND) 90 | add_custom_target(doxygen 91 | COMMAND 92 | ${PROJECT_NAME}_VERSION=v${${PROJECT_NAME}_VERSION} 93 | ${DOXYGEN_EXECUTABLE} 94 | ${CMAKE_CURRENT_LIST_DIR}/doc/Doxyfile 95 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} 96 | COMMENT "Generating API documentation with Doxygen" 97 | VERBATIM 98 | ) 99 | endif() 100 | 101 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 102 | # Create target 'pack' 103 | get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) 104 | include(cmake/packaging.cmake) 105 | 106 | add_custom_target(pack 107 | COMMAND 108 | ${CMAKE_CPACK_COMMAND} "-G" "DEB" 109 | "-D" "CPACK_COMPONENTS_GROUPING=ALL_COMPONENTS_IN_ONE" 110 | # Denne virker ikke. Alle pakkene blir "installert" likevel. 111 | #COMMAND 112 | # ${CMAKE_CPACK_COMMAND} "-G" "DEB" 113 | # "-D" "CPACK_COMPONENTS_GROUPING=IGNORE" 114 | COMMENT "Running CPack. Please wait..." 115 | DEPENDS ${CPACK_COMPONENTS_ALL} 116 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 117 | ) 118 | 119 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 120 | # Create target 'uninstall' 121 | add_custom_target(uninstall 122 | "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_LIST_DIR}/cmake/uninstall.cmake" 123 | ) 124 | -------------------------------------------------------------------------------- /loader/test/test_proxel_library.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "testing/dummy_value_adapter.h" 3 | 4 | #include "superflow/loader/proxel_library.h" 5 | #include "proxels/lib_path.h" 6 | #include "proxels/fauxade.h" 7 | 8 | #include "gtest/gtest.h" 9 | 10 | using namespace flow; 11 | 12 | TEST(ProxelLibrary, load_libary_by_library_name) 13 | { 14 | ASSERT_NO_FATAL_FAILURE( 15 | load::ProxelLibrary(flow::test::local::lib_dir, "proxels") 16 | ); 17 | } 18 | 19 | TEST(ProxelLibrary, load_libary_by_full_path) 20 | { 21 | ASSERT_NO_FATAL_FAILURE(load::ProxelLibrary(boost::dll::fs::path{flow::test::local::lib_path})); 22 | } 23 | 24 | TEST(ProxelLibrary, nonexisting_path) 25 | { 26 | ASSERT_THROW( 27 | load::ProxelLibrary(boost::dll::fs::path{flow::test::local::lib_path} / "non-existing path"), 28 | std::invalid_argument 29 | ); 30 | } 31 | 32 | TEST(ProxelLibrary, empty_path) 33 | { 34 | ASSERT_THROW( 35 | load::ProxelLibrary(boost::dll::fs::path{}), 36 | std::invalid_argument 37 | ); 38 | } 39 | 40 | TEST(ProxelLibrary, loadFactories_from_library) 41 | { 42 | const load::ProxelLibrary library{flow::test::local::lib_dir, "proxels"}; 43 | 44 | ASSERT_NO_THROW( 45 | std::ignore = library.loadFactories() 46 | ); 47 | } 48 | 49 | TEST(ProxelLibrary, nonexisting_proxel_name) 50 | { 51 | const load::ProxelLibrary library{flow::test::local::lib_dir, "proxels"}; 52 | const auto factories = library.loadFactories(); 53 | 54 | ASSERT_FALSE(factories.empty()); 55 | 56 | EXPECT_THROW( 57 | factories.get("Non-existing proxel name"), 58 | std::invalid_argument 59 | ); 60 | } 61 | 62 | TEST(ProxelLibrary, can_create_proxel_from_factory) 63 | { 64 | const load::ProxelLibrary library{flow::test::local::lib_dir, "proxels"}; 65 | const auto factories = library.loadFactories(); 66 | 67 | ASSERT_FALSE(factories.empty()); 68 | 69 | const flow::test::DummyValueAdapter properties; 70 | const auto& dummy_factory = factories.get("Dummy"); 71 | const auto dummy_proxel = std::invoke(dummy_factory, properties); 72 | 73 | EXPECT_EQ(ProxelStatus::State::Paused, dummy_proxel->getStatus().state); 74 | } 75 | 76 | TEST(ProxelLibrary, can_create_proxel_with_value) 77 | { 78 | const load::ProxelLibrary library{flow::test::local::lib_dir, "proxels"}; 79 | const auto factories = library.loadFactories(); 80 | 81 | ASSERT_FALSE(factories.empty()); 82 | 83 | const flow::test::DummyValueAdapter properties; 84 | const auto yummy_proxel = std::invoke(factories.get("Yummy"), properties); 85 | 86 | EXPECT_EQ(ProxelStatus::State::AwaitingInput, yummy_proxel->getStatus().state); 87 | ASSERT_EQ( 88 | std::to_string(properties.int_value), 89 | yummy_proxel->getStatus().info 90 | ); 91 | } 92 | 93 | 94 | TEST(ProxelLibrary, load_factories_with_args) 95 | { 96 | const load::ProxelLibrary library{flow::test::local::lib_dir, "proxels"}; 97 | const auto fauxade_ptr = std::make_shared(42); 98 | ASSERT_EQ(42, fauxade_ptr->val); 99 | 100 | const auto factories = library.loadFactories( 101 | fauxade_ptr 102 | ); 103 | 104 | ASSERT_FALSE(factories.empty()); 105 | 106 | const flow::test::DummyValueAdapter properties; 107 | const auto& mummy_factory = factories.get("Mummy"); 108 | const auto mummy_proxel = std::invoke(mummy_factory, properties); 109 | 110 | ASSERT_EQ("42", mummy_proxel->getStatus().info); 111 | } 112 | 113 | TEST(ProxelLibrary, ProxelWorks) 114 | { 115 | const load::ProxelLibrary library{flow::test::local::lib_dir, "proxels"}; 116 | const auto factories = library.loadFactories(); 117 | 118 | ASSERT_FALSE(factories.empty()); 119 | 120 | const flow::test::DummyValueAdapter properties; 121 | const auto& dummy_factory = factories.get("Dummy"); 122 | const auto dummy_proxel = std::invoke(dummy_factory, properties); 123 | 124 | EXPECT_EQ(ProxelStatus::State::Paused, dummy_proxel->getStatus().state); 125 | dummy_proxel->start(); 126 | EXPECT_EQ(ProxelStatus::State::Running, dummy_proxel->getStatus().state); 127 | dummy_proxel->stop(); 128 | EXPECT_EQ(ProxelStatus::State::Unavailable, dummy_proxel->getStatus().state); 129 | } 130 | -------------------------------------------------------------------------------- /core/test/test_graph_factory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "templated_testproxel.h" 3 | 4 | #include "superflow/graph_factory.h" 5 | 6 | #include "gtest/gtest.h" 7 | 8 | using namespace flow; 9 | 10 | class TestPropertyList 11 | { 12 | 13 | public: 14 | using Input = std::map; 15 | 16 | explicit TestPropertyList(Input input) 17 | : input_{std::move(input)} 18 | {} 19 | 20 | bool hasKey(const std::string& key) const 21 | { return input_.count(key) > 0; } 22 | 23 | template 24 | T convertValue(const std::string& key) const 25 | { 26 | if (!hasKey(key)) 27 | { throw std::invalid_argument({"Could not find key '" + key + "' in PropertyList."}); } 28 | 29 | return static_cast(input_.at(key)); 30 | } 31 | 32 | private: 33 | Input input_; 34 | }; 35 | 36 | template 37 | Factory getProxelFactory() 38 | { 39 | return [](const TestPropertyList& properties) 40 | { 41 | return F::create(properties); 42 | }; 43 | } 44 | 45 | TEST(GraphFactory, createGraph) 46 | { 47 | using TestProxel = TemplatedProxel; 48 | 49 | const FactoryMap factories( 50 | { 51 | {"TemplatedProxel", getProxelFactory()} 52 | }); 53 | 54 | // for hver ting (sensor, publisher, proxel, ...) 55 | TestPropertyList::Input properties1 = {{"init_value", 42.0}}; 56 | TestPropertyList::Input properties2 = {{"init_value", 21.0}}; 57 | 58 | const auto config = std::vector> { 59 | {"proxel1", "TemplatedProxel", TestPropertyList{std::move(properties1)}}, 60 | {"proxel2", "TemplatedProxel", TestPropertyList{std::move(properties2)}} 61 | }; 62 | 63 | std::vector connections{ 64 | {"proxel1", "outport", "proxel2", "inport"} 65 | }; 66 | 67 | auto graph = createGraph(factories, config, connections); 68 | 69 | Proxel::Ptr raw_ptr = graph.getProxel("proxel1"); 70 | ASSERT_TRUE(raw_ptr != nullptr); 71 | 72 | auto ptr2 = graph.getProxel("proxel2"); 73 | ASSERT_TRUE(ptr2 != nullptr); 74 | ASSERT_EQ(21.0, ptr2->getStoredValue()); 75 | 76 | graph.start(); 77 | ASSERT_EQ(21.0, ptr2->getStoredValue()); 78 | ASSERT_EQ(42.0, ptr2->getValue()); 79 | } 80 | 81 | TEST(GraphFactory, createGraphWithout_getProxelFactory) 82 | { 83 | using Plist = TestPropertyList; 84 | using MyProxel = TemplatedProxel; 85 | 86 | const Factory factory = MyProxel::create; 87 | const FactoryMap factories{{{"MyProxel", factory}}}; 88 | 89 | const Plist properties1{{{"init_value", 42.0}}}; 90 | const Plist properties2{{{"init_value", 21.0}}}; 91 | 92 | const auto configs = std::vector> { 93 | {"proxel1", "MyProxel", properties1}, 94 | {"proxel2", "MyProxel", properties2} 95 | }; 96 | 97 | const std::vector connections{ 98 | {"proxel1", "outport", "proxel2", "inport"} 99 | }; 100 | 101 | Graph graph = createGraph(factories, configs, connections); 102 | 103 | const Proxel::Ptr raw_ptr = graph.getProxel("proxel1"); 104 | ASSERT_TRUE(raw_ptr != nullptr); 105 | 106 | const auto ptr2 = graph.getProxel("proxel2"); 107 | ASSERT_TRUE(ptr2 != nullptr); 108 | ASSERT_EQ(21.0, ptr2->getStoredValue()); 109 | 110 | graph.start(); 111 | ASSERT_EQ(21.0, ptr2->getStoredValue()); 112 | ASSERT_EQ(42.0, ptr2->getValue()); 113 | 114 | } 115 | 116 | TEST(GraphFactory, multiple_definitions_of_same_proxel_id_throws) 117 | { 118 | using Plist = TestPropertyList; 119 | using MyProxel = TemplatedProxel; 120 | 121 | const Factory factory = MyProxel::create; 122 | const FactoryMap factories{{{"MyProxel", factory}}}; 123 | 124 | const Plist properties1{{{"init_value", 42.0}}}; 125 | const Plist properties2{{{"init_value", 21.0}}}; 126 | 127 | const auto configs = std::vector> { 128 | {"proxel1", "MyProxel", properties1}, 129 | {"proxel1", "MyProxel", properties1}, 130 | {"proxel2", "MyProxel", properties2} 131 | }; 132 | 133 | const std::vector connections{ 134 | {"proxel1", "outport", "proxel2", "inport"} 135 | }; 136 | 137 | ASSERT_THROW(createGraph(factories, configs, connections), std::invalid_argument); 138 | } 139 | 140 | -------------------------------------------------------------------------------- /cmake/cmake-boilerplate.cmake: -------------------------------------------------------------------------------- 1 | # This macro is meant to be called once for all modules, since it's a set of spells 2 | # that all modules will have to cast to be installed properly. 3 | # It requires arguments in this order: 4 | # - _name : The name of the generated library 5 | # 6 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 7 | include(GNUInstallDirs) 8 | 9 | macro(init_module) 10 | set(_namespace "superflow") 11 | set(_module "${PROJECT_NAME}") 12 | set(target_name "${_namespace}-${_module}") 13 | 14 | message(STATUS "* Init module '${_module}'") 15 | endmacro() 16 | 17 | macro(add_library_boilerplate) 18 | set(gcc_compiler_flags 19 | -Wall 20 | -Wcast-align 21 | -Wcast-qual 22 | -Werror 23 | -Wextra 24 | -Wfloat-conversion 25 | -Winit-self 26 | -Winit-self 27 | -Wlogical-op 28 | -Wmissing-declarations 29 | -Wnon-virtual-dtor 30 | -Wold-style-cast 31 | -Woverloaded-virtual 32 | -Wpedantic 33 | -Wpointer-arith 34 | -Wshadow 35 | -Wsuggest-override 36 | -Wuninitialized 37 | -Wunknown-pragmas 38 | -Wunreachable-code 39 | -Wunused-local-typedefs 40 | ) 41 | 42 | set(msvc_compiler_flags 43 | /W4 44 | /WX 45 | ) 46 | 47 | list(APPEND built_components ${target_name}) 48 | set(built_components ${built_components} PARENT_SCOPE) 49 | 50 | message(STATUS "* Add cmake boilerplate for target '${target_name}'") 51 | 52 | file(GLOB_RECURSE HEADER_FILES include/*.h) 53 | file(GLOB_RECURSE SRC_FILES src/*.cpp) 54 | 55 | add_library(${target_name} 56 | ${HEADER_FILES} 57 | ${SRC_FILES} 58 | ) 59 | 60 | add_library(${_namespace}::${_module} ALIAS ${target_name}) 61 | 62 | set(msvc_cxx "$") 63 | set(gcc_like_cxx "$") 64 | set(config_coverage "$") 65 | set(add_coverage "$") 66 | 67 | target_compile_options(${target_name} PRIVATE 68 | "$<${gcc_like_cxx}:$>" 69 | "$<${msvc_cxx}:$>" 70 | ) 71 | 72 | target_compile_options(${target_name} PUBLIC "$<${add_coverage}:--coverage;-g;-O0>") 73 | target_link_options( ${target_name} PUBLIC "$<${add_coverage}:--coverage>") 74 | 75 | target_include_directories(${target_name} 76 | PRIVATE $ 77 | ) 78 | 79 | target_include_directories(${target_name} 80 | SYSTEM INTERFACE 81 | $ 82 | $ 83 | ) 84 | 85 | set_target_properties(${target_name} PROPERTIES 86 | LINKER_LANGUAGE CXX 87 | CXX_STANDARD_REQUIRED ON 88 | CXX_STANDARD 17 89 | POSITION_INDEPENDENT_CODE ON 90 | BUILD_RPATH $ORIGIN 91 | INSTALL_RPATH $ORIGIN 92 | WINDOWS_EXPORT_ALL_SYMBOLS ON 93 | ) 94 | 95 | # Prepend the top-level project name to all installed library files 96 | string(TOLOWER "${target_name}" output_name) 97 | set_target_properties(${target_name} PROPERTIES 98 | EXPORT_NAME ${_module} 99 | OUTPUT_NAME ${output_name} 100 | ) 101 | 102 | common_boilerplate() 103 | 104 | endmacro() 105 | 106 | macro(common_boilerplate) 107 | set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/${_module}-config.cmake.in") 108 | set(project_config_out "${CMAKE_BINARY_DIR}/${_namespace}-${_module}-config.cmake") 109 | 110 | configure_package_config_file( 111 | ${project_config_in} 112 | ${project_config_out} 113 | INSTALL_DESTINATION ${config_install_dir} 114 | NO_SET_AND_CHECK_MACRO 115 | NO_CHECK_REQUIRED_COMPONENTS_MACRO 116 | ) 117 | 118 | install(FILES 119 | ${project_config_out} 120 | DESTINATION ${config_install_dir} 121 | COMPONENT ${_module} 122 | ) 123 | 124 | install(TARGETS ${target_name} 125 | EXPORT ${targets_export_name} 126 | COMPONENT ${_module} 127 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 128 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 129 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 130 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 131 | ) 132 | 133 | install( 134 | DIRECTORY "include/" 135 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 136 | COMPONENT ${_module} 137 | FILES_MATCHING PATTERN "*.h" 138 | ) 139 | endmacro() 140 | -------------------------------------------------------------------------------- /core/include/superflow/utils/mutexed.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace flow 8 | { 9 | /// \brief A wrapper class for protecting an object with a mutex. 10 | /// \note Note that since Mutexed is derived from T, T cannot be pointer or refrence. 11 | /// 12 | /// \code{.cpp} 13 | /// Mutexed mutexed("hello"); 14 | /// { 15 | /// std::scoped_lock lock{mutexed}; 16 | /// mutexed = "hello"; 17 | /// } 18 | /// 19 | /// mutexed.store("bye"); 20 | /// std::string bye = mutexed.load(); 21 | /// 22 | /// mutexed.read([&bye](auto const& str){ bye = str; } 23 | /// mutexed.write([](auto& str){ str = "hello"; } 24 | /// \endcode 25 | /// \tparam T The type of object you want to protect. 26 | /// \see http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/ 27 | /// \see https://stackoverflow.com/questions/4127333/should-mutexes-be-mutable/4128689#4128689 28 | template 29 | class Mutexed : public T 30 | { 31 | public: 32 | /// \brief Construct a new Mutexed 33 | using T::T; 34 | 35 | /// \brief Assign a new value to the mutexed object, interpreted as a T. 36 | /// \note NOT threadsafe! Must be used in a manner like this: 37 | /// Mutexed mutexed("hello"); 38 | /// { 39 | /// std::scoped_lock lock{mutexed}; 40 | /// mutexed = "hello"; 41 | /// } 42 | /// \see store, write 43 | /// \return 44 | template 45 | Mutexed& operator=(Args&& ... args) 46 | { 47 | T::operator=(std::forward(args)...); 48 | return *this; 49 | } 50 | 51 | /// Intentionally slice object from type 'Mutexed' to 'T'. 52 | [[nodiscard]] T slice() const 53 | { return *this; } 54 | 55 | /// Intentionally slice object from type 'Mutexed' to 'T'. 56 | [[nodiscard]] T& slice() 57 | { return *this; } 58 | 59 | /// \brief Thread safe assignment of a new value to Mutexed 60 | /// \tparam Args any types applicable to the creation of T 61 | /// \param args any arguments applicable to the creation of T 62 | template 63 | void store(Args&& ... args) 64 | { 65 | std::scoped_lock lock{*this}; 66 | T::operator=(std::forward(args)...); 67 | } 68 | 69 | /// Thread safe slicing of object from type 'Mutexed' to 'T'. 70 | /// Will create a copy, so this method is not applicable to non-copyable 'T's 71 | /// \return 72 | [[nodiscard]] T load() const 73 | { 74 | std::scoped_lock lock{*this}; 75 | return slice(); 76 | } 77 | 78 | /// \brief Provides unique access to the data by giving a `const T&` to the reader function. 79 | /// A unique lock on the mutex is acquired prior to calling the supplied reader function, 80 | /// so multiple simultaneous calls to read is not possible. 81 | /// \tparam Invokable any invokable type that is invokable with `const T&` as argument 82 | /// \param reader the invocable (a function or lambda, typically) 83 | /// \return whatever the Invokable returns 84 | template 85 | auto read(const Invokable& reader) const 86 | { 87 | static_assert( 88 | std::is_invocable_v, 89 | "The provided `reader` is not invokable with `const T&` as argument" 90 | ); 91 | 92 | std::scoped_lock lock{*this}; 93 | return std::invoke(reader, slice()); 94 | } 95 | 96 | /// \brief Provides unique access to the data by giving a `T&` to the writer function. 97 | /// A unique lock on the mutex is acquired prior to calling the supplied writer function, 98 | /// so simultaneous calls to read or write is not possible. 99 | /// \tparam Invokable any invokable type that is invokable with `T&` as argument 100 | /// \param writer the invocable (a function or lambda, typically) 101 | /// \return whatever the Invokable returns 102 | template 103 | auto write(const Invokable& writer) 104 | { 105 | static_assert( 106 | std::is_invocable_v, 107 | "The provided `writer` is not invokable with `T&` as argument" 108 | ); 109 | 110 | std::scoped_lock lock{*this}; 111 | return std::invoke(writer, slice()); 112 | } 113 | 114 | void lock() const ///< C++ named requirement: Mutex 115 | { this->mu_.lock(); } 116 | 117 | void unlock() const ///< C++ named requirement: Mutex 118 | { this->mu_.unlock(); }; 119 | 120 | bool try_lock() const noexcept ///< C++ named requirement: Mutex 121 | { return this->mu_.try_lock(); } 122 | 123 | private: 124 | mutable std::mutex mu_; 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /curses/src/proxel_window.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #include "superflow/curses/proxel_window.h" 3 | #include "ncursescpp/ncursescpp.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace flow::curses 9 | { 10 | ProxelWindow::ProxelWindow() 11 | : ProxelWindow{0, 0, 0, 0} 12 | {} 13 | 14 | ProxelWindow::ProxelWindow( 15 | const int x, 16 | const int y, 17 | const int width, 18 | const size_t max_ports_shown 19 | ) 20 | : x_{x} 21 | , y_{y} 22 | , width_{width} 23 | , max_ports_shown_{max_ports_shown} 24 | , window_{x, y, width, inner_height} 25 | {} 26 | 27 | void ProxelWindow::renderStatus( 28 | const std::string& name, 29 | const ProxelStatus& status 30 | ) 31 | { 32 | std::vector lines; 33 | 34 | { 35 | std::ostringstream ss; 36 | ss << "state: " << status.state; 37 | 38 | lines.push_back(ss.str()); 39 | } 40 | 41 | { 42 | const size_t max_info = 5; 43 | 44 | const auto max_line_length = static_cast(width_ - 2); 45 | std::vector info_lines = getLines(status.info, max_line_length); 46 | const size_t num_info = std::min(max_info, info_lines.size()); 47 | 48 | std::move( 49 | info_lines.begin(), 50 | std::next(info_lines.begin(), num_info), 51 | std::back_inserter(lines) 52 | ); 53 | } 54 | 55 | const auto color = getStateColor(status.state); 56 | window_.render(name, lines, color); 57 | 58 | { 59 | constexpr int width = 10; 60 | constexpr int height = 2; 61 | int i = 0; 62 | for (const auto& kv : status.ports) 63 | { 64 | if (max_ports_shown_ > 0 && i >= static_cast(max_ports_shown_)) 65 | { 66 | break; 67 | } 68 | 69 | Window port_window( 70 | x_ + i * width + 1, 71 | y_ + inner_height + 1, 72 | width, 73 | height); 74 | 75 | std::vector port_lines; 76 | 77 | const PortStatus& port_status = kv.second; 78 | 79 | if (port_status.num_connections != PortStatus::undefined) 80 | { 81 | std::ostringstream ss; 82 | ss << "C:" 83 | << std::setfill(' ') << std::setw(width - 4) 84 | << port_status.num_connections; 85 | 86 | port_lines.push_back(ss.str()); 87 | } 88 | 89 | if (port_status.num_transactions != PortStatus::undefined) 90 | { 91 | std::ostringstream ss; 92 | ss << "T:" 93 | << std::setfill(' ') << std::setw(width - 4) 94 | << port_status.num_transactions; 95 | 96 | port_lines.push_back(ss.str()); 97 | } 98 | 99 | port_window.render(kv.first, port_lines, color); 100 | ++i; 101 | } 102 | } 103 | } 104 | 105 | int ProxelWindow::getHeight() 106 | { 107 | return inner_height + 2; 108 | } 109 | 110 | std::vector ProxelWindow::getLines( 111 | const std::string& str, 112 | const size_t max_line_length) 113 | { 114 | const std::vector raw_lines = split(str, '\n'); 115 | 116 | std::vector lines; 117 | 118 | for (const std::string& raw_line : raw_lines) 119 | { 120 | std::vector chunks = split(raw_line, max_line_length); 121 | 122 | std::move(chunks.begin(), chunks.end(), std::back_inserter(lines)); 123 | } 124 | 125 | return lines; 126 | } 127 | 128 | std::vector ProxelWindow::split( 129 | const std::string& str, 130 | const char sep) 131 | { 132 | std::istringstream ss{str}; 133 | std::vector lines; 134 | 135 | for (std::string line; std::getline(ss, line, sep);) 136 | { 137 | lines.push_back(std::move(line)); 138 | } 139 | 140 | return lines; 141 | } 142 | 143 | std::vector ProxelWindow::split( 144 | const std::string& str, 145 | const size_t chunk_size) 146 | { 147 | std::vector chunks; 148 | 149 | for (size_t i = 0; i < str.length(); i += chunk_size) 150 | { 151 | chunks.push_back(str.substr(i, chunk_size)); 152 | } 153 | 154 | return chunks; 155 | } 156 | 157 | nccpp::Color ProxelWindow::getStateColor(const ProxelStatus::State state) 158 | { 159 | using nccpp::colors::red; 160 | using nccpp::colors::green; 161 | using nccpp::colors::blue; 162 | using nccpp::colors::yellow; 163 | using nccpp::colors::black; 164 | using nccpp::colors::white; 165 | 166 | switch (state) 167 | { 168 | case ProxelStatus::State::Running: 169 | return {green, black}; 170 | case ProxelStatus::State::Crashed: 171 | return {white, red}; 172 | case ProxelStatus::State::AwaitingInput: 173 | return {yellow, black}; 174 | case ProxelStatus::State::Paused: 175 | return {blue, black}; 176 | default: 177 | return {white, black}; 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /core/include/superflow/connection_manager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/policy.h" 5 | #include "superflow/port.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace flow 11 | { 12 | /// \brief A utility class for handling connections between 13 | /// Ports, while also encforcing ConnectPolicy. 14 | /// 15 | /// A ConnectionManager is typically owned by a Port to keep 16 | /// track of its connections. 17 | /// The class focuses on connections only, and is agnostic to communication. 18 | /// \tparam P Supported ConnectPolicys are Single and Multi. 19 | template 20 | class ConnectionManager 21 | { 22 | public: 23 | /// \brief Register a new connection and call other->connect(owner). 24 | /// \param owner Typically the owner of the ConnectionManager 25 | /// \param other The external Port to connect to. 26 | void connect( 27 | const Port::Ptr& owner, 28 | const Port::Ptr& other 29 | ); 30 | 31 | /// \brief Disconnect from all registered connections. 32 | /// \param owner Typically the owner of the ConnectionManager 33 | void disconnect( 34 | const Port::Ptr& owner 35 | ) noexcept; 36 | 37 | /// \brief Disconnect a specific Port 38 | /// \param owner Typically the owner of the ConnectionManager 39 | /// \param other The external Port to disconnect from. 40 | void disconnect( 41 | const Port::Ptr& owner, 42 | const Port::Ptr& other 43 | ) noexcept; 44 | 45 | /// \brief Get the number of connection pairs managed by the ConnectionManager 46 | size_t getNumConnections() const; 47 | 48 | /// \brief True if the ConnectionManager manages at least one connection. 49 | bool isConnected() const; 50 | 51 | private: 52 | std::unordered_set connections_; 53 | 54 | bool hasPort(const Port::Ptr& ptr) const; 55 | 56 | Port::Ptr front() const; 57 | }; 58 | 59 | template<> 60 | inline bool ConnectionManager::hasPort( 61 | const Port::Ptr& ptr 62 | ) const 63 | { 64 | return connections_.find(ptr) != connections_.end(); 65 | } 66 | 67 | template<> 68 | inline Port::Ptr ConnectionManager::front() const 69 | { 70 | return connections_.empty() 71 | ? nullptr 72 | : *connections_.cbegin(); 73 | } 74 | 75 | template<> 76 | inline void ConnectionManager::connect( 77 | const Port::Ptr& owner, 78 | const Port::Ptr& other 79 | ) 80 | { 81 | if (hasPort(other)) 82 | { return; } 83 | 84 | connections_.insert(other); 85 | 86 | try 87 | { 88 | other->connect(owner); 89 | } 90 | catch (...) 91 | { 92 | connections_.erase(other); 93 | std::rethrow_exception(std::current_exception()); 94 | } 95 | } 96 | 97 | template<> 98 | inline void ConnectionManager::connect( 99 | const Port::Ptr& owner, 100 | const Port::Ptr& other 101 | ) 102 | { 103 | if (other == front()) 104 | { return; } 105 | 106 | if (!connections_.empty()) 107 | { throw std::invalid_argument("Attempted connecting multiple ports to Single-port"); } 108 | 109 | connections_.insert(other); 110 | 111 | try 112 | { 113 | other->connect(owner); 114 | } 115 | catch (...) 116 | { 117 | connections_.clear(); 118 | std::rethrow_exception(std::current_exception()); 119 | } 120 | } 121 | 122 | template 123 | inline void ConnectionManager

::disconnect( 124 | const Port::Ptr& owner 125 | ) noexcept 126 | { 127 | std::unordered_set old_connections; 128 | std::swap(connections_, old_connections); 129 | 130 | for (const auto& connection : old_connections) 131 | { 132 | connection->disconnect(owner); 133 | } 134 | } 135 | 136 | template<> 137 | inline void ConnectionManager::disconnect( 138 | const Port::Ptr& owner, 139 | const Port::Ptr& other 140 | ) noexcept 141 | { 142 | auto it = connections_.find(other); 143 | 144 | if (it == connections_.end()) 145 | { 146 | // already disconnected 147 | return; 148 | } 149 | 150 | connections_.erase(it); 151 | other->disconnect(owner); 152 | } 153 | 154 | template<> 155 | inline void ConnectionManager::disconnect( 156 | const Port::Ptr& owner, 157 | const Port::Ptr& other 158 | ) noexcept 159 | { 160 | if (other != front()) 161 | { 162 | // already disconnected 163 | return; 164 | } 165 | 166 | connections_.clear(); 167 | other->disconnect(owner); 168 | } 169 | 170 | template 171 | inline size_t ConnectionManager

::getNumConnections() const 172 | { 173 | return connections_.size(); 174 | } 175 | 176 | template 177 | inline bool ConnectionManager

::isConnected() const 178 | { 179 | return getNumConnections() > 0; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /core/include/superflow/graph.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/proxel.h" 5 | #include "superflow/port.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace flow 13 | { 14 | 15 | /// \brief Processing graph responsible for starting, stopping and monitoring Proxels 16 | /// 17 | /// Graph is the manager of Proxels, mainly providing a data structure for them to exist 18 | /// and in the current implementation providing them with worker threads. 19 | /// Graph can also be queried for Proxel statuses to monitor workload and processing times. 20 | /// \see Proxel 21 | class Graph 22 | { 23 | public: 24 | /// A function which is called if a Proxel chrashes, and the graph handles exceptions. 25 | /// \param proxel_name First argument, the name of the crashed proxel 26 | /// \param what Second argument, the `what` of the caught exception. 27 | using CrashLogger = std::function; 28 | 29 | Graph() = default; 30 | 31 | /// Constructor that creates graph with a predefined set of proxels. 32 | /// Proxels will otherwise have to be added using the `add` method. 33 | /// \param proxels map which maps unique names to Proxels. 34 | explicit Graph(std::map proxels); 35 | 36 | Graph(Graph&&) = default; 37 | 38 | Graph(const Graph&) = delete; 39 | 40 | ~Graph(); 41 | 42 | /// Call the `start` method of every Proxel, using a new thread per element. 43 | /// Threads are not detatched. 44 | /// \param handle_exceptions true if Graph should catch exceptions from proxels 45 | /// \param crash_logger function that logs error messages caught from proxels. Has effect only 46 | /// if `handle_exceptions` is also `true`. 47 | /// \see CrashLogger, defaultCrashLogger 48 | void start( 49 | bool handle_exceptions = true, 50 | const CrashLogger& crash_logger = defaultCrashLogger 51 | ); 52 | 53 | /// Call the `stop` method of every Proxel, expecting the proxel thread to terminate. 54 | /// Threads are joined. 55 | void stop(); 56 | 57 | /// \brief Add a new proxel to the Graph. 58 | /// \param proxel_id Unique name for the Proxel 59 | /// \param proxel pointer to the proxel 60 | void add(const std::string& proxel_id, Proxel::Ptr&& proxel); 61 | 62 | /// \brief Request a pointer to a Proxel 63 | /// \tparam ProxelType optional template argument in order to get a specific sub class of Proxel. 64 | /// \param proxel_name Unique name of Proxel 65 | /// \return A pointer to the requested Proxel 66 | /// \throws std::invalid argument if Proxel does not exist or is not of requested type. 67 | template 68 | std::shared_ptr getProxel(const std::string& proxel_name) const; 69 | 70 | /// \brief Create a connection between ports in two Proxels. 71 | /// \param proxel1 Unique name of the first Proxel 72 | /// \param proxel1_port Unique name of the first Proxel's port 73 | /// \param proxel2 Unique name of the second Proxel 74 | /// \param proxel2_port Unique name of the second Proxel's port 75 | void connect(const std::string& proxel1, const std::string& proxel1_port, 76 | const std::string& proxel2, const std::string& proxel2_port) const; 77 | 78 | /// \brief Retreive the current status of all proxels. 79 | /// \see ProxelStatusMap 80 | /// \return 81 | [[nodiscard]] ProxelStatusMap getProxelStatuses() const; 82 | 83 | /// A CrashLogger that prints the error message to std::cerr. 84 | /// \see CrashLogger 85 | static void defaultCrashLogger(const std::string& proxel_name, const std::string& what); 86 | 87 | /// A CrashLogger that does absolutely nothing 88 | /// \see CrashLogger 89 | static const CrashLogger quietCrashLogger; 90 | 91 | private: 92 | std::map proxels_; 93 | std::map crashes_; 94 | 95 | std::map proxel_threads_; 96 | 97 | [[nodiscard]] bool isRunning() const; 98 | }; 99 | 100 | template 101 | std::shared_ptr Graph::getProxel(const std::string& proxel_name) const 102 | { 103 | const Proxel::Ptr raw_ptr = getProxel(proxel_name); // does not recurse infinitely 104 | const auto ptr = std::dynamic_pointer_cast(raw_ptr); 105 | 106 | if (ptr == nullptr) 107 | { throw std::invalid_argument({"Proxel '" + proxel_name + "' is not of requested type."}); } 108 | 109 | return ptr; 110 | } 111 | 112 | template<> 113 | inline Proxel::Ptr Graph::getProxel(const std::string& proxel_name) const 114 | { 115 | try 116 | { return proxels_.at(proxel_name); } 117 | catch (...) 118 | { throw std::invalid_argument(std::string("Proxel '" + proxel_name + "' does not exist")); } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /core/test/test_shared_mutexed.cpp: -------------------------------------------------------------------------------- 1 | #include "superflow/utils/shared_mutexed.h" 2 | #include "gtest/gtest.h" 3 | 4 | #include 5 | 6 | using namespace std::chrono_literals; 7 | 8 | TEST(SharedMutexed, multiple_read_can_happen_simultaneously) 9 | { 10 | const flow::SharedMutexed mutexed{"original"}; 11 | 12 | std::promise release_readers; 13 | auto must_wait = release_readers.get_future().share(); 14 | std::promise all_readers_started; 15 | std::atomic_size_t active_readers{0}; 16 | std::atomic_size_t simultaneous_readers{0}; 17 | 18 | std::vector> readers; 19 | 20 | constexpr size_t num_readers{10}; 21 | for (size_t i{0}; i < num_readers; ++i) 22 | { 23 | readers.emplace_back( 24 | std::async( 25 | std::launch::async, 26 | [&all_readers_started, &must_wait, &active_readers, &simultaneous_readers, &mutexed] 27 | { 28 | return mutexed.read( 29 | [&all_readers_started, &must_wait, &active_readers,&simultaneous_readers](const auto& value)-> std::string 30 | { 31 | ++simultaneous_readers; 32 | 33 | if (++active_readers == num_readers) 34 | { all_readers_started.set_value(); } 35 | // All reader threads have certainly been created 36 | 37 | must_wait.wait(); 38 | --simultaneous_readers; 39 | --active_readers; 40 | return value; 41 | } 42 | ); 43 | } 44 | ) 45 | ); 46 | } 47 | 48 | { // Assert that all readers have certainly been created 49 | const auto future_status = all_readers_started.get_future().wait_for(1s); 50 | ASSERT_NE(future_status, std::future_status::timeout); 51 | 52 | EXPECT_EQ(active_readers, num_readers); 53 | EXPECT_EQ(simultaneous_readers.load(), num_readers); 54 | } 55 | 56 | release_readers.set_value(); 57 | for (auto& reader: readers) 58 | { ASSERT_EQ("original", reader.get()); } 59 | 60 | EXPECT_EQ(active_readers, 0); 61 | } 62 | 63 | TEST(SharedMutexed, store_must_wait_for_multiple_read) 64 | { 65 | flow::SharedMutexed mutexed{"original"}; 66 | 67 | std::promise release_readers; 68 | auto must_wait = release_readers.get_future().share(); 69 | std::atomic_size_t active_readers{0}; 70 | 71 | std::vector> readers; 72 | constexpr size_t num_readers{10}; 73 | { 74 | std::promise all_readers_started; 75 | for (size_t i{0}; i < num_readers; ++i) 76 | { 77 | readers.emplace_back( 78 | std::async( 79 | std::launch::async, [&all_readers_started, &must_wait, &active_readers, &mutexed] 80 | { 81 | return mutexed.read( 82 | [&all_readers_started, &must_wait, &active_readers](const auto& value) -> std::string 83 | { 84 | if (++active_readers == num_readers) 85 | { all_readers_started.set_value(); } 86 | // All reader threads have certainly been created 87 | 88 | must_wait.wait(); 89 | --active_readers; 90 | return value; 91 | } 92 | ); 93 | } 94 | ) 95 | ); 96 | } 97 | 98 | { // Assert that all readers have certainly been created 99 | const auto future_status = all_readers_started.get_future().wait_for(1s); 100 | ASSERT_NE(future_status, std::future_status::timeout); 101 | 102 | // They are all busy with 'read', but are blocked by the 'must_wait' 103 | ASSERT_EQ(active_readers, num_readers); 104 | } 105 | } 106 | 107 | std::promise writer_started; 108 | auto writer = std::async( 109 | std::launch::async, [&writer_started, &active_readers, &mutexed] 110 | { 111 | writer_started.set_value(); 112 | mutexed.store("store"); 113 | mutexed.store(mutexed.load() + "," + std::to_string(active_readers)); 114 | } 115 | ); 116 | 117 | { // Assert that the writer has certainly been created 118 | const auto future_status = writer_started.get_future().wait_for(1s); 119 | ASSERT_NE(future_status, std::future_status::timeout); 120 | } 121 | 122 | // When writer is eventually started, 123 | // all readers are still blocked in 'read', so writer is also blocked. 124 | ASSERT_EQ(active_readers, num_readers); 125 | 126 | // All the readers must be served before writer can lock mutexed. 127 | // All readers will read the initial value 128 | release_readers.set_value(); 129 | for (auto& reader: readers) 130 | { 131 | ASSERT_EQ("original", reader.get()); 132 | } 133 | 134 | const auto future_status = writer.wait_for(1s); 135 | ASSERT_NE(future_status, std::future_status::timeout); 136 | 137 | EXPECT_EQ(active_readers, 0); 138 | EXPECT_EQ("store,0", mutexed); 139 | } 140 | -------------------------------------------------------------------------------- /loader/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### loader 3 | The `loader` module enables dynamic loading of proxel libraries (shared libraries) like plugins. 4 | A `loader`-compatible library has embedded a list of Factorys for the proxels it contains, 5 | which assists in the creation of a corresponding _FactoryMap_. 6 | The advantage of this is that the user doesn't have to include any specific header files from the 7 | library in their consuming application, and with the addition of the `yaml` module, 8 | no hard coded proxel names are required at all in the compiled client code. 9 | 10 | There are three important aspects when dealing with proxel libraries: 11 | 12 | 1. When compiling the proxel library, you must have already decided on a concrete `PropertyList` (or "value adapter"). 13 | At the time of writing, the `flow::yaml::YAMLPropertyList` is the only PropertyList we have implemented. 14 | 2. For each proxel, the factory must be "registered" using the macro `REGISTER_PROXEL_FACTORY` 15 | from the header file `"superflow/loader/register_factory.h"`. 16 | 3. When loading the library into the consuming application, create a `flow::load::ProxelLibrary` for each shared library, 17 | and keep them in scope as long as their proxels are in use. 18 | 19 | #### 1. PropertyList 20 | The interface of a `PropertyList` is defined through the templated function 21 | `flow::value` from `"superflow/value.h"`. 22 | It is required that a valid `PropertyList` defines the following methods: 23 | 24 | - `bool hasKey(const std::string& key)`, which tells if the given key exists or not. 25 | - `T convertValue(const std::string& key)`, which retrieves a value from the list. 26 | 27 | In addition, `flow::load::loadFactories` demands the existence of the static string `PropertyList::adapter_name`. 28 | It must contain the value of the macro `LOADER_ADAPTER_NAME` (see the next section). 29 | 30 | #### 2. REGISTER_PROXEL_FACTORY 31 | This macro will export a _named symbol_ to a _named section_ in the shared library, 32 | which can later be retrieved by those names. See the `Boost::DLL` documentation for more details. 33 | 34 | For the user, we want this to be as pleasant an experience as possible: 35 | 36 | ```cpp 37 | #include "my/proxel.h" 38 | #include "superflow/loader/register_factory.h" 39 | 40 | template 41 | flow::Proxel::Ptr createMyProxel(const PropertyList& adapter) 42 | { 43 | return std::make_shared( 44 | flow::value(adapter, "key") 45 | ); 46 | } 47 | 48 | REGISTER_PROXEL_FACTORY(MyProxel, createMyProxel) 49 | ``` 50 | 51 | In order for this to work, we demand that the author of the PropertyList defines the following macros: 52 | 53 | - `LOADER_ADAPTER_HEADER`, path to the concrete property list header file 54 | - `LOADER_ADAPTER_NAME`, a short, unique identifier for the property list 55 | - `LOADER_ADAPTER_TYPE`, the concrete type of property list 56 | 57 | For the `flow::yaml` module, these macros are defined through the target's "interface compile definitions". 58 | That means that when you link your proxel library to `flow::yaml`, they will be automatically defined. 59 | 60 | #### 3. ProxelLibrary 61 | 62 | A proxel library and its _FactoryMap_ are accessed through the use of a `flow::load::ProxelLibrary` object. 63 | You construct it using the path to the shared library file, and fetch the _FactoryMap_ using the method `loadFactories`. 64 | An object of this class must not go out of scope as long as its proxels are in use. 65 | That will cause the shared library to be unloaded, and your application to crash! 66 | 67 | ```cpp 68 | const flow::load::ProxelLibrary library{"path/to/library"}; 69 | const auto factories = library.loadFactories(); 70 | ``` 71 | 72 | You can collect factories from multiple libraries using the free function `loadFactories`. 73 | Remember that the libraries mustn't go out of scope, hence we keep the vector. 74 | 75 | ```cpp 76 | const std::vector library_paths{ 77 | {"path/to/library1"}, 78 | {"path/to/library2"} 79 | }; 80 | 81 | const auto factories = flow::load::loadFactories( 82 | library_paths 83 | ); 84 | ``` 85 | 86 | ##### Pro tip 87 | If you separate the name of the library and the path to the directory in which the library resides, 88 | Boost can automatically determine the prefix and postfix of the shared library file. 89 | 90 | ```cpp 91 | const std::string library_directory{"/directory"}; 92 | const std::string library_name{"myproxels"}; 93 | const flow::load::ProxelLibrary library{library_directory, library_name}; 94 | 95 | /// Linux result: /directory/libmyproxels.so 96 | /// Windows result: /directory/myproxels.dll 97 | ``` 98 | 99 | #### Further reading 100 | 101 | See the tests in the `loader` module for more examples, or read the report for more details. 102 | 103 | --- 104 | -------------------------------------------------------------------------------- /yaml/include/superflow/yaml/yaml.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Forsvarets forskningsinstitutt. All rights reserved. 2 | #pragma once 3 | 4 | #include "superflow/yaml/factory.h" 5 | 6 | #include "yaml-cpp/yaml.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace flow { class Graph; } 12 | 13 | namespace flow::yaml 14 | { 15 | /// Identifies a section in the YAML file. 16 | /// This is a "path" because it can be nested in the Yaml file, e.g. 17 | /// {"Toplevel", "Sublevel", "SubSubLevel", "Proxels"} 18 | using SectionPath = std::vector; 19 | 20 | /// Sections in the YAML file to look for proxels. 21 | const std::vector default_proxel_section_paths = {{"Proxels"}}; 22 | 23 | /// \brief Create a flow::Graph from the given YAML config file. 24 | /// 25 | /// The structure and contents of the configuration file must follow a distinct pattern. 26 | /// Two lists are required: \b Proxels and \b Connections. 27 | /// In the `Proxels` list, an entry consists of a unique name, followed by configuration parameters for that proxel. 28 | /// The only required parameter is `type`, which is used as a key to the FactoryMap, i.e. to retrieve the correct Factory. 29 | /// The `Connections` list defines how specific ports on specific proxels are connected together. 30 | /// 31 | /// An additional feature included in version 1.5 is the option to include other config files 32 | /// using the \b Includes list. All included files must obey the pattern of a config file, but proxels and connection 33 | /// specifications can be scattered across the union of the files. 34 | /// Only the first file (the one given as argument to this function) is parsed for includes. 35 | /// 36 | /// The listing below is a valid example of a configuration file for the yaml module. 37 | /// For further documentation, see the superflow report 19/00776. 38 | /// \code{.yml} 39 | /// %YAML 1.2 40 | /// --- 41 | /// Includes: 42 | /// - sunny_day.yaml 43 | /// - rainy_day.yaml 44 | /// 45 | /// Proxels: 46 | /// proxel_1: # unique name of the proxel 47 | /// type : "MyProxel" # class name 48 | /// my_param : 42 # proxel-specific parameter 49 | /// 50 | /// proxel_2: # unique name of the proxel 51 | /// type : "YourProxel" # class name 52 | /// enable : false # leave this proxel out of the graph 53 | /// 54 | /// Connections: 55 | /// - [proxel_1: 'out', proxel_2: 'in'] 56 | /// ... 57 | /// \endcode 58 | /// \param config_file_path Absolute path to the YAML configuration file 59 | /// \param factory_map Container for mapping Proxel types to their respective Factories 60 | /// \param proxel_section_paths Where to find proxels in the config file. Default is a top level "Proxels" section. 61 | /// \param config_search_directory If the config file utilizes the 'Includes' feature, 62 | /// search for included files relative to this directory. 63 | /// \return A new graph, ready to be started 64 | /// \throws std::runtime_error If the file(s) cannot be found or if something is wrong with the syntax 65 | flow::Graph createGraph( 66 | const std::string& config_file_path, 67 | const FactoryMap& factory_map, 68 | const std::vector& proxel_section_paths = default_proxel_section_paths, 69 | const std::string& config_search_directory = {} 70 | ); 71 | 72 | flow::Graph createGraph( 73 | const YAML::Node& root, 74 | const FactoryMap& factory_map, 75 | const std::vector& proxel_section_paths = default_proxel_section_paths, 76 | const std::string& config_search_directory = {} 77 | ); 78 | 79 | /// \brief Returns a list with the ID of all proxel configs with `flag` set to true. 80 | /// Useful for creating subsets of proxels that require special treatment. For example: 81 | /// \code{.yml} 82 | /// Proxels: 83 | /// my_proxel_1: 84 | /// type: MyProxel 85 | /// my-flag: true 86 | /// 87 | /// my_proxel_2: 88 | /// type: MyProxel 89 | /// my-flag: false 90 | /// \endcode 91 | /// `getFlaggedProxels(..., "my-flag")` will then return a list containing only "my_proxel_1". 92 | /// \param root The YAML::Node to parse for flagged proxels 93 | /// \param flag The key to look for, e.g. "my-flag" 94 | /// \param proxel_section_paths Sections in the YAML::Node to look for proxels. 95 | /// \return List of proxels with the `flag` enabled 96 | [[nodiscard]] std::vector getFlaggedProxels( 97 | const YAML::Node& root, 98 | const std::string& flag, 99 | const std::vector& proxel_section_paths = default_proxel_section_paths 100 | ); 101 | 102 | std::vector extractLibPaths( 103 | const std::string& config_path, 104 | const std::string& section_name = "LibraryPaths" 105 | ); 106 | 107 | std::string generateDOTFile( 108 | const std::string& config_file_path, 109 | const std::vector& proxel_section_paths = default_proxel_section_paths, 110 | const std::string& config_search_directory = {} 111 | ); 112 | } 113 | -------------------------------------------------------------------------------- /core/include/superflow/utils/shared_mutexed.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Forsvarets forskningsinstitutt (FFI). All rights reserved. 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace flow 9 | { 10 | /// \brief Exactly the same as Mutexed, except with a `shared_mutex` that 11 | /// allows multiple concurrent reads. 12 | /// It is better suited for scenarios were read operations are frequent and expensive. 13 | /// \see Mutexed 14 | /// \note Note that performing expensive operations while holding a lock is often a bad sign. 15 | /// There may be better ways to solve the problem than using a SharedMutexed. 16 | template 17 | class SharedMutexed : public T 18 | { 19 | public: 20 | /// \brief Construct a new SharedMutexed 21 | /// \tparam Args any types applicable to the constructor of T 22 | /// \param args any arguments applicable to the constructor of T 23 | template 24 | explicit SharedMutexed(Args&& ... args) 25 | : T(std::forward(args)...) 26 | {} 27 | 28 | /// \brief Assign a new value to the mutexed object, interpreted as a T. 29 | /// \note NOT threadsafe! Must be used in a manner like this: 30 | /// SharedMutexed mutexed("hello"); 31 | /// { 32 | /// std::scoped_lock lock{mutexed}; 33 | /// mutexed = "hello"; 34 | /// } 35 | /// \tparam Args any types applicable to the assignment of T 36 | /// \param args any arguments applicable to the assignment of T 37 | /// \see store, write 38 | /// \return 39 | template 40 | SharedMutexed& operator=(Args&& ... args) 41 | { 42 | T& t = *this; 43 | t = {std::forward(args)...}; 44 | return *this; 45 | } 46 | 47 | /// Intentionally slice object from type 'SharedMutexed' to 'T'. 48 | [[nodiscard]] T slice() const 49 | { return *this; } 50 | 51 | /// Intentionally slice object from type 'SharedMutexed' to 'T'. 52 | [[nodiscard]] T& slice() 53 | { return *this; } 54 | 55 | /// \brief Thread safe assignment of a new value to SharedMutexed 56 | /// \tparam Args any types applicable to the creation of T 57 | /// \param args any arguments applicable to the creation of T 58 | template 59 | void store(Args&& ... args) 60 | { 61 | T& t = *this; 62 | std::scoped_lock lock{*this}; 63 | t = {std::forward(args)...}; 64 | } 65 | 66 | /// Thread safe slicing of object from type 'SharedMutexed' to 'T'. 67 | /// Will create a copy, so this method is not applicable to non-copyable 'T's. 68 | /// Concurrent load operations are possible 69 | /// \return 70 | [[nodiscard]] T load() const 71 | { 72 | std::shared_lock lock{*this}; 73 | return slice(); 74 | } 75 | 76 | /// \brief Provides shared access to the data by giving a `const T&` to the reader function. 77 | /// A shared lock on the mutex is acquired prior to calling the supplied reader function, 78 | /// so multiple simultaneous calls to read is possible. 79 | /// \tparam Invokable any invokable type that is invokable with `const T&` as argument 80 | /// \param reader the invocable (a function or lambda, typically) 81 | /// \return whatever the Invokable returns 82 | template 83 | auto read(const Invokable& reader) const 84 | { 85 | static_assert( 86 | std::is_invocable_v, 87 | "The provided `reader` is not invokable with `const T&` as argument" 88 | ); 89 | 90 | std::shared_lock lock{*this}; 91 | return std::invoke(reader, slice()); 92 | } 93 | 94 | /// \brief Provides unique access to the data by giving a `T&` to the writer function. 95 | /// A unique lock on the mutex is acquired prior to calling the supplied writer function, 96 | /// so simultaneous calls to read or write is not possible. 97 | /// \tparam Invokable any invokable type that is invokable with `T&` as argument 98 | /// \param writer the invocable (a function or lambda, typically) 99 | /// \return whatever the Invokable returns 100 | template 101 | auto write(const Invokable& writer) 102 | { 103 | static_assert( 104 | std::is_invocable_v, 105 | "The provided `writer` is not invokable with `T&` as argument" 106 | ); 107 | 108 | std::scoped_lock lock{*this}; 109 | return std::invoke(writer, slice()); 110 | } 111 | 112 | void lock() const ///< C++ named requirement: Mutex 113 | { this->mu_.lock(); } 114 | 115 | void unlock() const ///< C++ named requirement: Mutex 116 | { this->mu_.unlock(); }; 117 | 118 | bool try_lock() const noexcept ///< C++ named requirement: Mutex 119 | { return this->mu_.try_lock(); } 120 | 121 | void lock_shared() const ///< C++ named requirement: SharedMutex 122 | { this->mu_.lock_shared(); } 123 | 124 | void unlock_shared() const ///< C++ named requirement: SharedMutex 125 | { this->mu_.unlock_shared(); }; 126 | 127 | bool try_lock_shared() const noexcept ///< C++ named requirement: SharedMutex 128 | { return this->mu_.try_lock_shared(); } 129 | 130 | private: 131 | mutable std::shared_mutex mu_; 132 | }; 133 | } 134 | --------------------------------------------------------------------------------