From 74e17d67b6657688e08ad3d59f83ce245d42be68 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Fri, 19 Dec 2025 15:47:47 +0100 Subject: [PATCH 1/5] #36 Add SetStepDuration and TimeAdvanceModes Signed-off-by: Konrad Breitsprecher --- Demos/api/Orchestration/CMakeLists.txt | 1 + Demos/api/Orchestration/DynSimStep.cpp | 90 +++++++++++++++++++ Demos/api/Orchestration/SimStep.cpp | 15 ++-- .../IntegrationTests/Hourglass/MockCapi.cpp | 12 +++ .../IntegrationTests/Hourglass/MockCapi.hpp | 6 ++ .../Hourglass/Test_HourglassOrchestration.cpp | 33 +++++++ SilKit/include/silkit/capi/Orchestration.h | 32 +++++++ .../orchestration/LifecycleService.hpp | 16 +++- .../orchestration/TimeSyncService.hpp | 20 ++++- .../orchestration/ILifecycleService.hpp | 2 + .../orchestration/ITimeSyncService.hpp | 4 + .../orchestration/OrchestrationDatatypes.hpp | 8 ++ SilKit/source/capi/CapiOrchestration.cpp | 37 ++++++++ SilKit/source/capi/Test_CapiSymbols.cpp | 1 + .../core/mock/participant/MockParticipant.hpp | 6 +- .../orchestration/LifecycleService.cpp | 16 ++++ .../orchestration/LifecycleService.hpp | 1 + .../orchestration/Test_LifecycleService.cpp | 5 +- .../orchestration/TimeConfiguration.cpp | 67 ++++++++++---- .../orchestration/TimeConfiguration.hpp | 11 ++- .../orchestration/TimeSyncService.cpp | 11 ++- .../orchestration/TimeSyncService.hpp | 7 +- docs/api/capi/capi-orchestration.rst | 1 + 23 files changed, 371 insertions(+), 31 deletions(-) create mode 100644 Demos/api/Orchestration/DynSimStep.cpp diff --git a/Demos/api/Orchestration/CMakeLists.txt b/Demos/api/Orchestration/CMakeLists.txt index 3250bff88..ff118459a 100644 --- a/Demos/api/Orchestration/CMakeLists.txt +++ b/Demos/api/Orchestration/CMakeLists.txt @@ -5,5 +5,6 @@ make_silkit_demo(SilKitDemoAutonomous Autonomous.cpp OFF) make_silkit_demo(SilKitDemoCoordinated Coordinated.cpp OFF) make_silkit_demo(SilKitDemoSimStep SimStep.cpp OFF) +make_silkit_demo(SilKitDemoDynSimStep DynSimStep.cpp OFF) make_silkit_demo(SilKitDemoSimStepAsync SimStepAsync.cpp OFF) diff --git a/Demos/api/Orchestration/DynSimStep.cpp b/Demos/api/Orchestration/DynSimStep.cpp new file mode 100644 index 000000000..9e34c9d70 --- /dev/null +++ b/Demos/api/Orchestration/DynSimStep.cpp @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include +#include + +#include "silkit/SilKit.hpp" + +using namespace std::chrono_literals; + +std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) +{ + out << std::chrono::duration_cast(timestamp).count() << "ms"; + return out; +} + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; + return -1; + } + std::string participantName(argv[1]); + + try + { + // Setup participant, lifecycle, time synchronization and logging. + const std::string registryUri = "silkit://localhost:8500"; + const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; + auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); + + auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); + auto logger = participant->GetLogger(); + + auto* lifecycleService = + participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Coordinated}); + + auto* timeSyncService = lifecycleService->CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode::ByMinimalDuration); + + const auto stepSize = 10ms; + static int stepCounter = 0; + std::random_device rd; + std::mt19937 rng(rd()); + auto bounded_rand = [&rng](unsigned range) { + std::uniform_int_distribution dist(1, range); + return dist(rng); + }; + + + timeSyncService->SetSimulationStepHandler( + [logger, timeSyncService, participantName, bounded_rand](std::chrono::nanoseconds now, + std::chrono::nanoseconds duration) { + // The invocation of this handler marks the beginning of a simulation step. + { + std::stringstream ss; + ss << "--------- Simulation step T=" << now << ", duration=" << duration << " ---------"; + logger->Info(ss.str()); + } + + if (bounded_rand(10) == 1)// && participantName == "P1") + { + auto rndStepDuration = bounded_rand(10); + timeSyncService->SetStepDuration(std::chrono::milliseconds(rndStepDuration)); + std::stringstream ss; + ss << "--------- Changing step size to " << rndStepDuration << "ms ---------"; + logger->Info(ss.str()); + } + + std::this_thread::sleep_for(500ms); + // All messages sent here are guaranteed to arrive at other participants before their next simulation step is called. + // So here, we can rely on having received all messages from the past (< now). + // Note that this guarantee only holds for messages sent within a simulation step, + // not for messages send outside of this handler (e.g. directly in a reception handler). + + // Returning from the handler marks the end of a simulation step. + }, stepSize); + + auto finalStateFuture = lifecycleService->StartLifecycle(); + finalStateFuture.get(); + } + catch (const std::exception& error) + { + std::cerr << "Something went wrong: " << error.what() << std::endl; + return -2; + } + + return 0; +} diff --git a/Demos/api/Orchestration/SimStep.cpp b/Demos/api/Orchestration/SimStep.cpp index 6a4eb207b..f3e7e741d 100644 --- a/Demos/api/Orchestration/SimStep.cpp +++ b/Demos/api/Orchestration/SimStep.cpp @@ -38,15 +38,18 @@ int main(int argc, char** argv) auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - const auto stepSize = 1ms; + const auto stepSize = 2ms; + timeSyncService->SetSimulationStepHandler( - [logger](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { + [logger](std::chrono::nanoseconds now, std::chrono::nanoseconds duration) { // The invocation of this handler marks the beginning of a simulation step. + { + std::stringstream ss; + ss << "--------- Simulation step T=" << now << ", duration=" << duration << " ---------"; + logger->Info(ss.str()); + } - std::stringstream ss; - ss << "--------- Simulation step T=" << now << " ---------"; - logger->Info(ss.str()); - + std::this_thread::sleep_for(500ms); // All messages sent here are guaranteed to arrive at other participants before their next simulation step is called. // So here, we can rely on having received all messages from the past (< now). // Note that this guarantee only holds for messages sent within a simulation step, diff --git a/SilKit/IntegrationTests/Hourglass/MockCapi.cpp b/SilKit/IntegrationTests/Hourglass/MockCapi.cpp index ccedbf562..fca076561 100644 --- a/SilKit/IntegrationTests/Hourglass/MockCapi.cpp +++ b/SilKit/IntegrationTests/Hourglass/MockCapi.cpp @@ -614,6 +614,12 @@ extern "C" return globalCapi->SilKit_TimeSyncService_Create(outTimeSyncService, lifecycleService); } + SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_Create_With_TimeAdvanceMode(SilKit_TimeSyncService** outTimeSyncService, + SilKit_LifecycleService* lifecycleService, SilKit_TimeAdvanceMode timeAdvanceMode) + { + return globalCapi->SilKit_TimeSyncService_Create_With_TimeAdvanceMode(outTimeSyncService, lifecycleService, timeAdvanceMode); + } + SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_SetSimulationStepHandler( SilKit_TimeSyncService* timeSyncService, void* context, SilKit_TimeSyncService_SimulationStepHandler_t handler, SilKit_NanosecondsTime initialStepSize) @@ -641,6 +647,12 @@ extern "C" return globalCapi->SilKit_TimeSyncService_Now(timeSyncService, outNanosecondsTime); } + SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_SetStepDuration(SilKit_TimeSyncService* timeSyncService, + SilKit_NanosecondsTime stepDuration) + { + return globalCapi->SilKit_TimeSyncService_SetStepDuration(timeSyncService, stepDuration); + } + SilKit_ReturnCode SilKitCALL SilKit_Experimental_TimeSyncService_AddOtherSimulationStepsCompletedHandler( SilKit_TimeSyncService* timeSyncService, void* context, SilKit_Experimental_TimeSyncService_OtherSimulationStepsCompletedHandler_t handler, diff --git a/SilKit/IntegrationTests/Hourglass/MockCapi.hpp b/SilKit/IntegrationTests/Hourglass/MockCapi.hpp index 1b863ea8b..528ee2d5b 100644 --- a/SilKit/IntegrationTests/Hourglass/MockCapi.hpp +++ b/SilKit/IntegrationTests/Hourglass/MockCapi.hpp @@ -325,6 +325,9 @@ class MockCapi MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_Create, (SilKit_TimeSyncService * *outTimeSyncService, SilKit_LifecycleService* lifecycleService)); + MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_Create_With_TimeAdvanceMode, + (SilKit_TimeSyncService * *outTimeSyncService, SilKit_LifecycleService* lifecycleService, SilKit_TimeAdvanceMode timeAdvanceMode)); + MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_SetSimulationStepHandler, (SilKit_TimeSyncService * timeSyncService, void* context, SilKit_TimeSyncService_SimulationStepHandler_t handler, SilKit_NanosecondsTime initialStepSize)); @@ -339,6 +342,9 @@ class MockCapi MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_Now, (SilKit_TimeSyncService * timeSyncService, SilKit_NanosecondsTime* outNanosecondsTime)); + MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_SetStepDuration, + (SilKit_TimeSyncService * timeSyncService, SilKit_NanosecondsTime stepDuration)); + MOCK_METHOD(SilKit_ReturnCode, SilKit_Experimental_TimeSyncService_AddOtherSimulationStepsCompletedHandler, (SilKit_TimeSyncService * timeSyncService, void* context, SilKit_Experimental_TimeSyncService_OtherSimulationStepsCompletedHandler_t handler, diff --git a/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp b/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp index f575072af..e5caed799 100644 --- a/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp +++ b/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp @@ -148,6 +148,8 @@ class Test_HourglassOrchestration : public SilKitHourglassTests::MockCapiTest .WillByDefault(DoAll(SetArgPointee<0>(mockLifecycleService), Return(SilKit_ReturnCode_SUCCESS))); ON_CALL(capi, SilKit_TimeSyncService_Create(_, _)) .WillByDefault(DoAll(SetArgPointee<0>(mockTimeSyncService), Return(SilKit_ReturnCode_SUCCESS))); + ON_CALL(capi, SilKit_TimeSyncService_Create_With_TimeAdvanceMode(_, _, _)) + .WillByDefault(DoAll(SetArgPointee<0>(mockTimeSyncService), Return(SilKit_ReturnCode_SUCCESS))); ON_CALL(capi, SilKit_SystemMonitor_Create(_, _)) .WillByDefault(DoAll(SetArgPointee<0>(mockSystemMonitor), Return(SilKit_ReturnCode_SUCCESS))); ON_CALL(capi, SilKit_Experimental_SystemController_Create(_, _)) @@ -362,6 +364,23 @@ TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_Create) mockLifecycleService}; } +TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_Create_With_TimeAdvanceMode) +{ + EXPECT_CALL(capi, SilKit_TimeSyncService_Create_With_TimeAdvanceMode(testing::_, mockLifecycleService, + SilKit_TimeAdvanceMode_ByMinimalDuration)); + + SilKit::DETAIL_SILKIT_DETAIL_NAMESPACE_NAME::Impl::Services::Orchestration::TimeSyncService + timeSyncService_ByMinimalDuration{ + mockLifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode::ByMinimalDuration}; + + EXPECT_CALL(capi, SilKit_TimeSyncService_Create_With_TimeAdvanceMode(testing::_, mockLifecycleService, + SilKit_TimeAdvanceMode_ByOwnDuration)); + + SilKit::DETAIL_SILKIT_DETAIL_NAMESPACE_NAME::Impl::Services::Orchestration::TimeSyncService + timeSyncService_ByOwnDuration{ + mockLifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode::ByOwnDuration}; +} + TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_SetSimulationStepHandler) { const std::chrono::nanoseconds initialStepSize{0x123456}; @@ -415,6 +434,20 @@ TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_Now) EXPECT_EQ(timeSyncService.Now(), nanoseconds); } +TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_SetStepDuration) +{ + const std::chrono::nanoseconds stepDuration{0x123456}; + + SilKit::DETAIL_SILKIT_DETAIL_NAMESPACE_NAME::Impl::Services::Orchestration::TimeSyncService timeSyncService{ + mockLifecycleService}; + + EXPECT_CALL(capi, SilKit_TimeSyncService_SetStepDuration(mockTimeSyncService, testing::_)) + .WillOnce(Return(SilKit_ReturnCode_SUCCESS)); + + timeSyncService.SetStepDuration(stepDuration); +} + + TEST_F(Test_HourglassOrchestration, SilKit_Experimental_TimeSyncService_AddOtherSimulationStepsCompletedHandler) { using testing::_; diff --git a/SilKit/include/silkit/capi/Orchestration.h b/SilKit/include/silkit/capi/Orchestration.h index 572a7e7c4..4f7185422 100644 --- a/SilKit/include/silkit/capi/Orchestration.h +++ b/SilKit/include/silkit/capi/Orchestration.h @@ -94,6 +94,12 @@ typedef int8_t SilKit_OperationMode; #define SilKit_OperationMode_Autonomous ((SilKit_OperationMode)20) +/*! The TimeAdvanceMode. */ +typedef int8_t SilKit_TimeAdvanceMode; + +#define SilKit_TimeAdvanceMode_ByOwnDuration ((SilKit_TimeAdvanceMode)0) +#define SilKit_TimeAdvanceMode_ByMinimalDuration ((SilKit_TimeAdvanceMode)10) + /*! Details about a status change of a participant. */ typedef struct { @@ -452,6 +458,20 @@ SilKitAPI SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_Create(SilKit_Time typedef SilKit_ReturnCode(SilKitFPTR* SilKit_TimeSyncService_Create_t)(SilKit_TimeSyncService** outTimeSyncService, SilKit_LifecycleService* lifecycleService); +/*! \brief Create a time sync service at this SIL Kit simulation participant. + * \param outTimeSyncService Pointer that refers to the resulting time sync service (out parameter). + * \param lifecycleService The lifecyle service at which the time sync service should be created. + * \param timeAdvanceMode The time advance mode for this time sync service. + * + * The object returned must not be deallocated using free()! + */ +SilKitAPI SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_Create_With_TimeAdvanceMode(SilKit_TimeSyncService** outTimeSyncService, + SilKit_LifecycleService* lifecycleService, SilKit_TimeAdvanceMode timeAdvanceMode); + +typedef SilKit_ReturnCode(SilKitFPTR* SilKit_TimeSyncService_Create_With_TimeAdvanceMode_t)(SilKit_TimeSyncService** outTimeSyncService, + SilKit_LifecycleService* lifecycleService, + SilKit_TimeAdvanceMode timeAdvanceMode); + /*! \brief The handler to be called if the simulation task is due * * \param context The user provided context passed in \ref SilKit_TimeSyncService_SetSimulationStepHandler @@ -528,6 +548,18 @@ typedef SilKit_ReturnCode(SilKitFPTR* SilKit_TimeSyncService_Now_t)(SilKit_TimeS SilKit_NanosecondsTime* outNanosecondsTime); +/*! \brief Set the duration of the next simulation step + * + * \param timeSyncService The time sync service obtained via \ref SilKit_TimeSyncService_Create. + * \param stepDuration The step size in nanoseconds. + */ +SilKitAPI SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_SetStepDuration(SilKit_TimeSyncService* timeSyncService, + SilKit_NanosecondsTime stepDuration); + +typedef SilKit_ReturnCode(SilKitFPTR* SilKit_TimeSyncService_SetStepDuration_t)(SilKit_TimeSyncService* timeSyncService, + SilKit_NanosecondsTime stepDuration); + + /* * * System Monitor diff --git a/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp b/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp index b62e5162d..0ac8da498 100644 --- a/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp +++ b/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp @@ -66,6 +66,9 @@ class LifecycleService : public SilKit::Services::Orchestration::ILifecycleServi inline auto CreateTimeSyncService() -> SilKit::Services::Orchestration::ITimeSyncService* override; + inline auto CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode) + -> SilKit::Services::Orchestration::ITimeSyncService* override; + private: SilKit_LifecycleService* _lifecycleService{nullptr}; @@ -315,11 +318,22 @@ auto LifecycleService::Status() const -> const SilKit::Services::Orchestration:: auto LifecycleService::CreateTimeSyncService() -> SilKit::Services::Orchestration::ITimeSyncService* { - _timeSyncService = std::make_unique(_lifecycleService); + _timeSyncService = std::make_unique( + _lifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode::ByOwnDuration); return _timeSyncService.get(); } +// TODO bkd: Needed or can I use a default in the function above? +auto LifecycleService::CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode) + -> SilKit::Services::Orchestration::ITimeSyncService* +{ + _timeSyncService = std::make_unique(_lifecycleService, timeAdvanceMode); + + return _timeSyncService.get(); +} + + } // namespace Orchestration } // namespace Services } // namespace Impl diff --git a/SilKit/include/silkit/detail/impl/services/orchestration/TimeSyncService.hpp b/SilKit/include/silkit/detail/impl/services/orchestration/TimeSyncService.hpp index 3e27a7025..82cb4bda5 100644 --- a/SilKit/include/silkit/detail/impl/services/orchestration/TimeSyncService.hpp +++ b/SilKit/include/silkit/detail/impl/services/orchestration/TimeSyncService.hpp @@ -22,8 +22,12 @@ namespace Orchestration { class TimeSyncService : public SilKit::Services::Orchestration::ITimeSyncService { public: + inline explicit TimeSyncService(SilKit_LifecycleService* lifecycleService); + inline explicit TimeSyncService(SilKit_LifecycleService* lifecycleService, + SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode); + inline ~TimeSyncService() override = default; inline void SetSimulationStepHandler(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) override; @@ -35,7 +39,9 @@ class TimeSyncService : public SilKit::Services::Orchestration::ITimeSyncService inline auto Now() const -> std::chrono::nanoseconds override; -public: + inline void SetStepDuration(std::chrono::nanoseconds stepDuration) override; + + public: inline auto ExperimentalAddOtherSimulationStepsCompletedHandler( SilKit::Experimental::Services::Orchestration::OtherSimulationStepsCompletedHandler) -> SilKit::Util::HandlerId; @@ -85,6 +91,12 @@ TimeSyncService::TimeSyncService(SilKit_LifecycleService* lifecycleService) ThrowOnError(returnCode); } +TimeSyncService::TimeSyncService(SilKit_LifecycleService* lifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode) +{ + const auto returnCode = SilKit_TimeSyncService_Create_With_TimeAdvanceMode(&_timeSyncService, lifecycleService, static_cast(timeAdvanceMode)); + ThrowOnError(returnCode); +} + void TimeSyncService::SetSimulationStepHandler(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) { auto ownedHandlerPtr = std::make_unique(std::move(task)); @@ -140,6 +152,12 @@ auto TimeSyncService::Now() const -> std::chrono::nanoseconds return std::chrono::nanoseconds{nanosecondsTime}; } +void TimeSyncService::SetStepDuration(std::chrono::nanoseconds stepDuration) +{ + const auto returnCode = SilKit_TimeSyncService_SetStepDuration(_timeSyncService, stepDuration.count()); + ThrowOnError(returnCode); +} + inline auto TimeSyncService::ExperimentalAddOtherSimulationStepsCompletedHandler(std::function handler) -> SilKit::Util::HandlerId { diff --git a/SilKit/include/silkit/services/orchestration/ILifecycleService.hpp b/SilKit/include/silkit/services/orchestration/ILifecycleService.hpp index ce771796b..5f55f3698 100644 --- a/SilKit/include/silkit/services/orchestration/ILifecycleService.hpp +++ b/SilKit/include/silkit/services/orchestration/ILifecycleService.hpp @@ -191,6 +191,8 @@ class ILifecycleService /*! \brief Return the ITimeSyncService for the current ILifecycleService. */ virtual auto CreateTimeSyncService() -> ITimeSyncService* = 0; + + virtual auto CreateTimeSyncService(TimeAdvanceMode timeAdvanceMode) -> ITimeSyncService* = 0; }; } // namespace Orchestration diff --git a/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp b/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp index c7656a161..8fdc8287e 100644 --- a/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp +++ b/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp @@ -50,6 +50,10 @@ class ITimeSyncService /*! \brief Get the current simulation time */ virtual auto Now() const -> std::chrono::nanoseconds = 0; + + virtual void SetStepDuration(std::chrono::nanoseconds stepDuration) = 0; + + }; } // namespace Orchestration diff --git a/SilKit/include/silkit/services/orchestration/OrchestrationDatatypes.hpp b/SilKit/include/silkit/services/orchestration/OrchestrationDatatypes.hpp index 807baf45b..0eaed8a45 100644 --- a/SilKit/include/silkit/services/orchestration/OrchestrationDatatypes.hpp +++ b/SilKit/include/silkit/services/orchestration/OrchestrationDatatypes.hpp @@ -119,6 +119,14 @@ struct ParticipantConnectionInformation std::string participantName; }; +enum class TimeAdvanceMode : SilKit_TimeAdvanceMode +{ + //! Advance time based on the participant's own step duration + ByOwnDuration = SilKit_TimeAdvanceMode_ByOwnDuration, + //! Advance time based on the minimal step duration among all participants + ByMinimalDuration = SilKit_TimeAdvanceMode_ByMinimalDuration, +}; + } // namespace Orchestration } // namespace Services } // namespace SilKit diff --git a/SilKit/source/capi/CapiOrchestration.cpp b/SilKit/source/capi/CapiOrchestration.cpp index 303359aff..8e028d2b1 100644 --- a/SilKit/source/capi/CapiOrchestration.cpp +++ b/SilKit/source/capi/CapiOrchestration.cpp @@ -90,6 +90,23 @@ try } CAPI_CATCH_EXCEPTIONS +SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_Create_With_TimeAdvanceMode( + SilKit_TimeSyncService** outTimeSyncService, SilKit_LifecycleService* lifecycleService, + SilKit_TimeAdvanceMode timeAdvanceMode) +try +{ + ASSERT_VALID_OUT_PARAMETER(outTimeSyncService); + ASSERT_VALID_POINTER_PARAMETER(lifecycleService); + + auto cppLifecycleService = reinterpret_cast(lifecycleService); + auto cppTimeSyncService = cppLifecycleService->CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode(timeAdvanceMode)); + + *outTimeSyncService = reinterpret_cast(cppTimeSyncService); + + return SilKit_ReturnCode_SUCCESS; +} +CAPI_CATCH_EXCEPTIONS + SilKit_ReturnCode SilKitCALL SilKit_LifecycleService_SetCommunicationReadyHandler(SilKit_LifecycleService* lifecycleService, void* context, @@ -428,6 +445,26 @@ try CAPI_CATCH_EXCEPTIONS +SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_SetStepDuration(SilKit_TimeSyncService* cTimeSyncService, + SilKit_NanosecondsTime stepDuration) +try +{ + ASSERT_VALID_POINTER_PARAMETER(cTimeSyncService); + + auto* timeSyncService = reinterpret_cast(cTimeSyncService); + + if (stepDuration <= 0) + { + SilKit_error_string = "Step duration must be positive"; + return SilKit_ReturnCode_BADPARAMETER; + } + + timeSyncService->SetStepDuration(std::chrono::nanoseconds(stepDuration)); + return SilKit_ReturnCode_SUCCESS; +} +CAPI_CATCH_EXCEPTIONS + + SilKit_ReturnCode SilKitCALL SilKit_LifecycleService_Pause(SilKit_LifecycleService* clifecycleService, const char* reason) try diff --git a/SilKit/source/capi/Test_CapiSymbols.cpp b/SilKit/source/capi/Test_CapiSymbols.cpp index 8f0612407..8e933b33a 100644 --- a/SilKit/source/capi/Test_CapiSymbols.cpp +++ b/SilKit/source/capi/Test_CapiSymbols.cpp @@ -87,6 +87,7 @@ TEST(Test_CapiSymbols, DISABLED_link_all_public_symbols) (void)SilKit_SystemMonitor_Create(nullptr, nullptr); (void)SilKit_LifecycleService_Create(nullptr, nullptr, nullptr); (void)SilKit_TimeSyncService_Create(nullptr, nullptr); + (void)SilKit_TimeSyncService_Create_With_TimeAdvanceMode(nullptr, nullptr, 0); (void)SilKit_LifecycleService_SetCommunicationReadyHandler(nullptr, nullptr, nullptr); (void)SilKit_LifecycleService_SetCommunicationReadyHandlerAsync(nullptr, nullptr, nullptr); (void)SilKit_LifecycleService_CompleteCommunicationReadyHandlerAsync(nullptr); diff --git a/SilKit/source/core/mock/participant/MockParticipant.hpp b/SilKit/source/core/mock/participant/MockParticipant.hpp index 611a852e0..6f440de8d 100644 --- a/SilKit/source/core/mock/participant/MockParticipant.hpp +++ b/SilKit/source/core/mock/participant/MockParticipant.hpp @@ -71,6 +71,8 @@ class MockLifecycleService : public Services::Orchestration::ILifecycleService MOCK_METHOD(Services::Orchestration::ParticipantStatus&, Status, (), (override, const)); MOCK_METHOD(Services::Orchestration::ITimeSyncService*, GetTimeSyncService, (), ()); MOCK_METHOD(Services::Orchestration::ITimeSyncService*, CreateTimeSyncService, (), (override)); + MOCK_METHOD(Services::Orchestration::ITimeSyncService*, CreateTimeSyncService, + (SilKit::Services::Orchestration::TimeAdvanceMode), (override)); MOCK_METHOD(void, AddAsyncSubscriptionsCompletionHandler, (std::function /*handler*/)); MOCK_METHOD(Services::Orchestration::OperationMode, GetOperationMode, (), (const)); }; @@ -84,6 +86,7 @@ class MockTimeSyncService : public Services::Orchestration::ITimeSyncService (SimulationStepHandler task, std::chrono::nanoseconds initialStepSize), (override)); MOCK_METHOD(void, CompleteSimulationStep, (), (override)); MOCK_METHOD(std::chrono::nanoseconds, Now, (), (override, const)); + MOCK_METHOD(void, SetStepDuration, (std::chrono::nanoseconds)); }; class MockSystemMonitor : public Services::Orchestration::ISystemMonitor @@ -248,7 +251,8 @@ class DummyParticipant : public IParticipantInternal DummyParticipant() { ON_CALL(mockLifecycleService, GetTimeSyncService).WillByDefault(testing::Return(&mockTimeSyncService)); - ON_CALL(mockLifecycleService, CreateTimeSyncService).WillByDefault(testing::Return(&mockTimeSyncService)); + ON_CALL(mockLifecycleService, CreateTimeSyncService()).WillByDefault(testing::Return(&mockTimeSyncService)); + ON_CALL(mockLifecycleService, CreateTimeSyncService(testing::_)).WillByDefault(testing::Return(&mockTimeSyncService)); ON_CALL(logger, GetLogLevel()).WillByDefault(testing::Return(Services::Logging::Level::Debug)); } diff --git a/SilKit/source/services/orchestration/LifecycleService.cpp b/SilKit/source/services/orchestration/LifecycleService.cpp index e946dc344..6b25c5d49 100644 --- a/SilKit/source/services/orchestration/LifecycleService.cpp +++ b/SilKit/source/services/orchestration/LifecycleService.cpp @@ -407,6 +407,22 @@ auto LifecycleService::CreateTimeSyncService() -> ITimeSyncService* { _participant->RegisterTimeSyncService(_timeSyncService); _timeSyncActive = true; + _timeSyncService->SetTimeAdvanceMode(TimeAdvanceMode::ByOwnDuration); + return _timeSyncService; + } + else + { + throw ConfigurationError("You may not create the time synchronization service more than once."); + } +} + +auto LifecycleService::CreateTimeSyncService(TimeAdvanceMode timeAdvanceMode) -> ITimeSyncService* +{ + if (!_timeSyncActive) + { + _participant->RegisterTimeSyncService(_timeSyncService); + _timeSyncActive = true; + _timeSyncService->SetTimeAdvanceMode(timeAdvanceMode); return _timeSyncService; } else diff --git a/SilKit/source/services/orchestration/LifecycleService.hpp b/SilKit/source/services/orchestration/LifecycleService.hpp index fb75df174..798953d15 100644 --- a/SilKit/source/services/orchestration/LifecycleService.hpp +++ b/SilKit/source/services/orchestration/LifecycleService.hpp @@ -54,6 +54,7 @@ class LifecycleService void SetAbortHandler(AbortHandler handler) override; auto CreateTimeSyncService() -> ITimeSyncService* override; + auto CreateTimeSyncService(TimeAdvanceMode timeAdvanceMode) -> ITimeSyncService* override; auto GetTimeSyncService() -> ITimeSyncService*; auto StartLifecycle() -> std::future override; diff --git a/SilKit/source/services/orchestration/Test_LifecycleService.cpp b/SilKit/source/services/orchestration/Test_LifecycleService.cpp index 2093ca9a1..2f6975eb1 100644 --- a/SilKit/source/services/orchestration/Test_LifecycleService.cpp +++ b/SilKit/source/services/orchestration/Test_LifecycleService.cpp @@ -44,8 +44,8 @@ class MockTimeSync : public TimeSyncService MOCK_METHOD(void, SetSimulationStepHandlerAsync, (SimulationStepHandler task, std::chrono::nanoseconds initialStepSize), (override)); MOCK_METHOD(void, CompleteSimulationStep, (), (override)); - MOCK_METHOD(void, SetPeriod, (std::chrono::nanoseconds)); MOCK_METHOD(std::chrono::nanoseconds, Now, (), (override, const)); + MOCK_METHOD(void, SetStepDuration, (std::chrono::nanoseconds), (override)); }; class MockParticipant : public DummyParticipant @@ -1156,6 +1156,9 @@ TEST_F(Test_LifecycleService, error_on_create_time_sync_service_twice) LifecycleService lifecycleService(&participant); lifecycleService.SetLifecycleConfiguration(StartCoordinated()); + MockTimeSync mockTimeSync(&participant, &participant.mockTimeProvider, healthCheckConfig, &lifecycleService); + lifecycleService.SetTimeSyncService(&mockTimeSync); + EXPECT_NO_THROW({ try { diff --git a/SilKit/source/services/orchestration/TimeConfiguration.cpp b/SilKit/source/services/orchestration/TimeConfiguration.cpp index 98acf56c3..71775d261 100644 --- a/SilKit/source/services/orchestration/TimeConfiguration.cpp +++ b/SilKit/source/services/orchestration/TimeConfiguration.cpp @@ -20,6 +20,16 @@ TimeConfiguration::TimeConfiguration(Logging::ILoggerInternal* logger) _myNextTask.duration = 1ms; } +void TimeConfiguration::SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode) +{ + _timeAdvanceMode = timeAdvanceMode; +} + +auto TimeConfiguration::GetTimeAdvanceMode() const -> TimeAdvanceMode +{ + return _timeAdvanceMode; +} + void TimeConfiguration::SetBlockingMode(bool blocking) { _blocking = blocking; @@ -82,36 +92,61 @@ void TimeConfiguration::OnReceiveNextSimStep(const std::string& participantName, participantName, nextStep.timePoint.count(), itOtherNextTask->second.timePoint.count()); } - _otherNextTasks.at(participantName) = std::move(nextStep); + _otherNextTasks.at(participantName) = nextStep; Logging::Debug(_logger, "Updated _otherNextTasks for participant {} with time {}", participantName, nextStep.timePoint.count()); } -void TimeConfiguration::SynchronizedParticipantRemoved(const std::string& otherParticipantName) + +void TimeConfiguration::SetStepDuration(std::chrono::nanoseconds duration) { Lock lock{_mx}; - if (_otherNextTasks.find(otherParticipantName) != _otherNextTasks.end()) + _myNextTask.duration = duration; +} + +auto TimeConfiguration::GetMinimalOtherDuration() const -> std::chrono::nanoseconds +{ + Lock lock{_mx}; + if (_otherNextTasks.empty()) { - const std::string errorMessage{"Participant " + otherParticipantName + " unknown."}; - throw SilKitError{errorMessage}; + return std::chrono::nanoseconds{0}; } - auto it = _otherNextTasks.find(otherParticipantName); - if (it != _otherNextTasks.end()) + + auto minDuration = std::chrono::nanoseconds::max(); + for (const auto& entry : _otherNextTasks) { - _otherNextTasks.erase(it); + if (entry.second.duration < minDuration) + { + minDuration = entry.second.duration; + } } -} -void TimeConfiguration::SetStepDuration(std::chrono::nanoseconds duration) -{ - Lock lock{_mx}; - _myNextTask.duration = duration; + return minDuration; } + void TimeConfiguration::AdvanceTimeStep() { - Lock lock{_mx}; - _currentTask = _myNextTask; - _myNextTask.timePoint = _currentTask.timePoint + _currentTask.duration; + { + Lock lock{_mx}; + _currentTask = _myNextTask; + } + + if (_timeAdvanceMode == TimeAdvanceMode::ByMinimalDuration) + { + auto minOtherDuration = GetMinimalOtherDuration(); + if (minOtherDuration < _currentTask.duration) + { + Logging::Info(_logger, "Adjusting my step duration from {}ms to minimal other duration {}ms", + _currentTask.duration.count() / 1000000, minOtherDuration.count() / 1000000); + Lock lock{_mx}; + _currentTask.duration = minOtherDuration; + } + } + + { + Lock lock{_mx}; + _myNextTask.timePoint = _currentTask.timePoint + _currentTask.duration; + } } auto TimeConfiguration::CurrentSimStep() const -> NextSimTask diff --git a/SilKit/source/services/orchestration/TimeConfiguration.hpp b/SilKit/source/services/orchestration/TimeConfiguration.hpp index 59157c963..d0041546c 100755 --- a/SilKit/source/services/orchestration/TimeConfiguration.hpp +++ b/SilKit/source/services/orchestration/TimeConfiguration.hpp @@ -26,8 +26,6 @@ class TimeConfiguration bool RemoveSynchronizedParticipant(const std::string& otherParticipantName); auto GetSynchronizedParticipantNames() -> std::vector; void OnReceiveNextSimStep(const std::string& participantName, NextSimTask nextStep); - void SynchronizedParticipantRemoved(const std::string& otherParticipantName); - void SetStepDuration(std::chrono::nanoseconds duration); void AdvanceTimeStep(); auto CurrentSimStep() const -> NextSimTask; auto NextSimStep() const -> NextSimTask; @@ -41,6 +39,12 @@ class TimeConfiguration bool IsHopOn(); bool HoppedOn(); + void SetStepDuration(std::chrono::nanoseconds duration); + auto GetMinimalOtherDuration() const -> std::chrono::nanoseconds; + + auto GetTimeAdvanceMode() const -> TimeAdvanceMode; + void SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode); + private: //Members mutable std::mutex _mx; using Lock = std::unique_lock; @@ -51,6 +55,9 @@ class TimeConfiguration bool _hoppedOn = false; Logging::ILoggerInternal* _logger; + + TimeAdvanceMode _timeAdvanceMode{TimeAdvanceMode::ByOwnDuration}; + }; } // namespace Orchestration diff --git a/SilKit/source/services/orchestration/TimeSyncService.cpp b/SilKit/source/services/orchestration/TimeSyncService.cpp index d21b9888e..4f93c99c3 100644 --- a/SilKit/source/services/orchestration/TimeSyncService.cpp +++ b/SilKit/source/services/orchestration/TimeSyncService.cpp @@ -192,6 +192,7 @@ struct SynchronizedPolicy : public ITimeSyncPolicy return false; } + // No other participant has a lower time point if (_configuration->OtherParticipantHasLowerTimepoint()) { return false; @@ -453,9 +454,9 @@ void TimeSyncService::SetSimulationStepHandlerAsync(SimulationStepHandler task, _timeConfiguration.SetStepDuration(initialStepSize); } -void TimeSyncService::SetPeriod(std::chrono::nanoseconds period) +void TimeSyncService::SetStepDuration(std::chrono::nanoseconds stepDuration) { - _timeConfiguration.SetStepDuration(period); + _timeConfiguration.SetStepDuration(stepDuration); } bool TimeSyncService::SetupTimeSyncPolicy(bool isSynchronizingVirtualTime) @@ -830,6 +831,12 @@ bool TimeSyncService::IsBlocking() const return _timeConfiguration.IsBlocking(); } +void TimeSyncService::SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode) +{ + _timeConfiguration.SetTimeAdvanceMode(timeAdvanceMode); +} + + } // namespace Orchestration } // namespace Services } // namespace SilKit diff --git a/SilKit/source/services/orchestration/TimeSyncService.hpp b/SilKit/source/services/orchestration/TimeSyncService.hpp index 9cf516c08..1fa6fd304 100644 --- a/SilKit/source/services/orchestration/TimeSyncService.hpp +++ b/SilKit/source/services/orchestration/TimeSyncService.hpp @@ -56,7 +56,9 @@ class TimeSyncService void SetSimulationStepHandler(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) override; void SetSimulationStepHandlerAsync(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) override; void CompleteSimulationStep() override; - void SetPeriod(std::chrono::nanoseconds period); + void SetStepDuration(std::chrono::nanoseconds stepDuration); + + void ReceiveMsg(const IServiceEndpoint* from, const NextSimTask& task) override; auto Now() const -> std::chrono::nanoseconds override; @@ -99,6 +101,8 @@ class TimeSyncService void RemoveOtherSimulationStepsCompletedHandler(HandlerId handlerId); void InvokeOtherSimulationStepsCompletedHandlers(); + void SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode); + private: // ---------------------------------------- // private methods @@ -154,6 +158,7 @@ class TimeSyncService std::atomic _wallClockReachedBeforeCompletion{false}; Util::SynchronizedHandlers> _otherSimulationStepsCompletedHandlers; + }; // ================================================================================ diff --git a/docs/api/capi/capi-orchestration.rst b/docs/api/capi/capi-orchestration.rst index 20c1cf8c7..18b8f7d84 100644 --- a/docs/api/capi/capi-orchestration.rst +++ b/docs/api/capi/capi-orchestration.rst @@ -18,6 +18,7 @@ Orchestration C API .. doxygenfunction:: SilKit_LifecycleService_Continue .. doxygenfunction:: SilKit_TimeSyncService_Create +.. doxygenfunction:: SilKit_TimeSyncService_Create_With_TimeAdvanceMode .. doxygenfunction:: SilKit_TimeSyncService_SetSimulationStepHandler .. doxygenfunction:: SilKit_TimeSyncService_SetSimulationStepHandlerAsync .. doxygenfunction:: SilKit_TimeSyncService_CompleteSimulationStep \ No newline at end of file From 95de11b10679e1197aac56a446aab09ecf73b619 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Thu, 8 Jan 2026 15:09:56 +0100 Subject: [PATCH 2/5] Change TimeAdvanceMode::ByMinimalDuration to trigger SimTasks with timestamps of all other sync. participants; Add Integration Tests; Remove automatic creation of TimeSynService in SimTestHarness Signed-off-by: Konrad Breitsprecher --- Demos/api/Orchestration/DynSimStep.cpp | 84 +++- Demos/api/Orchestration/SimStep.cpp | 3 +- SilKit/IntegrationTests/CMakeLists.txt | 5 + .../Hourglass/Test_HourglassOrchestration.cpp | 1 - .../IntegrationTests/ITest_AsyncSimTask.cpp | 5 +- .../IntegrationTests/ITest_DynStepSizes.cpp | 359 ++++++++++++++++++ .../SimTestHarness/SimTestHarness.cpp | 5 - .../orchestration/LifecycleService.hpp | 2 +- .../orchestration/ITimeSyncService.hpp | 1 - .../orchestration/TimeConfiguration.cpp | 64 ++-- .../orchestration/TimeConfiguration.hpp | 3 +- .../orchestration/TimeSyncService.hpp | 3 +- 12 files changed, 484 insertions(+), 51 deletions(-) create mode 100644 SilKit/IntegrationTests/ITest_DynStepSizes.cpp diff --git a/Demos/api/Orchestration/DynSimStep.cpp b/Demos/api/Orchestration/DynSimStep.cpp index 9e34c9d70..47a406f40 100644 --- a/Demos/api/Orchestration/DynSimStep.cpp +++ b/Demos/api/Orchestration/DynSimStep.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "silkit/SilKit.hpp" @@ -17,13 +18,74 @@ std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) int main(int argc, char** argv) { - if (argc != 2) + if (argc != 5 && argc != 6) { - std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << " " << std::endl; + std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << + " " + " " + "[-A (Autonomous) | -C (Coordinated)] " + "[-M (ByMinimalDuration) | -D (ByOwnDuration)] " + "[-R (Optional; Randomize StepSize (1ms to 10ms) with 10% probability every step)]" << std::endl; return -1; } + // Arg 1: Participant Name std::string participantName(argv[1]); + // Arg 2: Step Size + auto stepSize = std::chrono::milliseconds(std::stoi(argv[2])); + std::cout << "Starting with stepSize=" << stepSize << std::endl; + + // Arg 3: Operation Mode + auto operationMode = SilKit::Services::Orchestration::OperationMode::Coordinated; + if (std::string(argv[3]) == "-A") + { + std::cout << "Using OperationMode::Autonomous" << std::endl; + operationMode = SilKit::Services::Orchestration::OperationMode::Autonomous; + } + else if (std::string(argv[3]) == "-C") + { + std::cout << "Using OperationMode::Coordinated" << std::endl; + } + else + { + std::cerr << "Unknown third argument '" << argv[3] << "'. Did you mean '-A' for autonomous mode or '-C' for coordinated mode?" << std::endl; + return -1; + } + + // Arg 4: Time Advance Mode + auto timeAdvanceMode = SilKit::Services::Orchestration::TimeAdvanceMode::ByMinimalDuration; + if (std::string(argv[4]) == "-M") + { + std::cout << "Using TimeAdvanceMode::ByMinimalDuration" << std::endl; + } + else if (std::string(argv[4]) == "-D") + { + timeAdvanceMode = SilKit::Services::Orchestration::TimeAdvanceMode::ByOwnDuration; + std::cout << "Using TimeAdvanceMode::ByOwnDuration" << std::endl; + } + else + { + std::cerr << "Unknown argument '" << argv[4] + << "'. Did you mean '-M' for TimeAdvanceMode::ByMinimalDuration or '-D' for TimeAdvanceMode::ByOwnDuration?" << std::endl; + return -1; + } + + // Arg 5: Optional Randomize Step Size + bool randomizeStepSize = false; + if (argc == 6) + { + if (std::string(argv[5]) == "-R") + { + randomizeStepSize = true; + std::cout << "Randomizing step size every 10 steps." << std::endl << std::endl; + } + else + { + std::cerr << "Unknown argument '" << argv[5] << "'. Did you mean '-R' to randomize the step size every 10 steps?" << std::endl; + return -1; + } + } + try { // Setup participant, lifecycle, time synchronization and logging. @@ -34,13 +96,10 @@ int main(int argc, char** argv) auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); auto logger = participant->GetLogger(); - auto* lifecycleService = - participant->CreateLifecycleService({SilKit::Services::Orchestration::OperationMode::Coordinated}); + auto* lifecycleService = participant->CreateLifecycleService({operationMode}); - auto* timeSyncService = lifecycleService->CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode::ByMinimalDuration); + auto* timeSyncService = lifecycleService->CreateTimeSyncService(timeAdvanceMode); - const auto stepSize = 10ms; - static int stepCounter = 0; std::random_device rd; std::mt19937 rng(rd()); auto bounded_rand = [&rng](unsigned range) { @@ -48,9 +107,9 @@ int main(int argc, char** argv) return dist(rng); }; - timeSyncService->SetSimulationStepHandler( - [logger, timeSyncService, participantName, bounded_rand](std::chrono::nanoseconds now, + [randomizeStepSize, logger, timeSyncService, participantName, bounded_rand]( + std::chrono::nanoseconds now, std::chrono::nanoseconds duration) { // The invocation of this handler marks the beginning of a simulation step. { @@ -59,7 +118,7 @@ int main(int argc, char** argv) logger->Info(ss.str()); } - if (bounded_rand(10) == 1)// && participantName == "P1") + if (randomizeStepSize && bounded_rand(10) == 1) { auto rndStepDuration = bounded_rand(10); timeSyncService->SetStepDuration(std::chrono::milliseconds(rndStepDuration)); @@ -69,12 +128,7 @@ int main(int argc, char** argv) } std::this_thread::sleep_for(500ms); - // All messages sent here are guaranteed to arrive at other participants before their next simulation step is called. - // So here, we can rely on having received all messages from the past (< now). - // Note that this guarantee only holds for messages sent within a simulation step, - // not for messages send outside of this handler (e.g. directly in a reception handler). - // Returning from the handler marks the end of a simulation step. }, stepSize); auto finalStateFuture = lifecycleService->StartLifecycle(); diff --git a/Demos/api/Orchestration/SimStep.cpp b/Demos/api/Orchestration/SimStep.cpp index f3e7e741d..9d09df535 100644 --- a/Demos/api/Orchestration/SimStep.cpp +++ b/Demos/api/Orchestration/SimStep.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT #include +#include #include "silkit/SilKit.hpp" @@ -38,7 +39,7 @@ int main(int argc, char** argv) auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - const auto stepSize = 2ms; + const auto stepSize = 5ms; timeSyncService->SetSimulationStepHandler( [logger](std::chrono::nanoseconds now, std::chrono::nanoseconds duration) { diff --git a/SilKit/IntegrationTests/CMakeLists.txt b/SilKit/IntegrationTests/CMakeLists.txt index 0eb1b3a59..062939788 100644 --- a/SilKit/IntegrationTests/CMakeLists.txt +++ b/SilKit/IntegrationTests/CMakeLists.txt @@ -118,6 +118,11 @@ add_silkit_test_to_executable(SilKitIntegrationTests SOURCES ITest_SimTask.cpp ) +add_silkit_test_to_executable(SilKitIntegrationTests + SOURCES ITest_DynStepSizes.cpp +) + + add_silkit_test_to_executable(SilKitFunctionalTests SOURCES FTest_WallClockCoupling.cpp ) diff --git a/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp b/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp index e5caed799..0e007b39a 100644 --- a/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp +++ b/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp @@ -447,7 +447,6 @@ TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_SetStepDuration) timeSyncService.SetStepDuration(stepDuration); } - TEST_F(Test_HourglassOrchestration, SilKit_Experimental_TimeSyncService_AddOtherSimulationStepsCompletedHandler) { using testing::_; diff --git a/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp b/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp index e97fb04e0..68edc222f 100644 --- a/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp +++ b/SilKit/IntegrationTests/ITest_AsyncSimTask.cpp @@ -302,11 +302,12 @@ auto MakeCompletionThread(SimParticipant* p, ParticipantData* d) -> std::thread TEST(ITest_AsyncSimTask, test_async_simtask_other_simulation_steps_completed_handler) { - SimTestHarness testHarness({"A", "B", "C"}, "silkit://localhost:0"); + SimTestHarness testHarness({"A", "B", "C", "D"}, "silkit://localhost:0"); const auto a = testHarness.GetParticipant("A"); const auto b = testHarness.GetParticipant("B"); const auto c = testHarness.GetParticipant("C"); + const auto d = testHarness.GetParticipant("D"); ParticipantData ad, bd, cd; @@ -316,6 +317,8 @@ TEST(ITest_AsyncSimTask, test_async_simtask_other_simulation_steps_completed_han b->GetOrCreateLifecycleService()->SetStopHandler([&bd] { bd.running = false; }); c->GetOrCreateLifecycleService()->SetStopHandler([&cd] { cd.running = false; }); + d->GetOrCreateTimeSyncService()->SetSimulationStepHandler([](auto, auto) {}, 1ms); + const auto aLifecycleService = a->GetOrCreateLifecycleService(); a->GetOrCreateTimeSyncService()->SetSimulationStepHandlerAsync([aLifecycleService, &ad](auto now, auto) { diff --git a/SilKit/IntegrationTests/ITest_DynStepSizes.cpp b/SilKit/IntegrationTests/ITest_DynStepSizes.cpp new file mode 100644 index 000000000..f83e187c2 --- /dev/null +++ b/SilKit/IntegrationTests/ITest_DynStepSizes.cpp @@ -0,0 +1,359 @@ +// SPDX-FileCopyrightText: 2023 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include "ITestFixture.hpp" + +using namespace std::chrono_literals; + +namespace testing { +namespace internal { +template +class UniversalPrinter> +{ +public: + static void Print(const std::chrono::duration& value, ::std::ostream* os) + { + *os << std::chrono::duration_cast(value).count() << "ns"; + } +}; + +template +class UniversalPrinter, std::chrono::duration>> +{ +public: + static void Print(const std::pair, std::chrono::duration>& p, + ::std::ostream* os) + { + *os << "("; + UniversalPrinter::Print(p.first, os); + *os << ", "; + UniversalPrinter::Print(p.second, os); + *os << ")"; + } +}; + +} // namespace internal +} // namespace testing + +inline std::string ToString(const std::chrono::nanoseconds& ns) +{ + std::ostringstream os; + testing::internal::UniversalPrinter::Print(ns, &os); + return os.str(); +} +inline std::string ToString(const std::pair& p) +{ + std::ostringstream os; + testing::internal::UniversalPrinter>::Print(p, &os); + return os.str(); +} + +namespace { + +using namespace SilKit::Tests; +using namespace SilKit::Config; +using namespace SilKit::Services; +using namespace SilKit::Services::Orchestration; + +template +struct Dummy +{ + std::chrono::duration value; + + explicit Dummy(const std::chrono::duration& v) + : value{v} + { + } + + bool operator==(const Dummy& other) const + { + return value == other.value; + } + + friend void PrintTo(const Dummy& d, std::ostream* os) + { + *os << std::chrono::duration_cast(d.value).count() << "ns"; + } +}; + +#define SILKIT_ASSERT_CHRONO_EQ(expected, actual) ASSERT_EQ(Dummy{(expected)}, Dummy{(actual)}) +#define SILKIT_EXPECT_CHRONO_EQ(expected, actual) EXPECT_EQ(Dummy{(expected)}, Dummy{(actual)}) + + +struct ParticipantParams +{ + std::string name{}; + std::chrono::nanoseconds initialStepSize{1ms}; + TimeAdvanceMode timeAdvanceMode{TimeAdvanceMode::ByOwnDuration}; + + // Change step size at these time points + std::map changeStepSizeAtTimePoints{}; + + // Result: recorded time points and durations + std::vector> timePointsAndDurations{}; +}; + +struct ITest_DynStepSizes : ITest_SimTestHarness +{ + using ITest_SimTestHarness::ITest_SimTestHarness; + void RunTestSetup(std::vector& participantsParams); + void AssertAllStepsEqual(const std::vector& participantsParams); + void AssertAscendingStepsWithReferenceDuration(const std::vector& participantsParams, + std::chrono::nanoseconds refDuration); + void AssertStepsEqual(const std::vector& s1, + const std::vector& s2); +}; + +void ITest_DynStepSizes::RunTestSetup(std::vector& participantsParams) +{ + std::vector participantNames; + for (const auto& participantParams : participantsParams) + { + participantNames.push_back(participantParams.name); + } + SetupFromParticipantList(participantNames); + + std::mutex mx; + + for (auto& participantParams : participantsParams) + { + auto&& simParticipant = _simTestHarness->GetParticipant(participantParams.name); + auto&& lifecycleService = simParticipant->GetOrCreateLifecycleService(); + auto* timeSyncService = lifecycleService->CreateTimeSyncService(participantParams.timeAdvanceMode); + timeSyncService->SetSimulationStepHandler([timeSyncService, &participantParams, &mx, lifecycleService](auto now, auto duration) { + if (now >= 100ms) + { + lifecycleService->Stop("stopping the test at 100ms"); + } + else + { + std::lock_guard lock(mx); + participantParams.timePointsAndDurations.emplace_back(now, duration); + + // Check if we need to change the step size at this time point + auto it = participantParams.changeStepSizeAtTimePoints.find(now); + if (it != participantParams.changeStepSizeAtTimePoints.end()) + { + auto newStepSize = it->second; + timeSyncService->SetStepDuration(newStepSize); + } + } + }, participantParams.initialStepSize); + } + + auto ok = _simTestHarness->Run(5s); + ASSERT_TRUE(ok) << "SimTestHarness should terminate without timeout"; +} + +void ITest_DynStepSizes::AssertAllStepsEqual(const std::vector& participantsParams) +{ + for (size_t i = 1; i < participantsParams.size(); ++i) + { + const auto& ref = participantsParams[0].timePointsAndDurations; + const auto& cmp = participantsParams[i].timePointsAndDurations; + + ASSERT_EQ(ref.size(), cmp.size()) + << "Different number of steps for " << participantsParams[0].name << " and " << participantsParams[i].name; + + for (size_t j = 0; j < ref.size(); ++j) + { + EXPECT_EQ(ref[j], cmp[j]) << "Differenz at index " << j << ": " << participantsParams[0].name + << "(now=" << ToString(ref[j].first) << ", duration=" << ToString(ref[j].second) << ")" + << " vs " << participantsParams[i].name << "(now=" << ToString(cmp[j].first) + << ", duration=" << ToString(cmp[j].second) << ")"; + } + } +} + +void ITest_DynStepSizes::AssertAscendingStepsWithReferenceDuration( + const std::vector& participantsParams, std::chrono::nanoseconds refDuration) +{ + for (const auto& participant : participantsParams) + { + const auto& steps = participant.timePointsAndDurations; + ASSERT_FALSE(steps.empty()) << "No simulation steps for participant " << participant.name; + + for (size_t i = 0; i < steps.size(); ++i) + { + // Check if duration matches the reference duration + EXPECT_EQ(steps[i].second, refDuration) << "Duration mismatch for " << participant.name << " at index " << i + << ": expected " << ToString(refDuration) << ", got " << ToString(steps[i].second); + + // Check if time points are strictly increasing by refDuration + if (i > 0) + { + auto diff = steps[i].first - steps[i - 1].first; + EXPECT_EQ(diff, refDuration) + << "Timestep difference for " << participant.name << " at index " << i << " is " << ToString(diff) + << ", expected " << ToString(refDuration) << " (" << ToString(steps[i - 1].first) << " -> " + << ToString(steps[i].first) << ")"; + } + } + } +} + +void ITest_DynStepSizes::AssertStepsEqual(const std::vector& s1, + const std::vector& s2) +{ + ASSERT_EQ(s1.size(), s2.size()) << "Different number of steps"; + + for (size_t j = 0; j < s1.size(); ++j) + { + SILKIT_EXPECT_CHRONO_EQ(s1[j], s2[j]) << "Differenz at index " << j << ": " + << "s1 now=" << ToString(s1[j]) << " vs s2 now=" << ToString(s2[j]); + } +} + +// Zero duration is invalid and should throw +TEST_F(ITest_DynStepSizes, invalid_duration) +{ + auto invalidDuration = 0ns; + std::vector participantsParams = {{"P1", invalidDuration, TimeAdvanceMode::ByMinimalDuration}}; + EXPECT_THROW(RunTestSetup(participantsParams), SilKit::SilKitError); +} + +// Single participant with both time advance modes +TEST_F(ITest_DynStepSizes, one_participant_ByMinimalDuration) +{ + auto refDuration = 5ms; + std::vector participantsParams = {{"P1", refDuration, TimeAdvanceMode::ByMinimalDuration}}; + RunTestSetup(participantsParams); + AssertAscendingStepsWithReferenceDuration(participantsParams, refDuration); +} +TEST_F(ITest_DynStepSizes, one_participant_ByOwnDuration) +{ + auto refDuration = 5ms; + std::vector participantsParams = {{"P1", refDuration, TimeAdvanceMode::ByOwnDuration}}; + RunTestSetup(participantsParams); + AssertAscendingStepsWithReferenceDuration(participantsParams, refDuration); +} + +// Two/Three participants with ByMinimalDuration mode; Expect steps aligned to the minimal duration +TEST_F(ITest_DynStepSizes, two_participants_ByMinimalDuration) +{ + std::vector participantsParams = {{"P1", 1ms, TimeAdvanceMode::ByMinimalDuration}, + {"P2", 5ms, TimeAdvanceMode::ByMinimalDuration}}; + RunTestSetup(participantsParams); + AssertAscendingStepsWithReferenceDuration(participantsParams, 1ms); +} +TEST_F(ITest_DynStepSizes, three_participants_ByMinimalDuration) +{ + std::vector participantsParams = {{"P1", 1ms, TimeAdvanceMode::ByMinimalDuration}, + {"P2", 2ms, TimeAdvanceMode::ByMinimalDuration}, + {"P3", 3ms, TimeAdvanceMode::ByMinimalDuration}}; + RunTestSetup(participantsParams); + AssertAscendingStepsWithReferenceDuration(participantsParams, 1ms); +} + +// Two participants with mixed modes; Expect steps aligned to the minimal/own duration +TEST_F(ITest_DynStepSizes, two_participants_MixedTimeAdvanceModes) +{ + std::vector participantsParams = {{"P1", 5ms, TimeAdvanceMode::ByMinimalDuration}, + {"P2", 1ms, TimeAdvanceMode::ByOwnDuration}}; + RunTestSetup(participantsParams); + AssertAscendingStepsWithReferenceDuration(participantsParams, 1ms); +} + +// Three participants with mixed modes; Expect steps of P3(ByMinimalDuration) are equal to the union of P1,P2(ByOwnDuration) +TEST_F(ITest_DynStepSizes, three_participants_MixedTimeAdvanceModes) +{ + std::vector participantsParams = {{"P1", 2ms, TimeAdvanceMode::ByOwnDuration}, + {"P2", 3ms, TimeAdvanceMode::ByOwnDuration}, + {"P3", 4ms, TimeAdvanceMode::ByMinimalDuration}}; + RunTestSetup(participantsParams); + + AssertAscendingStepsWithReferenceDuration({participantsParams[0]}, 2ms); + AssertAscendingStepsWithReferenceDuration({participantsParams[1]}, 3ms); + + // Collect nows for P1 and P2 + std::set unionNows; + for (size_t i = 0; i < 2; ++i) + { + for (const auto& step : participantsParams[i].timePointsAndDurations) + { + unionNows.insert(step.first); + } + } + // Convert unionNows to sorted vector + std::vector unionNowsVec(unionNows.begin(), unionNows.end()); + // Collect nows for P3 + std::vector p3Nows; + for (const auto& step : participantsParams[2].timePointsAndDurations) + { + p3Nows.push_back(step.first); + } + // Compare P3 nows with union of P1 and P2 nows + AssertStepsEqual(p3Nows, unionNowsVec); + +} + +// Change to a different step size during simulation +TEST_F(ITest_DynStepSizes, one_participant_change_step_size) +{ + std::vector participantsParams = { + {"P1", 1ms, TimeAdvanceMode::ByOwnDuration, {{9ms, 10ms}, {80ms, 2ms}}}}; + RunTestSetup(participantsParams); + + ParticipantParams refData; + refData.name = "Reference"; + refData.timePointsAndDurations = { + {0ms, 1ms}, {1ms, 1ms}, {2ms, 1ms}, {3ms, 1ms}, {4ms, 1ms}, {5ms, 1ms}, {6ms, 1ms}, {7ms, 1ms}, + {8ms, 1ms}, {9ms, 1ms}, {10ms, 10ms}, {20ms, 10ms}, {30ms, 10ms}, {40ms, 10ms}, {50ms, 10ms}, {60ms, 10ms}, + {70ms, 10ms}, {80ms, 10ms}, {90ms, 2ms}, {92ms, 2ms}, {94ms, 2ms}, {96ms, 2ms}, {98ms, 2ms}}; + + AssertAllStepsEqual({participantsParams[0], refData}); +} + +// Change to different step sizes during simulation; mixed time advance modes +TEST_F(ITest_DynStepSizes, two_participants_mixed_change_step_size) +{ + std::vector participantsParams = { + {"P1", 1ms, TimeAdvanceMode::ByOwnDuration, {{9ms, 10ms}, {80ms, 2ms}}}, + {"P2", 20ms, TimeAdvanceMode::ByMinimalDuration} + }; + + RunTestSetup(participantsParams); + + ParticipantParams refData; + refData.name = "Reference"; + refData.timePointsAndDurations = { + {0ms, 1ms}, {1ms, 1ms}, {2ms, 1ms}, {3ms, 1ms}, {4ms, 1ms}, {5ms, 1ms}, {6ms, 1ms}, {7ms, 1ms}, + {8ms, 1ms}, {9ms, 1ms}, {10ms, 10ms}, {20ms, 10ms}, {30ms, 10ms}, {40ms, 10ms}, {50ms, 10ms}, {60ms, 10ms}, + {70ms, 10ms}, {80ms, 10ms}, {90ms, 2ms}, {92ms, 2ms}, {94ms, 2ms}, {96ms, 2ms}, {98ms, 2ms}}; + + + AssertAllStepsEqual({participantsParams[0], refData}); + // P2 (ByMinimalDuration) follows P1 (ByOwnDuration) + AssertAllStepsEqual({participantsParams[0], participantsParams[1]}); +} + +// Change to different step sizes during simulation; both participants with ByMinimalDuration +TEST_F(ITest_DynStepSizes, two_participants_ByMinimalDuration_change_step_size) +{ + std::vector participantsParams = { + {"P1", 1ms, TimeAdvanceMode::ByMinimalDuration, {{9ms, 10ms}, {80ms, 2ms}}}, + {"P2", 20ms, TimeAdvanceMode::ByMinimalDuration}}; + + RunTestSetup(participantsParams); + + ParticipantParams refData; + refData.name = "Reference"; + refData.timePointsAndDurations = { + {0ms, 1ms}, {1ms, 1ms}, {2ms, 1ms}, {3ms, 1ms}, {4ms, 1ms}, {5ms, 1ms}, {6ms, 1ms}, {7ms, 1ms}, + {8ms, 1ms}, {9ms, 1ms}, {10ms, 10ms}, {20ms, 10ms}, {30ms, 10ms}, {40ms, 10ms}, {50ms, 10ms}, {60ms, 10ms}, + {70ms, 10ms}, {80ms, 10ms}, {90ms, 2ms}, {92ms, 2ms}, {94ms, 2ms}, {96ms, 2ms}, {98ms, 2ms}}; + + + AssertAllStepsEqual({participantsParams[0], refData}); + AssertAllStepsEqual({participantsParams[0], participantsParams[1]}); +} + + +} //end namespace diff --git a/SilKit/IntegrationTests/SimTestHarness/SimTestHarness.cpp b/SilKit/IntegrationTests/SimTestHarness/SimTestHarness.cpp index 134a73534..1a04fc5d8 100644 --- a/SilKit/IntegrationTests/SimTestHarness/SimTestHarness.cpp +++ b/SilKit/IntegrationTests/SimTestHarness/SimTestHarness.cpp @@ -304,11 +304,6 @@ void SimTestHarness::AddParticipant(const std::string& participantName, const st // mandatory sim task for time synced simulation // by default, we do no operation during simulation task, the user should override this auto* lifecycleService = participant->GetOrCreateLifecycleService(startConfiguration); - if (startConfiguration.operationMode == SilKit::Services::Orchestration::OperationMode::Coordinated) - { - auto* timeSyncService = participant->GetOrCreateTimeSyncService(); - timeSyncService->SetSimulationStepHandler([](auto, auto) {}, 1ms); - } lifecycleService->SetCommunicationReadyHandler([]() {}); diff --git a/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp b/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp index 0ac8da498..4f03c6f33 100644 --- a/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp +++ b/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp @@ -324,7 +324,7 @@ auto LifecycleService::CreateTimeSyncService() -> SilKit::Services::Orchestratio return _timeSyncService.get(); } -// TODO bkd: Needed or can I use a default in the function above? +// TODO bkd: Needed or can I use a default in the function above? auto LifecycleService::CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode) -> SilKit::Services::Orchestration::ITimeSyncService* { diff --git a/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp b/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp index 8fdc8287e..5c6e91a7c 100644 --- a/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp +++ b/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp @@ -53,7 +53,6 @@ class ITimeSyncService virtual void SetStepDuration(std::chrono::nanoseconds stepDuration) = 0; - }; } // namespace Orchestration diff --git a/SilKit/source/services/orchestration/TimeConfiguration.cpp b/SilKit/source/services/orchestration/TimeConfiguration.cpp index 71775d261..8e7a53195 100644 --- a/SilKit/source/services/orchestration/TimeConfiguration.cpp +++ b/SilKit/source/services/orchestration/TimeConfiguration.cpp @@ -101,52 +101,72 @@ void TimeConfiguration::OnReceiveNextSimStep(const std::string& participantName, void TimeConfiguration::SetStepDuration(std::chrono::nanoseconds duration) { Lock lock{_mx}; + + if (duration == 0ns) + { + throw SilKitError("Attempted to set step duration to zero."); + } + _myNextTask.duration = duration; } -auto TimeConfiguration::GetMinimalOtherDuration() const -> std::chrono::nanoseconds +auto TimeConfiguration::GetMinimalAlignedDuration() const -> std::chrono::nanoseconds { - Lock lock{_mx}; if (_otherNextTasks.empty()) { - return std::chrono::nanoseconds{0}; + return std::chrono::nanoseconds::max(); } - auto minDuration = std::chrono::nanoseconds::max(); + auto earliestOtherTimepoint = std::chrono::nanoseconds::max(); for (const auto& entry : _otherNextTasks) { - if (entry.second.duration < minDuration) + // Both start and end of other participant's step could be the earliest next timepoint + auto nextStepStart = entry.second.timePoint; + auto nextStepEnd = entry.second.timePoint + entry.second.duration; + if (nextStepStart > _currentTask.timePoint && nextStepStart < earliestOtherTimepoint) { - minDuration = entry.second.duration; + earliestOtherTimepoint = nextStepStart; + } + else if (nextStepEnd < earliestOtherTimepoint) + { + earliestOtherTimepoint = nextStepEnd; } } - return minDuration; -} + //Logging::Info(_logger, "Earliest next timepoint among other participants is {}ms", + // std::chrono::duration_cast(earliestOtherTimepoint).count()); + auto minAlignedDuration = earliestOtherTimepoint - _currentTask.timePoint; -void TimeConfiguration::AdvanceTimeStep() -{ + if (minAlignedDuration < 0ns) { - Lock lock{_mx}; - _currentTask = _myNextTask; + Logging::Error(_logger, + "Chonology error: Calculated minimal aligned duration is non-positive ({}ns). This indicates " + "that at least one participant has not advanced its time correctly.", + minAlignedDuration.count()); + return std::chrono::nanoseconds::max(); } + + return minAlignedDuration; +} + +void TimeConfiguration::AdvanceTimeStep() +{ + Lock lock{_mx}; + _currentTask = _myNextTask; if (_timeAdvanceMode == TimeAdvanceMode::ByMinimalDuration) { - auto minOtherDuration = GetMinimalOtherDuration(); - if (minOtherDuration < _currentTask.duration) + auto minAlignedDuration = GetMinimalAlignedDuration(); + if (minAlignedDuration < _currentTask.duration) { - Logging::Info(_logger, "Adjusting my step duration from {}ms to minimal other duration {}ms", - _currentTask.duration.count() / 1000000, minOtherDuration.count() / 1000000); - Lock lock{_mx}; - _currentTask.duration = minOtherDuration; + //Logging::Info(_logger, "Adjusting my step duration from {}ms to {}ms", + // std::chrono::duration_cast(_currentTask.duration).count(), + // std::chrono::duration_cast(minAlignedDuration).count()); + _currentTask.duration = minAlignedDuration; } } - { - Lock lock{_mx}; - _myNextTask.timePoint = _currentTask.timePoint + _currentTask.duration; - } + _myNextTask.timePoint = _currentTask.timePoint + _currentTask.duration; } auto TimeConfiguration::CurrentSimStep() const -> NextSimTask diff --git a/SilKit/source/services/orchestration/TimeConfiguration.hpp b/SilKit/source/services/orchestration/TimeConfiguration.hpp index d0041546c..2e3f642da 100755 --- a/SilKit/source/services/orchestration/TimeConfiguration.hpp +++ b/SilKit/source/services/orchestration/TimeConfiguration.hpp @@ -40,7 +40,7 @@ class TimeConfiguration bool HoppedOn(); void SetStepDuration(std::chrono::nanoseconds duration); - auto GetMinimalOtherDuration() const -> std::chrono::nanoseconds; + auto GetMinimalAlignedDuration() const -> std::chrono::nanoseconds; auto GetTimeAdvanceMode() const -> TimeAdvanceMode; void SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode); @@ -57,7 +57,6 @@ class TimeConfiguration Logging::ILoggerInternal* _logger; TimeAdvanceMode _timeAdvanceMode{TimeAdvanceMode::ByOwnDuration}; - }; } // namespace Orchestration diff --git a/SilKit/source/services/orchestration/TimeSyncService.hpp b/SilKit/source/services/orchestration/TimeSyncService.hpp index 1fa6fd304..adb2ea6dc 100644 --- a/SilKit/source/services/orchestration/TimeSyncService.hpp +++ b/SilKit/source/services/orchestration/TimeSyncService.hpp @@ -56,8 +56,7 @@ class TimeSyncService void SetSimulationStepHandler(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) override; void SetSimulationStepHandlerAsync(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) override; void CompleteSimulationStep() override; - void SetStepDuration(std::chrono::nanoseconds stepDuration); - + void SetStepDuration(std::chrono::nanoseconds stepDuration) override; void ReceiveMsg(const IServiceEndpoint* from, const NextSimTask& task) override; auto Now() const -> std::chrono::nanoseconds override; From c2426f1ea1eecf66a660f28d539b2431628ca506 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Sun, 8 Mar 2026 12:42:40 +0100 Subject: [PATCH 3/5] rework: reorganize and activate debug statements --- .../IntegrationTests/ITest_DynStepSizes.cpp | 35 +++++++++++-------- .../orchestration/TimeConfiguration.cpp | 27 +++++++------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/SilKit/IntegrationTests/ITest_DynStepSizes.cpp b/SilKit/IntegrationTests/ITest_DynStepSizes.cpp index f83e187c2..d79e11182 100644 --- a/SilKit/IntegrationTests/ITest_DynStepSizes.cpp +++ b/SilKit/IntegrationTests/ITest_DynStepSizes.cpp @@ -92,12 +92,13 @@ struct ParticipantParams std::string name{}; std::chrono::nanoseconds initialStepSize{1ms}; TimeAdvanceMode timeAdvanceMode{TimeAdvanceMode::ByOwnDuration}; - + // Change step size at these time points - std::map changeStepSizeAtTimePoints{}; - + std::map + changeStepSizeAtTimePoints{}; + // Result: recorded time points and durations - std::vector> timePointsAndDurations{}; + std::vector> timePointsAndDurations{}; }; struct ITest_DynStepSizes : ITest_SimTestHarness @@ -124,10 +125,16 @@ void ITest_DynStepSizes::RunTestSetup(std::vector& participan for (auto& participantParams : participantsParams) { - auto&& simParticipant = _simTestHarness->GetParticipant(participantParams.name); + std::string participantConfiguration; + participantConfiguration += R"({"Logging":{"Sinks":[{"Type":"File","Level":"Trace","LogName":")"; + participantConfiguration += "DynStepSizes_" + participantParams.name; + participantConfiguration += R"("}]}})"; + + auto&& simParticipant = _simTestHarness->GetParticipant(participantParams.name, participantConfiguration); auto&& lifecycleService = simParticipant->GetOrCreateLifecycleService(); auto* timeSyncService = lifecycleService->CreateTimeSyncService(participantParams.timeAdvanceMode); - timeSyncService->SetSimulationStepHandler([timeSyncService, &participantParams, &mx, lifecycleService](auto now, auto duration) { + timeSyncService->SetSimulationStepHandler( + [timeSyncService, &participantParams, &mx, lifecycleService](auto now, auto duration) { if (now >= 100ms) { lifecycleService->Stop("stopping the test at 100ms"); @@ -165,7 +172,8 @@ void ITest_DynStepSizes::AssertAllStepsEqual(const std::vector 0) @@ -207,7 +216,7 @@ void ITest_DynStepSizes::AssertStepsEqual(const std::vector std::chrono::nanose // Both start and end of other participant's step could be the earliest next timepoint auto nextStepStart = entry.second.timePoint; auto nextStepEnd = entry.second.timePoint + entry.second.duration; - if (nextStepStart > _currentTask.timePoint && nextStepStart < earliestOtherTimepoint) + + if (nextStepStart > _currentTask.timePoint) { - earliestOtherTimepoint = nextStepStart; + earliestOtherTimepoint = std::min(earliestOtherTimepoint, nextStepStart); } - else if (nextStepEnd < earliestOtherTimepoint) + + if (nextStepEnd > _currentTask.timePoint) { - earliestOtherTimepoint = nextStepEnd; + earliestOtherTimepoint = std::min(earliestOtherTimepoint, nextStepEnd); } } - //Logging::Info(_logger, "Earliest next timepoint among other participants is {}ms", - // std::chrono::duration_cast(earliestOtherTimepoint).count()); - auto minAlignedDuration = earliestOtherTimepoint - _currentTask.timePoint; + Logging::Debug(_logger, "Earliest next timepoint among other participants is {}ns", earliestOtherTimepoint.count()); + + const auto minAlignedDuration = earliestOtherTimepoint - _currentTask.timePoint; if (minAlignedDuration < 0ns) { @@ -153,15 +155,16 @@ void TimeConfiguration::AdvanceTimeStep() { Lock lock{_mx}; _currentTask = _myNextTask; - + if (_timeAdvanceMode == TimeAdvanceMode::ByMinimalDuration) { - auto minAlignedDuration = GetMinimalAlignedDuration(); + const auto minAlignedDuration = GetMinimalAlignedDuration(); + if (minAlignedDuration < _currentTask.duration) { - //Logging::Info(_logger, "Adjusting my step duration from {}ms to {}ms", - // std::chrono::duration_cast(_currentTask.duration).count(), - // std::chrono::duration_cast(minAlignedDuration).count()); + Logging::Debug(_logger, "Adjusting my step duration from {}ms to {}ms", + std::chrono::duration_cast(_currentTask.duration).count(), + std::chrono::duration_cast(minAlignedDuration).count()); _currentTask.duration = minAlignedDuration; } } From e62c48e8ea4854ecccbeb2a75db27070ea451e67 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Sun, 8 Mar 2026 16:34:46 +0100 Subject: [PATCH 4/5] config: make default TimeAdvanceMode configurable (hacky boolean) --- .../orchestration/LifecycleService.hpp | 3 +-- .../config/ParticipantConfiguration.cpp | 3 ++- .../config/ParticipantConfiguration.hpp | 1 + .../ParticipantConfigurationFromXImpl.cpp | 4 ++++ .../config/ParticipantConfiguration_Full.json | 3 ++- .../config/ParticipantConfiguration_Full.yaml | 1 + SilKit/source/config/YamlReader.cpp | 1 + SilKit/source/config/YamlValidator.cpp | 1 + SilKit/source/config/YamlWriter.cpp | 1 + .../core/internal/IParticipantInternal.hpp | 3 +++ .../core/mock/participant/MockParticipant.hpp | 5 +++++ .../source/core/participant/Participant.hpp | 5 +++++ .../orchestration/LifecycleService.cpp | 19 +++++++++---------- 13 files changed, 36 insertions(+), 14 deletions(-) diff --git a/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp b/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp index 4f03c6f33..6cab9d4a1 100644 --- a/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp +++ b/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp @@ -318,8 +318,7 @@ auto LifecycleService::Status() const -> const SilKit::Services::Orchestration:: auto LifecycleService::CreateTimeSyncService() -> SilKit::Services::Orchestration::ITimeSyncService* { - _timeSyncService = std::make_unique( - _lifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode::ByOwnDuration); + _timeSyncService = std::make_unique(_lifecycleService); return _timeSyncService.get(); } diff --git a/SilKit/source/config/ParticipantConfiguration.cpp b/SilKit/source/config/ParticipantConfiguration.cpp index 583b9c050..5dec0a99b 100755 --- a/SilKit/source/config/ParticipantConfiguration.cpp +++ b/SilKit/source/config/ParticipantConfiguration.cpp @@ -169,7 +169,8 @@ bool operator==(const ParticipantConfiguration& lhs, const ParticipantConfigurat bool operator==(const TimeSynchronization& lhs, const TimeSynchronization& rhs) { - return lhs.animationFactor == rhs.animationFactor && lhs.enableMessageAggregation == rhs.enableMessageAggregation; + return lhs.animationFactor == rhs.animationFactor && lhs.enableMessageAggregation == rhs.enableMessageAggregation + && lhs.dynamicSimulationStep == rhs.dynamicSimulationStep; } bool operator==(const Experimental& lhs, const Experimental& rhs) diff --git a/SilKit/source/config/ParticipantConfiguration.hpp b/SilKit/source/config/ParticipantConfiguration.hpp index e1322cb46..e0289bbb3 100644 --- a/SilKit/source/config/ParticipantConfiguration.hpp +++ b/SilKit/source/config/ParticipantConfiguration.hpp @@ -309,6 +309,7 @@ struct TimeSynchronization { double animationFactor{0.0}; Aggregation enableMessageAggregation{Aggregation::Off}; + bool dynamicSimulationStep{false}; }; // ================================================================================ diff --git a/SilKit/source/config/ParticipantConfigurationFromXImpl.cpp b/SilKit/source/config/ParticipantConfigurationFromXImpl.cpp index d2e3f7c20..aa93a60f0 100644 --- a/SilKit/source/config/ParticipantConfigurationFromXImpl.cpp +++ b/SilKit/source/config/ParticipantConfigurationFromXImpl.cpp @@ -62,6 +62,7 @@ struct TimeSynchronizationCache { std::optional animationFactor; std::optional enableMessageAggregation; + std::optional dynamicSimulationStep; }; struct MetricsCache @@ -346,6 +347,8 @@ void Cache(const TimeSynchronization& root, TimeSynchronizationCache& cache) cache.animationFactor); CacheNonDefault(defaultObject.enableMessageAggregation, root.enableMessageAggregation, "TimeSynchronization.EnableMessageAggregation", cache.enableMessageAggregation); + CacheNonDefault(defaultObject.dynamicSimulationStep, root.dynamicSimulationStep, + "TimeSynchronization.DynamicSimulationStep", cache.dynamicSimulationStep); } void Cache(const Metrics& root, MetricsCache& cache) @@ -518,6 +521,7 @@ void MergeTimeSynchronizationCache(const TimeSynchronizationCache& cache, TimeSy { MergeCacheField(cache.animationFactor, timeSynchronization.animationFactor); MergeCacheField(cache.enableMessageAggregation, timeSynchronization.enableMessageAggregation); + MergeCacheField(cache.dynamicSimulationStep, timeSynchronization.dynamicSimulationStep); } void MergeMetricsCache(const MetricsCache& cache, Metrics& metrics) diff --git a/SilKit/source/config/ParticipantConfiguration_Full.json b/SilKit/source/config/ParticipantConfiguration_Full.json index 6f04a8ed1..801741333 100644 --- a/SilKit/source/config/ParticipantConfiguration_Full.json +++ b/SilKit/source/config/ParticipantConfiguration_Full.json @@ -264,7 +264,8 @@ "Experimental": { "TimeSynchronization": { "AnimationFactor": 1.5, - "EnableMessageAggregation": "Off" + "EnableMessageAggregation": "Off", + "DynamicSimulationStep": true }, "Metrics": { "CollectFromRemote": false, diff --git a/SilKit/source/config/ParticipantConfiguration_Full.yaml b/SilKit/source/config/ParticipantConfiguration_Full.yaml index 4c08d28a3..4effd1acf 100644 --- a/SilKit/source/config/ParticipantConfiguration_Full.yaml +++ b/SilKit/source/config/ParticipantConfiguration_Full.yaml @@ -191,6 +191,7 @@ Experimental: TimeSynchronization: AnimationFactor: 1.5 EnableMessageAggregation: Off + DynamicSimulationStep: true Metrics: CollectFromRemote: false Sinks: diff --git a/SilKit/source/config/YamlReader.cpp b/SilKit/source/config/YamlReader.cpp index 6a796eb39..c52205d89 100644 --- a/SilKit/source/config/YamlReader.cpp +++ b/SilKit/source/config/YamlReader.cpp @@ -455,6 +455,7 @@ void YamlReader::Read(SilKit::Config::TimeSynchronization& obj) { OptionalRead(obj.animationFactor, "AnimationFactor"); OptionalRead(obj.enableMessageAggregation, "EnableMessageAggregation"); + OptionalRead(obj.dynamicSimulationStep, "DynamicSimulationStep"); } void YamlReader::Read(SilKit::Config::Experimental& obj) diff --git a/SilKit/source/config/YamlValidator.cpp b/SilKit/source/config/YamlValidator.cpp index d22b36136..1b742de8e 100644 --- a/SilKit/source/config/YamlValidator.cpp +++ b/SilKit/source/config/YamlValidator.cpp @@ -147,6 +147,7 @@ const std::set schemaPaths_v1 = { "/Experimental/Metrics/Sinks/Type", "/Experimental/TimeSynchronization", "/Experimental/TimeSynchronization/AnimationFactor", + "/Experimental/TimeSynchronization/DynamicSimulationStep", "/Experimental/TimeSynchronization/EnableMessageAggregation", "/Extensions", "/Extensions/SearchPathHints", diff --git a/SilKit/source/config/YamlWriter.cpp b/SilKit/source/config/YamlWriter.cpp index 1bcadc815..4ecee7869 100644 --- a/SilKit/source/config/YamlWriter.cpp +++ b/SilKit/source/config/YamlWriter.cpp @@ -567,6 +567,7 @@ void YamlWriter::Write(const SilKit::Config::TimeSynchronization& obj) MakeMap(); NonDefaultWrite(obj.animationFactor, "AnimationFactor", defaultObj.animationFactor); NonDefaultWrite(obj.enableMessageAggregation, "EnableMessageAggregation", defaultObj.enableMessageAggregation); + NonDefaultWrite(obj.dynamicSimulationStep, "DynamicSimulationStep", defaultObj.dynamicSimulationStep); } diff --git a/SilKit/source/core/internal/IParticipantInternal.hpp b/SilKit/source/core/internal/IParticipantInternal.hpp index 3da426d3d..45ef324bb 100644 --- a/SilKit/source/core/internal/IParticipantInternal.hpp +++ b/SilKit/source/core/internal/IParticipantInternal.hpp @@ -10,6 +10,7 @@ #include "silkit/experimental/services/orchestration/ISystemController.hpp" #include "silkit/experimental/netsim/NetworkSimulatorDatatypes.hpp" +#include "ParticipantConfiguration.hpp" #include "internal_fwd.hpp" #include "IServiceEndpoint.hpp" @@ -53,6 +54,8 @@ class IParticipantInternal : public IParticipant // Public methods virtual auto GetParticipantName() const -> const std::string& = 0; + virtual auto GetParticipantConfiguration() const -> const SilKit::Config::ParticipantConfiguration& = 0; + /*! \brief Returns the URI of the registry this participant is connecting to. * * The URI must be specified in the configuration (which has priority) or the CreateParticipant call. diff --git a/SilKit/source/core/mock/participant/MockParticipant.hpp b/SilKit/source/core/mock/participant/MockParticipant.hpp index 6f440de8d..367f78f42 100644 --- a/SilKit/source/core/mock/participant/MockParticipant.hpp +++ b/SilKit/source/core/mock/participant/MockParticipant.hpp @@ -633,6 +633,10 @@ class DummyParticipant : public IParticipantInternal { return _registryUri; } + auto GetParticipantConfiguration() const -> const SilKit::Config::ParticipantConfiguration& override + { + return _participantConfiguration; + } virtual auto GetTimeProvider() -> Services::Orchestration::ITimeProvider* { @@ -737,6 +741,7 @@ class DummyParticipant : public IParticipantInternal MockParticipantReplies mockParticipantReplies; DummyNetworkSimulator mockNetworkSimulator; DummyMetricsManager mockMetricsManager; + SilKit::Config::ParticipantConfiguration _participantConfiguration; }; // ================================================================================ diff --git a/SilKit/source/core/participant/Participant.hpp b/SilKit/source/core/participant/Participant.hpp index 8f864b597..44bf970fe 100644 --- a/SilKit/source/core/participant/Participant.hpp +++ b/SilKit/source/core/participant/Participant.hpp @@ -166,6 +166,11 @@ class Participant final : public IParticipantInternal return _participantConfig.middleware.registryUri; } + auto GetParticipantConfiguration() const -> const SilKit::Config::ParticipantConfiguration& override + { + return _participantConfig; + } + void SendMsg(const IServiceEndpoint* from, const Services::Can::WireCanFrameEvent& msg) override; void SendMsg(const IServiceEndpoint* from, const Services::Can::CanFrameTransmitEvent& msg) override; void SendMsg(const IServiceEndpoint* from, const Services::Can::CanControllerStatus& msg) override; diff --git a/SilKit/source/services/orchestration/LifecycleService.cpp b/SilKit/source/services/orchestration/LifecycleService.cpp index 6b25c5d49..ccad9e7a4 100644 --- a/SilKit/source/services/orchestration/LifecycleService.cpp +++ b/SilKit/source/services/orchestration/LifecycleService.cpp @@ -403,20 +403,19 @@ auto LifecycleService::GetTimeSyncService() -> ITimeSyncService* auto LifecycleService::CreateTimeSyncService() -> ITimeSyncService* { - if (!_timeSyncActive) - { - _participant->RegisterTimeSyncService(_timeSyncService); - _timeSyncActive = true; - _timeSyncService->SetTimeAdvanceMode(TimeAdvanceMode::ByOwnDuration); - return _timeSyncService; - } - else + const auto participantConfiguration = _participant->GetParticipantConfiguration(); + + auto timeAdvanceMode = TimeAdvanceMode::ByOwnDuration; + + if (participantConfiguration.experimental.timeSynchronization.dynamicSimulationStep) { - throw ConfigurationError("You may not create the time synchronization service more than once."); + timeAdvanceMode = TimeAdvanceMode::ByMinimalDuration; } + + return CreateTimeSyncService(timeAdvanceMode); } -auto LifecycleService::CreateTimeSyncService(TimeAdvanceMode timeAdvanceMode) -> ITimeSyncService* +auto LifecycleService::CreateTimeSyncService(const TimeAdvanceMode timeAdvanceMode) -> ITimeSyncService* { if (!_timeSyncActive) { From c7ebe78cd5439828a778fd17bb1d35f9ffc47241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Fri, 19 Jun 2026 11:43:36 +0200 Subject: [PATCH 5/5] remove API for dynsteps, enable dyn steps by default, allo opt out by config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marius Börschig --- Demos/api/Orchestration/CMakeLists.txt | 1 - Demos/api/Orchestration/DynSimStep.cpp | 144 ------------------ Demos/api/Orchestration/SimStep.cpp | 16 +- .../IntegrationTests/Hourglass/MockCapi.cpp | 12 -- .../IntegrationTests/Hourglass/MockCapi.hpp | 6 - .../Hourglass/Test_HourglassOrchestration.cpp | 32 ---- .../IntegrationTests/ITest_DynStepSizes.cpp | 88 +++++++---- SilKit/include/silkit/capi/Orchestration.h | 32 ---- .../orchestration/LifecycleService.hpp | 13 -- .../orchestration/TimeSyncService.hpp | 20 +-- .../orchestration/ILifecycleService.hpp | 2 - .../orchestration/ITimeSyncService.hpp | 3 - .../orchestration/OrchestrationDatatypes.hpp | 8 - SilKit/source/capi/CapiOrchestration.cpp | 37 ----- SilKit/source/capi/Test_CapiSymbols.cpp | 1 - .../config/ParticipantConfiguration.hpp | 4 +- .../ParticipantConfiguration.schema.json | 5 + .../config/ParticipantConfiguration_Full.json | 2 +- .../config/ParticipantConfiguration_Full.yaml | 2 +- .../core/mock/participant/MockParticipant.hpp | 6 +- .../orchestration/LifecycleService.cpp | 32 ++-- .../orchestration/LifecycleService.hpp | 1 - .../orchestration/Test_LifecycleService.cpp | 1 - .../orchestration/TimeConfiguration.cpp | 12 +- .../orchestration/TimeConfiguration.hpp | 8 +- .../orchestration/TimeSyncService.cpp | 9 +- .../orchestration/TimeSyncService.hpp | 3 +- docs/api/capi/capi-orchestration.rst | 1 - 28 files changed, 102 insertions(+), 399 deletions(-) delete mode 100644 Demos/api/Orchestration/DynSimStep.cpp diff --git a/Demos/api/Orchestration/CMakeLists.txt b/Demos/api/Orchestration/CMakeLists.txt index ff118459a..3250bff88 100644 --- a/Demos/api/Orchestration/CMakeLists.txt +++ b/Demos/api/Orchestration/CMakeLists.txt @@ -5,6 +5,5 @@ make_silkit_demo(SilKitDemoAutonomous Autonomous.cpp OFF) make_silkit_demo(SilKitDemoCoordinated Coordinated.cpp OFF) make_silkit_demo(SilKitDemoSimStep SimStep.cpp OFF) -make_silkit_demo(SilKitDemoDynSimStep DynSimStep.cpp OFF) make_silkit_demo(SilKitDemoSimStepAsync SimStepAsync.cpp OFF) diff --git a/Demos/api/Orchestration/DynSimStep.cpp b/Demos/api/Orchestration/DynSimStep.cpp deleted file mode 100644 index 47a406f40..000000000 --- a/Demos/api/Orchestration/DynSimStep.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH -// -// SPDX-License-Identifier: MIT - -#include -#include -#include - -#include "silkit/SilKit.hpp" - -using namespace std::chrono_literals; - -std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp) -{ - out << std::chrono::duration_cast(timestamp).count() << "ms"; - return out; -} - -int main(int argc, char** argv) -{ - if (argc != 5 && argc != 6) - { - std::cerr << "Wrong number of arguments! Start demo with: " << argv[0] << - " " - " " - "[-A (Autonomous) | -C (Coordinated)] " - "[-M (ByMinimalDuration) | -D (ByOwnDuration)] " - "[-R (Optional; Randomize StepSize (1ms to 10ms) with 10% probability every step)]" << std::endl; - return -1; - } - // Arg 1: Participant Name - std::string participantName(argv[1]); - - // Arg 2: Step Size - auto stepSize = std::chrono::milliseconds(std::stoi(argv[2])); - std::cout << "Starting with stepSize=" << stepSize << std::endl; - - // Arg 3: Operation Mode - auto operationMode = SilKit::Services::Orchestration::OperationMode::Coordinated; - if (std::string(argv[3]) == "-A") - { - std::cout << "Using OperationMode::Autonomous" << std::endl; - operationMode = SilKit::Services::Orchestration::OperationMode::Autonomous; - } - else if (std::string(argv[3]) == "-C") - { - std::cout << "Using OperationMode::Coordinated" << std::endl; - } - else - { - std::cerr << "Unknown third argument '" << argv[3] << "'. Did you mean '-A' for autonomous mode or '-C' for coordinated mode?" << std::endl; - return -1; - } - - // Arg 4: Time Advance Mode - auto timeAdvanceMode = SilKit::Services::Orchestration::TimeAdvanceMode::ByMinimalDuration; - if (std::string(argv[4]) == "-M") - { - std::cout << "Using TimeAdvanceMode::ByMinimalDuration" << std::endl; - } - else if (std::string(argv[4]) == "-D") - { - timeAdvanceMode = SilKit::Services::Orchestration::TimeAdvanceMode::ByOwnDuration; - std::cout << "Using TimeAdvanceMode::ByOwnDuration" << std::endl; - } - else - { - std::cerr << "Unknown argument '" << argv[4] - << "'. Did you mean '-M' for TimeAdvanceMode::ByMinimalDuration or '-D' for TimeAdvanceMode::ByOwnDuration?" << std::endl; - return -1; - } - - // Arg 5: Optional Randomize Step Size - bool randomizeStepSize = false; - if (argc == 6) - { - if (std::string(argv[5]) == "-R") - { - randomizeStepSize = true; - std::cout << "Randomizing step size every 10 steps." << std::endl << std::endl; - } - else - { - std::cerr << "Unknown argument '" << argv[5] << "'. Did you mean '-R' to randomize the step size every 10 steps?" << std::endl; - return -1; - } - } - - try - { - // Setup participant, lifecycle, time synchronization and logging. - const std::string registryUri = "silkit://localhost:8500"; - const std::string configString = R"({"Logging":{"Sinks":[{"Type":"Stdout","Level":"Info"}]}})"; - auto participantConfiguration = SilKit::Config::ParticipantConfigurationFromString(configString); - - auto participant = SilKit::CreateParticipant(participantConfiguration, participantName, registryUri); - auto logger = participant->GetLogger(); - - auto* lifecycleService = participant->CreateLifecycleService({operationMode}); - - auto* timeSyncService = lifecycleService->CreateTimeSyncService(timeAdvanceMode); - - std::random_device rd; - std::mt19937 rng(rd()); - auto bounded_rand = [&rng](unsigned range) { - std::uniform_int_distribution dist(1, range); - return dist(rng); - }; - - timeSyncService->SetSimulationStepHandler( - [randomizeStepSize, logger, timeSyncService, participantName, bounded_rand]( - std::chrono::nanoseconds now, - std::chrono::nanoseconds duration) { - // The invocation of this handler marks the beginning of a simulation step. - { - std::stringstream ss; - ss << "--------- Simulation step T=" << now << ", duration=" << duration << " ---------"; - logger->Info(ss.str()); - } - - if (randomizeStepSize && bounded_rand(10) == 1) - { - auto rndStepDuration = bounded_rand(10); - timeSyncService->SetStepDuration(std::chrono::milliseconds(rndStepDuration)); - std::stringstream ss; - ss << "--------- Changing step size to " << rndStepDuration << "ms ---------"; - logger->Info(ss.str()); - } - - std::this_thread::sleep_for(500ms); - - }, stepSize); - - auto finalStateFuture = lifecycleService->StartLifecycle(); - finalStateFuture.get(); - } - catch (const std::exception& error) - { - std::cerr << "Something went wrong: " << error.what() << std::endl; - return -2; - } - - return 0; -} diff --git a/Demos/api/Orchestration/SimStep.cpp b/Demos/api/Orchestration/SimStep.cpp index 9d09df535..6a4eb207b 100644 --- a/Demos/api/Orchestration/SimStep.cpp +++ b/Demos/api/Orchestration/SimStep.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: MIT #include -#include #include "silkit/SilKit.hpp" @@ -39,18 +38,15 @@ int main(int argc, char** argv) auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - const auto stepSize = 5ms; - + const auto stepSize = 1ms; timeSyncService->SetSimulationStepHandler( - [logger](std::chrono::nanoseconds now, std::chrono::nanoseconds duration) { + [logger](std::chrono::nanoseconds now, std::chrono::nanoseconds /*duration*/) { // The invocation of this handler marks the beginning of a simulation step. - { - std::stringstream ss; - ss << "--------- Simulation step T=" << now << ", duration=" << duration << " ---------"; - logger->Info(ss.str()); - } - std::this_thread::sleep_for(500ms); + std::stringstream ss; + ss << "--------- Simulation step T=" << now << " ---------"; + logger->Info(ss.str()); + // All messages sent here are guaranteed to arrive at other participants before their next simulation step is called. // So here, we can rely on having received all messages from the past (< now). // Note that this guarantee only holds for messages sent within a simulation step, diff --git a/SilKit/IntegrationTests/Hourglass/MockCapi.cpp b/SilKit/IntegrationTests/Hourglass/MockCapi.cpp index fca076561..ccedbf562 100644 --- a/SilKit/IntegrationTests/Hourglass/MockCapi.cpp +++ b/SilKit/IntegrationTests/Hourglass/MockCapi.cpp @@ -614,12 +614,6 @@ extern "C" return globalCapi->SilKit_TimeSyncService_Create(outTimeSyncService, lifecycleService); } - SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_Create_With_TimeAdvanceMode(SilKit_TimeSyncService** outTimeSyncService, - SilKit_LifecycleService* lifecycleService, SilKit_TimeAdvanceMode timeAdvanceMode) - { - return globalCapi->SilKit_TimeSyncService_Create_With_TimeAdvanceMode(outTimeSyncService, lifecycleService, timeAdvanceMode); - } - SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_SetSimulationStepHandler( SilKit_TimeSyncService* timeSyncService, void* context, SilKit_TimeSyncService_SimulationStepHandler_t handler, SilKit_NanosecondsTime initialStepSize) @@ -647,12 +641,6 @@ extern "C" return globalCapi->SilKit_TimeSyncService_Now(timeSyncService, outNanosecondsTime); } - SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_SetStepDuration(SilKit_TimeSyncService* timeSyncService, - SilKit_NanosecondsTime stepDuration) - { - return globalCapi->SilKit_TimeSyncService_SetStepDuration(timeSyncService, stepDuration); - } - SilKit_ReturnCode SilKitCALL SilKit_Experimental_TimeSyncService_AddOtherSimulationStepsCompletedHandler( SilKit_TimeSyncService* timeSyncService, void* context, SilKit_Experimental_TimeSyncService_OtherSimulationStepsCompletedHandler_t handler, diff --git a/SilKit/IntegrationTests/Hourglass/MockCapi.hpp b/SilKit/IntegrationTests/Hourglass/MockCapi.hpp index 528ee2d5b..1b863ea8b 100644 --- a/SilKit/IntegrationTests/Hourglass/MockCapi.hpp +++ b/SilKit/IntegrationTests/Hourglass/MockCapi.hpp @@ -325,9 +325,6 @@ class MockCapi MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_Create, (SilKit_TimeSyncService * *outTimeSyncService, SilKit_LifecycleService* lifecycleService)); - MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_Create_With_TimeAdvanceMode, - (SilKit_TimeSyncService * *outTimeSyncService, SilKit_LifecycleService* lifecycleService, SilKit_TimeAdvanceMode timeAdvanceMode)); - MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_SetSimulationStepHandler, (SilKit_TimeSyncService * timeSyncService, void* context, SilKit_TimeSyncService_SimulationStepHandler_t handler, SilKit_NanosecondsTime initialStepSize)); @@ -342,9 +339,6 @@ class MockCapi MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_Now, (SilKit_TimeSyncService * timeSyncService, SilKit_NanosecondsTime* outNanosecondsTime)); - MOCK_METHOD(SilKit_ReturnCode, SilKit_TimeSyncService_SetStepDuration, - (SilKit_TimeSyncService * timeSyncService, SilKit_NanosecondsTime stepDuration)); - MOCK_METHOD(SilKit_ReturnCode, SilKit_Experimental_TimeSyncService_AddOtherSimulationStepsCompletedHandler, (SilKit_TimeSyncService * timeSyncService, void* context, SilKit_Experimental_TimeSyncService_OtherSimulationStepsCompletedHandler_t handler, diff --git a/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp b/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp index 0e007b39a..f575072af 100644 --- a/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp +++ b/SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp @@ -148,8 +148,6 @@ class Test_HourglassOrchestration : public SilKitHourglassTests::MockCapiTest .WillByDefault(DoAll(SetArgPointee<0>(mockLifecycleService), Return(SilKit_ReturnCode_SUCCESS))); ON_CALL(capi, SilKit_TimeSyncService_Create(_, _)) .WillByDefault(DoAll(SetArgPointee<0>(mockTimeSyncService), Return(SilKit_ReturnCode_SUCCESS))); - ON_CALL(capi, SilKit_TimeSyncService_Create_With_TimeAdvanceMode(_, _, _)) - .WillByDefault(DoAll(SetArgPointee<0>(mockTimeSyncService), Return(SilKit_ReturnCode_SUCCESS))); ON_CALL(capi, SilKit_SystemMonitor_Create(_, _)) .WillByDefault(DoAll(SetArgPointee<0>(mockSystemMonitor), Return(SilKit_ReturnCode_SUCCESS))); ON_CALL(capi, SilKit_Experimental_SystemController_Create(_, _)) @@ -364,23 +362,6 @@ TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_Create) mockLifecycleService}; } -TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_Create_With_TimeAdvanceMode) -{ - EXPECT_CALL(capi, SilKit_TimeSyncService_Create_With_TimeAdvanceMode(testing::_, mockLifecycleService, - SilKit_TimeAdvanceMode_ByMinimalDuration)); - - SilKit::DETAIL_SILKIT_DETAIL_NAMESPACE_NAME::Impl::Services::Orchestration::TimeSyncService - timeSyncService_ByMinimalDuration{ - mockLifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode::ByMinimalDuration}; - - EXPECT_CALL(capi, SilKit_TimeSyncService_Create_With_TimeAdvanceMode(testing::_, mockLifecycleService, - SilKit_TimeAdvanceMode_ByOwnDuration)); - - SilKit::DETAIL_SILKIT_DETAIL_NAMESPACE_NAME::Impl::Services::Orchestration::TimeSyncService - timeSyncService_ByOwnDuration{ - mockLifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode::ByOwnDuration}; -} - TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_SetSimulationStepHandler) { const std::chrono::nanoseconds initialStepSize{0x123456}; @@ -434,19 +415,6 @@ TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_Now) EXPECT_EQ(timeSyncService.Now(), nanoseconds); } -TEST_F(Test_HourglassOrchestration, SilKit_TimeSyncService_SetStepDuration) -{ - const std::chrono::nanoseconds stepDuration{0x123456}; - - SilKit::DETAIL_SILKIT_DETAIL_NAMESPACE_NAME::Impl::Services::Orchestration::TimeSyncService timeSyncService{ - mockLifecycleService}; - - EXPECT_CALL(capi, SilKit_TimeSyncService_SetStepDuration(mockTimeSyncService, testing::_)) - .WillOnce(Return(SilKit_ReturnCode_SUCCESS)); - - timeSyncService.SetStepDuration(stepDuration); -} - TEST_F(Test_HourglassOrchestration, SilKit_Experimental_TimeSyncService_AddOtherSimulationStepsCompletedHandler) { using testing::_; diff --git a/SilKit/IntegrationTests/ITest_DynStepSizes.cpp b/SilKit/IntegrationTests/ITest_DynStepSizes.cpp index d79e11182..dc2d231d0 100644 --- a/SilKit/IntegrationTests/ITest_DynStepSizes.cpp +++ b/SilKit/IntegrationTests/ITest_DynStepSizes.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "ITestFixture.hpp" using namespace std::chrono_literals; @@ -91,7 +93,10 @@ struct ParticipantParams { std::string name{}; std::chrono::nanoseconds initialStepSize{1ms}; - TimeAdvanceMode timeAdvanceMode{TimeAdvanceMode::ByOwnDuration}; + // Enables/disables the dynamic simulation step behavior via participant configuration + // (Experimental.TimeSynchronization.DynamicSimulationStep). true == align to the minimal step + // among all participants, false == always advance by the participant's own step size. + bool dynamicSimulationStep{true}; // Change step size at these time points std::map @@ -123,23 +128,36 @@ void ITest_DynStepSizes::RunTestSetup(std::vector& participan std::mutex mx; + using StepHandler = std::function; + // Keep the self-referential handlers alive for the whole run. + std::vector> stepHandlers; + for (auto& participantParams : participantsParams) { std::string participantConfiguration; participantConfiguration += R"({"Logging":{"Sinks":[{"Type":"File","Level":"Trace","LogName":")"; participantConfiguration += "DynStepSizes_" + participantParams.name; - participantConfiguration += R"("}]}})"; + participantConfiguration += R"("}]},"Experimental":{"TimeSynchronization":{"DynamicSimulationStep":)"; + participantConfiguration += participantParams.dynamicSimulationStep ? "true" : "false"; + participantConfiguration += R"(}}})"; auto&& simParticipant = _simTestHarness->GetParticipant(participantParams.name, participantConfiguration); auto&& lifecycleService = simParticipant->GetOrCreateLifecycleService(); - auto* timeSyncService = lifecycleService->CreateTimeSyncService(participantParams.timeAdvanceMode); - timeSyncService->SetSimulationStepHandler( - [timeSyncService, &participantParams, &mx, lifecycleService](auto now, auto duration) { + // The harness already created the time sync service for coordinated participants (reading the + // DynamicSimulationStep flag from the participant configuration above); reuse it and override the + // default no-op step handler below. + auto* timeSyncService = simParticipant->GetOrCreateTimeSyncService(); + + auto handler = std::make_shared(); + *handler = [timeSyncService, &participantParams, &mx, lifecycleService, handler](auto now, auto duration) { if (now >= 100ms) { lifecycleService->Stop("stopping the test at 100ms"); + return; } - else + + std::chrono::nanoseconds newStepSize{}; + bool changeStepSize = false; { std::lock_guard lock(mx); participantParams.timePointsAndDurations.emplace_back(now, duration); @@ -148,15 +166,31 @@ void ITest_DynStepSizes::RunTestSetup(std::vector& participan auto it = participantParams.changeStepSizeAtTimePoints.find(now); if (it != participantParams.changeStepSizeAtTimePoints.end()) { - auto newStepSize = it->second; - timeSyncService->SetStepDuration(newStepSize); + newStepSize = it->second; + changeStepSize = true; } } - }, participantParams.initialStepSize); + + // Vary the step size by re-registering the same handler with a new step size. This is the + // supported mechanism for dynamic step sizes (the network simulator drives it the same way). + // Must be the last statement: it replaces the currently executing handler. + if (changeStepSize) + { + timeSyncService->SetSimulationStepHandler(*handler, newStepSize); + } + }; + stepHandlers.push_back(handler); + timeSyncService->SetSimulationStepHandler(*handler, participantParams.initialStepSize); } auto ok = _simTestHarness->Run(5s); ASSERT_TRUE(ok) << "SimTestHarness should terminate without timeout"; + + // Break the shared_ptr<->std::function self-reference cycles so the handlers are not leaked. + for (auto& handler : stepHandlers) + { + *handler = nullptr; + } } void ITest_DynStepSizes::AssertAllStepsEqual(const std::vector& participantsParams) @@ -224,7 +258,7 @@ void ITest_DynStepSizes::AssertStepsEqual(const std::vector participantsParams = {{"P1", invalidDuration, TimeAdvanceMode::ByMinimalDuration}}; + std::vector participantsParams = {{"P1", invalidDuration, true}}; EXPECT_THROW(RunTestSetup(participantsParams), SilKit::SilKitError); } @@ -232,14 +266,14 @@ TEST_F(ITest_DynStepSizes, invalid_duration) TEST_F(ITest_DynStepSizes, one_participant_ByMinimalDuration) { auto refDuration = 5ms; - std::vector participantsParams = {{"P1", refDuration, TimeAdvanceMode::ByMinimalDuration}}; + std::vector participantsParams = {{"P1", refDuration, true}}; RunTestSetup(participantsParams); AssertAscendingStepsWithReferenceDuration(participantsParams, refDuration); } TEST_F(ITest_DynStepSizes, one_participant_ByOwnDuration) { auto refDuration = 5ms; - std::vector participantsParams = {{"P1", refDuration, TimeAdvanceMode::ByOwnDuration}}; + std::vector participantsParams = {{"P1", refDuration, false}}; RunTestSetup(participantsParams); AssertAscendingStepsWithReferenceDuration(participantsParams, refDuration); } @@ -247,16 +281,16 @@ TEST_F(ITest_DynStepSizes, one_participant_ByOwnDuration) // Two/Three participants with ByMinimalDuration mode; Expect steps aligned to the minimal duration TEST_F(ITest_DynStepSizes, two_participants_ByMinimalDuration) { - std::vector participantsParams = {{"P1", 1ms, TimeAdvanceMode::ByMinimalDuration}, - {"P2", 5ms, TimeAdvanceMode::ByMinimalDuration}}; + std::vector participantsParams = {{"P1", 1ms, true}, + {"P2", 5ms, true}}; RunTestSetup(participantsParams); AssertAscendingStepsWithReferenceDuration(participantsParams, 1ms); } TEST_F(ITest_DynStepSizes, three_participants_ByMinimalDuration) { - std::vector participantsParams = {{"P1", 1ms, TimeAdvanceMode::ByMinimalDuration}, - {"P2", 2ms, TimeAdvanceMode::ByMinimalDuration}, - {"P3", 3ms, TimeAdvanceMode::ByMinimalDuration}}; + std::vector participantsParams = {{"P1", 1ms, true}, + {"P2", 2ms, true}, + {"P3", 3ms, true}}; RunTestSetup(participantsParams); AssertAscendingStepsWithReferenceDuration(participantsParams, 1ms); } @@ -264,8 +298,8 @@ TEST_F(ITest_DynStepSizes, three_participants_ByMinimalDuration) // Two participants with mixed modes; Expect steps aligned to the minimal/own duration TEST_F(ITest_DynStepSizes, two_participants_MixedTimeAdvanceModes) { - std::vector participantsParams = {{"P1", 5ms, TimeAdvanceMode::ByMinimalDuration}, - {"P2", 1ms, TimeAdvanceMode::ByOwnDuration}}; + std::vector participantsParams = {{"P1", 5ms, true}, + {"P2", 1ms, false}}; RunTestSetup(participantsParams); AssertAscendingStepsWithReferenceDuration(participantsParams, 1ms); } @@ -273,9 +307,9 @@ TEST_F(ITest_DynStepSizes, two_participants_MixedTimeAdvanceModes) // Three participants with mixed modes; Expect steps of P3(ByMinimalDuration) are equal to the union of P1,P2(ByOwnDuration) TEST_F(ITest_DynStepSizes, three_participants_MixedTimeAdvanceModes) { - std::vector participantsParams = {{"P1", 2ms, TimeAdvanceMode::ByOwnDuration}, - {"P2", 3ms, TimeAdvanceMode::ByOwnDuration}, - {"P3", 4ms, TimeAdvanceMode::ByMinimalDuration}}; + std::vector participantsParams = {{"P1", 2ms, false}, + {"P2", 3ms, false}, + {"P3", 4ms, true}}; RunTestSetup(participantsParams); AssertAscendingStepsWithReferenceDuration({participantsParams[0]}, 2ms); @@ -306,7 +340,7 @@ TEST_F(ITest_DynStepSizes, three_participants_MixedTimeAdvanceModes) TEST_F(ITest_DynStepSizes, one_participant_change_step_size) { std::vector participantsParams = { - {"P1", 1ms, TimeAdvanceMode::ByOwnDuration, {{9ms, 10ms}, {80ms, 2ms}}}}; + {"P1", 1ms, false, {{9ms, 10ms}, {80ms, 2ms}}}}; RunTestSetup(participantsParams); ParticipantParams refData; @@ -323,8 +357,8 @@ TEST_F(ITest_DynStepSizes, one_participant_change_step_size) TEST_F(ITest_DynStepSizes, two_participants_mixed_change_step_size) { std::vector participantsParams = { - {"P1", 1ms, TimeAdvanceMode::ByOwnDuration, {{9ms, 10ms}, {80ms, 2ms}}}, - {"P2", 20ms, TimeAdvanceMode::ByMinimalDuration}}; + {"P1", 1ms, false, {{9ms, 10ms}, {80ms, 2ms}}}, + {"P2", 20ms, true}}; RunTestSetup(participantsParams); @@ -345,8 +379,8 @@ TEST_F(ITest_DynStepSizes, two_participants_mixed_change_step_size) TEST_F(ITest_DynStepSizes, two_participants_ByMinimalDuration_change_step_size) { std::vector participantsParams = { - {"P1", 1ms, TimeAdvanceMode::ByMinimalDuration, {{9ms, 10ms}, {80ms, 2ms}}}, - {"P2", 20ms, TimeAdvanceMode::ByMinimalDuration}}; + {"P1", 1ms, true, {{9ms, 10ms}, {80ms, 2ms}}}, + {"P2", 20ms, true}}; RunTestSetup(participantsParams); diff --git a/SilKit/include/silkit/capi/Orchestration.h b/SilKit/include/silkit/capi/Orchestration.h index 4f7185422..572a7e7c4 100644 --- a/SilKit/include/silkit/capi/Orchestration.h +++ b/SilKit/include/silkit/capi/Orchestration.h @@ -94,12 +94,6 @@ typedef int8_t SilKit_OperationMode; #define SilKit_OperationMode_Autonomous ((SilKit_OperationMode)20) -/*! The TimeAdvanceMode. */ -typedef int8_t SilKit_TimeAdvanceMode; - -#define SilKit_TimeAdvanceMode_ByOwnDuration ((SilKit_TimeAdvanceMode)0) -#define SilKit_TimeAdvanceMode_ByMinimalDuration ((SilKit_TimeAdvanceMode)10) - /*! Details about a status change of a participant. */ typedef struct { @@ -458,20 +452,6 @@ SilKitAPI SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_Create(SilKit_Time typedef SilKit_ReturnCode(SilKitFPTR* SilKit_TimeSyncService_Create_t)(SilKit_TimeSyncService** outTimeSyncService, SilKit_LifecycleService* lifecycleService); -/*! \brief Create a time sync service at this SIL Kit simulation participant. - * \param outTimeSyncService Pointer that refers to the resulting time sync service (out parameter). - * \param lifecycleService The lifecyle service at which the time sync service should be created. - * \param timeAdvanceMode The time advance mode for this time sync service. - * - * The object returned must not be deallocated using free()! - */ -SilKitAPI SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_Create_With_TimeAdvanceMode(SilKit_TimeSyncService** outTimeSyncService, - SilKit_LifecycleService* lifecycleService, SilKit_TimeAdvanceMode timeAdvanceMode); - -typedef SilKit_ReturnCode(SilKitFPTR* SilKit_TimeSyncService_Create_With_TimeAdvanceMode_t)(SilKit_TimeSyncService** outTimeSyncService, - SilKit_LifecycleService* lifecycleService, - SilKit_TimeAdvanceMode timeAdvanceMode); - /*! \brief The handler to be called if the simulation task is due * * \param context The user provided context passed in \ref SilKit_TimeSyncService_SetSimulationStepHandler @@ -548,18 +528,6 @@ typedef SilKit_ReturnCode(SilKitFPTR* SilKit_TimeSyncService_Now_t)(SilKit_TimeS SilKit_NanosecondsTime* outNanosecondsTime); -/*! \brief Set the duration of the next simulation step - * - * \param timeSyncService The time sync service obtained via \ref SilKit_TimeSyncService_Create. - * \param stepDuration The step size in nanoseconds. - */ -SilKitAPI SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_SetStepDuration(SilKit_TimeSyncService* timeSyncService, - SilKit_NanosecondsTime stepDuration); - -typedef SilKit_ReturnCode(SilKitFPTR* SilKit_TimeSyncService_SetStepDuration_t)(SilKit_TimeSyncService* timeSyncService, - SilKit_NanosecondsTime stepDuration); - - /* * * System Monitor diff --git a/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp b/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp index 6cab9d4a1..b62e5162d 100644 --- a/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp +++ b/SilKit/include/silkit/detail/impl/services/orchestration/LifecycleService.hpp @@ -66,9 +66,6 @@ class LifecycleService : public SilKit::Services::Orchestration::ILifecycleServi inline auto CreateTimeSyncService() -> SilKit::Services::Orchestration::ITimeSyncService* override; - inline auto CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode) - -> SilKit::Services::Orchestration::ITimeSyncService* override; - private: SilKit_LifecycleService* _lifecycleService{nullptr}; @@ -323,16 +320,6 @@ auto LifecycleService::CreateTimeSyncService() -> SilKit::Services::Orchestratio return _timeSyncService.get(); } -// TODO bkd: Needed or can I use a default in the function above? -auto LifecycleService::CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode) - -> SilKit::Services::Orchestration::ITimeSyncService* -{ - _timeSyncService = std::make_unique(_lifecycleService, timeAdvanceMode); - - return _timeSyncService.get(); -} - - } // namespace Orchestration } // namespace Services } // namespace Impl diff --git a/SilKit/include/silkit/detail/impl/services/orchestration/TimeSyncService.hpp b/SilKit/include/silkit/detail/impl/services/orchestration/TimeSyncService.hpp index 82cb4bda5..3e27a7025 100644 --- a/SilKit/include/silkit/detail/impl/services/orchestration/TimeSyncService.hpp +++ b/SilKit/include/silkit/detail/impl/services/orchestration/TimeSyncService.hpp @@ -22,12 +22,8 @@ namespace Orchestration { class TimeSyncService : public SilKit::Services::Orchestration::ITimeSyncService { public: - inline explicit TimeSyncService(SilKit_LifecycleService* lifecycleService); - inline explicit TimeSyncService(SilKit_LifecycleService* lifecycleService, - SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode); - inline ~TimeSyncService() override = default; inline void SetSimulationStepHandler(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) override; @@ -39,9 +35,7 @@ class TimeSyncService : public SilKit::Services::Orchestration::ITimeSyncService inline auto Now() const -> std::chrono::nanoseconds override; - inline void SetStepDuration(std::chrono::nanoseconds stepDuration) override; - - public: +public: inline auto ExperimentalAddOtherSimulationStepsCompletedHandler( SilKit::Experimental::Services::Orchestration::OtherSimulationStepsCompletedHandler) -> SilKit::Util::HandlerId; @@ -91,12 +85,6 @@ TimeSyncService::TimeSyncService(SilKit_LifecycleService* lifecycleService) ThrowOnError(returnCode); } -TimeSyncService::TimeSyncService(SilKit_LifecycleService* lifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode) -{ - const auto returnCode = SilKit_TimeSyncService_Create_With_TimeAdvanceMode(&_timeSyncService, lifecycleService, static_cast(timeAdvanceMode)); - ThrowOnError(returnCode); -} - void TimeSyncService::SetSimulationStepHandler(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) { auto ownedHandlerPtr = std::make_unique(std::move(task)); @@ -152,12 +140,6 @@ auto TimeSyncService::Now() const -> std::chrono::nanoseconds return std::chrono::nanoseconds{nanosecondsTime}; } -void TimeSyncService::SetStepDuration(std::chrono::nanoseconds stepDuration) -{ - const auto returnCode = SilKit_TimeSyncService_SetStepDuration(_timeSyncService, stepDuration.count()); - ThrowOnError(returnCode); -} - inline auto TimeSyncService::ExperimentalAddOtherSimulationStepsCompletedHandler(std::function handler) -> SilKit::Util::HandlerId { diff --git a/SilKit/include/silkit/services/orchestration/ILifecycleService.hpp b/SilKit/include/silkit/services/orchestration/ILifecycleService.hpp index 5f55f3698..ce771796b 100644 --- a/SilKit/include/silkit/services/orchestration/ILifecycleService.hpp +++ b/SilKit/include/silkit/services/orchestration/ILifecycleService.hpp @@ -191,8 +191,6 @@ class ILifecycleService /*! \brief Return the ITimeSyncService for the current ILifecycleService. */ virtual auto CreateTimeSyncService() -> ITimeSyncService* = 0; - - virtual auto CreateTimeSyncService(TimeAdvanceMode timeAdvanceMode) -> ITimeSyncService* = 0; }; } // namespace Orchestration diff --git a/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp b/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp index 5c6e91a7c..c7656a161 100644 --- a/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp +++ b/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp @@ -50,9 +50,6 @@ class ITimeSyncService /*! \brief Get the current simulation time */ virtual auto Now() const -> std::chrono::nanoseconds = 0; - - virtual void SetStepDuration(std::chrono::nanoseconds stepDuration) = 0; - }; } // namespace Orchestration diff --git a/SilKit/include/silkit/services/orchestration/OrchestrationDatatypes.hpp b/SilKit/include/silkit/services/orchestration/OrchestrationDatatypes.hpp index 0eaed8a45..807baf45b 100644 --- a/SilKit/include/silkit/services/orchestration/OrchestrationDatatypes.hpp +++ b/SilKit/include/silkit/services/orchestration/OrchestrationDatatypes.hpp @@ -119,14 +119,6 @@ struct ParticipantConnectionInformation std::string participantName; }; -enum class TimeAdvanceMode : SilKit_TimeAdvanceMode -{ - //! Advance time based on the participant's own step duration - ByOwnDuration = SilKit_TimeAdvanceMode_ByOwnDuration, - //! Advance time based on the minimal step duration among all participants - ByMinimalDuration = SilKit_TimeAdvanceMode_ByMinimalDuration, -}; - } // namespace Orchestration } // namespace Services } // namespace SilKit diff --git a/SilKit/source/capi/CapiOrchestration.cpp b/SilKit/source/capi/CapiOrchestration.cpp index 8e028d2b1..303359aff 100644 --- a/SilKit/source/capi/CapiOrchestration.cpp +++ b/SilKit/source/capi/CapiOrchestration.cpp @@ -90,23 +90,6 @@ try } CAPI_CATCH_EXCEPTIONS -SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_Create_With_TimeAdvanceMode( - SilKit_TimeSyncService** outTimeSyncService, SilKit_LifecycleService* lifecycleService, - SilKit_TimeAdvanceMode timeAdvanceMode) -try -{ - ASSERT_VALID_OUT_PARAMETER(outTimeSyncService); - ASSERT_VALID_POINTER_PARAMETER(lifecycleService); - - auto cppLifecycleService = reinterpret_cast(lifecycleService); - auto cppTimeSyncService = cppLifecycleService->CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode(timeAdvanceMode)); - - *outTimeSyncService = reinterpret_cast(cppTimeSyncService); - - return SilKit_ReturnCode_SUCCESS; -} -CAPI_CATCH_EXCEPTIONS - SilKit_ReturnCode SilKitCALL SilKit_LifecycleService_SetCommunicationReadyHandler(SilKit_LifecycleService* lifecycleService, void* context, @@ -445,26 +428,6 @@ try CAPI_CATCH_EXCEPTIONS -SilKit_ReturnCode SilKitCALL SilKit_TimeSyncService_SetStepDuration(SilKit_TimeSyncService* cTimeSyncService, - SilKit_NanosecondsTime stepDuration) -try -{ - ASSERT_VALID_POINTER_PARAMETER(cTimeSyncService); - - auto* timeSyncService = reinterpret_cast(cTimeSyncService); - - if (stepDuration <= 0) - { - SilKit_error_string = "Step duration must be positive"; - return SilKit_ReturnCode_BADPARAMETER; - } - - timeSyncService->SetStepDuration(std::chrono::nanoseconds(stepDuration)); - return SilKit_ReturnCode_SUCCESS; -} -CAPI_CATCH_EXCEPTIONS - - SilKit_ReturnCode SilKitCALL SilKit_LifecycleService_Pause(SilKit_LifecycleService* clifecycleService, const char* reason) try diff --git a/SilKit/source/capi/Test_CapiSymbols.cpp b/SilKit/source/capi/Test_CapiSymbols.cpp index 8e933b33a..8f0612407 100644 --- a/SilKit/source/capi/Test_CapiSymbols.cpp +++ b/SilKit/source/capi/Test_CapiSymbols.cpp @@ -87,7 +87,6 @@ TEST(Test_CapiSymbols, DISABLED_link_all_public_symbols) (void)SilKit_SystemMonitor_Create(nullptr, nullptr); (void)SilKit_LifecycleService_Create(nullptr, nullptr, nullptr); (void)SilKit_TimeSyncService_Create(nullptr, nullptr); - (void)SilKit_TimeSyncService_Create_With_TimeAdvanceMode(nullptr, nullptr, 0); (void)SilKit_LifecycleService_SetCommunicationReadyHandler(nullptr, nullptr, nullptr); (void)SilKit_LifecycleService_SetCommunicationReadyHandlerAsync(nullptr, nullptr, nullptr); (void)SilKit_LifecycleService_CompleteCommunicationReadyHandlerAsync(nullptr); diff --git a/SilKit/source/config/ParticipantConfiguration.hpp b/SilKit/source/config/ParticipantConfiguration.hpp index e0289bbb3..8db87a021 100644 --- a/SilKit/source/config/ParticipantConfiguration.hpp +++ b/SilKit/source/config/ParticipantConfiguration.hpp @@ -309,7 +309,9 @@ struct TimeSynchronization { double animationFactor{0.0}; Aggregation enableMessageAggregation{Aggregation::Off}; - bool dynamicSimulationStep{false}; + //! When enabled (the default), a participant aligns its simulation step duration to the minimal + //! step among all synchronized participants. Set to false to opt out. + bool dynamicSimulationStep{true}; }; // ================================================================================ diff --git a/SilKit/source/config/ParticipantConfiguration.schema.json b/SilKit/source/config/ParticipantConfiguration.schema.json index 0800c842f..e04200f57 100644 --- a/SilKit/source/config/ParticipantConfiguration.schema.json +++ b/SilKit/source/config/ParticipantConfiguration.schema.json @@ -838,6 +838,11 @@ "enum": [ "Off", "On", "Auto" ], "description": "Decide for simulations with time synchronization, if a message aggregation is performed. In case of the Auto mode, the message aggregation is enabled for simulations using the synchronous simulation step handler.", "default": "Off" + }, + "DynamicSimulationStep": { + "type": "boolean", + "description": "When enabled (the default), a participant aligns its simulation step duration to the minimal step among all synchronized participants, allowing dynamic step sizes. Set to false to opt out.", + "default": true } }, "additionalProperties": false diff --git a/SilKit/source/config/ParticipantConfiguration_Full.json b/SilKit/source/config/ParticipantConfiguration_Full.json index 801741333..6fe22e703 100644 --- a/SilKit/source/config/ParticipantConfiguration_Full.json +++ b/SilKit/source/config/ParticipantConfiguration_Full.json @@ -265,7 +265,7 @@ "TimeSynchronization": { "AnimationFactor": 1.5, "EnableMessageAggregation": "Off", - "DynamicSimulationStep": true + "DynamicSimulationStep": false }, "Metrics": { "CollectFromRemote": false, diff --git a/SilKit/source/config/ParticipantConfiguration_Full.yaml b/SilKit/source/config/ParticipantConfiguration_Full.yaml index 4effd1acf..f88844e17 100644 --- a/SilKit/source/config/ParticipantConfiguration_Full.yaml +++ b/SilKit/source/config/ParticipantConfiguration_Full.yaml @@ -191,7 +191,7 @@ Experimental: TimeSynchronization: AnimationFactor: 1.5 EnableMessageAggregation: Off - DynamicSimulationStep: true + DynamicSimulationStep: false Metrics: CollectFromRemote: false Sinks: diff --git a/SilKit/source/core/mock/participant/MockParticipant.hpp b/SilKit/source/core/mock/participant/MockParticipant.hpp index 367f78f42..3f67ef4c4 100644 --- a/SilKit/source/core/mock/participant/MockParticipant.hpp +++ b/SilKit/source/core/mock/participant/MockParticipant.hpp @@ -71,8 +71,6 @@ class MockLifecycleService : public Services::Orchestration::ILifecycleService MOCK_METHOD(Services::Orchestration::ParticipantStatus&, Status, (), (override, const)); MOCK_METHOD(Services::Orchestration::ITimeSyncService*, GetTimeSyncService, (), ()); MOCK_METHOD(Services::Orchestration::ITimeSyncService*, CreateTimeSyncService, (), (override)); - MOCK_METHOD(Services::Orchestration::ITimeSyncService*, CreateTimeSyncService, - (SilKit::Services::Orchestration::TimeAdvanceMode), (override)); MOCK_METHOD(void, AddAsyncSubscriptionsCompletionHandler, (std::function /*handler*/)); MOCK_METHOD(Services::Orchestration::OperationMode, GetOperationMode, (), (const)); }; @@ -86,7 +84,6 @@ class MockTimeSyncService : public Services::Orchestration::ITimeSyncService (SimulationStepHandler task, std::chrono::nanoseconds initialStepSize), (override)); MOCK_METHOD(void, CompleteSimulationStep, (), (override)); MOCK_METHOD(std::chrono::nanoseconds, Now, (), (override, const)); - MOCK_METHOD(void, SetStepDuration, (std::chrono::nanoseconds)); }; class MockSystemMonitor : public Services::Orchestration::ISystemMonitor @@ -251,8 +248,7 @@ class DummyParticipant : public IParticipantInternal DummyParticipant() { ON_CALL(mockLifecycleService, GetTimeSyncService).WillByDefault(testing::Return(&mockTimeSyncService)); - ON_CALL(mockLifecycleService, CreateTimeSyncService()).WillByDefault(testing::Return(&mockTimeSyncService)); - ON_CALL(mockLifecycleService, CreateTimeSyncService(testing::_)).WillByDefault(testing::Return(&mockTimeSyncService)); + ON_CALL(mockLifecycleService, CreateTimeSyncService).WillByDefault(testing::Return(&mockTimeSyncService)); ON_CALL(logger, GetLogLevel()).WillByDefault(testing::Return(Services::Logging::Level::Debug)); } diff --git a/SilKit/source/services/orchestration/LifecycleService.cpp b/SilKit/source/services/orchestration/LifecycleService.cpp index ccad9e7a4..1d136603b 100644 --- a/SilKit/source/services/orchestration/LifecycleService.cpp +++ b/SilKit/source/services/orchestration/LifecycleService.cpp @@ -403,31 +403,21 @@ auto LifecycleService::GetTimeSyncService() -> ITimeSyncService* auto LifecycleService::CreateTimeSyncService() -> ITimeSyncService* { - const auto participantConfiguration = _participant->GetParticipantConfiguration(); - - auto timeAdvanceMode = TimeAdvanceMode::ByOwnDuration; - - if (participantConfiguration.experimental.timeSynchronization.dynamicSimulationStep) + if (_timeSyncActive) { - timeAdvanceMode = TimeAdvanceMode::ByMinimalDuration; + throw ConfigurationError("You may not create the time synchronization service more than once."); } - return CreateTimeSyncService(timeAdvanceMode); -} + _participant->RegisterTimeSyncService(_timeSyncService); + _timeSyncActive = true; -auto LifecycleService::CreateTimeSyncService(const TimeAdvanceMode timeAdvanceMode) -> ITimeSyncService* -{ - if (!_timeSyncActive) - { - _participant->RegisterTimeSyncService(_timeSyncService); - _timeSyncActive = true; - _timeSyncService->SetTimeAdvanceMode(timeAdvanceMode); - return _timeSyncService; - } - else - { - throw ConfigurationError("You may not create the time synchronization service more than once."); - } + // Dynamic simulation step sizes are enabled by default and can be disabled per participant via + // Experimental.TimeSynchronization.DynamicSimulationStep. + const auto& participantConfiguration = _participant->GetParticipantConfiguration(); + _timeSyncService->SetDynamicStepSizeEnabled( + participantConfiguration.experimental.timeSynchronization.dynamicSimulationStep); + + return _timeSyncService; } void LifecycleService::ReceiveMsg(const IServiceEndpoint*, const SystemCommand& command) diff --git a/SilKit/source/services/orchestration/LifecycleService.hpp b/SilKit/source/services/orchestration/LifecycleService.hpp index 798953d15..fb75df174 100644 --- a/SilKit/source/services/orchestration/LifecycleService.hpp +++ b/SilKit/source/services/orchestration/LifecycleService.hpp @@ -54,7 +54,6 @@ class LifecycleService void SetAbortHandler(AbortHandler handler) override; auto CreateTimeSyncService() -> ITimeSyncService* override; - auto CreateTimeSyncService(TimeAdvanceMode timeAdvanceMode) -> ITimeSyncService* override; auto GetTimeSyncService() -> ITimeSyncService*; auto StartLifecycle() -> std::future override; diff --git a/SilKit/source/services/orchestration/Test_LifecycleService.cpp b/SilKit/source/services/orchestration/Test_LifecycleService.cpp index 2f6975eb1..5bff246a7 100644 --- a/SilKit/source/services/orchestration/Test_LifecycleService.cpp +++ b/SilKit/source/services/orchestration/Test_LifecycleService.cpp @@ -45,7 +45,6 @@ class MockTimeSync : public TimeSyncService (SimulationStepHandler task, std::chrono::nanoseconds initialStepSize), (override)); MOCK_METHOD(void, CompleteSimulationStep, (), (override)); MOCK_METHOD(std::chrono::nanoseconds, Now, (), (override, const)); - MOCK_METHOD(void, SetStepDuration, (std::chrono::nanoseconds), (override)); }; class MockParticipant : public DummyParticipant diff --git a/SilKit/source/services/orchestration/TimeConfiguration.cpp b/SilKit/source/services/orchestration/TimeConfiguration.cpp index a3f107b62..b931d0af8 100644 --- a/SilKit/source/services/orchestration/TimeConfiguration.cpp +++ b/SilKit/source/services/orchestration/TimeConfiguration.cpp @@ -20,14 +20,10 @@ TimeConfiguration::TimeConfiguration(Logging::ILoggerInternal* logger) _myNextTask.duration = 1ms; } -void TimeConfiguration::SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode) +void TimeConfiguration::SetDynamicStepSizeEnabled(bool enabled) { - _timeAdvanceMode = timeAdvanceMode; -} - -auto TimeConfiguration::GetTimeAdvanceMode() const -> TimeAdvanceMode -{ - return _timeAdvanceMode; + Lock lock{_mx}; + _dynamicStepSizeEnabled = enabled; } void TimeConfiguration::SetBlockingMode(bool blocking) @@ -156,7 +152,7 @@ void TimeConfiguration::AdvanceTimeStep() Lock lock{_mx}; _currentTask = _myNextTask; - if (_timeAdvanceMode == TimeAdvanceMode::ByMinimalDuration) + if (_dynamicStepSizeEnabled) { const auto minAlignedDuration = GetMinimalAlignedDuration(); diff --git a/SilKit/source/services/orchestration/TimeConfiguration.hpp b/SilKit/source/services/orchestration/TimeConfiguration.hpp index 2e3f642da..a4d2c6096 100755 --- a/SilKit/source/services/orchestration/TimeConfiguration.hpp +++ b/SilKit/source/services/orchestration/TimeConfiguration.hpp @@ -42,8 +42,7 @@ class TimeConfiguration void SetStepDuration(std::chrono::nanoseconds duration); auto GetMinimalAlignedDuration() const -> std::chrono::nanoseconds; - auto GetTimeAdvanceMode() const -> TimeAdvanceMode; - void SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode); + void SetDynamicStepSizeEnabled(bool enabled); private: //Members mutable std::mutex _mx; @@ -56,7 +55,10 @@ class TimeConfiguration bool _hoppedOn = false; Logging::ILoggerInternal* _logger; - TimeAdvanceMode _timeAdvanceMode{TimeAdvanceMode::ByOwnDuration}; + // When enabled, each simulation step is shortened ("aligned") to the minimal duration among all + // synchronized participants (see GetMinimalAlignedDuration / AdvanceTimeStep). Enabled by default + // via participant configuration; can be turned off with Experimental.TimeSynchronization.DynamicSimulationStep. + bool _dynamicStepSizeEnabled{false}; }; } // namespace Orchestration diff --git a/SilKit/source/services/orchestration/TimeSyncService.cpp b/SilKit/source/services/orchestration/TimeSyncService.cpp index 4f93c99c3..2511ab016 100644 --- a/SilKit/source/services/orchestration/TimeSyncService.cpp +++ b/SilKit/source/services/orchestration/TimeSyncService.cpp @@ -454,11 +454,6 @@ void TimeSyncService::SetSimulationStepHandlerAsync(SimulationStepHandler task, _timeConfiguration.SetStepDuration(initialStepSize); } -void TimeSyncService::SetStepDuration(std::chrono::nanoseconds stepDuration) -{ - _timeConfiguration.SetStepDuration(stepDuration); -} - bool TimeSyncService::SetupTimeSyncPolicy(bool isSynchronizingVirtualTime) { std::lock_guard lock{_timeSyncPolicyMx}; @@ -831,9 +826,9 @@ bool TimeSyncService::IsBlocking() const return _timeConfiguration.IsBlocking(); } -void TimeSyncService::SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode) +void TimeSyncService::SetDynamicStepSizeEnabled(bool enabled) { - _timeConfiguration.SetTimeAdvanceMode(timeAdvanceMode); + _timeConfiguration.SetDynamicStepSizeEnabled(enabled); } diff --git a/SilKit/source/services/orchestration/TimeSyncService.hpp b/SilKit/source/services/orchestration/TimeSyncService.hpp index adb2ea6dc..f2d23e70d 100644 --- a/SilKit/source/services/orchestration/TimeSyncService.hpp +++ b/SilKit/source/services/orchestration/TimeSyncService.hpp @@ -56,7 +56,6 @@ class TimeSyncService void SetSimulationStepHandler(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) override; void SetSimulationStepHandlerAsync(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize) override; void CompleteSimulationStep() override; - void SetStepDuration(std::chrono::nanoseconds stepDuration) override; void ReceiveMsg(const IServiceEndpoint* from, const NextSimTask& task) override; auto Now() const -> std::chrono::nanoseconds override; @@ -100,7 +99,7 @@ class TimeSyncService void RemoveOtherSimulationStepsCompletedHandler(HandlerId handlerId); void InvokeOtherSimulationStepsCompletedHandlers(); - void SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode); + void SetDynamicStepSizeEnabled(bool enabled); private: // ---------------------------------------- diff --git a/docs/api/capi/capi-orchestration.rst b/docs/api/capi/capi-orchestration.rst index 18b8f7d84..20c1cf8c7 100644 --- a/docs/api/capi/capi-orchestration.rst +++ b/docs/api/capi/capi-orchestration.rst @@ -18,7 +18,6 @@ Orchestration C API .. doxygenfunction:: SilKit_LifecycleService_Continue .. doxygenfunction:: SilKit_TimeSyncService_Create -.. doxygenfunction:: SilKit_TimeSyncService_Create_With_TimeAdvanceMode .. doxygenfunction:: SilKit_TimeSyncService_SetSimulationStepHandler .. doxygenfunction:: SilKit_TimeSyncService_SetSimulationStepHandlerAsync .. doxygenfunction:: SilKit_TimeSyncService_CompleteSimulationStep \ No newline at end of file