diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index 08ff1fa..8b8e49b 100644 --- a/.github/workflows/cross-compile.yml +++ b/.github/workflows/cross-compile.yml @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-18.04, windows-2019 ] + os: [ ubuntu-20.04, windows-2019 ] steps: - uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: deploy: needs: build - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: "Download binaries" diff --git a/CMakeLists.txt b/CMakeLists.txt index f852e01..872ffa6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,18 +15,18 @@ if (MSVC) string(REPLACE "/MDd" "/MTd" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") endif () -if (WIN32) - set(TARGET_PLATFORM win) -elseif (APPLE) - set(TARGET_PLATFORM darwin) +if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(BITNESS 64) else () - set(TARGET_PLATFORM linux) + set(BITNESS 32) endif () -if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") - set(TARGET_PLATFORM ${TARGET_PLATFORM}64) +if (WIN32) + set(TARGET_PLATFORM win${BITNESS}) +elseif (APPLE) + set(TARGET_PLATFORM darwin${BITNESS}) else () - set(TARGET_PLATFORM ${TARGET_PLATFORM}32) + set(TARGET_PLATFORM linux${BITNESS}) endif () add_subdirectory(export) diff --git a/README.md b/README.md index 5908f69..ee37f27 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,19 @@ # FMU4cpp (early prototype) -FMU4cpp is a CMake template repository that allows you to easily create cross-platform FMUs compatible with [FMI 2.0](https://fmi-standard.org/downloads/) -for Co-simulation using C++. +FMU4cpp is a CMake template repository that allows you to easily create cross-platform FMUs +compatible with [FMI 2.0](https://fmi-standard.org/downloads/) for Co-simulation using C++. -The framework handles everything including generating the `modelDescription.xml`, -and packaging of the content into an FMU archive. +The framework generates the required `modelDescription.xml` and further packages +the necessary content into a ready-to-use FMU archive. ### How do I get started? 1. Change the value of the `modelIdentifier` variable in `CMakeLists.txt` to something more appropriate. -2. Edit the content of `src/model.cpp`. +2. Edit the content of [model.cpp](src/model.cpp). 3. Build. An FMU named `.fmu` is now located in your build folder. -Such easy, such wow. #### Cross-compilation @@ -22,6 +21,9 @@ Cross-compilation (64-bit linux/windows) occurs automatically when you push your Simply rename the produced `model.zip` to `.fmu`. +Such easy, such wow. + + ### Requirements * C++17 compiler * CMake >= 3.15 diff --git a/export/src/fmi2/fmi2FunctionTypes.h b/export/include/fmi2/fmi2FunctionTypes.h similarity index 100% rename from export/src/fmi2/fmi2FunctionTypes.h rename to export/include/fmi2/fmi2FunctionTypes.h diff --git a/export/src/fmi2/fmi2Functions.h b/export/include/fmi2/fmi2Functions.h similarity index 100% rename from export/src/fmi2/fmi2Functions.h rename to export/include/fmi2/fmi2Functions.h diff --git a/export/src/fmi2/fmi2TypesPlatform.h b/export/include/fmi2/fmi2TypesPlatform.h similarity index 100% rename from export/src/fmi2/fmi2TypesPlatform.h rename to export/include/fmi2/fmi2TypesPlatform.h diff --git a/export/include/fmu4cpp/fmu_base.hpp b/export/include/fmu4cpp/fmu_base.hpp index 2a874dd..6327906 100644 --- a/export/include/fmu4cpp/fmu_base.hpp +++ b/export/include/fmu4cpp/fmu_base.hpp @@ -13,6 +13,7 @@ #include "fmu_base.hpp" #include "fmu_except.hpp" #include "fmu_variable.hpp" +#include "logger.hpp" #include "model_info.hpp" namespace fmu4cpp { @@ -35,28 +36,28 @@ namespace fmu4cpp { } std::optional get_int_variable(const std::string &name) { - for (auto &v: integers_) { + for (const auto &v: integers_) { if (v.name() == name) return v; } return std::nullopt; } std::optional get_real_variable(const std::string &name) { - for (auto &v: reals_) { + for (const auto &v: reals_) { if (v.name() == name) return v; } return std::nullopt; } std::optional get_bool_variable(const std::string &name) { - for (auto &v: booleans_) { + for (const auto &v: booleans_) { if (v.name() == name) return v; } return std::nullopt; } std::optional get_string_variable(const std::string &name) { - for (auto &v: strings_) { + for (const auto &v: strings_) { if (v.name() == name) return v; } return std::nullopt; @@ -128,7 +129,7 @@ namespace fmu4cpp { void set_string(const unsigned int vr[], size_t nvr, const char *const value[]) { for (unsigned i = 0; i < nvr; i++) { unsigned int ref = vr[i]; - booleans_[ref].set(value[i]); + strings_[ref].set(value[i]); } } @@ -136,6 +137,22 @@ namespace fmu4cpp { [[nodiscard]] std::string make_description() const; + void __set_logger(logger *logger) { + logger_ = logger; + } + + void log(fmi2Status s, const std::string &message) { + if (logger_) { + logger_->log(s, message); + } + } + + void debugLog(fmi2Status s, const std::string &message) { + if (logger_) { + logger_->debug(s, message); + } + } + virtual ~fmu_base() = default; protected: @@ -160,8 +177,10 @@ namespace fmu4cpp { void register_variable(BoolVariable v); void register_variable(StringVariable v); + private: - size_t numVariables{}; + logger *logger_ = nullptr; + size_t numVariables_{}; std::string instanceName_; std::string resourceLocation_; diff --git a/export/include/fmu4cpp/logger.hpp b/export/include/fmu4cpp/logger.hpp new file mode 100644 index 0000000..f756eb1 --- /dev/null +++ b/export/include/fmu4cpp/logger.hpp @@ -0,0 +1,47 @@ + +#ifndef FMU4CPP_TEMPLATE_LOGGER_HPP +#define FMU4CPP_TEMPLATE_LOGGER_HPP + +#include + +#include "fmi2/fmi2FunctionTypes.h" + +namespace fmu4cpp { + class logger { + + public: + logger(fmi2ComponentEnvironment c, fmi2CallbackFunctions f, std::string instanceName) + : c_(c), + fmiLogger_(f.logger), + instanceName_(std::move(instanceName)) {} + + void setDebugLogging(bool flag) { + debugLogging_ = flag; + } + + // Logs a message. + template + void log(fmi2Status s, const std::string &message, Args &&...args) { + msgBuf_ = message; + fmiLogger_(c_, instanceName_.c_str(), s, "", msgBuf_.c_str(), std::forward(args)...); + } + + // Logs a message. + template + void debug(fmi2Status s, const std::string &message, Args &&...args) { + if (debugLogging_) { + log(s, message, std::forward(args)...); + } + } + + private: + bool debugLogging_{false}; + std::string instanceName_; + fmi2ComponentEnvironment c_; + fmi2CallbackLogger fmiLogger_; + std::string msgBuf_; + }; + +}// namespace fmu4cpp + +#endif//FMU4CPP_TEMPLATE_LOGGER_HPP diff --git a/export/src/CMakeLists.txt b/export/src/CMakeLists.txt index c19dbc7..68e0e31 100644 --- a/export/src/CMakeLists.txt +++ b/export/src/CMakeLists.txt @@ -8,7 +8,11 @@ configure_file( ) set(publicHeaders + "fmi2/fmi2Functions.h" + "fmi2/fmi2FunctionTypes.h" + "fmi2/fmi2TypesPlatform.h" "fmu4cpp/lib_info.hpp" + "fmu4cpp/logger.hpp" "fmu4cpp/model_info.hpp" "fmu4cpp/fmu_base.hpp" "fmu4cpp/fmu_except.hpp" @@ -16,9 +20,6 @@ set(publicHeaders ) set(privateHeaders - "fmi2/fmi2Functions.h" - "fmi2/fmi2FunctionTypes.h" - "fmi2/fmi2TypesPlatform.h" "fmu4cpp/hash.hpp" "fmu4cpp/time.hpp" ) diff --git a/export/src/fmu4cpp/fmi2.cpp b/export/src/fmu4cpp/fmi2.cpp index e77812f..84a5514 100644 --- a/export/src/fmu4cpp/fmi2.cpp +++ b/export/src/fmu4cpp/fmi2.cpp @@ -8,6 +8,7 @@ #include #include "fmu4cpp/fmu_base.hpp" +#include "fmu4cpp/logger.hpp" namespace { @@ -15,12 +16,15 @@ namespace { struct Component { Component(std::unique_ptr slave, fmi2CallbackFunctions callbackFunctions) - : lastSuccessfulTime{std::numeric_limits::quiet_NaN()}, callbackFunctions(callbackFunctions), slave(std::move(slave)) {} + : lastSuccessfulTime{std::numeric_limits::quiet_NaN()}, + slave(std::move(slave)), + logger(this->slave.get(), callbackFunctions, this->slave->instanceName()) { + this->slave->__set_logger(&logger); + } - // Co-simulation double lastSuccessfulTime; - fmi2CallbackFunctions callbackFunctions; std::unique_ptr slave; + fmu4cpp::logger logger; }; }// namespace @@ -48,7 +52,7 @@ fmi2Component fmi2Instantiate(fmi2String instanceName, fmi2String fmuGUID, fmi2String fmuResourceLocation, const fmi2CallbackFunctions *functions, - fmi2Boolean visible, + fmi2Boolean /*visible*/, fmi2Boolean loggingOn) { if (fmuType != fmi2CoSimulation) { @@ -77,9 +81,14 @@ fmi2Component fmi2Instantiate(fmi2String instanceName, auto guid = slave->guid(); if (guid != fmuGUID) { std::cerr << "[fmu4cpp] Error. Wrong guid!" << std::endl; + fmu4cpp::logger l(nullptr, *functions, instanceName); + l.log(fmi2Fatal, "", "Error. Wrong guid!"); return nullptr; } - return new Component(std::move(slave), *functions); + + auto c = new Component(std::move(slave), *functions); + c->logger.setDebugLogging(loggingOn); + return c; } fmi2Status fmi2SetupExperiment(fmi2Component c, @@ -354,7 +363,8 @@ fmi2Status fmi2SetDebugLogging(fmi2Component c, size_t nCategories, const fmi2String categories[]) { auto component = reinterpret_cast(c); - return fmi2Error; + component->logger.setDebugLogging(loggingOn); + return fmi2OK; } fmi2Status fmi2SetRealInputDerivatives(fmi2Component, diff --git a/export/src/fmu4cpp/fmu_base.cpp b/export/src/fmu4cpp/fmu_base.cpp index 8ed1805..ef3a6cb 100644 --- a/export/src/fmu4cpp/fmu_base.cpp +++ b/export/src/fmu4cpp/fmu_base.cpp @@ -221,19 +221,19 @@ namespace fmu4cpp { } IntVariable fmu_base::integer(const std::string &name, const std::function &getter, const std::optional> &setter) { - return {name, static_cast(integers_.size()), numVariables++, getter, setter}; + return {name, static_cast(integers_.size()), numVariables_++, getter, setter}; } RealVariable fmu_base::real(const std::string &name, const std::function &getter, const std::optional> &setter) { - return {name, static_cast(reals_.size()), numVariables++, getter, setter}; + return {name, static_cast(reals_.size()), numVariables_++, getter, setter}; } BoolVariable fmu_base::boolean(const std::string &name, const std::function &getter, const std::optional> &setter) { - return {name, static_cast(booleans_.size()), numVariables++, getter, setter}; + return {name, static_cast(booleans_.size()), numVariables_++, getter, setter}; } StringVariable fmu_base::string(const std::string &name, const std::function &getter, const std::optional> &setter) { - return {name, static_cast(strings_.size()), numVariables++, getter, setter}; + return {name, static_cast(strings_.size()), numVariables_++, getter, setter}; } void fmu_base::register_variable(IntVariable v) { diff --git a/export/src/fmu4cpp/hash.hpp b/export/src/fmu4cpp/hash.hpp index fca2281..e185ea4 100644 --- a/export/src/fmu4cpp/hash.hpp +++ b/export/src/fmu4cpp/hash.hpp @@ -9,18 +9,33 @@ //https://stackoverflow.com/questions/66764096/calculating-stdhash-using-different-compilers inline uint64_t fnv1a(std::string const &text) { - // assumes 64 bit - uint64_t constexpr fnv_prime = 1099511628211ULL; - uint64_t constexpr fnv_offset_basis = 14695981039346656037ULL; + if (sizeof(void *) == 4) { + // 32-bit environment + uint32_t constexpr fnv_prime = 16777619U; + uint32_t constexpr fnv_offset_basis = 2166136261U; - uint64_t hash = fnv_offset_basis; + uint32_t hash = fnv_offset_basis; - for (auto c: text) { - hash ^= c; - hash *= fnv_prime; - } + for (auto c: text) { + hash ^= c; + hash *= fnv_prime; + } + + return hash; + } else { + // 64-bit environment + uint64_t constexpr fnv_prime = 1099511628211ULL; + uint64_t constexpr fnv_offset_basis = 14695981039346656037ULL; + + uint64_t hash = fnv_offset_basis; - return hash; + for (auto c: text) { + hash ^= c; + hash *= fnv_prime; + } + + return hash; + } } #endif//FMU4CPP_TEMPLATE_HASH_HPP diff --git a/export/tests/basic_test.cpp b/export/tests/basic_test.cpp index 9db0ef9..6a3cadd 100644 --- a/export/tests/basic_test.cpp +++ b/export/tests/basic_test.cpp @@ -16,14 +16,17 @@ class Model : public fmu4cpp::fmu_base { .setCausality(fmu4cpp::causality_t::OUTPUT)); register_variable(boolean("myBoolean", [this] { return boolean_; }) .setCausality(fmu4cpp::causality_t::OUTPUT)); + register_variable(string("myString", [this] { return str_; }) + .setCausality(fmu4cpp::causality_t::OUTPUT)); Model::reset(); } bool do_step(double currentTime, double dt) override { real_ = currentTime; - integer_++; + ++integer_; boolean_ = !boolean_; + str_ = std::to_string(integer_); return true; } @@ -31,12 +34,14 @@ class Model : public fmu4cpp::fmu_base { boolean_ = false; integer_ = 0; real_ = 0; + str_ = "0"; } private: bool boolean_; int integer_; double real_; + std::string str_; }; fmu4cpp::model_info fmu4cpp::get_model_info() { @@ -62,6 +67,8 @@ TEST_CASE("basic") { REQUIRE(integer); auto boolean = instance->get_bool_variable("myBoolean"); REQUIRE(boolean); + auto str = instance->get_string_variable("myString"); + REQUIRE(str); instance->setup_experiment(t, {}, {}); instance->enter_initialisation_mode(); @@ -74,8 +81,17 @@ TEST_CASE("basic") { REQUIRE(real->get() == Approx(t)); REQUIRE(boolean->get() == (i % 2 == 0)); REQUIRE(integer->get() == ++i); + REQUIRE(str->get() == std::to_string(i)); t += dt; } + + instance->reset(); + + REQUIRE(real->get() == 0); + REQUIRE(boolean->get() == false); + REQUIRE(integer->get() == 0); + REQUIRE(str->get() == "0"); + instance->terminate(); } \ No newline at end of file diff --git a/src/model.cpp b/src/model.cpp index 7e252ba..6de0cd3 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -64,6 +64,7 @@ class Model : public fmu_base { } bool do_step(double currentTime, double dt) override { + debugLog(fmi2OK, "hello@ " + std::to_string(currentTime)); return true; }