├── .gitignore ├── src ├── lib │ ├── protocol_domain_test.cpp │ ├── protocol_exe_data_test.cpp │ ├── protobuf_test.cpp │ ├── error_test.cpp │ ├── bus_slave_setup.cpp │ ├── util_filesystem_test.cpp │ ├── async.cpp │ ├── protobuf.cpp │ ├── net_zmqx_util_test.cpp │ ├── util_filesystem.cpp │ ├── fmi_fmu2_test.cpp │ ├── slave_runner.cpp │ ├── net_zmqx_util.cpp │ ├── fmi_windows.cpp │ ├── bus_execution_manager.cpp │ ├── net_zmqx_messaging_test.cpp │ ├── util_console_test.cpp │ ├── net_zmqx_messaging.cpp │ ├── protocol_exe_data.cpp │ ├── protocol_execution_test.cpp │ ├── protocol_domain.cpp │ ├── error.cpp │ ├── log.cpp │ ├── util_zip_test.cpp │ ├── net_zmqx_sockets_test.cpp │ ├── fmi_fmu1_test.cpp │ ├── bus_slave_controller.cpp │ ├── slave_logging.cpp │ ├── provider_provider.cpp │ ├── bus_variable_io.cpp │ └── util_test.cpp ├── slave │ └── CMakeLists.txt ├── provider │ └── CMakeLists.txt ├── master │ ├── CMakeLists.txt │ └── config_parser.hpp ├── include │ └── coral │ │ ├── protocol │ │ ├── exe_data.hpp │ │ ├── glue.hpp │ │ ├── domain.hpp │ │ └── execution.hpp │ │ ├── bus │ │ ├── slave_setup.hpp │ │ ├── slave_control_messenger_v0.hpp │ │ └── slave_provider_comm.hpp │ │ ├── protobuf.hpp │ │ ├── net │ │ ├── ip.hpp │ │ └── udp.hpp │ │ ├── fmi │ │ ├── glue.hpp │ │ └── windows.hpp │ │ └── util │ │ └── console.hpp └── CMakeLists.txt ├── vcpkg-test-deps.txt ├── tmp_tests ├── 10sec_run.info ├── short_run.info ├── test_fmus.info └── double_spring.info ├── test_data ├── ziptest.zip └── fmi1_cs │ └── identity.fmu ├── external └── fmus │ ├── fmi2_cs │ └── WaterTank_Control.fmu │ └── README.md ├── vcpkg-deps.txt ├── proto ├── net.proto ├── testing.proto ├── exe_data.proto ├── domain.proto ├── CMakeLists.txt ├── model.proto └── execution.proto ├── .editorconfig ├── include └── coral │ ├── master.hpp │ ├── provider.hpp │ ├── fmi.hpp │ ├── slave.hpp │ ├── slave │ ├── exception.hpp │ ├── runner.hpp │ └── logging.hpp │ ├── master │ ├── execution_options.hpp │ └── cluster.hpp │ ├── fmi │ ├── fmu.hpp │ └── importer.hpp │ ├── util │ └── filesystem.hpp │ ├── provider │ ├── slave_creator.hpp │ └── provider.hpp │ └── log.hpp ├── cmake ├── project-config.cmake.in ├── build-info-file.cmake ├── InstallPrerequisites.cmake ├── CopyPrerequisites.cmake ├── FindCPPZMQ.cmake ├── CompatFindProtobuf.cmake ├── FindLIBZIP.cmake ├── ObjectTargetFunctions.cmake └── FindFMILIB.cmake ├── .travis.yml ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.swp 3 | -------------------------------------------------------------------------------- /src/lib/protocol_domain_test.cpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vcpkg-test-deps.txt: -------------------------------------------------------------------------------- 1 | boost-chrono 2 | boost-thread 3 | gtest 4 | -------------------------------------------------------------------------------- /tmp_tests/10sec_run.info: -------------------------------------------------------------------------------- 1 | start 0.0 2 | stop 10.0 3 | step_size 0.01 4 | -------------------------------------------------------------------------------- /tmp_tests/short_run.info: -------------------------------------------------------------------------------- 1 | start 0.0 2 | stop 1.02 3 | step_size 0.01 4 | -------------------------------------------------------------------------------- /test_data/ziptest.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viproma/coral/HEAD/test_data/ziptest.zip -------------------------------------------------------------------------------- /test_data/fmi1_cs/identity.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viproma/coral/HEAD/test_data/fmi1_cs/identity.fmu -------------------------------------------------------------------------------- /external/fmus/fmi2_cs/WaterTank_Control.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viproma/coral/HEAD/external/fmus/fmi2_cs/WaterTank_Control.fmu -------------------------------------------------------------------------------- /vcpkg-deps.txt: -------------------------------------------------------------------------------- 1 | boost-algorithm 2 | boost-bimap 3 | boost-core 4 | boost-filesystem 5 | boost-format 6 | boost-lexical-cast 7 | boost-numeric-conversion 8 | boost-random 9 | boost-range 10 | boost-program-options 11 | boost-property-tree 12 | boost-uuid 13 | cppzmq 14 | fmilib 15 | libzip 16 | protobuf 17 | zeromq 18 | -------------------------------------------------------------------------------- /tmp_tests/test_fmus.info: -------------------------------------------------------------------------------- 1 | name "Test input/output" 2 | descriptions "Two FMUs that test transfer of all variable types" 3 | 4 | slaves { 5 | out { type testOutput } 6 | in { type testInput } 7 | } 8 | 9 | connections { 10 | in.real_in out.real_out 11 | in.int_in out.int_out 12 | in.bool_in out.bool_out 13 | in.string_in out.string_out 14 | } 15 | -------------------------------------------------------------------------------- /proto/net.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2013-present, SINTEF Ocean. 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | syntax="proto2"; 7 | package coralproto.net; 8 | 9 | message SlaveLocator 10 | { 11 | required string control_endpoint = 1; 12 | required string data_pub_endpoint = 2; 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; Cross-editor configuration file (http://editorconfig.org/) 2 | ; 3 | ; Visual Studio: Please install the EditorConfig extension. 4 | ; Notepad++: Please install the EditorConfig plugin. 5 | ; 6 | ; Alternatively, manually change editor settings to match the 7 | ; following. 8 | 9 | root = true ; This is the topmost .editorconfig file 10 | 11 | [*] 12 | end_of_line = lf 13 | insert_final_newline = true 14 | indent_style = space 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /proto/testing.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2013-present, SINTEF Ocean. 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | // The message types in this file are only used for testing purposes. 7 | syntax="proto2"; 8 | package coralproto.testing; 9 | 10 | message IntString 11 | { 12 | optional int32 i = 1; 13 | optional string s = 2; 14 | } 15 | -------------------------------------------------------------------------------- /proto/exe_data.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2013-present, SINTEF Ocean. 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | syntax="proto2"; 7 | package coralproto.exe_data; 8 | 9 | import "model.proto"; 10 | 11 | 12 | // A timestamped variable value 13 | message TimestampedValue 14 | { 15 | required int32 timestep_id = 1; 16 | required model.ScalarValue value = 2; 17 | } 18 | -------------------------------------------------------------------------------- /src/slave/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (_target "coralslave") 2 | add_executable (${_target} "main.cpp") 3 | target_link_libraries (${_target} 4 | PRIVATE "coral" 5 | ${FMILIB_LIBRARIES} 6 | ${CPPZMQ_LIBRARIES} 7 | ) 8 | target_include_directories (${_target} 9 | PRIVATE ${publicHeaderDir} 10 | ${privateHeaderDir}) 11 | install (TARGETS ${_target} ${targetInstallDestinations}) 12 | 13 | if (CORAL_INSTALL_DEPENDENCIES) 14 | include (InstallPrerequisites) 15 | install_prerequisites (${_target}) 16 | endif () 17 | -------------------------------------------------------------------------------- /src/provider/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (_target "coralslaveprovider") 2 | add_executable (${_target} "main.cpp") 3 | target_link_libraries (${_target} 4 | PRIVATE "coral" 5 | ${FMILIB_LIBRARIES} 6 | ${CPPZMQ_LIBRARIES} 7 | ) 8 | target_include_directories (${_target} 9 | PRIVATE ${publicHeaderDir} 10 | ${privateHeaderDir}) 11 | install (TARGETS ${_target} ${targetInstallDestinations}) 12 | 13 | if (CORAL_INSTALL_DEPENDENCIES) 14 | include (InstallPrerequisites) 15 | install_prerequisites (${_target}) 16 | endif () 17 | -------------------------------------------------------------------------------- /src/master/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (_headers 2 | "config_parser.hpp" 3 | ) 4 | set (_sources 5 | "config_parser.cpp" 6 | "main.cpp" 7 | ) 8 | 9 | set (_target "coralmaster") 10 | add_executable (${_target} ${_headers} ${_sources}) 11 | target_link_libraries (${_target} PRIVATE "coral" ${CPPZMQ_LIBRARIES}) 12 | target_include_directories (${_target} 13 | PRIVATE ${publicHeaderDir} 14 | ${privateHeaderDir}) 15 | install (TARGETS ${_target} ${targetInstallDestinations}) 16 | 17 | if (CORAL_INSTALL_DEPENDENCIES) 18 | include (InstallPrerequisites) 19 | install_prerequisites (${_target}) 20 | endif () 21 | -------------------------------------------------------------------------------- /src/lib/protocol_exe_data_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace ed = coral::protocol::exe_data; 5 | 6 | TEST(coral_protocol_exe_data, CreateAndParse) 7 | { 8 | ed::Message msg; 9 | msg.variable = coral::model::Variable(123, 456); 10 | msg.value = 3.14; 11 | msg.timestepID = 100; 12 | 13 | std::vector raw; 14 | ed::CreateMessage(msg, raw); 15 | 16 | const auto msg2 = ed::ParseMessage(raw); 17 | EXPECT_EQ(msg.variable, msg2.variable); 18 | EXPECT_EQ(msg.value, msg2.value); 19 | EXPECT_EQ(msg.timestepID, msg2.timestepID); 20 | } 21 | -------------------------------------------------------------------------------- /external/fmus/README.md: -------------------------------------------------------------------------------- 1 | The FMUs in this directory (including its subdirectories) were downloaded 2 | from the [FMI web site] (see "Test FMUs"). They were not created by SINTEF 3 | Ocean or the Coral contributors, and have nothing to do with the Coral 4 | software or the ViProMa project. 5 | 6 | The FMUs are: 7 | 8 | - [WaterTank_control.fmu], an FMI 2.0 co-simulation FMU created by OpenModelica. 9 | 10 | For more information about these, follow the links above. 11 | 12 | [FMI web site]: https://fmi-standard.org/downloads 13 | [WaterTank_control.fmu]: https://trac.fmi-standard.org/browser/branches/public/Test_FMUs/FMI_2.0/CoSimulation/linux32/OpenModelica/v1.11.0/WaterTank_Control 14 | -------------------------------------------------------------------------------- /src/lib/protobuf_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifdef _MSC_VER 5 | # pragma warning(push, 0) 6 | #endif 7 | #include 8 | #ifdef _MSC_VER 9 | # pragma warning(pop) 10 | #endif 11 | 12 | 13 | using namespace coral::protobuf; 14 | 15 | TEST(coral_protobuf, SerializeAndParse) 16 | { 17 | coralproto::testing::IntString pbSrc; 18 | pbSrc.set_i(123); 19 | pbSrc.set_s("Hello World!"); 20 | 21 | zmq::message_t zMsg; 22 | SerializeToFrame(pbSrc, zMsg); 23 | 24 | coralproto::testing::IntString pbTgt; 25 | ParseFromFrame(zMsg, pbTgt); 26 | EXPECT_EQ(123, pbTgt.i()); 27 | EXPECT_EQ("Hello World!", pbTgt.s()); 28 | } 29 | -------------------------------------------------------------------------------- /include/coral/master.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Main module header for coral::master. Includes all headers in `coral/master/`. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_MASTER_HPP_INCLUDED 11 | #define CORAL_MASTER_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | 16 | 17 | namespace coral 18 | { 19 | /// API for use by applications that implement a simulation master. 20 | namespace master 21 | { 22 | } 23 | } 24 | #endif // header guard 25 | -------------------------------------------------------------------------------- /include/coral/provider.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Main module header for coral::provider. 4 | Includes all headers in `coral/provider/`. 5 | \copyright 6 | Copyright 2013-present, SINTEF Ocean. 7 | This Source Code Form is subject to the terms of the Mozilla Public 8 | License, v. 2.0. If a copy of the MPL was not distributed with this 9 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | */ 11 | #ifndef CORAL_PROVIDER_HPP_INCLUDED 12 | #define CORAL_PROVIDER_HPP_INCLUDED 13 | 14 | #include 15 | #include 16 | 17 | 18 | namespace coral 19 | { 20 | /// API for use by applications that implement a slave provider. 21 | namespace provider 22 | { 23 | } 24 | } 25 | #endif // header guard 26 | -------------------------------------------------------------------------------- /include/coral/fmi.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Main module header for coral::fmi. Includes all headers in `coral/fmi/`. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_FMI_HPP_INCLUDED 11 | #define CORAL_FMI_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | namespace coral 20 | { 21 | /// Classes and functions related to the Functional Mock-up Interface (FMI). 22 | namespace fmi 23 | { 24 | } 25 | } 26 | #endif // header guard 27 | -------------------------------------------------------------------------------- /cmake/project-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | set (_old_PACKAGE_PREFIX_DIR "${PACKAGE_PREFIX_DIR}") 3 | set (_old_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) 4 | set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PACKAGE_PREFIX_DIR}/cmake" "${CMAKE_CURRENT_LIST_DIR}") 5 | find_package (Boost REQUIRED COMPONENTS filesystem program_options random chrono thread) 6 | find_package (ZeroMQ REQUIRED) 7 | find_package (FMILIB REQUIRED) 8 | find_package (LIBZIP REQUIRED) 9 | include ("CompatFindProtobuf") 10 | set (CMAKE_MODULE_PATH ${_old_CMAKE_MODULE_PATH}) 11 | unset (_old_CMAKE_MODULE_PATH) 12 | set (PACKAGE_PREFIX_DIR "${_old_PACKAGE_PREFIX_DIR}") 13 | unset (_old_PACKAGE_PREFIX_DIR) 14 | 15 | include ("@PACKAGE_exportFileConfig@") 16 | set (coral_LIBRARIES "coral") 17 | set_and_check (coral_INCLUDE_DIRS "@PACKAGE_includeDirsConfig@") 18 | -------------------------------------------------------------------------------- /include/coral/slave.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Main module header for coral::slave. Includes all headers in `coral/slave/`. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_SLAVE_HPP_INCLUDED 11 | #define CORAL_SLAVE_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | namespace coral 20 | { 21 | /// API for use by applications that implement a simulation slave. 22 | namespace slave 23 | { 24 | } 25 | } 26 | #endif // header guard 27 | -------------------------------------------------------------------------------- /proto/domain.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2013-present, SINTEF Ocean. 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | syntax="proto2"; 7 | package coralproto.domain; 8 | 9 | import "model.proto"; 10 | import "net.proto"; 11 | 12 | 13 | message SlaveTypeInfo 14 | { 15 | required model.SlaveTypeDescription description = 1; 16 | } 17 | 18 | message SlaveTypeList 19 | { 20 | repeated SlaveTypeInfo slave_type = 1; 21 | } 22 | 23 | message InstantiateSlaveData 24 | { 25 | required string slave_type_uuid = 1; 26 | 27 | // The special value -1 means "never" 28 | required int32 timeout_ms = 2; 29 | } 30 | 31 | message InstantiateSlaveReply 32 | { 33 | required net.SlaveLocator slave_locator = 1; 34 | } 35 | 36 | message Error 37 | { 38 | optional string message = 1; 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/error_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | 7 | TEST(coral_error, ErrnoMessage) 8 | { 9 | EXPECT_TRUE(coral::error::ErrnoMessage("", 0).empty()); 10 | EXPECT_EQ("foo", coral::error::ErrnoMessage("foo", 0)); 11 | const auto m1 = coral::error::ErrnoMessage("", EINVAL); 12 | EXPECT_LT(m1.find("nvalid"), std::string::npos); 13 | const auto m2 = coral::error::ErrnoMessage("foo", EINVAL); 14 | EXPECT_LT(m2.find("foo"), std::string::npos); 15 | EXPECT_LT(m2.find(m1), std::string::npos); 16 | } 17 | 18 | TEST(coral_error, sim_error) 19 | { 20 | // Test implicit conversion from sim_error to error_code 21 | std::error_code code = coral::error::sim_error::cannot_perform_timestep; 22 | EXPECT_TRUE(code == coral::error::sim_error::cannot_perform_timestep); 23 | EXPECT_EQ(coral::error::sim_category(), code.category()); 24 | EXPECT_NE(std::string::npos, code.message().find("time step")); 25 | } 26 | -------------------------------------------------------------------------------- /cmake/build-info-file.cmake: -------------------------------------------------------------------------------- 1 | # This file generates a custom target which creates a text file that 2 | # contains information about the current build. The following info 3 | # is included: 4 | # 5 | # - Hash and timestamp of the last Git commit 6 | # - The build type 7 | set (_buildInfoFile "${CMAKE_BINARY_DIR}/build-info.txt") 8 | 9 | find_package(Git) 10 | if(Git_FOUND OR GIT_FOUND) 11 | set(_gitCommand "${GIT_EXECUTABLE}" "log" "-n1" "--pretty=format:Git commit: %H %ci%n") 12 | else() 13 | set(_gitCommand "${CMAKE_COMMAND}" "-E" "echo" "Git commit: (undetermined)") 14 | endif() 15 | 16 | add_custom_command( 17 | OUTPUT "${_buildInfoFile}" 18 | COMMAND ${_gitCommand} ">" "${_buildInfoFile}" 19 | COMMAND "${CMAKE_COMMAND}" "-E" "echo" "Build type: $${CMAKE_BUILD_TYPE}" ">>" "${_buildInfoFile}" 20 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 21 | COMMENT "Generating build information file" 22 | VERBATIM 23 | ) 24 | add_custom_target("build-info" ALL DEPENDS "${_buildInfoFile}") 25 | install(FILES "${_buildInfoFile}" DESTINATION "${docInstallDir}") 26 | -------------------------------------------------------------------------------- /src/lib/bus_slave_setup.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | 13 | namespace coral 14 | { 15 | namespace bus 16 | { 17 | 18 | 19 | SlaveSetup::SlaveSetup() 20 | : startTime(std::numeric_limits::signaling_NaN()), 21 | stopTime(std::numeric_limits::signaling_NaN()) 22 | { 23 | } 24 | 25 | 26 | SlaveSetup::SlaveSetup( 27 | coral::model::TimePoint startTime_, 28 | coral::model::TimePoint stopTime_, 29 | const std::string& executionName_, 30 | std::chrono::milliseconds variableRecvTimeout_) 31 | : startTime(startTime_), 32 | stopTime(stopTime_), 33 | executionName(executionName_), 34 | variableRecvTimeout(variableRecvTimeout_) 35 | { 36 | assert(startTime <= stopTime); 37 | } 38 | 39 | 40 | }} // namespace 41 | -------------------------------------------------------------------------------- /src/lib/util_filesystem_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | TEST(coral_util_filesystem, TempDir) 8 | { 9 | namespace fs = boost::filesystem; 10 | fs::path d; 11 | { 12 | auto tmp = coral::util::TempDir(); 13 | d = tmp.Path(); 14 | ASSERT_FALSE(d.empty()); 15 | ASSERT_TRUE(fs::exists(d)); 16 | EXPECT_TRUE(fs::is_directory(d)); 17 | EXPECT_TRUE(fs::is_empty(d)); 18 | 19 | auto tmp2 = std::move(tmp); 20 | EXPECT_TRUE(tmp.Path().empty()); 21 | EXPECT_EQ(d, tmp2.Path()); 22 | EXPECT_TRUE(fs::exists(d)); 23 | 24 | auto tmp3 = coral::util::TempDir(); 25 | const auto d3 = tmp3.Path(); 26 | EXPECT_TRUE(fs::exists(d3)); 27 | ASSERT_FALSE(fs::equivalent(d, d3)); 28 | 29 | tmp3 = std::move(tmp2); 30 | EXPECT_TRUE(tmp2.Path().empty()); 31 | EXPECT_FALSE(fs::exists(d3)); 32 | EXPECT_EQ(d, tmp3.Path()); 33 | EXPECT_TRUE(fs::exists(d)); 34 | } 35 | EXPECT_FALSE(fs::exists(d)); 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/async.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | 10 | namespace coral 11 | { 12 | namespace async 13 | { 14 | 15 | 16 | // ============================================================================= 17 | // CommThreadDead 18 | // ============================================================================= 19 | 20 | // TODO: Reimplement this stuff in terms of std::nested_exception when that 21 | // becomes available in Visual Studio. 22 | 23 | CommThreadDead::CommThreadDead(std::exception_ptr originalException) noexcept 24 | : m_originalException{originalException} 25 | { 26 | assert(m_originalException); 27 | } 28 | 29 | 30 | std::exception_ptr CommThreadDead::OriginalException() const noexcept 31 | { 32 | return m_originalException; 33 | } 34 | 35 | 36 | const char* CommThreadDead::what() const noexcept 37 | { 38 | return "Background communication thread terminated due to an exception"; 39 | } 40 | 41 | 42 | }} 43 | -------------------------------------------------------------------------------- /cmake/InstallPrerequisites.cmake: -------------------------------------------------------------------------------- 1 | # Installs a target's prerequisites (i.e., the shared libraries it 2 | # depends on). 3 | function(install_prerequisites target) 4 | add_custom_command( 5 | TARGET "${target}" 6 | POST_BUILD 7 | COMMAND 8 | "${CMAKE_COMMAND}" 9 | "-E" 10 | "make_directory" 11 | "${CMAKE_BINARY_DIR}/prerequisites/${target}/$" 12 | COMMAND 13 | "${CMAKE_COMMAND}" 14 | "-DEXECUTABLE=$" 15 | "-DTARGET_DIR=${CMAKE_BINARY_DIR}/prerequisites/${target}/$" 16 | "-DSEARCH_DIRS=${CMAKE_PREFIX_PATH}" 17 | "-P" "${CMAKE_SOURCE_DIR}/cmake/CopyPrerequisites.cmake" 18 | VERBATIM) 19 | install( 20 | DIRECTORY "${CMAKE_BINARY_DIR}/prerequisites/${target}/$/" 21 | DESTINATION "lib" 22 | USE_SOURCE_PERMISSIONS 23 | FILES_MATCHING PATTERN "*.so*") 24 | install( 25 | DIRECTORY "${CMAKE_BINARY_DIR}/prerequisites/${target}/$/" 26 | DESTINATION "bin" 27 | USE_SOURCE_PERMISSIONS 28 | FILES_MATCHING PATTERN "*.dll") 29 | endfunction(install_prerequisites) 30 | -------------------------------------------------------------------------------- /tmp_tests/double_spring.info: -------------------------------------------------------------------------------- 1 | name "Double spring/mass system" 2 | description "A mass connected to a spring connected to a mass connected to a spring connected to the wall..." 3 | 4 | slaves { 5 | spring1 { 6 | type no.viproma.demo.spring_1d 7 | init { 8 | length 2.0 9 | pos_b 1.0 10 | } 11 | } 12 | mass1 { 13 | type no.viproma.demo.mass_1d 14 | init { 15 | pos 1.0 16 | } 17 | } 18 | spring2 { 19 | type no.viproma.demo.spring_1d 20 | init { 21 | length 2.0 22 | pos_a 1.0 23 | pos_b 3.0 24 | } 25 | } 26 | mass2 { 27 | type no.viproma.demo.mass_1d 28 | init { 29 | pos 3.0 30 | } 31 | } 32 | } 33 | 34 | connections { 35 | spring1.pos_b mass1.pos 36 | mass1.force1 spring1.force_b 37 | 38 | spring2.pos_b mass2.pos 39 | mass2.force1 spring2.force_b 40 | 41 | spring2.pos_a mass1.pos 42 | mass1.force2 spring2.force_a 43 | } 44 | 45 | scenario { 46 | 5 { 47 | mass1.mass 0.1 48 | spring1.stiffness 10.0 49 | } 50 | 9 { 51 | mass1.pos 1.0 52 | mass2.pos 3.0 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/protobuf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | 11 | 12 | void coral::protobuf::SerializeToFrame( 13 | const google::protobuf::MessageLite& source, 14 | zmq::message_t& target) 15 | { 16 | const auto size = source.ByteSize(); 17 | assert (size >= 0); 18 | target.rebuild(size); 19 | if (!source.SerializeToArray(target.data(), size)) { 20 | throw SerializationException("Failed to serialize message"); 21 | } 22 | } 23 | 24 | 25 | void coral::protobuf::ParseFromFrame( 26 | const zmq::message_t& source, 27 | google::protobuf::MessageLite& target) 28 | { 29 | if (!target.ParseFromArray( 30 | source.data(), 31 | boost::numeric_cast(source.size()))) { 32 | throw SerializationException("Failed to parse message"); 33 | } 34 | } 35 | 36 | 37 | coral::protobuf::SerializationException::SerializationException(const std::string& msg) 38 | : std::runtime_error(msg) 39 | { 40 | } 41 | -------------------------------------------------------------------------------- /cmake/CopyPrerequisites.cmake: -------------------------------------------------------------------------------- 1 | # Copies an executable file's prerequisites to some directory 2 | # 3 | # Usage: 4 | # cmake 5 | # -DEXECUTABLE="path/to/executable" 6 | # -DTARGET_DIR="path/to/target/dir" 7 | # -DSEARCH_DIRS="list/of;dirs/to/search" 8 | # -P CopyPrerequisites.cmake 9 | 10 | # Ensure we use an absolute path to the executable. 11 | if(NOT EXISTS "${EXECUTABLE}") 12 | message(FATAL_ERROR "EXECUTABLE does not exist (value: ${EXECUTABLE})") 13 | elseif(IS_ABSOLUTE "${EXECUTABLE}") 14 | set(absExecutable "${EXECUTABLE}") 15 | else() 16 | set(absExecutable "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE}") 17 | endif() 18 | 19 | if (NOT IS_DIRECTORY "${TARGET_DIR}") 20 | message(FATAL_ERROR "TARGET_DIR is not a directory (value: ${TARGET_DIR})") 21 | endif() 22 | 23 | set(searchDirs) 24 | foreach(d IN LISTS SEARCH_DIRS) 25 | list(APPEND searchDirs "${d}" "${d}/lib" "${d}/bin") 26 | endforeach() 27 | 28 | include(GetPrerequisites) 29 | get_prerequisites("${absExecutable}" prerequisites 1 1 "" "${searchDirs}") 30 | foreach(p IN LISTS prerequisites) 31 | gp_resolve_item("${absExecutable}" "${p}" "" "${searchDirs}" fullPath) 32 | message(STATUS "Copying '${fullPath}' to '${TARGET_DIR}'") 33 | file(COPY "${fullPath}" DESTINATION "${TARGET_DIR}" USE_SOURCE_PERMISSIONS) 34 | endforeach() 35 | -------------------------------------------------------------------------------- /src/include/coral/protocol/exe_data.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Module header for coral::protocol::exe_data 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_PROTOCOL_EXE_DATA_HPP 11 | #define CORAL_PROTOCOL_EXE_DATA_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | namespace coral 19 | { 20 | namespace protocol 21 | { 22 | 23 | /** 24 | \brief Functions for constructing and parsing messages sent on the execution 25 | data channel, i.e. variable values. 26 | */ 27 | namespace exe_data 28 | { 29 | const size_t HEADER_SIZE = 6; 30 | 31 | struct Message 32 | { 33 | coral::model::Variable variable; 34 | coral::model::StepID timestepID; 35 | coral::model::ScalarValue value; 36 | }; 37 | 38 | Message ParseMessage(const std::vector& rawMsg); 39 | 40 | void CreateMessage(const Message& message, std::vector& rawOut); 41 | 42 | void Subscribe(zmq::socket_t& socket, const coral::model::Variable& variable); 43 | 44 | void Unsubscribe(zmq::socket_t& socket, const coral::model::Variable& variable); 45 | 46 | }}} // namespace 47 | #endif // header guard 48 | -------------------------------------------------------------------------------- /proto/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Creates a library target that contains the generated protocol buffers code. 2 | set (protoFiles 3 | "domain.proto" 4 | "execution.proto" 5 | "exe_data.proto" 6 | "model.proto" 7 | "net.proto" 8 | "testing.proto" 9 | ) 10 | 11 | # We want the files to appear in the Visual Studio UI, so we add them 12 | # as source files in the library below, but prevent compilation with 13 | # a CMake property. 14 | set_source_files_properties (${protoFiles} PROPERTIES HEADER_FILE_ONLY TRUE) 15 | source_group("Protobuf Files" FILES ${protoFiles}) 16 | 17 | if (COMMAND protobuf_generate) 18 | protobuf_generate(LANG cpp OUT_VAR sources PROTOS ${protoFiles}) 19 | set (headers) 20 | elseif (COMMAND protobuf_generate_cpp) 21 | protobuf_generate_cpp(sources headers ${protoFiles}) 22 | elseif (COMMAND PROTOBUF_GENERATE_CPP) 23 | PROTOBUF_GENERATE_CPP(sources headers ${protoFiles}) 24 | else () 25 | message(FATAL_ERROR "No protobuf_generate function available") 26 | endif () 27 | 28 | set(target "coralproto") 29 | add_library(${target} OBJECT ${sources} ${headers} ${protoFiles}) 30 | if (MSVC) 31 | target_compile_options(${target} PRIVATE "/W0") 32 | endif () 33 | include(ObjectTargetFunctions) 34 | object_target_link_libraries(${target} PUBLIC protobuf::libprotobuf) 35 | target_include_directories(${target} INTERFACE "${CMAKE_CURRENT_BINARY_DIR}") 36 | if (UNIX) 37 | target_compile_options (${target} PRIVATE "-fPIC") 38 | endif() 39 | -------------------------------------------------------------------------------- /src/include/coral/bus/slave_setup.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Defines the coral::bus::SlaveSetup class 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_BUS_SLAVE_SETUP_HPP 11 | #define CORAL_BUS_SLAVE_SETUP_HPP 12 | 13 | #include 14 | #include 15 | 16 | 17 | namespace coral 18 | { 19 | namespace bus 20 | { 21 | 22 | 23 | /** 24 | \brief Configuration data which is sent to each slave as they are added 25 | to the simulation. 26 | */ 27 | struct SlaveSetup 28 | { 29 | SlaveSetup(); 30 | SlaveSetup( 31 | coral::model::TimePoint startTime, 32 | coral::model::TimePoint stopTime, 33 | const std::string& executionName, 34 | std::chrono::milliseconds variableRecvTimeout); 35 | coral::model::TimePoint startTime; 36 | coral::model::TimePoint stopTime; 37 | std::string executionName; 38 | 39 | /** 40 | \brief How long a slave should wait to receive variable values from 41 | other slaves before assuming that the connection is broken or 42 | that a subscription has failed to take effect. 43 | */ 44 | std::chrono::milliseconds variableRecvTimeout; 45 | }; 46 | 47 | 48 | }} // namespace 49 | #endif // header guard 50 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Compiler/language settings that apply to only our own code (and not, say, 2 | # the code generated by the Protocol Buffers compiler). 3 | if (CMAKE_COMPILER_IS_GNUCXX) 4 | add_compile_options("-Wall") # Enable all warnings 5 | add_compile_options("-Wno-unused-function") # ...except this one. 6 | add_compile_options("-Werror") # Treat all warnings as errors. 7 | elseif (MSVC) 8 | add_compile_options("/W3") # Enable warning level 3 9 | add_compile_options("/WX") # Treat all warnings as errors. 10 | endif() 11 | 12 | # Same, only macro definitions. 13 | if (CORAL_ENABLE_DEBUG_LOGGING OR CORAL_ENABLE_TRACE_LOGGING) 14 | set_property (DIRECTORY APPEND PROPERTY 15 | COMPILE_DEFINITIONS "CORAL_LOG_DEBUG_ENABLED") 16 | else () 17 | set_property (DIRECTORY APPEND PROPERTY 18 | COMPILE_DEFINITIONS "$<$:CORAL_LOG_DEBUG_ENABLED>") 19 | endif () 20 | if (CORAL_ENABLE_TRACE_LOGGING) 21 | set_property (DIRECTORY APPEND PROPERTY 22 | COMPILE_DEFINITIONS "CORAL_LOG_TRACE_ENABLED") 23 | endif () 24 | # The directory in which private headers are located. 25 | # These are the headers that should be available to all submodules in this 26 | # project, but which should not be installed as part of the public API. 27 | set (privateHeaderDir "${CMAKE_CURRENT_SOURCE_DIR}/include") 28 | 29 | add_subdirectory ("lib") 30 | add_subdirectory ("master") 31 | add_subdirectory ("provider") 32 | add_subdirectory ("slave") 33 | -------------------------------------------------------------------------------- /cmake/FindCPPZMQ.cmake: -------------------------------------------------------------------------------- 1 | # - Finds zmq.hpp 2 | # 3 | # A CMake variable or environment variable named CPPZMQ_DIR may be used to 4 | # provide an explicit location. (If both are set, the CMake variable takes 5 | # precedence.) 6 | # 7 | # The following variables are set if zmq.hpp is found: 8 | # 9 | # CPPZMQ_FOUND - Set to TRUE 10 | # CPPZMQ_LIBRARIES - The name of an INTERFACE IMPORTED target. 11 | # CPPZMQ_INCLUDE_DIRS - The directory which contains zmq.hpp. This is also 12 | # added as a dependency to the INTERFACE IMPORTED 13 | # target. 14 | # 15 | # If zmq.hpp is not found, CPPZMQ_FOUND is set to false. 16 | cmake_minimum_required (VERSION 3.0.0) 17 | set (CPPZMQ_DIR "${CPPZMQ_DIR}" CACHE PATH "The directory that contains zmq.hpp") 18 | 19 | if (NOT ZeroMQ_FOUND) 20 | find_package (ZeroMQ REQUIRED) 21 | endif () 22 | 23 | find_path(CPPZMQ_INCLUDE_DIRS "zmq.hpp" 24 | HINTS "${ZeroMQ_INCLUDE_DIR}" 25 | PATHS "${CPPZMQ_DIR}" "$ENV{CPPZMQ_DIR}" 26 | PATH_SUFFIXES "include" "cppzmq" 27 | ) 28 | mark_as_advanced (CPPZMQ_INCLUDE_DIRS) 29 | 30 | if (CPPZMQ_INCLUDE_DIRS) 31 | add_library ("cppzmq" INTERFACE IMPORTED) 32 | set_target_properties("cppzmq" PROPERTIES 33 | INTERFACE_INCLUDE_DIRECTORIES "${CPPZMQ_INCLUDE_DIRS}" 34 | INTERFACE_LINK_LIBRARIES "libzmq" 35 | ) 36 | set (CPPZMQ_LIBRARIES "cppzmq") 37 | endif () 38 | 39 | # Standard find_package stuff. 40 | include (FindPackageHandleStandardArgs) 41 | find_package_handle_standard_args (CPPZMQ DEFAULT_MSG CPPZMQ_LIBRARIES CPPZMQ_INCLUDE_DIRS) 42 | -------------------------------------------------------------------------------- /src/include/coral/protobuf.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Main header file for coral::protobuf. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_PROTOBUF_HPP 11 | #define CORAL_PROTOBUF_HPP 12 | 13 | #include 14 | #include 15 | 16 | #ifdef _MSC_VER 17 | # pragma warning(push, 0) 18 | #endif 19 | #include 20 | #ifdef _MSC_VER 21 | # pragma warning(pop) 22 | #endif 23 | 24 | 25 | namespace coral 26 | { 27 | 28 | /// Functions for using Protobuf with ZMQ. 29 | namespace protobuf 30 | { 31 | 32 | 33 | /** 34 | \brief Serializes a Protobuf message into a ZMQ message. 35 | 36 | Any existing contents of `target` will be replaced. 37 | 38 | \throws SerializationException on failure. 39 | */ 40 | void SerializeToFrame( 41 | const google::protobuf::MessageLite& source, 42 | zmq::message_t& target); 43 | 44 | 45 | /** 46 | \brief Deserializes a Protobuf message from a ZMQ message. 47 | \throws SerializationException on failure. 48 | */ 49 | void ParseFromFrame( 50 | const zmq::message_t& source, 51 | google::protobuf::MessageLite& target); 52 | 53 | 54 | /// Exception that signals failure to serialize or deserialize a message. 55 | class SerializationException : public std::runtime_error 56 | { 57 | public: 58 | explicit SerializationException(const std::string& msg); 59 | }; 60 | 61 | 62 | }} // namespace 63 | #endif // header guard 64 | -------------------------------------------------------------------------------- /include/coral/slave/exception.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Exceptions specific to coral::slave. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_SLAVE_EXCEPTION_HPP_INCLUDED 11 | #define CORAL_SLAVE_EXCEPTION_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | namespace coral 20 | { 21 | namespace slave 22 | { 23 | 24 | 25 | /// Thrown when a communications timeout is reached. 26 | class TimeoutException : public std::runtime_error 27 | { 28 | public: 29 | explicit TimeoutException(std::chrono::milliseconds timeoutDuration) noexcept 30 | : std::runtime_error("Slave timed out due to lack of communication"), 31 | m_timeoutDuration(timeoutDuration) 32 | { 33 | } 34 | 35 | TimeoutException( 36 | const std::string& message, 37 | std::chrono::milliseconds timeoutDuration) noexcept 38 | : std::runtime_error( 39 | message + " (timeout: " + std::to_string(timeoutDuration.count()) + " ms)") 40 | , m_timeoutDuration(timeoutDuration) 41 | { 42 | } 43 | 44 | /// The duration of the timeout that was reached. 45 | std::chrono::milliseconds TimeoutDuration() const noexcept 46 | { 47 | return m_timeoutDuration; 48 | } 49 | 50 | private: 51 | std::chrono::milliseconds m_timeoutDuration; 52 | }; 53 | 54 | 55 | }} // namespace 56 | #endif // header guard 57 | -------------------------------------------------------------------------------- /include/coral/slave/runner.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Defines the coral::slave::Runner class and related functionality. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_SLAVE_RUNNER_HPP 11 | #define CORAL_SLAVE_RUNNER_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | namespace coral 21 | { 22 | 23 | // Forward declarations to avoid dependencies on internal classes. 24 | namespace bus { class SlaveAgent; } 25 | namespace net { class Reactor; } 26 | 27 | namespace slave 28 | { 29 | 30 | 31 | /// A class for running a slave instance 32 | class Runner 33 | { 34 | public: 35 | Runner( 36 | std::shared_ptr slaveInstance, 37 | const coral::net::Endpoint& controlEndpoint, 38 | const coral::net::Endpoint& dataPubEndpoint, 39 | std::chrono::seconds commTimeout); 40 | 41 | Runner(Runner&&) noexcept; 42 | 43 | Runner& operator=(Runner&&) noexcept; 44 | 45 | ~Runner(); 46 | 47 | coral::net::Endpoint BoundControlEndpoint(); 48 | coral::net::Endpoint BoundDataPubEndpoint(); 49 | 50 | void Run(); 51 | 52 | private: 53 | std::shared_ptr m_slaveInstance; 54 | std::unique_ptr m_reactor; 55 | std::unique_ptr m_slaveAgent; 56 | }; 57 | 58 | 59 | }} // namespace 60 | #endif // header guard 61 | -------------------------------------------------------------------------------- /src/include/coral/net/ip.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Internal module header for coral::net::ip. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_NET_IP_HPP 11 | #define CORAL_NET_IP_HPP 12 | 13 | #ifdef _WIN32 14 | # include 15 | #else 16 | # include 17 | #endif 18 | 19 | #include 20 | #include 21 | 22 | 23 | namespace coral 24 | { 25 | namespace net 26 | { 27 | namespace ip 28 | { 29 | 30 | 31 | /// Information about a network interface. 32 | struct NetworkInterfaceInfo 33 | { 34 | /// Interface name 35 | std::string name; 36 | 37 | /// IP address 38 | in_addr address; 39 | 40 | /// Subnet mask 41 | in_addr netmask; 42 | 43 | /// Broadcast address 44 | in_addr broadcastAddress; 45 | }; 46 | 47 | /** 48 | \brief Returns information about available active network interfaces. 49 | 50 | \note 51 | On Windows, the loopback interface (typically 127.0.0.1) does not have 52 | a name, so the NetworkInterfaceInfo::name field will be empty. 53 | 54 | \throws std::runtime_error on failure. 55 | */ 56 | std::vector GetNetworkInterfaces(); 57 | 58 | 59 | /// Converts an IP address to a string in dotted-decimal format. 60 | std::string IPAddressToString(in_addr address); 61 | 62 | 63 | /// Converts an IP address in dotted-decimal string format to an in_addr. 64 | in_addr StringToIPAddress(const std::string& address); 65 | 66 | 67 | }}} // namespace 68 | #endif // header guard 69 | -------------------------------------------------------------------------------- /src/lib/net_zmqx_util_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // std::bad_cast 4 | 5 | #include 6 | #include 7 | 8 | using namespace coral::net::zmqx; 9 | 10 | 11 | TEST(coral_net, GlobalContext) 12 | { 13 | auto& c1 = GlobalContext(); 14 | auto& c2 = GlobalContext(); 15 | EXPECT_EQ(&c1, &c2); 16 | } 17 | 18 | 19 | TEST(coral_net, BindToEphemeralPort) 20 | { 21 | zmq::context_t ctx; 22 | auto srv = zmq::socket_t(ctx, ZMQ_REP); 23 | const auto port = BindToEphemeralPort(srv); 24 | #ifdef _WIN32 25 | // Windows versions through XP use the port range 1025-5000 (but later 26 | // versions use the IANA recommended range, 49152-65535). 27 | EXPECT_GE(port, 1025); 28 | #else 29 | // Many Linux kernels use the port range 32768-61000. 30 | EXPECT_GE(port, 32768); 31 | #endif 32 | const auto endpoint = "tcp://localhost:" + std::to_string(port); 33 | auto cli = zmq::socket_t(ctx, ZMQ_REQ); 34 | cli.connect(endpoint.c_str()); 35 | cli.send("hello", 6); // sixth char is the terminating zero 36 | char buf[6]; 37 | srv.recv(buf, 6); 38 | EXPECT_STREQ("hello", buf); 39 | } 40 | 41 | 42 | TEST(coral_net, LastEndpoint) 43 | { 44 | zmq::context_t ctx; 45 | zmq::socket_t sck(ctx, ZMQ_REP); 46 | EXPECT_TRUE(LastEndpoint(sck).empty()); 47 | sck.bind("inproc://coral_net_LastEndpoint_test"); 48 | EXPECT_EQ("inproc://coral_net_LastEndpoint_test", LastEndpoint(sck)); 49 | } 50 | 51 | 52 | TEST(coral_net, EndpointPort) 53 | { 54 | EXPECT_EQ(1234, EndpointPort("tcp://some.addr:1234")); 55 | EXPECT_THROW(EndpointPort("Hello World!"), std::invalid_argument); 56 | EXPECT_THROW(EndpointPort("tcp://some.addr:port"), std::bad_cast); 57 | } 58 | -------------------------------------------------------------------------------- /cmake/CompatFindProtobuf.cmake: -------------------------------------------------------------------------------- 1 | # First try to locate Protobuf using its own config file (only works for 2 | # v3.4 upwards), and if that fails, use the CMake-supplied find module 3 | # and define an import target based on its results. 4 | cmake_minimum_required(VERSION 3.6) 5 | 6 | set (protobuf_MODULE_COMPATIBLE ON) 7 | find_package(protobuf CONFIG) 8 | if (NOT protobuf_FOUND) 9 | find_package(Protobuf REQUIRED MODULE) 10 | if (NOT TARGET protobuf::libprotobuf) 11 | string(REGEX MATCH "[.]so$" _soExtension Protobuf_LIBRARY) 12 | if (_soExtension) 13 | add_library(protobuf::libprotobuf SHARED IMPORTED) 14 | else () 15 | add_library(protobuf::libprotobuf STATIC IMPORTED) 16 | endif () 17 | set_property(TARGET protobuf::libprotobuf PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Protobuf_INCLUDE_DIRS}) 18 | 19 | if (Protobuf_LIBRARY) 20 | set_property(TARGET protobuf::libprotobuf APPEND PROPERTY IMPORTED_CONFIGURATIONS "RELEASE") 21 | set_property(TARGET protobuf::libprotobuf APPEND PROPERTY IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX") 22 | set_property(TARGET protobuf::libprotobuf PROPERTY IMPORTED_LOCATION_RELEASE "${Protobuf_LIBRARY}") 23 | endif () 24 | if (Protobuf_LIBRARY_DEBUG) 25 | set_property(TARGET protobuf::libprotobuf APPEND PROPERTY IMPORTED_CONFIGURATIONS "DEBUG") 26 | set_property(TARGET protobuf::libprotobuf APPEND PROPERTY IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX") 27 | set_property(TARGET protobuf::libprotobuf PROPERTY IMPORTED_LOCATION_DEBUG "${Protobuf_LIBRARY_DEBUG}") 28 | endif () 29 | if (UNIX) 30 | set_property(TARGET protobuf::libprotobuf PROPERTY INTERFACE_LINK_LIBRARIES "-lpthread") 31 | endif () 32 | endif () 33 | endif () 34 | -------------------------------------------------------------------------------- /proto/model.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2013-present, SINTEF Ocean. 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | syntax="proto2"; 7 | package coralproto.model; 8 | 9 | // The FMI variable data types. 10 | enum DataType 11 | { 12 | REAL = 1; 13 | INTEGER = 2; 14 | BOOLEAN = 3; 15 | STRING = 4; 16 | } 17 | 18 | // FMI variable causalities. 19 | enum Causality 20 | { 21 | PARAMETER = 1; 22 | CALCULATED_PARAMETER = 2; 23 | INPUT = 3; 24 | OUTPUT = 4; 25 | LOCAL = 5; 26 | } 27 | 28 | // FMI variable variabilities. 29 | enum Variability 30 | { 31 | CONSTANT = 1; 32 | FIXED = 2; 33 | TUNABLE = 3; 34 | DISCRETE = 4; 35 | CONTINUOUS = 5; 36 | } 37 | 38 | // Information about a variable 39 | message VariableDescription 40 | { 41 | required uint32 id = 1; 42 | required string name = 2; 43 | required DataType data_type = 3; 44 | required Causality causality = 4; 45 | required Variability variability = 5; 46 | } 47 | 48 | // Information about a slave type 49 | message SlaveTypeDescription 50 | { 51 | required string name = 1; 52 | required string uuid = 2; 53 | optional string description = 3; 54 | optional string author = 4; 55 | optional string version = 5; 56 | repeated VariableDescription variable = 6; 57 | } 58 | 59 | // The value of a variable 60 | message ScalarValue 61 | { 62 | // TODO: Replace with 'oneof' field (new feature in protobuf 2.6) 63 | optional double real_value = 1; 64 | optional int32 integer_value = 2; 65 | optional bool boolean_value = 3; 66 | optional string string_value = 4; 67 | } 68 | 69 | message Variable 70 | { 71 | required uint32 slave_id = 1; 72 | required uint32 variable_id = 2; 73 | } 74 | -------------------------------------------------------------------------------- /include/coral/master/execution_options.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Configuration options for an execution. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_MASTER_EXECUTION_OPTIONS_HPP 11 | #define CORAL_MASTER_EXECUTION_OPTIONS_HPP 12 | 13 | #include 14 | #include 15 | 16 | 17 | namespace coral 18 | { 19 | namespace master 20 | { 21 | 22 | 23 | /** 24 | * \brief 25 | * Configuration options for an execution. 26 | * 27 | * An object of this type may be passed to `Execution::Execution()`. 28 | */ 29 | struct ExecutionOptions 30 | { 31 | /** 32 | * \brief 33 | * The start time of the simulation. 34 | * 35 | * This must be less than `#maxTime`. 36 | */ 37 | coral::model::TimePoint startTime = 0.0; 38 | 39 | /** 40 | * \brief 41 | * The maximum simulation time point. 42 | * 43 | * This may be `coral::model::ETERNITY` (the default), signifying that 44 | * there is no predefined maximum time. Otherwise, it must be greater 45 | * than `#startTime`. 46 | * 47 | * This is currently not used by Coral itself, but may be used by some 48 | * slaves, e.g. to pre-allocate resources such as memory. 49 | */ 50 | coral::model::TimePoint maxTime = coral::model::ETERNITY; 51 | 52 | /** 53 | * \brief 54 | * Timeout used by the slaves to detect loss of communication with 55 | * other slaves. 56 | * 57 | * This is used when slaves exchange variable values among themselves. 58 | * A negative value means no timeout. 59 | */ 60 | std::chrono::milliseconds slaveVariableRecvTimeout = std::chrono::seconds(1); 61 | }; 62 | 63 | 64 | }} // namespace 65 | #endif // header guard 66 | -------------------------------------------------------------------------------- /src/lib/util_filesystem.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | 13 | namespace coral 14 | { 15 | namespace util 16 | { 17 | 18 | 19 | coral::util::TempDir::TempDir(const boost::filesystem::path& parent) 20 | { 21 | if (parent.empty()) { 22 | m_path = boost::filesystem::temp_directory_path() 23 | / boost::filesystem::unique_path(); 24 | } else if (parent.is_absolute()) { 25 | m_path = parent / boost::filesystem::unique_path(); 26 | } else { 27 | m_path = boost::filesystem::temp_directory_path() 28 | / parent / boost::filesystem::unique_path(); 29 | } 30 | boost::filesystem::create_directories(m_path); 31 | } 32 | 33 | coral::util::TempDir::TempDir(TempDir&& other) noexcept 34 | : m_path{std::move(other.m_path)} 35 | { 36 | // This doesn't seem to be guaranteed by path's move constructor: 37 | other.m_path.clear(); 38 | } 39 | 40 | coral::util::TempDir& coral::util::TempDir::operator=(TempDir&& other) noexcept 41 | { 42 | DeleteNoexcept(); 43 | m_path = std::move(other.m_path); 44 | // This doesn't seem to be guaranteed by path's move constructor: 45 | other.m_path.clear(); 46 | return *this; 47 | } 48 | 49 | coral::util::TempDir::~TempDir() 50 | { 51 | DeleteNoexcept(); 52 | } 53 | 54 | const boost::filesystem::path& coral::util::TempDir::Path() const 55 | { 56 | return m_path; 57 | } 58 | 59 | void coral::util::TempDir::DeleteNoexcept() noexcept 60 | { 61 | if (!m_path.empty()) { 62 | boost::system::error_code ignoreErrors; 63 | boost::filesystem::remove_all(m_path, ignoreErrors); 64 | m_path.clear(); 65 | } 66 | } 67 | 68 | 69 | }} // namespace 70 | -------------------------------------------------------------------------------- /src/lib/fmi_fmu2_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | #define STRINGIFY_IMPL(x) #x 10 | #define STRINGIFY(x) STRINGIFY_IMPL(x) 11 | namespace 12 | { 13 | const std::string fmuDir = STRINGIFY(CORAL_TEST_FMU_DIRECTORY); 14 | } 15 | 16 | 17 | TEST(coral_fmi, Fmu2) 18 | { 19 | auto importer = coral::fmi::Importer::Create(); 20 | const std::string modelName = "WaterTank_Control"; 21 | auto fmu = importer->Import( 22 | boost::filesystem::path(fmuDir) / "fmi2_cs" / (modelName+".fmu")); 23 | 24 | EXPECT_EQ(coral::fmi::FMIVersion::v2_0, fmu->FMIVersion()); 25 | const auto& d = fmu->Description(); 26 | EXPECT_EQ("WaterTank.Control", d.Name()); 27 | EXPECT_EQ("{ad6d7bad-97d1-4fb9-ab3e-00a0d051e42c}", d.UUID()); 28 | EXPECT_TRUE(d.Description().empty()); 29 | EXPECT_TRUE(d.Author().empty()); 30 | EXPECT_TRUE(d.Version().empty()); 31 | EXPECT_NE(nullptr, std::static_pointer_cast(fmu)->FmilibHandle()); 32 | 33 | auto instance = fmu->InstantiateSlave(); 34 | instance->Setup("testSlave", "testExecution", 0.0, 1.0, false, 0.0); 35 | 36 | bool foundValve = false; 37 | bool foundMinlevel = false; 38 | for (const auto& v : d.Variables()) { 39 | if (v.Name() == "valve") { 40 | foundValve = true; 41 | EXPECT_EQ(coral::model::CONTINUOUS_VARIABILITY, v.Variability()); 42 | EXPECT_EQ(coral::model::OUTPUT_CAUSALITY, v.Causality()); 43 | EXPECT_EQ(0.0, instance->GetRealVariable(v.ID())); 44 | } else if (v.Name() == "minlevel") { 45 | foundMinlevel = true; 46 | EXPECT_EQ(coral::model::FIXED_VARIABILITY, v.Variability()); 47 | EXPECT_EQ(coral::model::PARAMETER_CAUSALITY, v.Causality()); 48 | EXPECT_EQ(1.0, instance->GetRealVariable(v.ID())); 49 | } 50 | } 51 | EXPECT_TRUE(foundValve); 52 | EXPECT_TRUE(foundMinlevel); 53 | } 54 | -------------------------------------------------------------------------------- /src/include/coral/fmi/glue.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Conversions between FMI variable attributes and "our" attributes. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_FMI_GLUE_HPP 11 | #define CORAL_FMI_GLUE_HPP 12 | 13 | #include 14 | #include 15 | 16 | 17 | namespace coral 18 | { 19 | namespace fmi 20 | { 21 | 22 | 23 | /// Converts an FMI 1.0 base type to "our" data type. 24 | coral::model::DataType ToDataType(fmi1_base_type_enu_t t); 25 | 26 | 27 | /// Converts an FMI 2.0 base type to "our" data type. 28 | coral::model::DataType ToDataType(fmi2_base_type_enu_t t); 29 | 30 | 31 | /** 32 | \brief Converts an FMI 1.0 variable causality to "our" corresponding causality. 33 | 34 | The causality mapping is not unique, so the variable's variability is also 35 | needed. 36 | */ 37 | coral::model::Causality ToCausality( 38 | fmi1_causality_enu_t c, 39 | fmi1_variability_enu_t v); 40 | 41 | 42 | /// Converts an FMI 2.0 variable causality to "our" corresponding causality. 43 | coral::model::Causality ToCausality(fmi2_causality_enu_t c); 44 | 45 | 46 | /// Converts an FMI 1.0 variable variability to "our" corresponding variability. 47 | coral::model::Variability ToVariability(fmi1_variability_enu_t v); 48 | 49 | 50 | /// Converts an FMI 2.0 variable variability to "our" corresponding variability. 51 | coral::model::Variability ToVariability(fmi2_variability_enu_t v); 52 | 53 | 54 | /// Converts an FMI 1.0 variable description to a VariableDescription object. 55 | coral::model::VariableDescription ToVariable( 56 | fmi1_import_variable_t* fmiVariable, 57 | coral::model::VariableID id); 58 | 59 | 60 | /// Converts an FMI 2.0 variable description to a VariableDescription object. 61 | coral::model::VariableDescription ToVariable( 62 | fmi2_import_variable_t* fmiVariable, 63 | coral::model::VariableID id); 64 | 65 | 66 | }} // namespace 67 | #endif // header guard 68 | -------------------------------------------------------------------------------- /src/lib/slave_runner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | namespace coral 20 | { 21 | namespace slave 22 | { 23 | 24 | 25 | Runner::Runner( 26 | std::shared_ptr slaveInstance, 27 | const coral::net::Endpoint& controlEndpoint, 28 | const coral::net::Endpoint& dataPubEndpoint, 29 | std::chrono::seconds commTimeout) 30 | : m_slaveInstance(slaveInstance), 31 | m_reactor(std::make_unique()), 32 | m_slaveAgent(std::make_unique( 33 | *m_reactor, 34 | *slaveInstance, 35 | controlEndpoint, 36 | dataPubEndpoint, 37 | commTimeout)) 38 | { 39 | } 40 | 41 | 42 | Runner::Runner(Runner&& other) noexcept 43 | : m_slaveInstance(std::move(other.m_slaveInstance)), 44 | m_reactor(std::move(other.m_reactor)), 45 | m_slaveAgent(std::move(other.m_slaveAgent)) 46 | { 47 | } 48 | Runner& Runner::operator=(Runner&& other) noexcept 49 | { 50 | m_slaveInstance = std::move(other.m_slaveInstance); 51 | m_reactor = std::move(other.m_reactor); 52 | m_slaveAgent = std::move(other.m_slaveAgent); 53 | return *this; 54 | } 55 | 56 | 57 | // The destructor doesn't actually do anything, we just needed to declare 58 | // it explicitly in the header to be able to use std::unique_ptr with Reactor 59 | // as an incomplete type. 60 | Runner::~Runner() { } 61 | 62 | 63 | coral::net::Endpoint Runner::BoundControlEndpoint() 64 | { 65 | return m_slaveAgent->BoundControlEndpoint(); 66 | } 67 | 68 | 69 | coral::net::Endpoint Runner::BoundDataPubEndpoint() 70 | { 71 | return m_slaveAgent->BoundDataPubEndpoint(); 72 | } 73 | 74 | 75 | void Runner::Run() 76 | { 77 | m_reactor->Run(); 78 | } 79 | 80 | 81 | }} // namespace 82 | -------------------------------------------------------------------------------- /include/coral/fmi/fmu.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Defines a version-independent FMU interface. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_FMI_FMU_HPP 11 | #define CORAL_FMI_FMU_HPP 12 | 13 | #include 14 | #include 15 | 16 | 17 | namespace coral 18 | { 19 | namespace fmi 20 | { 21 | 22 | 23 | /** 24 | \brief Constants that refer to FMI version numbers. 25 | \see coral::fmilib::Fmu::FmiVersion() 26 | */ 27 | enum class FMIVersion 28 | { 29 | /// Unknown (or possibly unsupported) 30 | unknown = 0, 31 | 32 | /// FMI 1.0 33 | v1_0 = 10000, 34 | 35 | /// FMI 2.0 36 | v2_0 = 20000, 37 | }; 38 | 39 | 40 | class Importer; 41 | class SlaveInstance; 42 | 43 | /** 44 | \brief An interface for classes that represent imported FMUs. 45 | 46 | This is an abstract class which only defines the functions that are common 47 | between different FMI versions. Use coral::fmi::Importer::Import() to import 48 | an %FMU and create a coral::fmi::FMU object. 49 | */ 50 | class FMU 51 | { 52 | public: 53 | /// Which FMI standard version is used in this %FMU. 54 | virtual coral::fmi::FMIVersion FMIVersion() const = 0; 55 | 56 | /// A description of this %FMU. 57 | virtual const coral::model::SlaveTypeDescription& Description() const = 0; 58 | 59 | /// Creates a co-simulation slave instance of this %FMU. 60 | virtual std::shared_ptr InstantiateSlave() = 0; 61 | 62 | /// Returns the coral::fmi::Importer which was used to import this %FMU. 63 | virtual std::shared_ptr Importer() const = 0; 64 | 65 | virtual ~FMU() { } 66 | }; 67 | 68 | 69 | /// An FMI co-simulation slave instance. 70 | class SlaveInstance : public coral::slave::Instance 71 | { 72 | public: 73 | /// Returns a reference to the %FMU of which this is an instance. 74 | virtual std::shared_ptr FMU() const = 0; 75 | 76 | virtual ~SlaveInstance() { } 77 | }; 78 | 79 | 80 | }} // namespace 81 | #endif // header guard 82 | -------------------------------------------------------------------------------- /src/include/coral/fmi/windows.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Windows-specific things. 4 | \copyright 5 | Copyright 2017-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_FMI_WINDOWS_HPP 11 | #define CORAL_FMI_WINDOWS_HPP 12 | 13 | #ifdef _WIN32 14 | 15 | #include 16 | #include 17 | 18 | 19 | namespace coral 20 | { 21 | namespace fmi 22 | { 23 | 24 | 25 | /** 26 | * \brief 27 | * Temporarily adds a path to the PATH environment variable for the current 28 | * process. 29 | * 30 | * The path is added to PATH when the class is instantiated, and removed 31 | * again when the instance is destroyed. 32 | * 33 | * The purpose of this class is to add an FMU's binaries/ directory 34 | * to Windows' DLL search path. This solves a problem where Windows was 35 | * unable t to locate some DLLs that are indirectly loaded. Specifically, 36 | * the problem has been observed when the main FMU model DLL runs Java code 37 | * (through JNI), and that Java code loaded a second DLL, which again was 38 | * linked to further DLLs. The latter were located in the binaries/ 39 | * directory, but were not found by the dynamic loader because that directory 40 | * was not in the search path. 41 | * 42 | * Since environment variables are shared by the entire process, the functions 43 | * use a mutex to protect against concurrent access to the PATH variable while 44 | * it's being read, modified and written. (This does not protect against 45 | * access by client code, of course, which is a potential source of bugs. 46 | * See VIPROMA-67 for more info.) 47 | */ 48 | class AdditionalPath 49 | { 50 | public: 51 | /// Constructor. Adds `p` to `PATH`. 52 | AdditionalPath(const boost::filesystem::path& p); 53 | 54 | /// Destructor. Removes the path from `PATH` again. 55 | ~AdditionalPath(); 56 | 57 | private: 58 | std::wstring m_addedPath; 59 | }; 60 | 61 | 62 | /// Given `path/to/fmu`, returns `path/to/fmu/binaries/` 63 | boost::filesystem::path FMUBinariesDir(const boost::filesystem::path& baseDir); 64 | 65 | 66 | }} // namespace 67 | #endif // _WIN32 68 | #endif // header guard 69 | -------------------------------------------------------------------------------- /src/lib/net_zmqx_util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | 11 | 12 | namespace coral 13 | { 14 | namespace net 15 | { 16 | namespace zmqx 17 | { 18 | 19 | 20 | zmq::context_t& GlobalContext() 21 | { 22 | // Note: The global zmq::context_t object is intentionally allocated on 23 | // the heap and never destroyed. It used to be stored in the data segment, 24 | // and hence destroyed during the static tear-down sequence, but this caused 25 | // crashes when the library was used as a DLL. See: 26 | // https://github.com/zeromq/libzmq/issues/1144 (fixed, but the 27 | // problem has just shifted elsewhere) 28 | // http://stackoverflow.com/q/19795245 29 | // 30 | static auto globalContext = new zmq::context_t(); 31 | return *globalContext; 32 | } 33 | 34 | 35 | std::uint16_t BindToEphemeralPort( 36 | zmq::socket_t& socket, 37 | const std::string& networkInterface) 38 | { 39 | const auto endpoint = "tcp://" + networkInterface + ":*"; 40 | socket.bind(endpoint.c_str()); 41 | return EndpointPort(coral::net::zmqx::LastEndpoint(socket)); 42 | } 43 | 44 | 45 | std::string LastEndpoint(zmq::socket_t& socket) 46 | { 47 | const size_t MAX_ENDPOINT_SIZE = 257; // including terminating zero 48 | char buffer[MAX_ENDPOINT_SIZE]; 49 | size_t length = MAX_ENDPOINT_SIZE; 50 | socket.getsockopt(ZMQ_LAST_ENDPOINT, buffer, &length); 51 | assert (length > 0 && buffer[length-1] == '\0'); 52 | return std::string(buffer, length-1); 53 | } 54 | 55 | 56 | std::uint16_t EndpointPort(const std::string& endpoint) 57 | { 58 | // We expect a string on the form "tcp://addr:port", where the 'addr' and 59 | // 'port' substrings must both be at least one character long, and look for 60 | // the last colon. 61 | const size_t colonPos = endpoint.rfind(':'); 62 | if (endpoint.size() < 9 || colonPos < 7 || colonPos >= endpoint.size() - 1) { 63 | throw std::invalid_argument("Invalid endpoint specification: " + endpoint); 64 | } 65 | return boost::lexical_cast(endpoint.substr(colonPos + 1)); 66 | } 67 | 68 | 69 | }}} // namespace 70 | -------------------------------------------------------------------------------- /src/include/coral/protocol/glue.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Glue code that relates public APIs and internal communication protocols. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_PROTOCOL_GLUE_HPP 11 | #define CORAL_PROTOCOL_GLUE_HPP 12 | 13 | #include 14 | #include 15 | 16 | #ifdef _MSC_VER 17 | # pragma warning(push, 0) 18 | #endif 19 | #include 20 | #include 21 | #include 22 | #ifdef _MSC_VER 23 | # pragma warning(pop) 24 | #endif 25 | 26 | 27 | namespace coral 28 | { 29 | namespace protocol 30 | { 31 | 32 | 33 | /// Converts a variable description to a protocol buffer. 34 | coralproto::model::VariableDescription ToProto( 35 | const coral::model::VariableDescription& ourVariable); 36 | 37 | /// Converts a protocol buffer to a variable definition. 38 | coral::model::VariableDescription FromProto( 39 | const coralproto::model::VariableDescription& protoVariable); 40 | 41 | /// Converts a slave type description to a protocol buffer. 42 | coralproto::model::SlaveTypeDescription ToProto( 43 | const coral::model::SlaveTypeDescription& src); 44 | 45 | /// Converts a protocol buffer to a slave type description. 46 | coral::model::SlaveTypeDescription FromProto( 47 | const coralproto::model::SlaveTypeDescription& src); 48 | 49 | /// Converts a ScalarValue to a protocol buffer (in place). 50 | void ConvertToProto( 51 | const coral::model::ScalarValue& source, 52 | coralproto::model::ScalarValue& target); 53 | 54 | /// Converts a protocol buffer to a ScalarValue. 55 | coral::model::ScalarValue FromProto(const coralproto::model::ScalarValue& source); 56 | 57 | /// Converts a Variable to a protocol buffer (in place). 58 | void ConvertToProto( 59 | const coral::model::Variable& source, 60 | coralproto::model::Variable& target); 61 | 62 | /// Converts a protocol buffer to a Variable. 63 | coral::model::Variable FromProto(const coralproto::model::Variable& source); 64 | 65 | void ConvertToProto( 66 | const coral::net::SlaveLocator& source, 67 | coralproto::net::SlaveLocator& target); 68 | 69 | coral::net::SlaveLocator FromProto(const coralproto::net::SlaveLocator& source); 70 | 71 | }} // namespace 72 | #endif // header guard 73 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | dist: trusty 3 | sudo: enabled 4 | compiler: gcc 5 | 6 | before_install: 7 | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y 8 | - sudo apt-get update -q 9 | 10 | install: 11 | - sudo apt-get install doxygen g++-5 libboost-filesystem-dev libboost-program-options-dev libboost-random-dev libboost-thread-dev libprotobuf-dev protobuf-compiler -y 12 | 13 | - wget https://github.com/viproma/debian-fmilib/releases/download/debian%2F2.0.2-1/libfmilib2_2.0.2-1_amd64.deb 14 | - wget https://github.com/viproma/debian-fmilib/releases/download/debian%2F2.0.2-1/libfmilib2-dev_2.0.2-1_amd64.deb 15 | - sudo dpkg -i libfmilib2_2.0.2-1_amd64.deb libfmilib2-dev_2.0.2-1_amd64.deb 16 | 17 | - wget http://mirrors.kernel.org/ubuntu/pool/universe/libz/libzip/libzip4_1.1.2-1.1_amd64.deb 18 | - wget http://mirrors.kernel.org/ubuntu/pool/universe/libz/libzip/libzip-dev_1.1.2-1.1_amd64.deb 19 | - sudo apt-get install -y zlib1g-dev 20 | - sudo dpkg -i libzip4_1.1.2-1.1_amd64.deb libzip-dev_1.1.2-1.1_amd64.deb 21 | 22 | - wget http://download.opensuse.org/repositories/network:/messaging:/zeromq:/release-stable/xUbuntu_14.04/amd64/libzmq5_4.2.5_amd64.deb 23 | - wget http://download.opensuse.org/repositories/network:/messaging:/zeromq:/release-stable/xUbuntu_14.04/amd64/libzmq3-dev_4.2.5_amd64.deb 24 | - wget http://download.opensuse.org/repositories/network:/messaging:/zeromq:/release-stable/xUbuntu_14.04/amd64/libnorm1_1.5r6+dfsg1-9_amd64.deb 25 | - wget http://download.opensuse.org/repositories/network:/messaging:/zeromq:/release-stable/xUbuntu_14.04/amd64/libpgm-5.2-0_5.2.122~dfsg-2_amd64.deb 26 | - wget http://download.opensuse.org/repositories/network:/messaging:/zeromq:/release-stable/xUbuntu_14.04/amd64/libsodium18_1.0.11-1_amd64.deb 27 | - sudo apt-get install -y libunwind8 28 | - sudo dpkg -i libzmq5_4.2.5_amd64.deb libzmq3-dev_4.2.5_amd64.deb libnorm1_1.5r6+dfsg1-9_amd64.deb libpgm-5.2-0_5.2.122~dfsg-2_amd64.deb libsodium18_1.0.11-1_amd64.deb 29 | 30 | - sudo wget -P /usr/include https://raw.githubusercontent.com/zeromq/cppzmq/v4.2.3/zmq.hpp 31 | 32 | - curl -fsSL https://github.com/google/googletest/archive/release-1.8.0.tar.gz | tar -zxf - 33 | - pushd googletest-release-1.8.0 34 | - cmake -DBUILD_SHARED_LIBS=ON . && make && sudo make install 35 | - popd 36 | 37 | script: 38 | - export CC=gcc-5 39 | - export CXX=g++-5 40 | - cmake -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX -DCORAL_ENABLE_TRACE_LOGGING=TRUE. && cmake --build . && ctest -V 41 | -------------------------------------------------------------------------------- /src/lib/fmi_windows.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | #ifdef _WIN32 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | namespace coral 18 | { 19 | namespace fmi 20 | { 21 | 22 | namespace 23 | { 24 | // Maximum size of environment variables 25 | static const std::size_t MAX_ENV_VAR_SIZE = 32767; 26 | 27 | // Mutex to protect against concurrent access to PATH 28 | std::mutex g_pathEnvVarMutex{}; 29 | } 30 | 31 | 32 | AdditionalPath::AdditionalPath(const boost::filesystem::path& p) 33 | { 34 | std::lock_guard lock(g_pathEnvVarMutex); 35 | 36 | WCHAR currentPathZ[MAX_ENV_VAR_SIZE]; 37 | const auto currentPathLen = 38 | GetEnvironmentVariableW(L"PATH", currentPathZ, MAX_ENV_VAR_SIZE); 39 | const auto currentPath = std::wstring(currentPathZ, currentPathLen); 40 | 41 | if (currentPathLen > 0) { 42 | m_addedPath = L";"; 43 | } 44 | m_addedPath += p.wstring(); 45 | 46 | const auto newPath = currentPath + m_addedPath; 47 | if (!SetEnvironmentVariableW(L"PATH", newPath.c_str())) { 48 | assert(!"Failed to modify PATH environment variable"); 49 | } 50 | } 51 | 52 | 53 | AdditionalPath::~AdditionalPath() 54 | { 55 | std::lock_guard lock(g_pathEnvVarMutex); 56 | 57 | WCHAR currentPathZ[MAX_ENV_VAR_SIZE]; 58 | const auto currentPathLen = 59 | GetEnvironmentVariableW(L"PATH", currentPathZ, MAX_ENV_VAR_SIZE); 60 | const auto currentPath = std::wstring(currentPathZ, currentPathLen); 61 | 62 | const auto pos = currentPath.find(m_addedPath); 63 | if (pos < std::wstring::npos) { 64 | auto newPath = currentPath.substr(0, pos) 65 | + currentPath.substr(pos + m_addedPath.size()); 66 | if (!SetEnvironmentVariableW(L"PATH", newPath.c_str())) { 67 | assert(!"Failed to reset PATH environment variable"); 68 | } 69 | } 70 | } 71 | 72 | 73 | boost::filesystem::path FMUBinariesDir(const boost::filesystem::path& baseDir) 74 | { 75 | #ifdef _WIN64 76 | const auto platformSubdir = L"win64"; 77 | #else 78 | const auto platformSubdir = L"win32"; 79 | #endif // _WIN64 80 | return baseDir / L"binaries" / platformSubdir; 81 | } 82 | 83 | 84 | }} // namespace 85 | #endif // _WIN32 86 | -------------------------------------------------------------------------------- /include/coral/util/filesystem.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * \brief Filesystem utilities. 4 | * \copyright 5 | * Copyright 2013-present, SINTEF Ocean. 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_UTIL_FILESYSTEM_HPP 11 | #define CORAL_UTIL_FILESYSTEM_HPP 12 | 13 | #include 14 | #include 15 | 16 | 17 | namespace coral 18 | { 19 | namespace util 20 | { 21 | 22 | 23 | /** 24 | * \brief An RAII object that creates a unique directory on construction 25 | * and recursively deletes it again on destruction. 26 | */ 27 | class TempDir 28 | { 29 | public: 30 | /** 31 | * \brief Creates a new temporary directory. 32 | * 33 | * The name of the new directory will be randomly generated, and there are 34 | * three options of where it will be created, depending on the value of 35 | * `parent`. In the following, `temp` refers to a directory suitable for 36 | * temporary files under the conventions of the operating system (e.g. 37 | * `/tmp` under UNIX-like systems), and `name` refers to the randomly 38 | * generated name mentioned above. 39 | * 40 | * - If `parent` is empty: `temp/name` 41 | * - If `parent` is relative: `temp/parent/name` 42 | * - If `parent` is absolute: `parent/name` 43 | */ 44 | explicit TempDir( 45 | const boost::filesystem::path& parent = boost::filesystem::path()); 46 | 47 | TempDir(const TempDir&) = delete; 48 | TempDir& operator=(const TempDir&) = delete; 49 | 50 | /** 51 | * \brief Move constructor. 52 | * 53 | * Ownership of the directory is transferred from `other` to `this`. 54 | * Afterwards, `other` no longer refers to any directory, meaning that 55 | * `other.Path()` will return an empty path, and its destructor will not 56 | * perform any filesystem operations. 57 | */ 58 | TempDir(TempDir&& other) noexcept; 59 | 60 | /// Move assignment operator. See TempDir(TempDir&&) for semantics. 61 | TempDir& operator=(TempDir&&) noexcept; 62 | 63 | /// Destructor. Recursively deletes the directory. 64 | ~TempDir() noexcept; 65 | 66 | /// Returns the path to the directory. 67 | const boost::filesystem::path& Path() const; 68 | 69 | private: 70 | void DeleteNoexcept() noexcept; 71 | 72 | boost::filesystem::path m_path; 73 | }; 74 | 75 | 76 | }} // namespace 77 | #endif // header guard 78 | -------------------------------------------------------------------------------- /proto/execution.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2013-present, SINTEF Ocean. 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | syntax="proto2"; 7 | package coralproto.execution; 8 | 9 | import "model.proto"; 10 | 11 | 12 | // Message type identifiers. 13 | enum MessageType 14 | { 15 | // Handshake 16 | MSG_DENIED = 0; 17 | MSG_HELLO = 1; 18 | 19 | // Commands 20 | MSG_SETUP = 10; 21 | MSG_SET_VARS = 11; 22 | MSG_STEP = 12; 23 | MSG_ACCEPT_STEP = 13; 24 | MSG_TERMINATE = 14; 25 | MSG_DESCRIBE = 15; 26 | MSG_SET_PEERS = 16; 27 | MSG_RESEND_VARS = 17; 28 | 29 | // Responses 30 | MSG_READY = 30; 31 | MSG_STEP_OK = 31; 32 | MSG_STEP_FAILED = 32; 33 | MSG_ERROR = 33; 34 | MSG_FATAL_ERROR = 34; 35 | } 36 | 37 | // The body of an ERROR/FATAL_ERROR message. 38 | message ErrorInfo 39 | { 40 | enum Code 41 | { 42 | UNSPECIFIED_ERROR = 0; 43 | INVALID_REQUEST = 1; 44 | CANNOT_SET_VARIABLE = 2; 45 | TIMED_OUT = 3; 46 | } 47 | 48 | optional Code code = 1 [default = UNSPECIFIED_ERROR]; 49 | optional string details = 2; 50 | } 51 | 52 | // Information sent by a slave about itself 53 | message SlaveDescription 54 | { 55 | required model.SlaveTypeDescription type_description = 1; 56 | } 57 | 58 | // The ID number and a value for one of a slave's variables. 59 | message SlaveVariableSetting 60 | { 61 | required uint32 variable_id = 1; 62 | optional model.ScalarValue value = 2; 63 | optional model.Variable connected_output = 3; 64 | } 65 | 66 | // The body of a SETUP message 67 | message SetupData 68 | { 69 | required uint32 slave_id = 1; 70 | required double start_time = 2; 71 | optional double stop_time = 3; 72 | optional string execution_name = 4; 73 | optional string slave_name = 5; 74 | optional int32 variable_recv_timeout_ms = 6; // -1 = infinite 75 | } 76 | 77 | // A message that is sent by the master to a slave to set some of its variables. 78 | message SetVarsData 79 | { 80 | repeated SlaveVariableSetting variable = 1; 81 | } 82 | 83 | // The body of a STEP message 84 | message StepData 85 | { 86 | required int32 step_id = 1; 87 | required double timepoint = 2; 88 | required double stepsize = 3; 89 | } 90 | 91 | // The body of a SET_PEERS message 92 | message SetPeersData 93 | { 94 | repeated string peer = 1; 95 | } 96 | -------------------------------------------------------------------------------- /src/lib/bus_execution_manager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | 11 | 12 | namespace coral 13 | { 14 | namespace bus 15 | { 16 | 17 | 18 | ExecutionManager::ExecutionManager( 19 | coral::net::Reactor& reactor, 20 | const std::string& executionName, 21 | const coral::master::ExecutionOptions& options) 22 | : m_private(std::make_unique( 23 | reactor, executionName, options)) 24 | { 25 | } 26 | 27 | 28 | ExecutionManager::~ExecutionManager() 29 | { 30 | // For the moment, the destructor does nothing. We just need it to be able 31 | // to use std::unique_ptr (for m_private) with an undefined type (i.e., 32 | // ExecutionManagerPrivate) in the header. 33 | } 34 | 35 | 36 | void ExecutionManager::Reconstitute( 37 | const std::vector& slavesToAdd, 38 | std::chrono::milliseconds commTimeout, 39 | ReconstituteHandler onComplete, 40 | SlaveReconstituteHandler onSlaveComplete) 41 | { 42 | m_private->Reconstitute( 43 | slavesToAdd, commTimeout, 44 | std::move(onComplete), std::move(onSlaveComplete)); 45 | } 46 | 47 | 48 | void ExecutionManager::Reconfigure( 49 | const std::vector& slaveConfigs, 50 | std::chrono::milliseconds commTimeout, 51 | ReconfigureHandler onComplete, 52 | SlaveReconfigureHandler onSlaveComplete) 53 | { 54 | m_private->Reconfigure( 55 | slaveConfigs, commTimeout, 56 | std::move(onComplete), std::move(onSlaveComplete)); 57 | } 58 | 59 | 60 | void ExecutionManager::Step( 61 | coral::model::TimeDuration stepSize, 62 | std::chrono::milliseconds timeout, 63 | StepHandler onComplete, 64 | ExecutionManager::SlaveStepHandler onSlaveStepComplete) 65 | { 66 | m_private->Step( 67 | stepSize, 68 | timeout, 69 | std::move(onComplete), 70 | std::move(onSlaveStepComplete)); 71 | } 72 | 73 | 74 | void ExecutionManager::AcceptStep( 75 | std::chrono::milliseconds timeout, 76 | AcceptStepHandler onComplete, 77 | SlaveAcceptStepHandler onSlaveAcceptStepComplete) 78 | { 79 | m_private->AcceptStep( 80 | timeout, 81 | std::move(onComplete), 82 | std::move(onSlaveAcceptStepComplete)); 83 | } 84 | 85 | 86 | void ExecutionManager::Terminate() 87 | { 88 | m_private->Terminate(); 89 | } 90 | 91 | 92 | }} // namespace 93 | -------------------------------------------------------------------------------- /src/lib/net_zmqx_messaging_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace coral::net::zmqx; 7 | 8 | 9 | TEST(coral_net, WaitForIncomingOutgoing) 10 | { 11 | auto ctx = zmq::context_t(); 12 | auto sender = zmq::socket_t(ctx, ZMQ_PUSH); 13 | const auto tOut0 = std::chrono::steady_clock::now(); 14 | EXPECT_FALSE(WaitForOutgoing(sender, std::chrono::milliseconds(200))); 15 | const auto tOut1 = std::chrono::steady_clock::now(); 16 | EXPECT_LT(std::chrono::milliseconds(180), tOut1 - tOut0); 17 | EXPECT_GT(std::chrono::milliseconds(220), tOut1 - tOut0); 18 | 19 | auto recver = zmq::socket_t(ctx, ZMQ_PULL); 20 | const auto tIn0 = std::chrono::steady_clock::now(); 21 | EXPECT_FALSE(WaitForIncoming(recver, std::chrono::milliseconds(200))); 22 | const auto tIn1 = std::chrono::steady_clock::now(); 23 | EXPECT_LT(std::chrono::milliseconds(180), tIn1 - tIn0); 24 | EXPECT_GT(std::chrono::milliseconds(220), tIn1 - tIn0); 25 | 26 | const auto endpoint = std::string("inproc://") 27 | + ::testing::UnitTest::GetInstance()->current_test_info()->test_case_name(); 28 | recver.bind(endpoint.c_str()); 29 | sender.connect(endpoint.c_str()); 30 | EXPECT_TRUE(WaitForOutgoing(sender, std::chrono::milliseconds(200))); 31 | zmq::message_t msg{"foo", 3}; 32 | sender.send(msg); 33 | EXPECT_TRUE(msg.size() == 0); 34 | EXPECT_TRUE(WaitForIncoming(recver, std::chrono::milliseconds(200))); 35 | recver.recv(&msg); 36 | EXPECT_EQ("foo", ToString(msg)); 37 | } 38 | 39 | TEST(coral_net, SendReceiveMessage) 40 | { 41 | auto ctx = zmq::context_t(); 42 | auto sender = zmq::socket_t(ctx, ZMQ_PUSH); 43 | auto recver = zmq::socket_t(ctx, ZMQ_PULL); 44 | const auto endpoint = std::string("inproc://") 45 | + ::testing::UnitTest::GetInstance()->current_test_info()->test_case_name(); 46 | recver.bind(endpoint.c_str()); 47 | sender.connect(endpoint.c_str()); 48 | 49 | std::vector srcMsg; 50 | srcMsg.push_back(zmq::message_t(123)); 51 | srcMsg.push_back(zmq::message_t()); 52 | Send(sender, srcMsg, SendFlag::more); 53 | EXPECT_TRUE(srcMsg.empty()); 54 | srcMsg.push_back(zmq::message_t(321)); 55 | Send(sender, srcMsg); 56 | 57 | std::vector tgtMsg(1); 58 | Receive(recver, tgtMsg); 59 | ASSERT_EQ( 3U, tgtMsg.size()); 60 | EXPECT_EQ(123U, tgtMsg[0].size()); 61 | EXPECT_EQ( 0U, tgtMsg[1].size()); 62 | EXPECT_EQ(321U, tgtMsg[2].size()); 63 | } 64 | 65 | TEST(coral_net, ToFrame_ToString) 66 | { 67 | auto msg = ToFrame("foo"); 68 | EXPECT_EQ("foo", ToString(msg)); 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/util_console_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | TEST(coral_util, CommandLine) 7 | { 8 | const char *const *const argv0 = nullptr; 9 | const int argc0 = 0; 10 | EXPECT_TRUE(coral::util::CommandLine(argc0, argv0).empty()); 11 | 12 | const char* argv2[2] = { "hello", "world" }; 13 | const int argc2 = 2; 14 | const auto cmd2 = coral::util::CommandLine(argc2, argv2); 15 | EXPECT_EQ(argc2, static_cast(cmd2.size())); 16 | EXPECT_EQ(argv2[0], cmd2[0]); 17 | EXPECT_EQ(argv2[1], cmd2[1]); 18 | } 19 | 20 | 21 | TEST(coral_util, ParseArguments) 22 | { 23 | using namespace coral::util; 24 | namespace po = boost::program_options; 25 | 26 | std::vector args; 27 | po::options_description options; 28 | po::options_description positionalOptions; 29 | po::positional_options_description positions; 30 | std::stringstream out; 31 | 32 | std::string commandName = "test"; 33 | std::string commandDescription = "This is a test"; 34 | 35 | auto vals = ParseArguments( 36 | args, options, positionalOptions, positions, 37 | out, commandName, commandDescription); 38 | EXPECT_TRUE(!!vals); 39 | EXPECT_TRUE(vals->empty()); 40 | 41 | options.add_options()("switch", po::value()); 42 | vals = ParseArguments( 43 | args, options, positionalOptions, positions, 44 | out, commandName, commandDescription); 45 | EXPECT_TRUE(!!vals); 46 | EXPECT_TRUE(vals->empty()); 47 | 48 | positionalOptions.add_options()("arg", po::value()); 49 | positions.add("arg", 1); 50 | vals = ParseArguments( 51 | args, options, positionalOptions, positions, 52 | out, commandName, commandDescription); 53 | EXPECT_FALSE(!!vals); 54 | 55 | args.push_back("foo"); 56 | vals = ParseArguments( 57 | args, options, positionalOptions, positions, 58 | out, commandName, commandDescription); 59 | EXPECT_TRUE(!!vals); 60 | EXPECT_EQ(1u, vals->size()); 61 | EXPECT_EQ(1u, vals->count("arg")); 62 | EXPECT_EQ("foo", (*vals)["arg"].as()); 63 | 64 | args.push_back("--switch=42"); 65 | vals = ParseArguments( 66 | args, options, positionalOptions, positions, 67 | out, commandName, commandDescription); 68 | EXPECT_TRUE(!!vals); 69 | EXPECT_EQ(2u, vals->size()); 70 | EXPECT_EQ(1u, vals->count("arg")); 71 | EXPECT_EQ(1u, vals->count("switch")); 72 | EXPECT_EQ("foo", (*vals)["arg"].as()); 73 | EXPECT_EQ(42, (*vals)["switch"].as()); 74 | 75 | args.push_back("--help"); 76 | vals = ParseArguments( 77 | args, options, positionalOptions, positions, 78 | out, commandName, commandDescription); 79 | EXPECT_FALSE(!!vals); 80 | } 81 | -------------------------------------------------------------------------------- /include/coral/slave/logging.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Defines the coral::slave::LoggingInstance class. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_SLAVE_LOGGING_HPP_INCLUDED 11 | #define CORAL_SLAVE_LOGGING_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | 20 | namespace coral 21 | { 22 | namespace slave 23 | { 24 | 25 | 26 | /// A slave instance wrapper that logs variable values to a file. 27 | class LoggingInstance : public Instance 28 | { 29 | public: 30 | /** 31 | \brief Constructs a LoggingInstance that wraps the given slave 32 | instance and adds logging to it. 33 | 34 | \param [in] instance 35 | The slave instance to be wrapped by this one. 36 | \param [in] outputFilePrefix 37 | A directory and prefix for a CSV output file. An execution- and 38 | slave-specific name as well as a ".csv" extension will be appended 39 | to this name. If no prefix is required, and the string only 40 | contains a directory name, it should end with a directory separator 41 | (a slash). 42 | */ 43 | explicit LoggingInstance( 44 | std::shared_ptr instance, 45 | const std::string& outputFilePrefix = std::string{}); 46 | 47 | // slave::Instance methods. 48 | coral::model::SlaveTypeDescription TypeDescription() const override; 49 | void Setup( 50 | const std::string& slaveName, 51 | const std::string& executionName, 52 | coral::model::TimePoint startTime, 53 | coral::model::TimePoint stopTime, 54 | bool adaptiveStepSize, 55 | double relativeTolerance) override; 56 | void StartSimulation() override; 57 | void EndSimulation() override; 58 | bool DoStep(coral::model::TimePoint currentT, coral::model::TimeDuration deltaT) override; 59 | double GetRealVariable(coral::model::VariableID variable) const override; 60 | int GetIntegerVariable(coral::model::VariableID variable) const override; 61 | bool GetBooleanVariable(coral::model::VariableID variable) const override; 62 | std::string GetStringVariable(coral::model::VariableID variable) const override; 63 | bool SetRealVariable(coral::model::VariableID variable, double value) override; 64 | bool SetIntegerVariable(coral::model::VariableID variable, int value) override; 65 | bool SetBooleanVariable(coral::model::VariableID variable, bool value) override; 66 | bool SetStringVariable(coral::model::VariableID variable, const std::string& value) override; 67 | 68 | private: 69 | std::shared_ptr m_instance; 70 | std::string m_outputFilePrefix; 71 | std::ofstream m_outputStream; 72 | }; 73 | 74 | 75 | }} // namespace 76 | #endif // header guard 77 | -------------------------------------------------------------------------------- /src/lib/net_zmqx_messaging.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #ifdef _WIN32 8 | # define NOMINMAX 9 | #endif 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | namespace 19 | { 20 | bool PollSingleSocket( 21 | zmq::socket_t& socket, 22 | short events, 23 | std::chrono::milliseconds timeout) 24 | { 25 | const auto timeout_ms = std::max(static_cast(timeout.count()), -1L); 26 | auto pollItem = zmq::pollitem_t{static_cast(socket), 0, events, 0}; 27 | return zmq::poll(&pollItem, 1, timeout_ms) == 1; 28 | } 29 | } 30 | 31 | 32 | bool coral::net::zmqx::WaitForOutgoing( 33 | zmq::socket_t& socket, 34 | std::chrono::milliseconds timeout) 35 | { 36 | return PollSingleSocket(socket, ZMQ_POLLOUT, timeout); 37 | } 38 | 39 | 40 | bool coral::net::zmqx::WaitForIncoming( 41 | zmq::socket_t& socket, 42 | std::chrono::milliseconds timeout) 43 | { 44 | return PollSingleSocket(socket, ZMQ_POLLIN, timeout); 45 | } 46 | 47 | 48 | namespace 49 | { 50 | void SendFrames( 51 | zmq::socket_t& socket, 52 | std::vector& message, 53 | coral::net::zmqx::SendFlag flags) 54 | { 55 | assert (!message.empty()); 56 | const auto last = --message.end(); 57 | for (auto it = begin(message); it != last; ++it) { 58 | socket.send(*it, ZMQ_SNDMORE); 59 | } 60 | socket.send( 61 | *last, 62 | (flags & coral::net::zmqx::SendFlag::more) != coral::net::zmqx::SendFlag::none 63 | ? ZMQ_SNDMORE 64 | : 0); 65 | message.clear(); 66 | } 67 | } 68 | 69 | 70 | void coral::net::zmqx::Send( 71 | zmq::socket_t& socket, 72 | std::vector& message, 73 | SendFlag flags) 74 | { 75 | CORAL_INPUT_CHECK(!message.empty()); 76 | SendFrames(socket, message, flags); 77 | assert (message.empty()); 78 | } 79 | 80 | 81 | void coral::net::zmqx::Receive( 82 | zmq::socket_t& socket, 83 | std::vector& message) 84 | { 85 | message.clear(); 86 | do { 87 | message.emplace_back(); 88 | socket.recv(&message.back()); 89 | } while (message.back().more()); 90 | } 91 | 92 | 93 | std::string coral::net::zmqx::ToString(const zmq::message_t& frame) 94 | { 95 | return std::string(static_cast(frame.data()), frame.size()); 96 | } 97 | 98 | 99 | zmq::message_t coral::net::zmqx::ToFrame(const std::string& s) 100 | { 101 | auto msg = zmq::message_t(s.size()); 102 | std::memcpy(msg.data(), s.data(), s.size()); 103 | return msg; 104 | } 105 | -------------------------------------------------------------------------------- /src/include/coral/protocol/domain.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Main header file for coral::protocol::domain. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_PROTOCOL_DOMAIN_HPP 11 | #define CORAL_PROTOCOL_DOMAIN_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef _MSC_VER 18 | # pragma warning(push, 0) 19 | #endif 20 | #include 21 | #ifdef _MSC_VER 22 | # pragma warning(pop) 23 | #endif 24 | 25 | 26 | namespace coral 27 | { 28 | namespace protocol 29 | { 30 | /** 31 | \brief Functions for constructing and parsing messages sent between domain 32 | participants. 33 | */ 34 | namespace domain 35 | { 36 | 37 | 38 | const uint16_t MAX_PROTOCOL_VERSION = 0; 39 | 40 | 41 | void SubscribeToReports(zmq::socket_t& subSocket); 42 | 43 | 44 | enum MessageType 45 | { 46 | MSG_SLAVEPROVIDER_HELLO, 47 | MSG_UPDATE_AVAILABLE, 48 | MSG_GET_SLAVE_LIST, 49 | MSG_SLAVE_LIST, 50 | MSG_INSTANTIATE_SLAVE, 51 | MSG_INSTANTIATE_SLAVE_OK, 52 | MSG_INSTANTIATE_SLAVE_FAILED 53 | }; 54 | 55 | 56 | /** 57 | \brief Creates a message header for the given message type, using the given 58 | protocol version. 59 | */ 60 | zmq::message_t CreateHeader(MessageType messageType, uint16_t protocolVersion); 61 | 62 | 63 | /** 64 | \brief Creates a body-less addressed message. 65 | 66 | This function fills `message` with three frames: an envelope frame with the 67 | contents of `recipient`, an empty delimiter frame, and a header frame for the 68 | given message type and protocol version. Any previous contents of `message` 69 | will be replaced. 70 | */ 71 | void CreateAddressedMessage( 72 | std::vector& message, 73 | const std::string& recipient, 74 | MessageType messageType, 75 | uint16_t protocolVersion); 76 | 77 | 78 | /** 79 | \brief Creates an addressed message. 80 | */ 81 | void CreateAddressedMessage( 82 | std::vector& message, 83 | const std::string& recipient, 84 | MessageType messageType, 85 | uint16_t protocolVersion, 86 | const google::protobuf::MessageLite& body); 87 | 88 | 89 | /// The information in a message header. 90 | struct Header 91 | { 92 | /// Protocol version. 93 | uint16_t protocol; 94 | 95 | /// Message type. 96 | MessageType messageType; 97 | }; 98 | 99 | 100 | /** 101 | \brief Parses a header frame and returns its contents as a Header object. 102 | 103 | \throws coral::error::ProtocolViolationException if the frame format is invalid, 104 | if the protocol version is not supported, or if the message type is 105 | unknown. 106 | */ 107 | Header ParseHeader(const zmq::message_t& headerFrame); 108 | 109 | 110 | }}} // namespace 111 | #endif // header guard 112 | -------------------------------------------------------------------------------- /src/lib/protocol_exe_data.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef _MSC_VER 15 | # pragma warning(push, 0) 16 | #endif 17 | #include 18 | #ifdef _MSC_VER 19 | # pragma warning(pop) 20 | #endif 21 | 22 | 23 | namespace ed = coral::protocol::exe_data; 24 | 25 | 26 | namespace { 27 | coral::model::Variable ParseHeader(const zmq::message_t& msg) 28 | { 29 | if (msg.size() != ed::HEADER_SIZE) { 30 | throw coral::error::ProtocolViolationException( 31 | "Invalid header frame"); 32 | } 33 | return coral::model::Variable( 34 | coral::util::DecodeUint16(static_cast(msg.data())), 35 | coral::util::DecodeUint32(static_cast(msg.data()) + 2)); 36 | } 37 | 38 | void CreateRawHeader( 39 | const coral::model::Variable& var, 40 | char buf[ed::HEADER_SIZE]) 41 | { 42 | coral::util::EncodeUint16(var.Slave(), buf); 43 | coral::util::EncodeUint32(var.ID(), buf + 2); 44 | } 45 | 46 | zmq::message_t CreateHeader(const coral::model::Variable& var) 47 | { 48 | auto msg = zmq::message_t(ed::HEADER_SIZE); 49 | CreateRawHeader(var, static_cast(msg.data())); 50 | return msg; 51 | } 52 | } 53 | 54 | 55 | ed::Message ed::ParseMessage(const std::vector& rawMsg) 56 | { 57 | if (rawMsg.size() != 2) { 58 | throw coral::error::ProtocolViolationException( 59 | "Wrong number of frames"); 60 | } 61 | Message m; 62 | m.variable = ParseHeader(rawMsg[0]); 63 | coralproto::exe_data::TimestampedValue timestampedValue; 64 | coral::protobuf::ParseFromFrame(rawMsg[1], timestampedValue); 65 | m.timestepID = timestampedValue.timestep_id(); 66 | m.value = coral::protocol::FromProto(timestampedValue.value()); 67 | return m; 68 | } 69 | 70 | 71 | void ed::CreateMessage( 72 | const ed::Message& message, 73 | std::vector& rawOut) 74 | { 75 | rawOut.clear(); 76 | rawOut.push_back(CreateHeader(message.variable)); 77 | coralproto::exe_data::TimestampedValue timestampedValue; 78 | coral::protocol::ConvertToProto(message.value, *timestampedValue.mutable_value()); 79 | timestampedValue.set_timestep_id(message.timestepID); 80 | rawOut.emplace_back(); 81 | coral::protobuf::SerializeToFrame(timestampedValue, rawOut[1]); 82 | } 83 | 84 | 85 | void ed::Subscribe(zmq::socket_t& socket, const coral::model::Variable& variable) 86 | { 87 | char header[HEADER_SIZE]; 88 | CreateRawHeader(variable, header); 89 | socket.setsockopt(ZMQ_SUBSCRIBE, header, HEADER_SIZE); 90 | } 91 | 92 | 93 | void ed::Unsubscribe(zmq::socket_t& socket, const coral::model::Variable& variable) 94 | { 95 | char header[HEADER_SIZE]; 96 | CreateRawHeader(variable, header); 97 | socket.setsockopt(ZMQ_UNSUBSCRIBE, header, HEADER_SIZE); 98 | } 99 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | This file documents all notable changes to Coral. This includes major 4 | new features, important bug fixes and breaking changes. For a more 5 | detailed list of all changes, click the header links for each version. 6 | This will take you to the relevant sections of the project's 7 | [Git commit history](https://github.com/viproma/coral). 8 | 9 | Coral version numbers follow the [Semantic Versioning](http://semver.org/) 10 | scheme. As long as we're still in the initial (pre-1.0) development stage, 11 | all new versions must be expected to contain backwards-incompatible changes. 12 | We don't guarantee that they'll all be documented here, but we'll try to 13 | list the bigger ones. 14 | 15 | ## [0.10.0] – 2018-12-11 16 | ### Added 17 | - A `--no-slave-console` switch to disable creation of new console windows 18 | for slaves in coralslaveprovider. This only has an effect on Windows. 19 | - User-settable log level via the `--log-level` switch. 20 | - File logging, controlled with the `--log-file` and `--log-file-dir` 21 | switches. 22 | ### Changed 23 | - The slave command line interface is now a bit more well-defined. 24 | - The FMI logger's "category" field is now included in the log messages. 25 | 26 | ## [0.9.1] – 2018-06-13 27 | ### Fixed 28 | - Issue [#54](https://github.com/viproma/coral/issues/54): 29 | Too low time precision in CSV output 30 | 31 | ## [0.9.0] – 2018-04-23 32 | This will be the last release which supports Visual Studio 2013. 33 | ### Added 34 | - A `--no-output` switch in coralslaveprovider, which disables file 35 | output of variable values. 36 | - The ability to change or break existing variable connections with 37 | `coral::master::Execution::Reconfigure()`. 38 | - `coral::fmi::Importer::ImportUnpacked()`, a function for importing 39 | FMUs which have already been unpacked. 40 | ### Fixed 41 | - Some minor issues that prevented Coral from being built with newer 42 | Visual Studio versions (2015 and 2017). 43 | 44 | ## [0.8.0] – 2017-04-07 45 | ### Added 46 | - Support for FMI 2.0. 47 | - A `--realtime` switch to enable soft real time synchronisation in 48 | coralmaster. 49 | - A `--debug-pause` switch which causes coralmaster to stop at the 50 | beginning of the simulation, before any FMI function calls, to allow 51 | time to attach a debugger to the slaves. 52 | - This CHANGELOG file. 53 | ### Changed 54 | - A negative number may now be given for any communication timeout, 55 | which will disable the timeout entirely. 56 | - The default network interface in coralmaster and coralslaveprovider 57 | has been changed from `*` to `127.0.0.1`. 58 | (See issue [#20](https://github.com/viproma/coral/issues/20).) 59 | 60 | ## [0.7.1] – 2017-03-09 61 | ### Fixed 62 | - Issue [#9](https://github.com/viproma/coral/issues/9): 63 | `coral/fmi/importer.hpp` depends on private header 64 | 65 | ## 0.7.0 – 2017-02-07 66 | First public release. 67 | 68 | [0.10.0]: https://github.com/viproma/coral/compare/v0.9.1...v0.10.0 69 | [0.9.1]: https://github.com/viproma/coral/compare/v0.9.0...v0.9.1 70 | [0.9.0]: https://github.com/viproma/coral/compare/v0.8.0...v0.9.0 71 | [0.8.0]: https://github.com/viproma/coral/compare/v0.7.1...v0.8.0 72 | [0.7.1]: https://github.com/viproma/coral/compare/v0.7.0...v0.7.1 73 | -------------------------------------------------------------------------------- /cmake/FindLIBZIP.cmake: -------------------------------------------------------------------------------- 1 | # - CMake script for locating libzip 2 | # 3 | # If the script succeeds, it will create an IMPORTED target named 4 | # "libzip::libzip", plus set the following variables: 5 | # 6 | # LIBZIP_FOUND - If the library was found 7 | # LIBZIP_INCLUDE_DIRS - The directory that contains the header files. 8 | # LIBZIP_LIBRARIES - Contains "libzip::libzip" 9 | # LIBZIP_LIBRARY - Path to static/import library file. 10 | # 11 | cmake_minimum_required (VERSION 3.0.0) 12 | 13 | # Find static library, and use its path prefix to provide a HINTS option to the 14 | # other find_*() commands. 15 | find_library (LIBZIP_LIBRARY "zip" 16 | PATHS ${LIBZIP_DIR} $ENV{LIBZIP_DIR} 17 | PATH_SUFFIXES "lib") 18 | mark_as_advanced (LIBZIP_LIBRARY) 19 | unset (_LIBZIP_hints) 20 | if (LIBZIP_LIBRARY) 21 | get_filename_component (_LIBZIP_prefix "${LIBZIP_LIBRARY}" PATH) 22 | get_filename_component (_LIBZIP_prefix "${_LIBZIP_prefix}" PATH) 23 | set (_LIBZIP_hints "HINTS" "${_LIBZIP_prefix}") 24 | unset (_LIBZIP_prefix) 25 | endif () 26 | 27 | # Find header files and, on Windows, the DLL 28 | find_path (LIBZIP_INCLUDE_DIR_zip "zip.h" 29 | ${_LIBZIP_hints} 30 | PATHS ${LIBZIP_DIR} $ENV{LIBZIP_DIR} 31 | PATH_SUFFIXES "include") 32 | find_path (LIBZIP_INCLUDE_DIR_zipconf "zipconf.h" 33 | ${_LIBZIP_hints} 34 | PATHS ${LIBZIP_DIR} $ENV{LIBZIP_DIR} 35 | PATH_SUFFIXES "include" "lib/libzip/include") 36 | set (LIBZIP_INCLUDE_DIRS 37 | "${LIBZIP_INCLUDE_DIR_zip}" "${LIBZIP_INCLUDE_DIR_zipconf}" 38 | ) 39 | 40 | mark_as_advanced (LIBZIP_INCLUDE_DIRS) 41 | 42 | if (WIN32) 43 | find_file (LIBZIP_DLL NAMES "zip.dll" "libzip.dll" 44 | ${_LIBZIP_hints} 45 | PATHS ${LIBZIP_DIR} $ENV{LIBZIP_DIR} 46 | PATH_SUFFIXES "bin" "lib" 47 | NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) 48 | mark_as_advanced (LIBZIP_DLL) 49 | endif () 50 | unset (_LIBZIP_hints) 51 | 52 | # Create the IMPORTED targets. 53 | if (LIBZIP_LIBRARY) 54 | add_library ("libzip::libzip" SHARED IMPORTED) 55 | set_target_properties ("libzip::libzip" PROPERTIES 56 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 57 | IMPORTED_LOCATION "${LIBZIP_LIBRARY}" 58 | INTERFACE_COMPILE_DEFINITIONS "LIBZIP_STATIC_LIB_ONLY" 59 | INTERFACE_INCLUDE_DIRECTORIES "${LIBZIP_INCLUDE_DIRS}") 60 | if (WIN32) 61 | set_target_properties ("libzip::libzip" PROPERTIES 62 | IMPORTED_IMPLIB "${LIBZIP_LIBRARY}" 63 | IMPORTED_LOCATION "${LIBZIP_DLL}") 64 | else () # not WIN32 65 | set_target_properties ("libzip::libzip" PROPERTIES 66 | IMPORTED_LOCATION "${LIBZIP_LIBRARY}") 67 | endif () 68 | 69 | set (LIBZIP_LIBRARIES "libzip::libzip") 70 | endif () 71 | 72 | # Debug print-out. 73 | if (LIBZIP_PRINT_VARS) 74 | message ("LIBZIP find script variables:") 75 | message (" LIBZIP_INCLUDE_DIRS = ${LIBZIP_INCLUDE_DIRS}") 76 | message (" LIBZIP_LIBRARIES = ${LIBZIP_LIBRARIES}") 77 | message (" LIBZIP_DLL = ${LIBZIP_DLL}") 78 | message (" LIBZIP_LIBRARY = ${LIBZIP_LIBRARY}") 79 | endif () 80 | 81 | # Standard find_package stuff. 82 | include (FindPackageHandleStandardArgs) 83 | find_package_handle_standard_args (LIBZIP DEFAULT_MSG LIBZIP_LIBRARIES LIBZIP_INCLUDE_DIRS) 84 | -------------------------------------------------------------------------------- /src/lib/protocol_execution_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _MSC_VER 8 | # pragma warning(push, 0) 9 | #endif 10 | #include 11 | #ifdef _MSC_VER 12 | # pragma warning(pop) 13 | #endif 14 | 15 | using namespace coral::protocol::execution; 16 | 17 | TEST(coral_protocol_execution, CreateHelloMessage) 18 | { 19 | coralproto::testing::IntString pbSrc; 20 | pbSrc.set_i(314); 21 | pbSrc.set_s("Hello"); 22 | std::vector msg; 23 | CreateHelloMessage(msg, 3, pbSrc); 24 | 25 | ASSERT_EQ(2U, msg.size()); 26 | EXPECT_EQ(coralproto::execution::MSG_HELLO, ParseMessageType(msg[0])); 27 | EXPECT_EQ(3, ParseHelloMessage(msg)); 28 | coralproto::testing::IntString pbTgt; 29 | coral::protobuf::ParseFromFrame(msg[1], pbTgt); 30 | EXPECT_EQ(314, pbTgt.i()); 31 | EXPECT_EQ("Hello", pbTgt.s()); 32 | } 33 | 34 | TEST(coral_protocol_execution, CreateDeniedMessage) 35 | { 36 | std::vector msg; 37 | CreateDeniedMessage(msg, "Hello World!"); 38 | ASSERT_EQ(2U, msg.size()); 39 | EXPECT_EQ(coralproto::execution::MSG_DENIED, ParseMessageType(msg[0])); 40 | try { 41 | ParseHelloMessage(msg); 42 | ADD_FAILURE(); 43 | } catch (const RemoteErrorException& e) { 44 | SUCCEED(); 45 | EXPECT_STRNE(nullptr, std::strstr(e.what(), "Hello World!")); 46 | } catch (...) { 47 | ADD_FAILURE(); 48 | } 49 | } 50 | 51 | TEST(coral_protocol_execution, CreateMessage) 52 | { 53 | coralproto::testing::IntString pbSrc; 54 | pbSrc.set_i(314); 55 | pbSrc.set_s("Hello"); 56 | std::vector msg; 57 | CreateMessage(msg, coralproto::execution::MSG_READY, pbSrc); 58 | 59 | ASSERT_EQ(2U, msg.size()); 60 | EXPECT_EQ(coralproto::execution::MSG_READY, ParseMessageType(msg[0])); 61 | coralproto::testing::IntString pbTgt; 62 | coral::protobuf::ParseFromFrame(msg[1], pbTgt); 63 | EXPECT_EQ(314, pbTgt.i()); 64 | EXPECT_EQ("Hello", pbTgt.s()); 65 | } 66 | 67 | TEST(coral_protocol_execution, CreateMessage_NonErrorMessage) 68 | { 69 | std::vector msg; 70 | CreateMessage(msg, coralproto::execution::MSG_READY); 71 | EXPECT_EQ(coralproto::execution::MSG_READY, NonErrorMessageType(msg)); 72 | } 73 | 74 | TEST(coral_protocol_execution, CreateErrorMessage_NonErrorMessage) 75 | { 76 | std::vector msg; 77 | CreateErrorMessage( 78 | msg, 79 | coralproto::execution::ErrorInfo::INVALID_REQUEST, 80 | "some error"); 81 | try { 82 | NonErrorMessageType(msg); 83 | ADD_FAILURE(); 84 | } catch (const RemoteErrorException&) { 85 | SUCCEED(); 86 | } 87 | } 88 | 89 | TEST(coral_protocol_execution, ParseMessageType_error) 90 | { 91 | zmq::message_t msg; 92 | ASSERT_THROW(ParseMessageType(msg), 93 | coral::error::ProtocolViolationException); 94 | } 95 | 96 | TEST(coral_protocol_execution, ParseHelloMessage_error) 97 | { 98 | std::vector msg; 99 | msg.push_back(zmq::message_t(4)); 100 | ASSERT_THROW(ParseHelloMessage(msg), 101 | coral::error::ProtocolViolationException); 102 | } 103 | -------------------------------------------------------------------------------- /include/coral/provider/slave_creator.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Defines the coral::provider::SlaveCreator interface and related functionality. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_PROVIDER_SLAVE_CREATOR_HPP_INCLUDED 11 | #define CORAL_PROVIDER_SLAVE_CREATOR_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | 20 | namespace coral 21 | { 22 | namespace provider 23 | { 24 | 25 | 26 | /// An interface for classes that create slaves of a specific type. 27 | class SlaveCreator 28 | { 29 | public: 30 | /// A description of this slave type. 31 | virtual const coral::model::SlaveTypeDescription& Description() const = 0; 32 | 33 | /** 34 | \brief Creates a new instance of this slave type. 35 | 36 | This function must report whether a slave was successfully instantiated. 37 | For example, the slave may represent a particular piece of hardware (e.g. 38 | a human interface device), of which there is only one. The function would 39 | then return `false` if multiple instantiations are attempted. 40 | 41 | If the function returns `true`, it must also update `slaveLocator` with 42 | information about the new slave. `slaveLocator.Endpoint()` may then have 43 | one of three forms: 44 | 45 | 1. "Normal", i.e. `transport://address` 46 | 2. Empty, which means that the slave is accessible through the same 47 | endpoint as the slave provider (typically a proxy), except of course 48 | with a different identity. 49 | 3. Only a port specification starting with a colon, e.g. `:12345`. 50 | This may be used if the slave provider is bound to a TCP endpoint, 51 | and the slave is accessible on the same hostname but with a different 52 | port number. 53 | 54 | If the function returns `false`, InstantiationFailureDescription() must 55 | return a textual description of the reasons for this. `slaveLocator` must 56 | then be left untouched. 57 | 58 | \param [in] timeout 59 | How long the master will wait for the slave to start up. If possible, 60 | instantiation should be aborted and considered "failed" after this 61 | time has passed. A negative value means that there is no timeout, 62 | the slave gets as much time as it needs. 63 | \param [out] slaveLocator 64 | An object that describes how to connect to the slave. See the list 65 | above for different endpoint formats. 66 | 67 | \returns `true` if a slave was successfully instantiated, `false` otherwise. 68 | */ 69 | virtual bool Instantiate( 70 | std::chrono::milliseconds timeout, 71 | coral::net::SlaveLocator& slaveLocator) = 0; 72 | 73 | /** 74 | \brief A textual description of why a previous Instantiate() call failed. 75 | 76 | This function is only called if Instantiate() has returned `false`. 77 | */ 78 | virtual std::string InstantiationFailureDescription() const = 0; 79 | 80 | // Virtual destructor to allow deletion through base class reference. 81 | virtual ~SlaveCreator() { } 82 | }; 83 | 84 | 85 | }} // namespace 86 | #endif // header guard 87 | 88 | -------------------------------------------------------------------------------- /src/lib/protocol_domain.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace dp = coral::protocol::domain; 17 | 18 | 19 | namespace 20 | { 21 | const size_t MAGIC_LENGTH = 4; 22 | const char* MAGIC = "DSDP"; // distributed simulation domain protocol 23 | const uint16_t MAX_PROTOCOL_VERSION = 0; 24 | } 25 | 26 | 27 | void dp::SubscribeToReports(zmq::socket_t& subSocket) 28 | { 29 | subSocket.setsockopt(ZMQ_SUBSCRIBE, MAGIC, MAGIC_LENGTH); 30 | } 31 | 32 | 33 | zmq::message_t dp::CreateHeader(MessageType messageType, uint16_t protocolVersion) 34 | { 35 | zmq::message_t msg(MAGIC_LENGTH + 4); 36 | const auto msgData = static_cast(msg.data()); 37 | std::memcpy(msgData, MAGIC, MAGIC_LENGTH); 38 | coral::util::EncodeUint16(protocolVersion, msgData + MAGIC_LENGTH); 39 | coral::util::EncodeUint16(messageType, msgData + MAGIC_LENGTH + 2); 40 | return msg; 41 | } 42 | 43 | 44 | void dp::CreateAddressedMessage( 45 | std::vector& message, 46 | const std::string& recipient, 47 | dp::MessageType messageType, 48 | uint16_t protocolVersion) 49 | { 50 | message.clear(); 51 | message.push_back(coral::net::zmqx::ToFrame(recipient)); 52 | message.push_back(zmq::message_t(0)); 53 | message.push_back(CreateHeader(messageType, protocolVersion)); 54 | } 55 | 56 | 57 | void dp::CreateAddressedMessage( 58 | std::vector& message, 59 | const std::string& recipient, 60 | dp::MessageType messageType, 61 | uint16_t protocolVersion, 62 | const google::protobuf::MessageLite& body) 63 | { 64 | CreateAddressedMessage(message, recipient, messageType, protocolVersion); 65 | message.push_back(zmq::message_t()); 66 | coral::protobuf::SerializeToFrame(body, message.back()); 67 | } 68 | 69 | 70 | dp::Header dp::ParseHeader(const zmq::message_t& headerFrame) 71 | { 72 | const size_t HEADER_SIZE = MAGIC_LENGTH + 2 + 2; 73 | const auto headerData = static_cast(headerFrame.data()); 74 | if (headerFrame.size() != HEADER_SIZE || 75 | std::memcmp(headerData, MAGIC, MAGIC_LENGTH) != 0) { 76 | throw coral::error::ProtocolViolationException("Invalid header frame"); 77 | } 78 | 79 | Header h; 80 | h.protocol = coral::util::DecodeUint16(headerData + MAGIC_LENGTH); 81 | if (h.protocol > MAX_PROTOCOL_VERSION) { 82 | throw coral::error::ProtocolViolationException("Unsupported protocol"); 83 | } 84 | const uint16_t t = coral::util::DecodeUint16(headerData + MAGIC_LENGTH + 2); 85 | switch (t) { 86 | case MSG_SLAVEPROVIDER_HELLO: 87 | case MSG_UPDATE_AVAILABLE: 88 | case MSG_GET_SLAVE_LIST: 89 | case MSG_SLAVE_LIST: 90 | case MSG_INSTANTIATE_SLAVE: 91 | case MSG_INSTANTIATE_SLAVE_OK: 92 | case MSG_INSTANTIATE_SLAVE_FAILED: 93 | h.messageType = static_cast(t); 94 | break; 95 | default: 96 | throw coral::error::ProtocolViolationException("Invalid request"); 97 | } 98 | return h; 99 | } 100 | -------------------------------------------------------------------------------- /src/lib/error.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | 13 | namespace coral 14 | { 15 | namespace error 16 | { 17 | 18 | std::string ErrnoMessage(const std::string& msg, int errnoValue) noexcept 19 | { 20 | if (errnoValue == 0) return msg; 21 | else if (msg.empty()) return std::strerror(errnoValue); 22 | else return msg + " (" + std::strerror(errnoValue) + ')'; 23 | } 24 | 25 | 26 | namespace 27 | { 28 | class generic_category_impl : public std::error_category 29 | { 30 | public: 31 | const char* name() const noexcept final override { return "generic"; } 32 | 33 | std::string message(int ev) const final override 34 | { 35 | switch (static_cast(ev)) { 36 | case generic_error::aborted: 37 | return "Operation aborted"; 38 | case generic_error::canceled: 39 | return "Operation canceled"; 40 | case generic_error::operation_failed: 41 | return "Operation failed"; 42 | case generic_error::fatal: 43 | return "An irrecoverable error occurred"; 44 | default: 45 | assert(!"Unknown simulation error code"); 46 | return "Unknown simulation error"; 47 | } 48 | } 49 | }; 50 | 51 | class sim_category_impl : public std::error_category 52 | { 53 | public: 54 | const char* name() const noexcept final override { return "simulation"; } 55 | 56 | std::string message(int ev) const final override 57 | { 58 | switch (static_cast(ev)) { 59 | case sim_error::cannot_perform_timestep: 60 | return "Slave unable to perform time step"; 61 | case sim_error::data_timeout: 62 | return "Slave-to-slave data communication timed out"; 63 | default: 64 | assert(!"Unknown simulation error code"); 65 | return "Unknown simulation error"; 66 | } 67 | } 68 | }; 69 | } 70 | 71 | 72 | const std::error_category& generic_category() noexcept 73 | { 74 | static generic_category_impl instance; 75 | return instance; 76 | } 77 | 78 | 79 | const std::error_category& sim_category() noexcept 80 | { 81 | static sim_category_impl instance; 82 | return instance; 83 | } 84 | 85 | 86 | std::error_code make_error_code(generic_error e) noexcept 87 | { 88 | return std::error_code( 89 | static_cast(e), 90 | generic_category()); 91 | } 92 | 93 | 94 | std::error_code make_error_code(sim_error e) noexcept 95 | { 96 | return std::error_code( 97 | static_cast(e), 98 | sim_category()); 99 | } 100 | 101 | 102 | std::error_condition make_error_condition(generic_error e) noexcept 103 | { 104 | return std::error_condition( 105 | static_cast(e), 106 | generic_category()); 107 | } 108 | 109 | 110 | std::error_condition make_error_condition(sim_error e) noexcept 111 | { 112 | return std::error_condition( 113 | static_cast(e), 114 | sim_category()); 115 | } 116 | 117 | 118 | }} // namespace 119 | -------------------------------------------------------------------------------- /include/coral/log.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Main header file for coral::log (but also contains a few macros). 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_LOG_HPP 11 | #define CORAL_LOG_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | namespace coral 21 | { 22 | /// Program logging facilities. 23 | namespace log 24 | { 25 | 26 | 27 | /// Log levels. 28 | enum Level 29 | { 30 | trace, 31 | debug, 32 | info, 33 | warning, 34 | error 35 | }; 36 | 37 | /** 38 | \brief Reads a log level from a string. 39 | 40 | This will remove leading and trailing whitespace, convert the string to 41 | lowercase, compare it to the names of the `Level` constants and return 42 | the one that matches. 43 | */ 44 | Level ParseLevel(std::string str); 45 | 46 | 47 | /// Writes a plain C string to the global logger. 48 | void Log(Level level, const char* message) noexcept; 49 | 50 | /// Writes a plain C++ string to the global logger. 51 | void Log(Level level, const std::string& message) noexcept; 52 | 53 | /// Writes a formatted message to the global logger. 54 | void Log(Level level, const boost::format& message) noexcept; 55 | 56 | 57 | namespace detail 58 | { 59 | // These are intended for use in the macros below 60 | void LogLoc(Level level, const char* file, int line, const char* message) noexcept; 61 | void LogLoc(Level level, const char* file, int line, const std::string& message) noexcept; 62 | void LogLoc(Level level, const char* file, int line, const boost::format& message) noexcept; 63 | } 64 | 65 | 66 | /** 67 | \def CORAL_LOG_TRACE(args) 68 | \brief If the macro CORAL_LOG_TRACE_ENABLED is defined, this is equivalent 69 | to calling `Log(trace, args)`, except that the file and line number 70 | are also logged. Otherwise, it is a no-op. 71 | */ 72 | #ifdef CORAL_LOG_TRACE_ENABLED 73 | # define CORAL_LOG_TRACE(...) coral::log::detail::LogLoc(coral::log::trace, __FILE__, __LINE__, __VA_ARGS__) 74 | #else 75 | # define CORAL_LOG_TRACE(...) ((void)0) 76 | #endif 77 | 78 | /** 79 | \def CORAL_LOG_DEBUG(args) 80 | \brief If either of the macros CORAL_LOG_DEBUG_ENABLED or CORAL_LOG_TRACE_ENABLED 81 | are defined, this is equivalent to calling `Log(debug, args)`, except 82 | that the file and line number are also logged. Otherwise, it is a no-op. 83 | */ 84 | #if defined(CORAL_LOG_DEBUG_ENABLED) || defined(CORAL_LOG_TRACE_ENABLED) 85 | # define CORAL_LOG_DEBUG(...) coral::log::detail::LogLoc(coral::log::debug, __FILE__, __LINE__, __VA_ARGS__) 86 | #else 87 | # define CORAL_LOG_DEBUG(...) ((void)0) 88 | #endif 89 | 90 | 91 | /** 92 | \brief Adds a new log sink. 93 | 94 | Until the first time this function is called, the library will use a default 95 | sink that prints messages to `std::clog` and which filters out anything below 96 | level `error`. 97 | 98 | The first time this function is called, the default sink will be *replaced* 99 | with the new one. Subsequent calls will add new sinks. 100 | */ 101 | void AddSink(std::shared_ptr stream, Level level = error); 102 | 103 | 104 | /// Convenience function for making a `std::shared_ptr` to `std::clog`. 105 | std::shared_ptr CLogPtr() noexcept; 106 | 107 | 108 | }} // namespace 109 | #endif // header guard 110 | -------------------------------------------------------------------------------- /cmake/ObjectTargetFunctions.cmake: -------------------------------------------------------------------------------- 1 | # Defines a couple of functions which allows one to treat OBJECT library 2 | # targets more like other library targets, in particular with regards to 3 | # propagation of transitive usage requirements. 4 | # 5 | cmake_minimum_required(VERSION 3.1) 6 | 7 | 8 | # Links a target to one or more OBJECT library targets. 9 | # 10 | # Transitive usage requirements specified in the INTERFACE_* properties 11 | # of each OBJECT target will be propagated, but only as part of the build 12 | # interface. They will not be part of the export (install) interface. 13 | # 14 | # Usage: 15 | # 16 | # target_link_objects( 17 | # 18 | # ... 19 | # [ ...] 20 | # ... 21 | # ) 22 | # 23 | function(target_link_objects target) 24 | set(privacy "PRIVATE") 25 | foreach(arg IN LISTS ARGN) 26 | if((arg STREQUAL "PRIVATE") OR (arg STREQUAL "PUBLIC") OR (arg STREQUAL "INTERFACE")) 27 | set(privacy "${arg}") 28 | else() 29 | target_sources(${target} PRIVATE $) 30 | target_compile_definitions(${target} ${privacy} $>) 31 | target_compile_features (${target} ${privacy} $>) 32 | target_compile_options (${target} ${privacy} $>) 33 | target_include_directories(${target} ${privacy} $>) 34 | target_link_libraries (${target} ${privacy} $>) 35 | endif() 36 | endforeach() 37 | endfunction(target_link_objects) 38 | 39 | 40 | # Propagates usage requirements from one or more library targets to an 41 | # OBJECT library target. 42 | # 43 | # Usage: 44 | # 45 | # object_target_link_libraries( 46 | # 47 | # ... 48 | # [ ...] 49 | # ... 50 | # ) 51 | # 52 | function(object_target_link_libraries target) 53 | set(privacy "PRIVATE") 54 | foreach(arg IN LISTS ARGN) 55 | if((arg STREQUAL "PRIVATE") OR (arg STREQUAL "PUBLIC") OR (arg STREQUAL "INTERFACE")) 56 | set(privacy "${arg}") 57 | else() 58 | target_compile_definitions(${target} ${privacy} $) 59 | target_compile_features(${target} ${privacy} $) 60 | target_compile_options(${target} ${privacy} $) 61 | target_include_directories(${target} ${privacy} $) 62 | if ((privacy STREQUAL "PUBLIC") OR (privacy STREQUAL "INTERFACE")) 63 | set_property( 64 | TARGET ${target} 65 | APPEND 66 | PROPERTY INTERFACE_LINK_LIBRARIES ${arg} 67 | ) 68 | get_target_property(deps ${arg} INTERFACE_LINK_LIBRARIES) 69 | if (deps) 70 | set_property( 71 | TARGET ${target} 72 | APPEND 73 | PROPERTY INTERFACE_LINK_LIBRARIES ${deps} 74 | ) 75 | endif () 76 | endif() 77 | endif() 78 | endforeach() 79 | endfunction(object_target_link_libraries) 80 | -------------------------------------------------------------------------------- /include/coral/provider/provider.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Defines the coral::provider::SlaveProvider class and related functionality. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_PROVIDER_PROVIDER_HPP_INCLUDED 11 | #define CORAL_PROVIDER_PROVIDER_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | 24 | // Forward declaration to avoid dependency on ZMQ headers 25 | namespace zmq { class socket_t; } 26 | 27 | 28 | namespace coral 29 | { 30 | namespace provider 31 | { 32 | 33 | 34 | /// A slave provider that runs in a background thread. 35 | class SlaveProvider 36 | { 37 | public: 38 | /** 39 | \brief Creates a background thread and runs a slave provider in it. 40 | 41 | \param [in] slaveProviderID 42 | A string which is used to uniquely identify the slave provider. 43 | Expect trouble if two slave providers have the same ID. 44 | \param [in] slaveTypes 45 | The slave types offered by the slave provider. 46 | \param [in] networkInterface 47 | The name or IP address (in dot-decimal format) of the network 48 | interface that should be used, or "*" for all available interfaces. 49 | \param [in] discoveryPort 50 | The UDP port used by others to discover this slave provider. 51 | \param [in] exceptionHandler 52 | A function that will be called if an exception is thrown in the 53 | background thread. If no handler is provided, or if the handler itself 54 | throws, std::terminate() will be called. If the handler returns without 55 | throwing, the background thread will simply terminate. (In this case, 56 | it is still necessary to call Stop() in the foreground thread before 57 | the SlaveProvider object is destroyed.) 58 | Note that the exception handler will be called *in* the background 59 | thread, so care should be taken not to implement it in a thread-unsafe 60 | manner. 61 | */ 62 | SlaveProvider( 63 | const std::string& slaveProviderID, 64 | std::vector>&& slaveTypes, 65 | const coral::net::ip::Address& networkInterface, 66 | coral::net::ip::Port discoveryPort, 67 | std::function exceptionHandler = nullptr); 68 | 69 | SlaveProvider(const SlaveProvider&) = delete; 70 | SlaveProvider& operator=(const SlaveProvider&) = delete; 71 | 72 | CORAL_DEFINE_DEFAULT_MOVE(SlaveProvider, m_killSocket, m_thread); 73 | 74 | /** 75 | \brief Destructs the SlaveProvider object; requires that Stop() has been 76 | called first. 77 | 78 | If the background thread has not been terminated with Stop() when the 79 | destructor runs, std::terminate() is called. 80 | */ 81 | ~SlaveProvider() noexcept; 82 | 83 | /** 84 | \brief Stops the slave provider. 85 | 86 | This will send a signal to the background thread that triggers a shutdown 87 | of the slave provider. The function blocks until the background thread has 88 | terminated. 89 | */ 90 | void Stop(); 91 | 92 | private: 93 | std::unique_ptr m_killSocket; 94 | std::thread m_thread; 95 | }; 96 | 97 | 98 | }} // namespace 99 | #endif // header guard 100 | -------------------------------------------------------------------------------- /src/lib/log.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | 16 | namespace coral 17 | { 18 | namespace log 19 | { 20 | 21 | 22 | Level ParseLevel(std::string str) 23 | { 24 | boost::trim(str); 25 | if (boost::iequals(str, "trace")) return trace; 26 | else if (boost::iequals(str, "debug")) return debug; 27 | else if (boost::iequals(str, "info")) return info; 28 | else if (boost::iequals(str, "warning")) return warning; 29 | else if (boost::iequals(str, "error")) return error; 30 | else throw std::runtime_error("Invalid log level: " + str); 31 | } 32 | 33 | 34 | namespace 35 | { 36 | struct Sink 37 | { 38 | Level level; 39 | std::shared_ptr stream; 40 | }; 41 | std::mutex g_mutex; 42 | std::vector g_sinks{{error, CLogPtr()}}; 43 | bool g_sinksAdded = false; 44 | 45 | // Returns a space-padded, human-readable string for each log level. 46 | const char* LevelNamePadded(Level level) 47 | { 48 | switch (level) { 49 | case trace: return " trace "; 50 | case debug: return " debug "; 51 | case info: return " info "; 52 | case warning: return "warning"; 53 | case error: return " error "; 54 | default: return "unknown"; 55 | } 56 | } 57 | } 58 | 59 | 60 | #define CORAL_IMPLEMENT_LOG \ 61 | std::lock_guard lock(g_mutex); \ 62 | for (const auto& sink : g_sinks) { \ 63 | if (level >= sink.level) { \ 64 | *sink.stream \ 65 | << '[' << LevelNamePadded(level) << "] " \ 66 | << message << std::endl; \ 67 | } \ 68 | } 69 | 70 | #define CORAL_IMPLEMENT_LOG_LOC \ 71 | std::lock_guard lock(g_mutex); \ 72 | for (const auto& sink : g_sinks) { \ 73 | if (level >= sink.level) { \ 74 | *sink.stream \ 75 | << '[' << LevelNamePadded(level) << "] " \ 76 | << message \ 77 | << " (" << file << ':' << line << ')' << std::endl; \ 78 | } \ 79 | } 80 | 81 | 82 | void Log(Level level, const char* message) noexcept 83 | { 84 | CORAL_IMPLEMENT_LOG 85 | } 86 | 87 | 88 | void Log(Level level, const std::string& message) noexcept 89 | { 90 | CORAL_IMPLEMENT_LOG 91 | } 92 | 93 | 94 | void Log(Level level, const boost::format& message) noexcept 95 | { 96 | CORAL_IMPLEMENT_LOG 97 | } 98 | 99 | 100 | void detail::LogLoc(Level level, const char* file, int line, const char* message) noexcept 101 | { 102 | CORAL_IMPLEMENT_LOG_LOC 103 | } 104 | 105 | 106 | void detail::LogLoc(Level level, const char* file, int line, const std::string& message) noexcept 107 | { 108 | CORAL_IMPLEMENT_LOG_LOC 109 | } 110 | 111 | 112 | void detail::LogLoc(Level level, const char* file, int line, const boost::format& message) noexcept 113 | { 114 | CORAL_IMPLEMENT_LOG_LOC 115 | } 116 | 117 | 118 | void AddSink(std::shared_ptr stream, Level level) 119 | { 120 | std::lock_guard lock(g_mutex); 121 | if (!g_sinksAdded) { 122 | g_sinks.front().level = level; 123 | g_sinks.front().stream = stream; 124 | g_sinksAdded = true; 125 | } else { 126 | g_sinks.push_back({level, stream}); 127 | } 128 | } 129 | 130 | 131 | std::shared_ptr CLogPtr() noexcept 132 | { 133 | return std::shared_ptr(&std::clog, [] (void*) { }); 134 | } 135 | 136 | 137 | }} // namespace 138 | -------------------------------------------------------------------------------- /src/include/coral/net/udp.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Module header for coral::net::udp 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_NET_UDP_HPP 11 | #define CORAL_NET_UDP_HPP 12 | 13 | #ifdef _WIN32 14 | # include 15 | #else 16 | # include 17 | #endif 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | 27 | namespace coral 28 | { 29 | namespace net 30 | { 31 | 32 | /// Facilities for communication over the User Datagram Protocol (UDP) 33 | namespace udp 34 | { 35 | 36 | 37 | /// A class for sending and receiving UDP broadcast messages. 38 | class BroadcastSocket 39 | { 40 | public: 41 | /// The native socket handle type (`SOCKET` on Windows, `int` on *NIX). 42 | #ifdef _WIN32 43 | typedef SOCKET NativeSocket; 44 | #else 45 | typedef int NativeSocket; 46 | #endif 47 | 48 | /// Flags that control the operation of this class. 49 | enum Flags 50 | { 51 | /** 52 | \brief Only send, don't receive (i.e., don't bind the socket). 53 | 54 | If this flag is set, Receive() won't work and shouldn't be called. 55 | */ 56 | onlySend = 1 57 | }; 58 | 59 | /** 60 | \brief Constructor. 61 | 62 | \param [in] networkInterface 63 | The name or IP address of the network interface to broadcast and listen 64 | on. The special value "*" means "all interfaces". 65 | \param [in] port 66 | The port to broadcast and listen on. 67 | \param [in] flags 68 | A bitwise OR of one or more Flags values, or zero to use defaults. 69 | 70 | \throws std::runtime_error on failure. 71 | */ 72 | BroadcastSocket( 73 | const ip::Address& networkInterface, 74 | ip::Port port, 75 | int flags = 0); 76 | 77 | /// Destructor 78 | ~BroadcastSocket() noexcept; 79 | 80 | BroadcastSocket(const BroadcastSocket&) = delete; 81 | BroadcastSocket& operator=(const BroadcastSocket&) = delete; 82 | 83 | /// Move constructor 84 | BroadcastSocket(BroadcastSocket&&) noexcept; 85 | /// Move assignment operator 86 | BroadcastSocket& operator=(BroadcastSocket&&) noexcept; 87 | 88 | /** 89 | \brief Broadcasts a message. 90 | 91 | \param [in] buffer 92 | A buffer of size at least `msgSize`, which contains the message data. 93 | \param [in] msgSize 94 | The size of the message. 95 | 96 | \throws std::runtime_error on failure. 97 | */ 98 | void Send(const char* buffer, std::size_t msgSize); 99 | 100 | /** 101 | \brief Receives a message. 102 | 103 | \param [in] buffer 104 | A pointer to an array of size at least `bufferSize` bytes, to which the 105 | message data will be written. 106 | \param [in] bufferSize 107 | The size of the supplied buffer. 108 | \param [in] sender 109 | If nonnull, the object pointed to by this parameter will be filled 110 | with the IP address of the message sender. 111 | 112 | \returns 113 | The number of bytes received. This may be more than `bufferSize`, 114 | meaning that the message has been truncated. 115 | 116 | \throws std::runtime_error on failure. 117 | */ 118 | std::size_t Receive( 119 | char* buffer, 120 | std::size_t bufferSize, 121 | ip::Address* sender); 122 | 123 | /// The native socket handle. 124 | NativeSocket NativeHandle() const noexcept; 125 | 126 | private: 127 | class Private; 128 | std::unique_ptr m_private; 129 | }; 130 | 131 | 132 | }}} // namespace 133 | #endif // header guard 134 | -------------------------------------------------------------------------------- /src/lib/util_zip_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | 11 | TEST(coral_util_zip, Archive) 12 | { 13 | namespace du = coral::util; 14 | namespace dz = coral::util::zip; 15 | namespace fs = boost::filesystem; 16 | 17 | // Info about the test archive file and its contents 18 | const std::uint64_t archiveEntryCount = 3; 19 | const std::string dirFilename = "images/"; 20 | const std::string binFilename = "smiley.png"; 21 | const std::string txtFilename = "a text file.txt"; 22 | const std::string dirName = dirFilename; 23 | const std::string binName = dirFilename + binFilename; 24 | const std::string txtName = txtFilename; 25 | const std::uint64_t binSize = 16489; 26 | const std::uint64_t txtSize = 13; 27 | 28 | // Test setup 29 | const auto testDataDir = std::getenv("CORAL_TEST_DATA_DIR"); 30 | ASSERT_STRNE(nullptr, testDataDir); 31 | const auto archivePath = boost::filesystem::path(testDataDir) / "ziptest.zip"; 32 | 33 | // Open archive 34 | auto archive = dz::Archive(archivePath); 35 | ASSERT_TRUE(archive.IsOpen()); 36 | 37 | // Get entry info 38 | ASSERT_EQ(archiveEntryCount, archive.EntryCount()); 39 | const auto dirIndex = archive.FindEntry(dirName); 40 | const auto binIndex = archive.FindEntry(binName); 41 | const auto txtIndex = archive.FindEntry(txtName); 42 | const auto invIndex = archive.FindEntry("no such entry"); 43 | ASSERT_NE(dz::INVALID_ENTRY_INDEX, dirIndex); 44 | ASSERT_NE(dz::INVALID_ENTRY_INDEX, binIndex); 45 | ASSERT_NE(dz::INVALID_ENTRY_INDEX, txtIndex); 46 | ASSERT_EQ(dz::INVALID_ENTRY_INDEX, invIndex); 47 | ASSERT_NE(dirIndex, binIndex); 48 | ASSERT_NE(dirIndex, txtIndex); 49 | ASSERT_NE(binIndex, txtIndex); 50 | ASSERT_EQ(dirName, archive.EntryName(dirIndex)); 51 | ASSERT_EQ(binName, archive.EntryName(binIndex)); 52 | ASSERT_EQ(txtName, archive.EntryName(txtIndex)); 53 | ASSERT_THROW(archive.EntryName(invIndex), dz::Exception); 54 | ASSERT_TRUE(archive.IsDirEntry(dirIndex)); 55 | ASSERT_FALSE(archive.IsDirEntry(binIndex)); 56 | ASSERT_FALSE(archive.IsDirEntry(txtIndex)); 57 | ASSERT_THROW(archive.IsDirEntry(invIndex), dz::Exception); 58 | 59 | // Extract entire archive 60 | { 61 | du::TempDir tempDir; 62 | archive.ExtractAll(tempDir.Path()); 63 | const auto dirExtracted = tempDir.Path() / dirName; 64 | const auto binExtracted = tempDir.Path() / binName; 65 | const auto txtExtracted = tempDir.Path() / txtName; 66 | ASSERT_TRUE(fs::exists(dirExtracted)); 67 | ASSERT_TRUE(fs::exists(binExtracted)); 68 | ASSERT_TRUE(fs::exists(txtExtracted)); 69 | ASSERT_TRUE(fs::is_directory(dirExtracted)); 70 | ASSERT_TRUE(fs::is_regular_file(binExtracted)); 71 | ASSERT_TRUE(fs::is_regular_file(txtExtracted)); 72 | ASSERT_EQ(binSize, fs::file_size(binExtracted)); 73 | ASSERT_EQ(txtSize, fs::file_size(txtExtracted)); 74 | ASSERT_THROW(archive.ExtractFileTo(binIndex, tempDir.Path()/"nonexistent"), std::runtime_error); 75 | } 76 | 77 | // Extract individual entries 78 | { 79 | du::TempDir tempDir; 80 | const auto binExtracted = archive.ExtractFileTo(binIndex, tempDir.Path()); 81 | const auto txtExtracted = archive.ExtractFileTo(txtIndex, tempDir.Path()); 82 | ASSERT_EQ(0, binExtracted.compare(tempDir.Path() / binFilename)); 83 | ASSERT_EQ(0, txtExtracted.compare(tempDir.Path() / txtFilename)); 84 | ASSERT_EQ(binSize, fs::file_size(binExtracted)); 85 | ASSERT_EQ(txtSize, fs::file_size(txtExtracted)); 86 | ASSERT_THROW(archive.ExtractFileTo(invIndex, tempDir.Path()), dz::Exception); 87 | ASSERT_THROW(archive.ExtractFileTo(binIndex, tempDir.Path()/"nonexistent"), std::runtime_error); 88 | } 89 | 90 | archive.Discard(); 91 | ASSERT_FALSE(archive.IsOpen()); 92 | ASSERT_NO_THROW(archive.Discard()); 93 | } 94 | -------------------------------------------------------------------------------- /src/master/config_parser.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #ifndef CORALMASTER_CONFIG_PARSER_HPP 8 | #define CORALMASTER_CONFIG_PARSER_HPP 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | struct SimulationEvent 23 | { 24 | SimulationEvent( 25 | coral::model::TimePoint t, 26 | coral::model::SlaveID s, 27 | coral::model::VariableID v, 28 | const coral::model::ScalarValue& n); 29 | coral::model::TimePoint timePoint; 30 | coral::model::SlaveID slave; 31 | coral::model::VariableID variable; 32 | coral::model::ScalarValue newValue; 33 | }; 34 | 35 | 36 | /** 37 | \brief Sets up the system to be simulated in an execution based on a 38 | configuration file. 39 | 40 | \param [in] path The path to the configuration file. 41 | \param [in] execution The execution controller. 42 | 43 | \throws std::runtime_error if there were errors in the configuraiton file. 44 | */ 45 | //TODO: Split this into two functions: one which reads the configuration 46 | // and one which applies it to the controller. 47 | void ParseSystemConfig( 48 | const std::string& path, 49 | coral::master::ProviderCluster& providers, 50 | coral::master::Execution& execution, 51 | std::vector& scenario, 52 | std::chrono::milliseconds commTimeout, 53 | std::chrono::milliseconds instantiationTimeout, 54 | std::ostream* warningLog, 55 | std::function postInstantiationHook); 56 | 57 | 58 | class SetVariablesException : public std::runtime_error 59 | { 60 | public: 61 | SetVariablesException(); 62 | const char* what() const noexcept override; 63 | void AddSlaveError(const std::string& slaveName, const std::string& errMsg); 64 | private: 65 | std::string m_msg; 66 | std::vector> m_slaveErrors; 67 | }; 68 | 69 | 70 | /// Configuration parameters for an execution run. 71 | struct ExecutionConfig 72 | { 73 | ExecutionConfig(); 74 | /// Simulation start time 75 | double startTime; 76 | 77 | /// Simulation stop time 78 | double stopTime; 79 | 80 | /// Simulation step size 81 | double stepSize; 82 | 83 | /** 84 | \brief General command/communications timeout, in milliseconds 85 | 86 | This is how long the master will wait for replies to commands sent to a 87 | slave before it considers the connection to be broken. It should generally 88 | be a short duration, as it is used for "cheap" operations (everything 89 | besides the "step" command). The default value is 1 second. 90 | */ 91 | std::chrono::milliseconds commTimeout; 92 | 93 | /** 94 | \brief Time step timeout multiplier 95 | 96 | This controls the amount of time the slaves get to carry out a time step. 97 | The timeout is set equal to `stepTimeoutMultiplier` times the step size, 98 | where the step size is assumed to be in seconds. 99 | 100 | The default value is 100, allowing for a simulation which runs at, at most, 101 | a hundredth of real-time speed. 102 | */ 103 | double stepTimeoutMultiplier; 104 | 105 | /** 106 | \brief Slave instantiation timeout, in milliseconds. 107 | 108 | This controls how long each slave gets from the moment the instantiation 109 | command is issued to the moment it is ready for a command from the master 110 | node. 111 | */ 112 | std::chrono::milliseconds instantiationTimeout; 113 | }; 114 | 115 | 116 | /** 117 | \brief Parses an execution configuration file. 118 | \throws std::runtime_error if there were errors in the configuration file. 119 | */ 120 | ExecutionConfig ParseExecutionConfig(const std::string& path); 121 | 122 | 123 | #endif // header guard 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE:** Coral is no longer maintained, as it has been superseded by the [_Open Simulation Platform_](https://opensimulationplatform.com). More specifically, the library part of Coral has been superseded by [_libcosim_](https://github.com/open-simulation-platform/libcosim), while the [_cosim_](https://github.com/open-simulation-platform/cosim-cli) CLI is a replacement for _coralmaster_. (_coralslave_ and _coralslaveprovider_ don't have direct equivalents, as _cosim_ does things slightly differently.) 2 | 3 | Coral 4 | ===== 5 | Coral is **free and open-source co-simulation software** built from the ground up with support for [FMI](https://fmi-standard.org) and distributed simulations in mind. It is primarily a **C++ library** that can be embedded into any application that needs to perform co-simulations. However, we've also made some simple **command-line applications** for testing, demonstration and research purposes. 6 | 7 | Coral was developed as part of the R&D project [Virtual Prototyping of Maritime Systems and Operations](http://viproma.no) (ViProMa) and maintained by [SINTEF Ocean](http://www.sintef.no/en/ocean/). 8 | 9 | Terms of use 10 | ------------ 11 | Coral is free and open-source software released under the terms of the 12 | [Mozilla Public License v. 2.0](http://mozilla.org/MPL/2.0/). For more 13 | information, see the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/). 14 | 15 | Documentation 16 | ------------- 17 | [Browse the API documentation online.](https://viproma.github.io/coral) 18 | 19 | Build requirements 20 | ------------------ 21 | The version/release numbers specified for compilers, tools and libraries below 22 | are the lowest ones used for the official Coral builds, and are therefore known 23 | to work. Other versions are likely to work too, especially if they are newer 24 | or at least have the same major release number, but this is not guaranteed. 25 | 26 | Supported platforms and compilers: 27 | 28 | - Windows: Visual Studio 2015 or newer. 29 | - Linux: GCC 4.9 or newer. 30 | 31 | Required build tools: 32 | 33 | - [CMake](http://cmake.org) v3.6, to generate the build system. 34 | - The [Protocol Buffers](https://developers.google.com/protocol-buffers/) 35 | compiler, to parse the protocol buffer files and generate C++ code for them. 36 | - [Doxygen](http://doxygen.org), to generate API documentation (optional). 37 | 38 | Required libraries: 39 | 40 | - [Boost](http://boost.org) v1.55.0 41 | - [ZeroMQ](http://zeromq.org) v4.0 (including 42 | [cppzmq](https://github.com/zeromq/cppzmq), which may not be included in 43 | the default installation for all platforms) 44 | - [FMI Library](http://jmodelica.org/FMILibrary) v2.0.3 45 | - [Protocol Buffers](https://developers.google.com/protocol-buffers/) v2.6 46 | - [libzip](http://www.nih.at/libzip/) v1.1 47 | - [zlib](http://www.zlib.net/) v1.2 (a dependency of libzip) 48 | 49 | Optional libraries (only necessary if you want to build and run tests): 50 | 51 | - [Google Test](https://github.com/google/googletest) 52 | 53 | The recommended way to obtain all these, which works on all supported 54 | platforms, is to use [vcpkg](https://github.com/Microsoft/vcpkg) and install 55 | the packages listed in [`vcpkg-deps.txt`](./vcpkg-deps.txt) and optionally 56 | [`vcpkg-test-deps.txt`](./vcpkg-test-deps.txt). 57 | 58 | 59 | Building 60 | -------- 61 | Coral is built using a fairly standard CMake procedure, so we refer to the 62 | [CMake documentation](http://cmake.org/cmake/help/documentation.html) for 63 | details and advanced usage, and only give a quick walk-through of the procedure 64 | here. 65 | 66 | First, in a terminal/command-line window, change to the root source directory 67 | (i.e., the one which contains the present README file), and enter the following 68 | commands: 69 | 70 | mkdir build 71 | cd build 72 | cmake .. 73 | 74 | This will locate dependencies and generate the platform-specific build system. 75 | Next, build the software by entering the following command (still within the 76 | `build` directory): 77 | 78 | cmake --build . 79 | 80 | Finally, it is a good idea to run the tests, to see that everything works as 81 | it should. The command for this is (unfortunately) platform dependent: 82 | 83 | cmake --build . --target RUN_TESTS &:: Windows 84 | cmake --build . --target test # Linux 85 | -------------------------------------------------------------------------------- /src/include/coral/util/console.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Utilities for writing console applications 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_UTIL_CONSOLE_HPP 11 | #define CORAL_UTIL_CONSOLE_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace coral 20 | { 21 | namespace util 22 | { 23 | 24 | /** 25 | \brief Returns a string vector with the same contents as the standard C 26 | program argument array. 27 | */ 28 | std::vector CommandLine(int argc, char const *const * argv); 29 | 30 | 31 | /** 32 | \brief Parses program arguments and, if necessary, prints a help message. 33 | 34 | This is a convenience function which takes two sets of program options, 35 | `options` and `positionalOptions`, where the former contains the ones that 36 | should be specified by the user with switches (e.g. `--foo`) and the latter 37 | contains the ones that are specified using "normal" (positional) arguments, 38 | and performs the following actions: 39 | 40 | 1. Adds the `--help` option to `options`. 41 | 2. Parses the arguments given in `args`, mapping them to options specified 42 | in `options` and `positionalOptions`. 43 | 3. If the `--help` option was specified, or positional arguments were 44 | expected and `args` is empty, prints a help message and returns an 45 | empty/false object. 46 | 4. Otherwise, returns the mapped option values. 47 | 48 | If an empty/false object is returned, it is recommended that the program 49 | exits more or less immediately. 50 | 51 | The help message is constructed using `boost::program_options` own formatted 52 | output functions, so it is recommended to include helpful option descriptions 53 | when adding options to `options` and `positionalOptions`. 54 | 55 | \param [in] args 56 | The command-line arguments as they were passed to the program 57 | (not including the program name). 58 | \param [in] options 59 | The options that should be specified with command-line switches 60 | (i.e., --switch or -s). 61 | \param [in] positionalOptions 62 | The options that should be interpreted as positional arguments. 63 | \param [in] positions 64 | An object that describes how to map positional arguments to options. 65 | \param [in,out] helpOutput 66 | The output stream to which a help message should be written. 67 | \param [in] commandName 68 | The command name, as it should be displayed in the help message. 69 | \param [in] commandDescription 70 | A description of what the command does, for the help message. 71 | \param [in] extraHelp 72 | Text to output below the standard help message. 73 | 74 | \returns 75 | A map which contains the parsed program options, or, if --help was 76 | specified or no arguments were given, an empty/false value. 77 | */ 78 | boost::optional ParseArguments( 79 | const std::vector& args, 80 | boost::program_options::options_description options, 81 | const boost::program_options::options_description& positionalOptions, 82 | const boost::program_options::positional_options_description& positions, 83 | std::ostream& helpOutput, 84 | const std::string& commandName, 85 | const std::string& commandDescription, 86 | const std::string& extraHelp = std::string()); 87 | 88 | 89 | /// Adds options that control logging. 90 | void AddLoggingOptions(boost::program_options::options_description& options); 91 | 92 | 93 | /** 94 | \brief Parses arguments that control logging (added with `AddLoggingOptions()`) 95 | and takes the appropriate actions. 96 | 97 | This will at least call `coral::log::AddSink()` once, to add logging to the 98 | standard error stream, and it may also call it an additional time to add 99 | logging to a file. 100 | */ 101 | void UseLoggingArguments( 102 | const boost::program_options::variables_map& arguments, 103 | const std::string& logFilePrefix); 104 | 105 | 106 | }} // namespace 107 | #endif // header guard 108 | -------------------------------------------------------------------------------- /src/lib/net_zmqx_sockets_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | 14 | using namespace coral::net::zmqx; 15 | 16 | 17 | namespace 18 | { 19 | void RequestReplyTest(ReqSocket& cli, RepSocket& svr) 20 | { 21 | std::vector m; 22 | m.push_back(zmq::message_t(5)); 23 | m.push_back(zmq::message_t(5)); 24 | std::memcpy(m[0].data(), "hello", 5); 25 | std::memcpy(m[1].data(), "world", 5); 26 | cli.Send(m); 27 | EXPECT_TRUE(m.empty()); 28 | 29 | svr.Receive(m); 30 | EXPECT_EQ(2U, m.size()); 31 | EXPECT_EQ(5U, m[0].size()); 32 | EXPECT_EQ(5U, m[1].size()); 33 | EXPECT_EQ(0, std::memcmp(m[0].data(), "hello", 5)); 34 | EXPECT_EQ(0, std::memcmp(m[1].data(), "world", 5)); 35 | 36 | std::memcpy(m[0].data(), "hallo", 5); 37 | std::memcpy(m[1].data(), "verda", 5); 38 | svr.Send(m); 39 | EXPECT_TRUE(m.empty()); 40 | 41 | cli.Receive(m); 42 | EXPECT_EQ(2U, m.size()); 43 | EXPECT_EQ(5U, m[0].size()); 44 | EXPECT_EQ(5U, m[1].size()); 45 | EXPECT_EQ(0, std::memcmp(m[0].data(), "hallo", 5)); 46 | EXPECT_EQ(0, std::memcmp(m[1].data(), "verda", 5)); 47 | } 48 | } 49 | 50 | TEST(coral_net, ReqRepSocketDirect) 51 | { 52 | ReqSocket cli; 53 | RepSocket svr; 54 | svr.Bind(coral::net::Endpoint{"tcp://*:12345"}); 55 | EXPECT_EQ("tcp://0.0.0.0:12345", svr.BoundEndpoint().URL()); 56 | cli.Connect(coral::net::Endpoint{"tcp://localhost:12345"}); 57 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 58 | RequestReplyTest(cli, svr); 59 | RequestReplyTest(cli, svr); 60 | svr.Close(); 61 | cli.Close(); 62 | // ...and do it again 63 | svr.Bind(coral::net::Endpoint{"tcp://*:12346"}); 64 | EXPECT_EQ("tcp://0.0.0.0:12346", svr.BoundEndpoint().URL()); 65 | cli.Connect(coral::net::Endpoint{"tcp://localhost:12346"}); 66 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 67 | RequestReplyTest(cli, svr); 68 | RequestReplyTest(cli, svr); 69 | } 70 | 71 | 72 | TEST(coral_net, ReqRepSocketDirectReverse) 73 | { 74 | ReqSocket cli; 75 | RepSocket svr; 76 | cli.Bind(coral::net::Endpoint{"tcp://*:12345"}); 77 | svr.Connect(coral::net::Endpoint{"tcp://localhost:12345"}); 78 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 79 | RequestReplyTest(cli, svr); 80 | RequestReplyTest(cli, svr); 81 | svr.Close(); 82 | cli.Close(); 83 | // ...and do it again 84 | cli.Bind(coral::net::Endpoint{"tcp://*:12346"}); 85 | svr.Connect(coral::net::Endpoint{"tcp://localhost:12346"}); 86 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 87 | RequestReplyTest(cli, svr); 88 | RequestReplyTest(cli, svr); 89 | } 90 | 91 | 92 | TEST(coral_net, ReqRepSocketOutOfOrder) 93 | { 94 | ReqSocket cli; 95 | RepSocket svr; 96 | svr.Bind(coral::net::Endpoint{"tcp://*:12345"}); 97 | cli.Connect(coral::net::Endpoint{"tcp://localhost:12345"}); 98 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 99 | 100 | std::vector m; 101 | m.push_back(zmq::message_t(5)); 102 | m.push_back(zmq::message_t(5)); 103 | std::memcpy(m[0].data(), "hello", 5); 104 | std::memcpy(m[1].data(), "world", 5); 105 | cli.Send(m); 106 | EXPECT_TRUE(m.empty()); 107 | m.push_back(zmq::message_t(12)); 108 | std::memcpy(m[0].data(), "out of order", 12); 109 | cli.Send(m); 110 | 111 | svr.Receive(m); 112 | EXPECT_EQ(2U, m.size()); 113 | EXPECT_EQ(5U, m[0].size()); 114 | EXPECT_EQ(5U, m[1].size()); 115 | EXPECT_EQ(0, std::memcmp(m[0].data(), "hello", 5)); 116 | EXPECT_EQ(0, std::memcmp(m[1].data(), "world", 5)); 117 | 118 | std::memcpy(m[0].data(), "hallo", 5); 119 | std::memcpy(m[1].data(), "verda", 5); 120 | svr.Send(m); 121 | EXPECT_TRUE(m.empty()); 122 | 123 | cli.Receive(m); 124 | EXPECT_EQ(2U, m.size()); 125 | EXPECT_EQ(5U, m[0].size()); 126 | EXPECT_EQ(5U, m[1].size()); 127 | EXPECT_EQ(0, std::memcmp(m[0].data(), "hallo", 5)); 128 | EXPECT_EQ(0, std::memcmp(m[1].data(), "verda", 5)); 129 | 130 | svr.Receive(m); 131 | EXPECT_EQ(1U, m.size()); 132 | EXPECT_EQ(12U, m[0].size()); 133 | EXPECT_EQ(0, std::memcmp(m[0].data(), "out of order", 5)); 134 | } 135 | -------------------------------------------------------------------------------- /src/lib/fmi_fmu1_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | namespace 12 | { 13 | void RunTests(std::shared_ptr fmu) 14 | { 15 | EXPECT_EQ(coral::fmi::FMIVersion::v1_0, fmu->FMIVersion()); 16 | const auto& d = fmu->Description(); 17 | EXPECT_EQ("no.viproma.demo.identity", d.Name()); 18 | EXPECT_EQ(36U, d.UUID().size()); 19 | EXPECT_EQ( 20 | "Has one input and one output of each type, and outputs are always set equal to inputs", 21 | d.Description()); 22 | EXPECT_EQ("Lars Tandle Kyllingstad", d.Author()); 23 | EXPECT_EQ("0.3", d.Version()); 24 | EXPECT_NE(nullptr, std::static_pointer_cast(fmu)->FmilibHandle()); 25 | 26 | coral::model::VariableID 27 | realIn = 0, integerIn = 0, booleanIn = 0, stringIn = 0, 28 | realOut = 0, integerOut = 0, booleanOut = 0, stringOut = 0; 29 | for (const auto& v : d.Variables()) { 30 | if (v.Name() == "realIn" ) realIn = v.ID(); 31 | else if (v.Name() == "integerIn" ) integerIn = v.ID(); 32 | else if (v.Name() == "booleanIn" ) booleanIn = v.ID(); 33 | else if (v.Name() == "stringIn" ) stringIn = v.ID(); 34 | else if (v.Name() == "realOut") realOut = v.ID(); 35 | else if (v.Name() == "integerOut") integerOut = v.ID(); 36 | else if (v.Name() == "booleanOut") booleanOut = v.ID(); 37 | else if (v.Name() == "stringOut") stringOut = v.ID(); 38 | 39 | if (v.Name() == "realIn" ) { 40 | EXPECT_EQ(coral::model::REAL_DATATYPE, v.DataType()); 41 | EXPECT_EQ(coral::model::DISCRETE_VARIABILITY, v.Variability()); 42 | EXPECT_EQ(coral::model::INPUT_CAUSALITY, v.Causality()); 43 | } else if (v.Name() == "stringOut") { 44 | EXPECT_EQ(coral::model::STRING_DATATYPE, v.DataType()); 45 | EXPECT_EQ(coral::model::DISCRETE_VARIABILITY, v.Variability()); 46 | EXPECT_EQ(coral::model::OUTPUT_CAUSALITY, v.Causality()); 47 | } 48 | } 49 | 50 | const double tMax = 1.0; 51 | const double dt = 0.1; 52 | double realVal = 0.0; 53 | int integerVal = 0; 54 | bool booleanVal = false; 55 | std::string stringVal; 56 | 57 | auto instance = fmu->InstantiateSlave(); 58 | instance->Setup("testSlave", "testExecution", 0.0, tMax, false, 0.0); 59 | instance->StartSimulation(); 60 | 61 | for (double t = 0; t < tMax; t += dt) { 62 | EXPECT_EQ(realVal, instance->GetRealVariable(realOut)); 63 | EXPECT_EQ(integerVal, instance->GetIntegerVariable(integerOut)); 64 | EXPECT_EQ(booleanVal, instance->GetBooleanVariable(booleanOut)); 65 | EXPECT_EQ(stringVal, instance->GetStringVariable(stringOut)); 66 | 67 | realVal += 1.0; 68 | integerVal += 1; 69 | booleanVal = !booleanVal; 70 | stringVal += 'a'; 71 | 72 | instance->SetRealVariable(realIn, realVal); 73 | instance->SetIntegerVariable(integerIn, integerVal); 74 | instance->SetBooleanVariable(booleanIn, booleanVal); 75 | instance->SetStringVariable(stringIn, stringVal); 76 | 77 | EXPECT_TRUE(instance->DoStep(t, dt)); 78 | } 79 | 80 | instance->EndSimulation(); 81 | } 82 | } 83 | 84 | 85 | TEST(coral_fmi, Fmu1) 86 | { 87 | const auto testDataDir = std::getenv("CORAL_TEST_DATA_DIR"); 88 | auto importer = coral::fmi::Importer::Create(); 89 | auto fmu = importer->Import( 90 | boost::filesystem::path(testDataDir) / "fmi1_cs" / "identity.fmu"); 91 | RunTests(fmu); 92 | } 93 | 94 | 95 | TEST(coral_fmi, Fmu1_unpacked) 96 | { 97 | const auto testDataDir = std::getenv("CORAL_TEST_DATA_DIR"); 98 | coral::util::TempDir unpackDir; 99 | coral::util::zip::Archive( 100 | boost::filesystem::path(testDataDir) / "fmi1_cs" / "identity.fmu") 101 | .ExtractAll(unpackDir.Path()); 102 | 103 | auto importer = coral::fmi::Importer::Create(); 104 | auto fmu = importer->ImportUnpacked(unpackDir.Path()); 105 | RunTests(fmu); 106 | 107 | importer->CleanCache(); 108 | EXPECT_TRUE(boost::filesystem::exists(unpackDir.Path())); 109 | } 110 | -------------------------------------------------------------------------------- /src/lib/bus_slave_controller.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | namespace coral 15 | { 16 | namespace bus 17 | { 18 | 19 | 20 | SlaveController::SlaveController( 21 | coral::net::Reactor& reactor, 22 | const coral::net::SlaveLocator& slaveLocator, 23 | coral::model::SlaveID slaveID, 24 | const std::string& slaveName, 25 | const SlaveSetup& setup, 26 | std::chrono::milliseconds timeout, 27 | ConnectHandler onComplete, 28 | int maxConnectionAttempts) 29 | { 30 | CORAL_INPUT_CHECK(slaveID != coral::model::INVALID_SLAVE_ID); 31 | m_pendingConnection = ConnectToSlave( 32 | reactor, 33 | slaveLocator, 34 | maxConnectionAttempts, 35 | timeout, 36 | [=] (const std::error_code& ec, SlaveControlConnection scc) { 37 | if (!ec) { 38 | m_messenger = MakeSlaveControlMessenger( 39 | std::move(scc), 40 | slaveID, 41 | slaveName, 42 | setup, 43 | onComplete); 44 | } else { 45 | onComplete(ec); 46 | } 47 | }); 48 | } 49 | 50 | 51 | SlaveController::~SlaveController() 52 | { 53 | // Intentionally does nothing. 54 | // We just need it to be able to use SlaveControlMessenger as an undefined 55 | // type in the header. 56 | } 57 | 58 | 59 | void SlaveController::Close() 60 | { 61 | m_pendingConnection.Close(); 62 | if (m_messenger) m_messenger->Close(); 63 | } 64 | 65 | 66 | SlaveState SlaveController::State() const noexcept 67 | { 68 | if (m_messenger) return m_messenger->State(); 69 | else if (m_pendingConnection) return SLAVE_BUSY; 70 | else return SLAVE_NOT_CONNECTED; 71 | } 72 | 73 | 74 | void SlaveController::GetDescription( 75 | std::chrono::milliseconds timeout, 76 | GetDescriptionHandler onComplete) 77 | { 78 | if (m_messenger) { 79 | m_messenger->GetDescription(timeout, std::move(onComplete)); 80 | } else { 81 | onComplete( 82 | std::make_error_code(std::errc::not_connected), 83 | coral::model::SlaveDescription()); 84 | } 85 | } 86 | 87 | 88 | void SlaveController::SetVariables( 89 | const std::vector& settings, 90 | std::chrono::milliseconds timeout, 91 | SetVariablesHandler onComplete) 92 | { 93 | CORAL_INPUT_CHECK(!settings.empty()); 94 | if (m_messenger) { 95 | m_messenger->SetVariables(settings, timeout, std::move(onComplete)); 96 | } else { 97 | onComplete(std::make_error_code(std::errc::not_connected)); 98 | } 99 | } 100 | 101 | 102 | void SlaveController::SetPeers( 103 | const std::vector& peers, 104 | std::chrono::milliseconds timeout, 105 | SetPeersHandler onComplete) 106 | { 107 | if (m_messenger) { 108 | m_messenger->SetPeers(peers, timeout, std::move(onComplete)); 109 | } else { 110 | onComplete(std::make_error_code(std::errc::not_connected)); 111 | } 112 | } 113 | 114 | 115 | void SlaveController::ResendVars( 116 | std::chrono::milliseconds timeout, 117 | ResendVarsHandler onComplete) 118 | { 119 | if (m_messenger) { 120 | m_messenger->ResendVars(timeout, std::move(onComplete)); 121 | } else { 122 | onComplete(std::make_error_code(std::errc::not_connected)); 123 | } 124 | } 125 | 126 | 127 | void SlaveController::Step( 128 | coral::model::StepID stepID, 129 | coral::model::TimePoint currentT, 130 | coral::model::TimeDuration deltaT, 131 | std::chrono::milliseconds timeout, 132 | StepHandler onComplete) 133 | { 134 | CORAL_INPUT_CHECK(deltaT >= 0.0); 135 | if (m_messenger) { 136 | m_messenger->Step(stepID, currentT, deltaT, timeout, std::move(onComplete)); 137 | } else { 138 | onComplete(std::make_error_code(std::errc::not_connected)); 139 | } 140 | } 141 | 142 | void SlaveController::AcceptStep( 143 | std::chrono::milliseconds timeout, 144 | AcceptStepHandler onComplete) 145 | { 146 | if (m_messenger) { 147 | m_messenger->AcceptStep(timeout, std::move(onComplete)); 148 | } else { 149 | onComplete(std::make_error_code(std::errc::not_connected)); 150 | } 151 | } 152 | 153 | 154 | void SlaveController::Terminate() 155 | { 156 | m_pendingConnection.Close(); 157 | if (m_messenger) { 158 | m_messenger->Terminate(); 159 | } 160 | } 161 | 162 | 163 | }} // namespace 164 | -------------------------------------------------------------------------------- /include/coral/master/cluster.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Defines the coral::master::ProviderCluster class and related functionality. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_MASTER_CLUSTER_HPP 11 | #define CORAL_MASTER_CLUSTER_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | namespace coral 25 | { 26 | namespace master 27 | { 28 | 29 | 30 | /** 31 | * \brief 32 | * A common communication interface to a cluster of slave providers. 33 | * 34 | * This class represents a common interface to several slave providers in a 35 | * network. It can be used to get information about the available slave types 36 | * and to instantiate slaves on specific providers. 37 | * 38 | * Slave providers are discovered automatically by listening for UDP 39 | * broadcast messages that they broadcast periodically. 40 | * 41 | * \remark 42 | * When an object of this class is created, it will spawn a background thread 43 | * that performs the actual communication with the slave providers. To ensure 44 | * that there is a one-to-one relationship between an object of this class and 45 | * its underlying communication thread, the objects are noncopyable (but 46 | * movable), and will attempt to shut down the thread on destruction. 47 | */ 48 | class ProviderCluster 49 | { 50 | public: 51 | /// Information about a slave type. 52 | struct SlaveType 53 | { 54 | /// A description of the slave type. 55 | coral::model::SlaveTypeDescription description; 56 | 57 | /// A list of IDs of slave providers that offer this slave type. 58 | std::vector providers; 59 | }; 60 | 61 | /** 62 | * \brief 63 | * Constructor. 64 | * 65 | * \param [in] networkInterface 66 | * The name or IP address of the network interface that should be used, 67 | * or "*" for all available interfaces. 68 | * \param [in] discoveryPort 69 | * The UDP port used for discovering other entities such as slave 70 | * providers. 71 | */ 72 | ProviderCluster( 73 | const coral::net::ip::Address& networkInterface, 74 | coral::net::ip::Port discoveryPort); 75 | 76 | /// Destructor. 77 | ~ProviderCluster() noexcept; 78 | 79 | // Disable copying 80 | ProviderCluster(const ProviderCluster&) = delete; 81 | ProviderCluster& operator=(const ProviderCluster&) = delete; 82 | 83 | /// Move constructor 84 | ProviderCluster(ProviderCluster&&) noexcept; 85 | 86 | /// Move assignment operator 87 | ProviderCluster& operator=(ProviderCluster&&) noexcept; 88 | 89 | /** 90 | * \brief 91 | * Returns the slave types which are offered by all slave providers 92 | * discovered so far. 93 | * 94 | * \warning 95 | * After an object of this class has been constructed, it may 96 | * take some time for it to discover all slave providers. 97 | * 98 | * \param [in] timeout 99 | * The communications timeout used to detect loss of communication 100 | * with slave providers. A negative value means no timeout. 101 | */ 102 | std::vector GetSlaveTypes(std::chrono::milliseconds timeout); 103 | 104 | /** 105 | * \brief 106 | * Requests that a slave be spawned by a specific slave provider. 107 | * 108 | * `timeout` specifies how long the slave provider should wait for 109 | * the slave to start up before assuming it has crashed or frozen. 110 | * The function will wait twice as long as this for the slave provider 111 | * to report that the slave has been successfully instantiated before 112 | * it assumes that the slave provider itself has crashed or the 113 | * connection has been lost. In both cases, an exception is thrown. 114 | * 115 | * \param [in] slaveProviderID 116 | * The ID of the slave provider that should instantiate the slave. 117 | * \param [in] slaveTypeUUID 118 | * The UUID that identifies the type of the slave that is to be 119 | * instantiated. 120 | * \param [in] timeout 121 | * How much time the slave gets to start up. 122 | * A negative value means no limit. 123 | * 124 | * \returns 125 | * An object that contains the information needed to connect to 126 | * the slave, which can be passed to `Execution::Reconstitute()`. 127 | */ 128 | coral::net::SlaveLocator InstantiateSlave( 129 | const std::string& slaveProviderID, 130 | const std::string& slaveTypeUUID, 131 | std::chrono::milliseconds timeout); 132 | 133 | private: 134 | class Private; 135 | std::unique_ptr m_private; 136 | }; 137 | 138 | 139 | }} //namespace 140 | #endif // header guard 141 | -------------------------------------------------------------------------------- /src/include/coral/protocol/execution.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Main header file for coral::protocol::execution. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_PROTOCOL_EXECUTION_HPP 11 | #define CORAL_PROTOCOL_EXECUTION_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef _MSC_VER 18 | # pragma warning(push, 0) 19 | #endif 20 | #include 21 | #include 22 | #ifdef _MSC_VER 23 | # pragma warning(pop) 24 | #endif 25 | 26 | 27 | 28 | namespace coral 29 | { 30 | namespace protocol 31 | { 32 | 33 | /** 34 | \brief Functions for constructing and parsing messages sent between execution 35 | participants. 36 | */ 37 | namespace execution 38 | { 39 | 40 | 41 | /** 42 | \brief Fills `message` with a body-less HELLO message that requests the 43 | given protocol version. 44 | 45 | Any pre-existing contents of `message` will be replaced. 46 | */ 47 | void CreateHelloMessage( 48 | std::vector& message, 49 | uint16_t protocolVersion); 50 | 51 | 52 | /** 53 | \brief Fills `message` with a HELLO message that requests the 54 | given protocol version. 55 | 56 | Any pre-existing contents of `message` will be replaced. 57 | */ 58 | void CreateHelloMessage( 59 | std::vector& message, 60 | uint16_t protocolVersion, 61 | const google::protobuf::MessageLite& body); 62 | 63 | /** 64 | \brief Fills 'message' with a DENIED message with the given reason string. 65 | 66 | Any pre-existing contents of `message` will be replaced. 67 | */ 68 | void CreateDeniedMessage( 69 | std::vector& message, 70 | const std::string& reason = std::string()); 71 | 72 | 73 | /** 74 | \brief Fills `message` with a body-less message of the given type. 75 | 76 | Any pre-existing contents of `message` will be replaced. 77 | */ 78 | void CreateMessage( 79 | std::vector& message, 80 | coralproto::execution::MessageType type); 81 | 82 | 83 | /** 84 | \brief Fills `message` with a body-less message of the given type. 85 | 86 | Any pre-existing contents of `message` will be replaced. 87 | */ 88 | void CreateMessage( 89 | std::vector& message, 90 | coralproto::execution::MessageType type, 91 | const google::protobuf::MessageLite& body); 92 | 93 | 94 | /** 95 | \brief Fills `message` with an ERROR message. 96 | 97 | Any pre-existing contents of `message` will be replaced. 98 | */ 99 | void CreateErrorMessage( 100 | std::vector& message, 101 | coralproto::execution::ErrorInfo::Code code, 102 | const std::string& details = std::string()); 103 | 104 | 105 | /** 106 | \brief Fills `message` with a FATAL_ERROR message. 107 | 108 | Any pre-existing contents of `message` will be replaced. 109 | */ 110 | void CreateFatalErrorMessage( 111 | std::vector& message, 112 | coralproto::execution::ErrorInfo::Code code, 113 | const std::string& details = std::string()); 114 | 115 | 116 | /** 117 | \brief Parses the first two bytes of `header` as an uint16_t. 118 | 119 | This function does *not* check whether the returned number is a valid 120 | message type. 121 | 122 | \throws coral::error::ProtocolViolationException if `header` is shorter than 123 | two bytes. 124 | */ 125 | uint16_t ParseMessageType(const zmq::message_t& header); 126 | 127 | 128 | /** 129 | \brief Parses the first two bytes of the message as an uint16_t, and throws 130 | an exception if it is an ERROR message. 131 | 132 | \throws RemoteErrorException if `message` is an ERROR message. 133 | \throws std::invalid_argument if `message` is empty. 134 | */ 135 | uint16_t NonErrorMessageType(const std::vector& message); 136 | 137 | 138 | /** 139 | \brief Exception which signifies that the remote end sent a DENIED or ERROR 140 | message. 141 | 142 | The `what()` function may be called to get the detailed error information 143 | that was received. 144 | */ 145 | class RemoteErrorException : public std::runtime_error 146 | { 147 | public: 148 | // Constructor for DENIED messages. 149 | explicit RemoteErrorException(const std::string& deniedReason); 150 | 151 | // Constructor for ERROR messages. 152 | explicit RemoteErrorException(const coralproto::execution::ErrorInfo& errorInfo); 153 | }; 154 | 155 | 156 | /** 157 | \brief Parses HELLO or DENIED messages. 158 | 159 | If `message` is a HELLO message, this function will parse it and return the 160 | protocol version. Otherwise, if it is a DENIED message, a RemoteErrorException 161 | will be thrown. If the message is neither of these types, a 162 | ProtocolViolationException will be thrown. 163 | 164 | \throws std::logic_error if `message` is empty. 165 | \throws RemoteErrorException if `message` is a DENIED message. 166 | \throws coral::error::ProtocolViolationException if `message` is not a HELLO 167 | or DENIED message. 168 | */ 169 | uint16_t ParseHelloMessage(const std::vector& message); 170 | 171 | 172 | }}} // namespace 173 | #endif // header guard 174 | -------------------------------------------------------------------------------- /include/coral/fmi/importer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief FMU import functionality. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_FMI_IMPORTER_HPP 11 | #define CORAL_FMI_IMPORTER_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | 23 | // Forward declarations to avoid external dependency on FMI Library. 24 | struct fmi_xml_context_t; 25 | typedef fmi_xml_context_t fmi_import_context_t; 26 | struct jm_callbacks; 27 | 28 | 29 | namespace coral 30 | { 31 | namespace fmi 32 | { 33 | 34 | class FMU; 35 | 36 | 37 | /** 38 | \brief Imports and caches FMUs. 39 | 40 | The main purpose of this class is to read %FMU files and create coral::fmi::FMU 41 | objects to represent them. This is done with the Import() function. 42 | 43 | An Importer object uses an on-disk cache that holds the unpacked contents 44 | of previously imported FMUs, so that they don't need to be unpacked anew every 45 | time they are imported. This is a huge time-saver when large and/or many FMUs 46 | are loaded. The path to this cache may be supplied by the user, in which case 47 | it is not automatically emptied on destruction. Thus, if the same path is 48 | supplied each time, the cache becomes persistent between program runs. 49 | It may be cleared manually by calling CleanCache(). 50 | 51 | \warning 52 | Currently there are no synchronisation mechanisms to protect the cache from 53 | concurrent use, so accessing the same cache from multiple 54 | instances/processes will likely cause problems. 55 | */ 56 | class Importer : public std::enable_shared_from_this 57 | { 58 | public: 59 | /** 60 | \brief Creates a new %FMU importer that uses a specific cache directory. 61 | 62 | The given directory will not be removed nor emptied on destruction. 63 | 64 | \param [in] cachePath 65 | The path to the directory which will hold the %FMU cache. If it does 66 | not exist already, it will be created. 67 | */ 68 | static std::shared_ptr Create( 69 | const boost::filesystem::path& cachePath); 70 | 71 | /** 72 | \brief Creates a new %FMU importer that uses a temporary cache directory. 73 | 74 | A new cache directory will be created in a location suitable for temporary 75 | files under the conventions of the operating system. It will be completely 76 | removed again on destruction. 77 | */ 78 | static std::shared_ptr Create(); 79 | 80 | private: 81 | // Private constructors, to force use of factory functions. 82 | Importer(const boost::filesystem::path& cachePath); 83 | Importer(coral::util::TempDir tempDir); 84 | 85 | public: 86 | /** 87 | \brief Imports and loads an %FMU. 88 | 89 | Loaded FMUs are managed using reference counting. If an %FMU is loaded, 90 | and then the same %FMU is loaded again before the first one has been 91 | destroyed, the second call will return a reference to the first one. 92 | (Two FMUs are deemed to be the same if they have the same path *or* the 93 | same GUID.) 94 | 95 | \param [in] fmuPath 96 | The path to the %FMU file. 97 | \returns 98 | An object which represents the imported %FMU. 99 | */ 100 | std::shared_ptr Import(const boost::filesystem::path& fmuPath); 101 | 102 | /** 103 | \brief Imports and loads an %FMU that has already been unpacked. 104 | 105 | This is more or less equivalent to `Import()`, but since the FMU is 106 | already unpacked its contents will be read from the specified directory 107 | rather than the cache. 108 | 109 | \param [in] unpackedFMUPath 110 | The path to a directory that holds the unpacked contents of an FMU. 111 | \returns 112 | An object which represents the imported %FMU. 113 | */ 114 | std::shared_ptr ImportUnpacked( 115 | const boost::filesystem::path& unpackedFMUPath); 116 | 117 | /** 118 | \brief Removes unused files and directories from the %FMU cache. 119 | 120 | This will remove all %FMU contents from the cache, except the ones for 121 | which there currently exist FMU objects. 122 | */ 123 | void CleanCache(); 124 | 125 | /// Returns the last FMI Library error message. 126 | std::string LastErrorMessage(); 127 | 128 | /// Returns a pointer to the underlying FMI Library import context. 129 | fmi_import_context_t* FmilibHandle() const; 130 | 131 | private: 132 | void PrunePtrCaches(); 133 | 134 | // Note: The order of these declarations is important! 135 | std::unique_ptr m_tempCacheDir; // Only used when no cache dir is given 136 | std::unique_ptr m_callbacks; 137 | std::unique_ptr m_handle; 138 | 139 | boost::filesystem::path m_fmuDir; 140 | boost::filesystem::path m_workDir; 141 | 142 | std::map> m_pathCache; 143 | std::map> m_guidCache; 144 | }; 145 | 146 | 147 | }} // namespace 148 | #endif // header guard 149 | -------------------------------------------------------------------------------- /src/include/coral/bus/slave_control_messenger_v0.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Defines the coral::bus::SlaveControlMessengerV0 class 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_BUS_SLAVE_CONTROL_MESSENGER_V0_HPP 11 | #define CORAL_BUS_SLAVE_CONTROL_MESSENGER_V0_HPP 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | 27 | // Forward declaration to avoid header dependency 28 | namespace google { namespace protobuf { class MessageLite; } } 29 | 30 | 31 | namespace coral 32 | { 33 | namespace bus 34 | { 35 | 36 | 37 | /** 38 | \brief An implementation of ISlaveControlMessenger for version 0 of the 39 | master/slave communication protocol. 40 | */ 41 | class SlaveControlMessengerV0 : public ISlaveControlMessenger 42 | { 43 | public: 44 | SlaveControlMessengerV0( 45 | coral::net::Reactor& reactor, 46 | coral::net::zmqx::ReqSocket socket, 47 | coral::model::SlaveID slaveID, 48 | const std::string& slaveName, 49 | const SlaveSetup& setup, 50 | std::chrono::milliseconds timeout, 51 | MakeSlaveControlMessengerHandler onComplete); 52 | 53 | ~SlaveControlMessengerV0() noexcept; 54 | 55 | SlaveState State() const noexcept override; 56 | 57 | void Close() override; 58 | 59 | void GetDescription( 60 | std::chrono::milliseconds timeout, 61 | GetDescriptionHandler onComplete) override; 62 | 63 | void SetVariables( 64 | const std::vector& settings, 65 | std::chrono::milliseconds timeout, 66 | SetVariablesHandler onComplete) override; 67 | 68 | void SetPeers( 69 | const std::vector& peers, 70 | std::chrono::milliseconds timeout, 71 | SetPeersHandler onComplete) override; 72 | 73 | void ResendVars( 74 | std::chrono::milliseconds timeout, 75 | ResendVarsHandler onComplete) override; 76 | 77 | void Step( 78 | coral::model::StepID stepID, 79 | coral::model::TimePoint currentT, 80 | coral::model::TimeDuration deltaT, 81 | std::chrono::milliseconds timeout, 82 | StepHandler onComplete) override; 83 | 84 | void AcceptStep( 85 | std::chrono::milliseconds timeout, 86 | AcceptStepHandler onComplete) override; 87 | 88 | void Terminate() override; 89 | 90 | private: 91 | typedef boost::variant AnyHandler; 92 | 93 | void Setup( 94 | coral::model::SlaveID slaveID, 95 | const std::string& slaveName, 96 | const SlaveSetup& setup, 97 | std::chrono::milliseconds timeout, 98 | VoidHandler onComplete); 99 | 100 | // Helper functions 101 | void Reset(); 102 | void SendCommand( 103 | int command, 104 | const google::protobuf::MessageLite* data, 105 | std::chrono::milliseconds timeout, 106 | AnyHandler onComplete); 107 | void PostSendCommand( 108 | int command, 109 | std::chrono::milliseconds timeout, 110 | AnyHandler onComplete); 111 | void RegisterTimeout(std::chrono::milliseconds timeout); 112 | void UnregisterTimeout(); 113 | 114 | // Event handlers 115 | void OnReply(); 116 | void OnReplyTimeout(); 117 | 118 | // Reply parsing/handling 119 | void SetupReplyReceived( 120 | const std::vector& msg, 121 | VoidHandler onComplete); 122 | void DescribeReplyReceived( 123 | const std::vector& msg, 124 | GetDescriptionHandler onComplete); 125 | void SetPeersReplyReceived( 126 | const std::vector& msg, 127 | VoidHandler onComplete); 128 | void SetVarsReplyReceived( 129 | const std::vector& msg, 130 | VoidHandler onComplete); 131 | void ResendVarsReplyReceived( 132 | const std::vector& msg, 133 | VoidHandler onComplete); 134 | void StepReplyReceived( 135 | const std::vector& msg, 136 | VoidHandler onComplete); 137 | void AcceptStepReplyReceived( 138 | const std::vector& msg, 139 | VoidHandler onComplete); 140 | 141 | // These guys perform the work which is common to several of the above 142 | // XyxReplyReceived() functions. 143 | void HandleExpectedReadyReply( 144 | const std::vector& msg, 145 | VoidHandler onComplete); 146 | void HandleErrorReply(int reply, AnyHandler onComplete); 147 | 148 | // Class invariant checker 149 | void CheckInvariant() const; 150 | 151 | coral::net::Reactor& m_reactor; 152 | coral::net::zmqx::ReqSocket m_socket; 153 | 154 | // State information 155 | SlaveState m_state; 156 | bool m_attachedToReactor; 157 | int m_currentCommand; 158 | AnyHandler m_onComplete; 159 | int m_replyTimeoutTimerId; 160 | }; 161 | 162 | 163 | }} // namespace 164 | #endif // header guard 165 | -------------------------------------------------------------------------------- /src/include/coral/bus/slave_provider_comm.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | \file 3 | \brief Slave provider client/server communication classes. 4 | \copyright 5 | Copyright 2013-present, SINTEF Ocean. 6 | This Source Code Form is subject to the terms of the Mozilla Public 7 | License, v. 2.0. If a copy of the MPL was not distributed with this 8 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 | */ 10 | #ifndef CORAL_BUS_SP_INFO_CLIENT_HPP 11 | #define CORAL_BUS_SP_INFO_CLIENT_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | 27 | namespace coral 28 | { 29 | namespace bus 30 | { 31 | 32 | 33 | /** 34 | \brief A class for communicating with a single slave provider. 35 | */ 36 | class SlaveProviderClient 37 | { 38 | public: 39 | /** 40 | \brief Constructor. 41 | 42 | \param [in] reactor 43 | Used to listen for incoming messages from the slave provider. 44 | \param [in] address 45 | The IP address or hostname of the slave provider. 46 | \param [in] port 47 | The port on which the slave provider is accepting connections. 48 | */ 49 | SlaveProviderClient( 50 | coral::net::Reactor& reactor, 51 | const coral::net::ip::Endpoint& endpoint); 52 | 53 | /// Destructor 54 | ~SlaveProviderClient() noexcept; 55 | 56 | SlaveProviderClient(const SlaveProviderClient&) = delete; 57 | SlaveProviderClient& operator=(const SlaveProviderClient&) = delete; 58 | 59 | /// Move constructor 60 | SlaveProviderClient(SlaveProviderClient&&) noexcept; 61 | 62 | /// Move assignment operator 63 | SlaveProviderClient& operator=(SlaveProviderClient&&) noexcept; 64 | 65 | /// Completion handler type for GetSlaveTypes(). 66 | typedef std::function 70 | GetSlaveTypesHandler; 71 | 72 | /** 73 | \brief Requests a list of slave types provided. 74 | 75 | \param [in] onComplete 76 | Function which is called when the result is ready, or with an error 77 | code in case of failure. 78 | \param [in] timeout 79 | Maximum time allowed for the request to complete. 80 | A negative value means that there is no time limit. 81 | */ 82 | void GetSlaveTypes( 83 | GetSlaveTypesHandler onComplete, 84 | std::chrono::milliseconds timeout); 85 | 86 | /// Completion handler type for InstantiateSlave(). 87 | typedef std::function 91 | InstantiateSlaveHandler; 92 | 93 | /** 94 | \brief Requests the instantiation of a slave. 95 | 96 | \param [in] slaveTypeUUID 97 | The slave type identifier. 98 | \param [in] instantiationTimeout 99 | The max allowed time for the slave to start up. 100 | A negative value means that there is no time limit (which is somewhat 101 | risky, because it means that the entire slave provider will freeze 102 | if the slave hangs during startup). 103 | \param [in] requestTimeout 104 | Additional time allowed for the whole request to complete. 105 | A negative value means that there is no time limit. 106 | \param [in] onComplete 107 | Function which is called with the slave address when the slave has 108 | been instantiated, or with an error code and message in case of failure. 109 | */ 110 | void InstantiateSlave( 111 | const std::string& slaveTypeUUID, 112 | std::chrono::milliseconds instantiationTimeout, 113 | std::chrono::milliseconds requestTimeout, 114 | InstantiateSlaveHandler onComplete); 115 | 116 | private: 117 | class Private; 118 | std::unique_ptr m_private; 119 | }; 120 | 121 | 122 | /** 123 | \brief An interface for the services offered by a slave provider, 124 | for use with MakeSlaveProviderServer(). 125 | */ 126 | class SlaveProviderOps 127 | { 128 | public: 129 | /// Returns the number of slave types provided. 130 | virtual int GetSlaveTypeCount() const noexcept = 0; 131 | 132 | /// Returns a description of the `index`th slave type. 133 | virtual coral::model::SlaveTypeDescription GetSlaveType(int index) const = 0; 134 | 135 | /// Instantiates a slave. 136 | virtual coral::net::SlaveLocator InstantiateSlave( 137 | const std::string& slaveTypeUUID, 138 | std::chrono::milliseconds timeout) = 0; 139 | 140 | virtual ~SlaveProviderOps() noexcept { } 141 | }; 142 | 143 | 144 | /** 145 | \brief Creates a server to be used by slave providers to handle incoming 146 | requests from a SlaveProviderClient. 147 | 148 | \param [in] server 149 | The server that will handle the requests. The function will add an 150 | appropriate protocol handler to this server. 151 | \param [in] slaveProvider 152 | The object that will carry out any incoming requests. 153 | */ 154 | void MakeSlaveProviderServer( 155 | coral::net::reqrep::Server& server, 156 | std::shared_ptr slaveProvider); 157 | 158 | 159 | }} // namespace 160 | #endif // header guard 161 | -------------------------------------------------------------------------------- /src/lib/slave_logging.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | namespace coral 20 | { 21 | namespace slave 22 | { 23 | 24 | 25 | LoggingInstance::LoggingInstance( 26 | std::shared_ptr instance, 27 | const std::string& outputFilePrefix) 28 | : m_instance{instance} 29 | , m_outputFilePrefix(outputFilePrefix) 30 | { 31 | if (m_outputFilePrefix.empty()) m_outputFilePrefix = "./"; 32 | } 33 | 34 | 35 | coral::model::SlaveTypeDescription LoggingInstance::TypeDescription() const 36 | { 37 | return m_instance->TypeDescription(); 38 | } 39 | 40 | 41 | void LoggingInstance::Setup( 42 | const std::string& slaveName, 43 | const std::string& executionName, 44 | coral::model::TimePoint startTime, 45 | coral::model::TimePoint stopTime, 46 | bool adaptiveStepSize, 47 | double relativeTolerance) 48 | { 49 | m_instance->Setup( 50 | slaveName, executionName, 51 | startTime, stopTime, 52 | adaptiveStepSize, relativeTolerance); 53 | 54 | auto outputFileName = m_outputFilePrefix; 55 | if (executionName.empty()) { 56 | outputFileName += coral::util::Timestamp(); 57 | } else { 58 | outputFileName += executionName; 59 | } 60 | outputFileName += '_'; 61 | if (slaveName.empty()) { 62 | outputFileName += TypeDescription().Name() + '_' 63 | + coral::util::RandomString(6, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); 64 | } else { 65 | outputFileName += slaveName; 66 | } 67 | outputFileName += ".csv"; 68 | 69 | CORAL_LOG_TRACE("LoggingInstance: Opening " + outputFileName); 70 | m_outputStream.open( 71 | outputFileName, 72 | std::ios_base::out | std::ios_base::trunc 73 | #ifdef _MSC_VER 74 | , _SH_DENYWR // Don't let other processes/threads write to the file 75 | #endif 76 | ); 77 | if (!m_outputStream.is_open()) { 78 | const int e = errno; 79 | throw std::runtime_error(coral::error::ErrnoMessage( 80 | "Error opening file \"" + outputFileName + "\" for writing", 81 | e)); 82 | } 83 | 84 | m_outputStream << "Time"; 85 | const auto typeDescription = TypeDescription(); 86 | for (const auto& var : typeDescription.Variables()) { 87 | m_outputStream << "," << var.Name(); 88 | } 89 | m_outputStream << std::endl; 90 | } 91 | 92 | 93 | void LoggingInstance::StartSimulation() 94 | { 95 | m_instance->StartSimulation(); 96 | } 97 | 98 | 99 | void LoggingInstance::EndSimulation() 100 | { 101 | m_instance->EndSimulation(); 102 | } 103 | 104 | 105 | namespace 106 | { 107 | void PrintVariable( 108 | std::ostream& out, 109 | const coral::model::VariableDescription& varInfo, 110 | Instance& slaveInstance) 111 | { 112 | out << ","; 113 | switch (varInfo.DataType()) { 114 | case coral::model::REAL_DATATYPE: 115 | out << slaveInstance.GetRealVariable(varInfo.ID()); 116 | break; 117 | case coral::model::INTEGER_DATATYPE: 118 | out << slaveInstance.GetIntegerVariable(varInfo.ID()); 119 | break; 120 | case coral::model::BOOLEAN_DATATYPE: 121 | out << slaveInstance.GetBooleanVariable(varInfo.ID()); 122 | break; 123 | case coral::model::STRING_DATATYPE: 124 | out << slaveInstance.GetStringVariable(varInfo.ID()); 125 | break; 126 | default: 127 | assert (false); 128 | } 129 | } 130 | } 131 | 132 | 133 | bool LoggingInstance::DoStep( 134 | coral::model::TimePoint currentT, 135 | coral::model::TimeDuration deltaT) 136 | { 137 | const auto ret = m_instance->DoStep(currentT, deltaT); 138 | 139 | m_outputStream << std::fixed << (currentT + deltaT) << std::defaultfloat; 140 | const auto typeDescription = TypeDescription(); 141 | for (const auto& var : typeDescription.Variables()) { 142 | PrintVariable(m_outputStream, var, *this); 143 | } 144 | m_outputStream << std::endl; 145 | 146 | return ret; 147 | } 148 | 149 | 150 | double LoggingInstance::GetRealVariable(coral::model::VariableID varRef) const 151 | { 152 | return m_instance->GetRealVariable(varRef); 153 | } 154 | 155 | 156 | int LoggingInstance::GetIntegerVariable(coral::model::VariableID varRef) const 157 | { 158 | return m_instance->GetIntegerVariable(varRef); 159 | } 160 | 161 | 162 | bool LoggingInstance::GetBooleanVariable(coral::model::VariableID varRef) const 163 | { 164 | return m_instance->GetBooleanVariable(varRef); 165 | } 166 | 167 | 168 | std::string LoggingInstance::GetStringVariable(coral::model::VariableID varRef) const 169 | { 170 | return m_instance->GetStringVariable(varRef); 171 | } 172 | 173 | 174 | bool LoggingInstance::SetRealVariable(coral::model::VariableID varRef, double value) 175 | { 176 | return m_instance->SetRealVariable(varRef, value); 177 | } 178 | 179 | 180 | bool LoggingInstance::SetIntegerVariable(coral::model::VariableID varRef, int value) 181 | { 182 | return m_instance->SetIntegerVariable(varRef, value); 183 | } 184 | 185 | 186 | bool LoggingInstance::SetBooleanVariable(coral::model::VariableID varRef, bool value) 187 | { 188 | return m_instance->SetBooleanVariable(varRef, value); 189 | } 190 | 191 | 192 | bool LoggingInstance::SetStringVariable(coral::model::VariableID varRef, const std::string& value) 193 | { 194 | return m_instance->SetStringVariable(varRef, value); 195 | } 196 | 197 | 198 | }} // namespace 199 | -------------------------------------------------------------------------------- /src/lib/provider_provider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | namespace coral 24 | { 25 | namespace provider 26 | { 27 | 28 | 29 | namespace 30 | { 31 | class MySlaveProviderOps : public coral::bus::SlaveProviderOps 32 | { 33 | public: 34 | MySlaveProviderOps( 35 | std::vector>&& slaveTypes) 36 | : m_slaveTypes(std::move(slaveTypes)) 37 | { 38 | } 39 | 40 | int GetSlaveTypeCount() const noexcept override 41 | { 42 | return boost::numeric_cast(m_slaveTypes.size()); 43 | } 44 | 45 | coral::model::SlaveTypeDescription GetSlaveType(int index) const override 46 | { 47 | return m_slaveTypes.at(index)->Description(); 48 | } 49 | 50 | coral::net::SlaveLocator InstantiateSlave( 51 | const std::string& slaveTypeUUID, 52 | std::chrono::milliseconds timeout) override 53 | { 54 | const auto st = std::find_if( 55 | begin(m_slaveTypes), 56 | end(m_slaveTypes), 57 | [&] (const decltype(m_slaveTypes)::value_type& e) { 58 | return e->Description().UUID() == slaveTypeUUID; 59 | }); 60 | if (st == end(m_slaveTypes)) { 61 | throw std::runtime_error("Unknown slave type"); 62 | } 63 | coral::net::SlaveLocator loc; 64 | if (!(*st)->Instantiate(timeout, loc)) { 65 | throw std::runtime_error((*st)->InstantiationFailureDescription()); 66 | } 67 | return loc; 68 | } 69 | 70 | private: 71 | const std::vector> m_slaveTypes; 72 | }; 73 | 74 | 75 | // Ok, this is all a bit ugly, but it's for a good cause, namely to handle 76 | // as many errors as possible in the foreground thread (see below). 77 | struct BackgroundThreadData 78 | { 79 | // Note that the order of declarations matters here, because the 80 | // other members depend on the reactor being kept alive. 81 | std::shared_ptr reactor; 82 | std::shared_ptr killSocket; 83 | std::shared_ptr server; 84 | std::shared_ptr beacon; 85 | }; 86 | 87 | void BackgroundThreadFunction( 88 | BackgroundThreadData objects, 89 | std::function exceptionHandler) 90 | { 91 | try { 92 | objects.reactor->Run(); 93 | objects.beacon->Stop(); 94 | } catch (...) { 95 | if (exceptionHandler) { 96 | exceptionHandler(std::current_exception()); 97 | } else { 98 | throw; 99 | } 100 | } 101 | } 102 | } 103 | 104 | 105 | SlaveProvider::SlaveProvider( 106 | const std::string& slaveProviderID, 107 | std::vector>&& slaveTypes, 108 | const coral::net::ip::Address& networkInterface, 109 | coral::net::ip::Port discoveryPort, 110 | std::function exceptionHandler) 111 | { 112 | CORAL_INPUT_CHECK(!slaveProviderID.empty()); 113 | 114 | // We do as much as setup as possible in the "foreground" thread, 115 | // so that exceptions are most likely to be thrown here. 116 | BackgroundThreadData bg; 117 | bg.reactor = std::make_shared(); 118 | 119 | const auto killEndpoint = "inproc://" + coral::util::RandomUUID(); 120 | m_killSocket = std::make_unique( 121 | coral::net::zmqx::GlobalContext(), ZMQ_PAIR); 122 | m_killSocket->bind(killEndpoint); 123 | bg.killSocket = std::make_shared( 124 | coral::net::zmqx::GlobalContext(), ZMQ_PAIR); 125 | bg.killSocket->connect(killEndpoint); 126 | bg.reactor->AddSocket( 127 | *bg.killSocket, 128 | [] (coral::net::Reactor& r, zmq::socket_t&) { r.Stop(); }); 129 | 130 | bg.server = std::make_shared( 131 | *bg.reactor, 132 | coral::net::ip::Endpoint{networkInterface, "*"}.ToEndpoint("tcp")); 133 | coral::bus::MakeSlaveProviderServer( 134 | *bg.server, 135 | std::make_shared(std::move(slaveTypes))); 136 | 137 | char beaconPayload[2]; 138 | coral::util::EncodeUint16( 139 | coral::net::zmqx::EndpointPort(bg.server->BoundEndpoint().URL()), 140 | beaconPayload); 141 | bg.beacon = std::make_shared( 142 | 0, 143 | "no.sintef.viproma.coral.slave_provider", 144 | slaveProviderID, 145 | beaconPayload, 146 | sizeof(beaconPayload), 147 | std::chrono::seconds(1), 148 | networkInterface, 149 | discoveryPort); 150 | 151 | m_thread = std::thread{&BackgroundThreadFunction, bg, exceptionHandler}; 152 | } 153 | 154 | 155 | // This is just here so we can declare m_killSocket to be a std::unique_ptr 156 | // to an incomplete type in the header. 157 | SlaveProvider::~SlaveProvider() noexcept { } 158 | 159 | 160 | void SlaveProvider::Stop() 161 | { 162 | if (m_thread.joinable()) { 163 | char dummy = 0; 164 | m_killSocket->send(&dummy, 0, ZMQ_DONTWAIT); 165 | m_killSocket->recv(&dummy, 1, ZMQ_DONTWAIT); 166 | m_thread.join(); 167 | } 168 | } 169 | 170 | 171 | }} // namespace 172 | -------------------------------------------------------------------------------- /src/lib/bus_variable_io.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013-present, SINTEF Ocean. 3 | This Source Code Form is subject to the terms of the Mozilla Public 4 | License, v. 2.0. If a copy of the MPL was not distributed with this 5 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | namespace coral 19 | { 20 | namespace bus 21 | { 22 | 23 | namespace 24 | { 25 | void EnforceConnected(const std::unique_ptr& s, bool state) 26 | { 27 | if (!s == state) { 28 | throw coral::error::PreconditionViolation( 29 | state ? "Not connected" : "Already connected"); 30 | } 31 | } 32 | } 33 | 34 | 35 | // ============================================================================= 36 | // class VariablePublisher 37 | // ============================================================================= 38 | 39 | VariablePublisher::VariablePublisher() 40 | { } 41 | 42 | 43 | void VariablePublisher::Bind(const coral::net::Endpoint& endpoint) 44 | { 45 | EnforceConnected(m_socket, false); 46 | m_socket = std::make_unique(coral::net::zmqx::GlobalContext(), ZMQ_PUB); 47 | try { 48 | m_socket->setsockopt(ZMQ_SNDHWM, 0); 49 | m_socket->setsockopt(ZMQ_RCVHWM, 0); 50 | m_socket->setsockopt(ZMQ_LINGER, 0); 51 | m_socket->bind(endpoint.URL().c_str()); 52 | } catch (...) { 53 | m_socket.reset(); 54 | throw; 55 | } 56 | } 57 | 58 | 59 | coral::net::Endpoint VariablePublisher::BoundEndpoint() const 60 | { 61 | EnforceConnected(m_socket, true); 62 | return coral::net::Endpoint{coral::net::zmqx::LastEndpoint(*m_socket)}; 63 | } 64 | 65 | 66 | void VariablePublisher::Publish( 67 | coral::model::StepID stepID, 68 | coral::model::SlaveID slaveID, 69 | coral::model::VariableID variableID, 70 | coral::model::ScalarValue value) 71 | { 72 | EnforceConnected(m_socket, true); 73 | coral::protocol::exe_data::Message m = { 74 | coral::model::Variable(slaveID, variableID), 75 | stepID, 76 | value 77 | }; 78 | std::vector d; 79 | coral::protocol::exe_data::CreateMessage(m, d); 80 | coral::net::zmqx::Send(*m_socket, d); 81 | } 82 | 83 | 84 | // ============================================================================= 85 | // class VariableSubscriber 86 | // ============================================================================= 87 | 88 | 89 | VariableSubscriber::VariableSubscriber() 90 | : m_currentStepID(coral::model::INVALID_STEP_ID) 91 | { } 92 | 93 | 94 | void VariableSubscriber::Connect( 95 | const coral::net::Endpoint* endpoints, 96 | std::size_t endpointsSize) 97 | { 98 | m_socket = std::make_unique(coral::net::zmqx::GlobalContext(), ZMQ_SUB); 99 | try { 100 | m_socket->setsockopt(ZMQ_SNDHWM, 0); 101 | m_socket->setsockopt(ZMQ_RCVHWM, 0); 102 | m_socket->setsockopt(ZMQ_LINGER, 0); 103 | for (std::size_t i = 0; i < endpointsSize; ++i) { 104 | m_socket->connect(endpoints[i].URL().c_str()); 105 | } 106 | for (const auto& variable : m_values) { 107 | coral::protocol::exe_data::Subscribe(*m_socket, variable.first); 108 | } 109 | } catch (...) { 110 | m_socket.reset(); 111 | throw; 112 | } 113 | } 114 | 115 | 116 | void VariableSubscriber::Subscribe(const coral::model::Variable& variable) 117 | { 118 | EnforceConnected(m_socket, true); 119 | coral::protocol::exe_data::Subscribe(*m_socket, variable); 120 | m_values.insert(std::make_pair(variable, ValueQueue())); 121 | } 122 | 123 | 124 | void VariableSubscriber::Unsubscribe(const coral::model::Variable& variable) 125 | { 126 | EnforceConnected(m_socket, true); 127 | if (m_values.erase(variable)) { 128 | coral::protocol::exe_data::Unsubscribe(*m_socket, variable); 129 | } 130 | } 131 | 132 | 133 | bool VariableSubscriber::Update( 134 | coral::model::StepID stepID, 135 | std::chrono::milliseconds timeout) 136 | { 137 | CORAL_PRECONDITION_CHECK(stepID >= m_currentStepID); 138 | m_currentStepID = stepID; 139 | 140 | std::vector rawMsg; 141 | for (auto& entry : m_values) { 142 | auto& valQueue = entry.second; 143 | // Pop off old data 144 | while (!valQueue.empty() && valQueue.front().first < m_currentStepID) { 145 | valQueue.pop(); 146 | } 147 | // If necessary, wait for new data 148 | while (valQueue.empty()) { 149 | if (!coral::net::zmqx::WaitForIncoming(*m_socket, timeout)) { 150 | CORAL_LOG_DEBUG( 151 | boost::format("Timeout waiting for variable %d from slave %d") 152 | % entry.first.ID() % entry.first.Slave()); 153 | return false; 154 | } 155 | coral::net::zmqx::Receive(*m_socket, rawMsg); 156 | const auto msg = coral::protocol::exe_data::ParseMessage(rawMsg); 157 | // Queue the variable value iff it is from the current (or a newer) 158 | // timestep and it is one we're listening for. (Wrt. the latter, 159 | // unsubscriptions may take time to come into effect.) 160 | if (msg.timestepID >= m_currentStepID) { 161 | auto it = m_values.find(msg.variable); 162 | if (it != m_values.end()) { 163 | it->second.emplace(msg.timestepID, msg.value); 164 | } 165 | } 166 | } 167 | } 168 | return true; 169 | } 170 | 171 | 172 | const coral::model::ScalarValue& VariableSubscriber::Value( 173 | const coral::model::Variable& variable) const 174 | { 175 | const auto& valQueue = m_values.at(variable); 176 | if (valQueue.empty()) { 177 | throw std::logic_error("Variable not updated yet"); 178 | } 179 | return valQueue.front().second; 180 | } 181 | 182 | }} // header guard 183 | -------------------------------------------------------------------------------- /src/lib/util_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | using namespace coral::util; 9 | 10 | TEST(coral_util, EncodeUint16) { 11 | char b[2] = { '\xFF', '\xFF' }; 12 | 13 | EncodeUint16(0, b); 14 | EXPECT_EQ('\x00', b[0]); 15 | EXPECT_EQ('\x00', b[1]); 16 | 17 | EncodeUint16(65535, b); 18 | EXPECT_EQ('\xFF', b[0]); 19 | EXPECT_EQ('\xFF', b[1]); 20 | 21 | EncodeUint16(4608, b); 22 | EXPECT_EQ('\x00', b[0]); 23 | EXPECT_EQ('\x12', b[1]); 24 | 25 | EncodeUint16(63, b); 26 | EXPECT_EQ('\x3F', b[0]); 27 | EXPECT_EQ('\x00', b[1]); 28 | 29 | EncodeUint16(15238, b); 30 | EXPECT_EQ('\x86', b[0]); 31 | EXPECT_EQ('\x3B', b[1]); 32 | } 33 | 34 | TEST(coral_util, EncodeUint32) { 35 | char b[4] = { '\xFF', '\xFF', '\xFF', '\xFF' }; 36 | 37 | EncodeUint32(0, b); 38 | EXPECT_EQ('\x00', b[0]); 39 | EXPECT_EQ('\x00', b[1]); 40 | EXPECT_EQ('\x00', b[2]); 41 | EXPECT_EQ('\x00', b[3]); 42 | 43 | EncodeUint32(65535, b); 44 | EXPECT_EQ('\xFF', b[0]); 45 | EXPECT_EQ('\xFF', b[1]); 46 | EXPECT_EQ('\x00', b[2]); 47 | EXPECT_EQ('\x00', b[3]); 48 | 49 | EncodeUint32(4294967295, b); 50 | EXPECT_EQ('\xFF', b[0]); 51 | EXPECT_EQ('\xFF', b[1]); 52 | EXPECT_EQ('\xFF', b[2]); 53 | EXPECT_EQ('\xFF', b[3]); 54 | 55 | EncodeUint32(2018915346, b); 56 | EXPECT_EQ('\x12', b[0]); 57 | EXPECT_EQ('\x34', b[1]); 58 | EXPECT_EQ('\x56', b[2]); 59 | EXPECT_EQ('\x78', b[3]); 60 | } 61 | 62 | TEST(coral_util, EncodeUint64) { 63 | char b[8] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; 64 | EncodeUint64(7460587310468789241UL, b); 65 | EXPECT_EQ('\xF9', b[0]); 66 | EXPECT_EQ('\x73', b[1]); 67 | EXPECT_EQ('\x47', b[2]); 68 | EXPECT_EQ('\x88', b[3]); 69 | EXPECT_EQ('\xA1', b[4]); 70 | EXPECT_EQ('\x54', b[5]); 71 | EXPECT_EQ('\x89', b[6]); 72 | EXPECT_EQ('\x67', b[7]); 73 | } 74 | 75 | TEST(coral_util, DecodeUint16) { 76 | EXPECT_EQ( 0u, DecodeUint16("\x00\x00")); 77 | EXPECT_EQ(65535u, DecodeUint16("\xFF\xFF")); 78 | EXPECT_EQ( 4608u, DecodeUint16("\x00\x12")); 79 | EXPECT_EQ( 63u, DecodeUint16("\x3F\x00")); 80 | EXPECT_EQ(15238u, DecodeUint16("\x86\x3B")); 81 | } 82 | 83 | TEST(coral_util, DecodeUint32) { 84 | EXPECT_EQ( 0u, DecodeUint32("\x00\x00\x00\x00")); 85 | EXPECT_EQ( 65535u, DecodeUint32("\xFF\xFF\x00\x00")); 86 | EXPECT_EQ(4294967295u, DecodeUint32("\xFF\xFF\xFF\xFF")); 87 | EXPECT_EQ(2018915346u, DecodeUint32("\x12\x34\x56\x78")); 88 | } 89 | 90 | TEST(coral_util, DecodeUint64) { 91 | EXPECT_EQ( 92 | 7460587310468789241UL, 93 | DecodeUint64("\xF9\x73\x47\x88\xA1\x54\x89\x67")); 94 | } 95 | 96 | TEST(coral_util, ArrayStringCmp) { 97 | char test[3] = { 'a', 'b', 'c' }; 98 | EXPECT_EQ(0, ArrayStringCmp(test, 3, "abc")); 99 | EXPECT_GT(0, ArrayStringCmp(test, 3, "abcd")); 100 | EXPECT_GT(0, ArrayStringCmp(test, 3, "abd")); 101 | EXPECT_LT(0, ArrayStringCmp(test, 3, "ab")); 102 | EXPECT_LT(0, ArrayStringCmp(test, 3, "abb")); 103 | } 104 | 105 | TEST(coral_util, RandomUUID) 106 | { 107 | const auto u = RandomUUID(); 108 | EXPECT_EQ(36U, u.size()); 109 | EXPECT_NE(u, RandomUUID()); 110 | } 111 | 112 | TEST(coral_util, RandomString) 113 | { 114 | const auto s = RandomString(10, "abcdefghijklmnopqrstuvwxyz"); 115 | ASSERT_EQ(10u, s.size()); 116 | for (char c : s) { 117 | EXPECT_GE(c, 'a'); 118 | EXPECT_LE(c, 'z'); 119 | } 120 | EXPECT_NE(s, RandomString(10, "abcdefghijklmnopqrstuvwxyz")); 121 | EXPECT_EQ("aaaa", RandomString(4, "a")); 122 | EXPECT_TRUE(RandomString(0, "abcd").empty()); 123 | EXPECT_THROW(RandomString(4, nullptr), std::invalid_argument); 124 | EXPECT_THROW(RandomString(4, ""), std::invalid_argument); 125 | } 126 | 127 | TEST(coral_util, MoveAndReplace_value) 128 | { 129 | int a = 123; 130 | int b = MoveAndReplace(a, 456); 131 | EXPECT_EQ(456, a); 132 | EXPECT_EQ(123, b); 133 | int c = MoveAndReplace(b); 134 | EXPECT_EQ(0, b); 135 | EXPECT_EQ(123, c); 136 | } 137 | 138 | TEST(coral_util, MoveAndReplace_class) 139 | { 140 | std::vector a; 141 | a.push_back(123); 142 | const auto dataPtr = a.data(); 143 | std::vector r; 144 | r.push_back(456); 145 | r.push_back(789); 146 | 147 | std::vector b = MoveAndReplace(a, r); 148 | ASSERT_EQ(2U, a.size()); 149 | EXPECT_EQ(456, a[0]); 150 | EXPECT_EQ(789, a[1]); 151 | 152 | // The following test is (most likely) not specified C++ behaviour, but 153 | // it would be a strange vector implementation that didn't implement a move 154 | // as a pointer move... 155 | EXPECT_EQ(1U, b.size()); 156 | EXPECT_EQ(dataPtr, b.data()); 157 | 158 | std::vector c = MoveAndReplace(b); 159 | EXPECT_TRUE(b.empty()); 160 | EXPECT_EQ(1U, c.size()); 161 | EXPECT_EQ(dataPtr, c.data()); 162 | } 163 | 164 | TEST(coral_util, LastCall) 165 | { 166 | int i = -1; 167 | 168 | std::function f0 = [&]() { 169 | ++i; 170 | EXPECT_FALSE(f0); 171 | }; 172 | EXPECT_TRUE(!!f0); 173 | LastCall(f0); 174 | EXPECT_EQ(0, i); 175 | 176 | std::function f1 = [&](int x) { 177 | ++i; 178 | EXPECT_EQ(123, x); 179 | EXPECT_FALSE(f1); 180 | }; 181 | EXPECT_TRUE(!!f1); 182 | LastCall(f1, 123); 183 | EXPECT_EQ(1, i); 184 | } 185 | 186 | TEST(coral_util, OnScopeExit) 187 | { 188 | int i = 0; 189 | { 190 | auto setToOne = OnScopeExit([&i]() { i = 1; }); 191 | EXPECT_EQ(0, i); 192 | } 193 | EXPECT_EQ(1, i); 194 | try { 195 | auto setToTwo = OnScopeExit([&i]() { i = 2; }); 196 | EXPECT_EQ(1, i); 197 | throw 0; 198 | } catch (...) { 199 | EXPECT_EQ(2, i); 200 | i = 3; 201 | } 202 | EXPECT_EQ(3, i); 203 | } 204 | 205 | TEST(coral_util, ThisExePath) 206 | { 207 | #ifdef _WIN32 208 | const auto expected = "coral_test.exe"; 209 | #else 210 | const auto expected = "coral_test"; 211 | #endif 212 | EXPECT_EQ(expected, ThisExePath().filename().string()); 213 | } 214 | -------------------------------------------------------------------------------- /cmake/FindFMILIB.cmake: -------------------------------------------------------------------------------- 1 | # - CMake script for locating FMI Library 2 | # 3 | # This script is run by the command find_package(FMILIB). The location of the 4 | # package may be explicitly specified using an environment variable named 5 | # FMILIB_DIR or a CMake variable of the same name. If both are specified, the 6 | # latter takes precedence. The variable should point to the package 7 | # installation prefix, i.e. the directory that contains the "bin", "lib" and 8 | # "include" subdirectories. 9 | # 10 | # The script searches for both static and shared/dynamic libraries, and creates 11 | # IMPORTED targets named "fmilib" and "fmilib_shared", respectively. These 12 | # targets will have several of their IMPORTED_* and INTERFACE_* properties set, 13 | # making explicit use of commands like include_directories() superfluous in most 14 | # cases. 15 | # 16 | # If the variable FMILIB_USE_SHARED_LIB is set to TRUE, FMILIB_LIBRARIES will 17 | # contain the name of the IMPORTED target "fmilib_shared". Otherwise, it will 18 | # contain "fmilib". 19 | # 20 | # After the script has completed, the variable FMILIB_FOUND will contain whether 21 | # the package was found or not. If it was, then the following variables will 22 | # also be set: 23 | # 24 | # FMILIB_INCLUDE_DIRS - The directory that contains the header files. 25 | # FMILIB_LIBRARIES - The name of an IMPORTED target. 26 | # 27 | # FMILIB_DLL - Path to dynamic library (Windows only). 28 | # FMILIB_LIBRARY - Path to static library. 29 | # FMILIB_SHARED_LIBRARY - Path to shared/import library. 30 | # 31 | cmake_minimum_required (VERSION 2.8.11) 32 | 33 | # Find static library, and use its path prefix to provide a HINTS option to the 34 | # other find_*() commands. 35 | if (UNIX) 36 | set (_FMILIB_oldsuffixes ${CMAKE_FIND_LIBRARY_SUFFIXES}) 37 | set (CMAKE_FIND_LIBRARY_SUFFIXES ".a") 38 | endif () 39 | find_library (FMILIB_LIBRARY 40 | NAMES "fmilib2" "fmilib" 41 | PATHS ${FMILIB_DIR} $ENV{FMILIB_DIR} 42 | PATH_SUFFIXES "lib") 43 | mark_as_advanced (FMILIB_LIBRARY) 44 | unset (_FMILIB_hints) 45 | if (FMILIB_LIBRARY) 46 | get_filename_component (_FMILIB_prefix "${FMILIB_LIBRARY}" PATH) 47 | get_filename_component (_FMILIB_prefix "${_FMILIB_prefix}" PATH) 48 | set (_FMILIB_hints "HINTS" "${_FMILIB_prefix}") 49 | unset (_FMILIB_prefix) 50 | endif () 51 | 52 | # Find shared/import library and append its path prefix to the HINTS option. 53 | if (UNIX) 54 | set (CMAKE_FIND_LIBRARY_SUFFIXES ".so") 55 | set (_FMILIB_shlibs "fmilib_shared" "fmilib2" "fmilib") 56 | else () 57 | set (_FMILIB_shlibs "fmilib_shared") 58 | endif () 59 | find_library (FMILIB_SHARED_LIBRARY 60 | NAMES ${_FMILIB_shlibs} 61 | ${_FMILIB_hints} 62 | PATHS ${FMILIB_DIR} $ENV{FMILIB_DIR} 63 | PATH_SUFFIXES "lib") 64 | mark_as_advanced (FMILIB_SHARED_LIBRARY) 65 | if (FMILIB_SHARED_LIBRARY) 66 | get_filename_component (_FMILIB_shared_prefix "${FMILIB_SHARED_LIBRARY}" PATH) 67 | get_filename_component (_FMILIB_shared_prefix "${_FMILIB_shared_prefix}" PATH) 68 | if (NOT _FMILIB_hints) 69 | set (_FMILIB_hints "HINTS") 70 | endif () 71 | list (APPEND _FMILIB_hints "${_FMILIB_shared_prefix}") 72 | unset (_FMILIB_shared_prefix) 73 | endif () 74 | 75 | # Reset CMAKE_FIND_LIBRARY_SUFFIXES 76 | if (UNIX) 77 | set (CMAKE_FIND_LIBRARY_SUFFIXES ${_FMILIB_oldsuffixes}) 78 | unset (_FMILIB_oldsuffixes) 79 | endif () 80 | 81 | # Find header files and, on Windows, the dynamic library 82 | find_path (FMILIB_INCLUDE_DIRS "fmilib.h" 83 | ${_FMILIB_hints} 84 | PATHS ${FMILIB_DIR} $ENV{FMILIB_DIR} 85 | PATH_SUFFIXES "include") 86 | mark_as_advanced (FMILIB_INCLUDE_DIRS) 87 | 88 | if (WIN32) 89 | find_file (FMILIB_DLL "fmilib_shared.dll" 90 | ${_FMILIB_hints} 91 | PATHS ${FMILIB_DIR} $ENV{FMILIB_DIR} 92 | PATH_SUFFIXES "bin" "lib" 93 | NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) 94 | mark_as_advanced (FMILIB_DLL) 95 | endif () 96 | unset (_FMILIB_hints) 97 | 98 | # Create the IMPORTED targets. 99 | if (FMILIB_LIBRARY) 100 | add_library ("fmilib" STATIC IMPORTED) 101 | set_target_properties ("fmilib" PROPERTIES 102 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 103 | IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_DL_LIBS}" 104 | IMPORTED_LOCATION "${FMILIB_LIBRARY}" 105 | INTERFACE_COMPILE_DEFINITIONS "FMILIB_STATIC_LIB_ONLY" 106 | INTERFACE_INCLUDE_DIRECTORIES "${FMILIB_INCLUDE_DIRS}") 107 | endif () 108 | 109 | if (FMILIB_SHARED_LIBRARY) 110 | add_library ("fmilib_shared" SHARED IMPORTED) 111 | set_target_properties ("fmilib_shared" PROPERTIES 112 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 113 | INTERFACE_INCLUDE_DIRECTORIES "${FMILIB_INCLUDE_DIRS}") 114 | if (WIN32) 115 | set_target_properties ("fmilib_shared" PROPERTIES 116 | IMPORTED_IMPLIB "${FMILIB_SHARED_LIBRARY}") 117 | if (FMILIB_DLL) 118 | set_target_properties ("fmilib_shared" PROPERTIES 119 | IMPORTED_LOCATION "${FMILIB_DLL}") 120 | endif () 121 | else () # not WIN32 122 | set_target_properties ("fmilib_shared" PROPERTIES 123 | IMPORTED_LOCATION "${FMILIB_SHARED_LIBRARY}") 124 | endif () 125 | endif () 126 | 127 | # Set the FMILIB_LIBRARIES variable. 128 | unset (FMILIB_LIBRARIES) 129 | if (FMILIB_USE_SHARED_LIB) 130 | if (FMILIB_SHARED_LIBRARY) 131 | set (FMILIB_LIBRARIES "fmilib_shared") 132 | endif () 133 | else () 134 | if (FMILIB_LIBRARY) 135 | set (FMILIB_LIBRARIES "fmilib") 136 | endif () 137 | endif () 138 | 139 | # Debug print-out. 140 | if (FMILIB_PRINT_VARS) 141 | message ("FMILIB find script variables:") 142 | message (" FMILIB_INCLUDE_DIRS = ${FMILIB_INCLUDE_DIRS}") 143 | message (" FMILIB_LIBRARIES = ${FMILIB_LIBRARIES}") 144 | message (" FMILIB_DLL = ${FMILIB_DLL}") 145 | message (" FMILIB_LIBRARY = ${FMILIB_LIBRARY}") 146 | message (" FMILIB_SHARED_LIBRARY = ${FMILIB_SHARED_LIBRARY}") 147 | endif () 148 | 149 | # Standard find_package stuff. 150 | include (FindPackageHandleStandardArgs) 151 | find_package_handle_standard_args (FMILIB DEFAULT_MSG FMILIB_LIBRARIES FMILIB_INCLUDE_DIRS) 152 | --------------------------------------------------------------------------------