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..47a406f40 --- /dev/null +++ b/Demos/api/Orchestration/DynSimStep.cpp @@ -0,0 +1,144 @@ +// 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 938b994f5..2e59b2656 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,15 +39,18 @@ int main(int argc, char** argv) auto* timeSyncService = lifecycleService->CreateTimeSyncService(); - const auto stepSize = 1ms; + const auto stepSize = 5ms; + 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/CMakeLists.txt b/SilKit/IntegrationTests/CMakeLists.txt index 634a56dce..adee367c4 100644 --- a/SilKit/IntegrationTests/CMakeLists.txt +++ b/SilKit/IntegrationTests/CMakeLists.txt @@ -114,6 +114,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/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..0e007b39a 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,19 @@ 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_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/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..4f03c6f33 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..5c6e91a7c 100644 --- a/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp +++ b/SilKit/include/silkit/services/orchestration/ITimeSyncService.hpp @@ -50,6 +50,9 @@ 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..8e7a53195 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,35 +92,80 @@ 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()) - { - const std::string errorMessage{"Participant " + otherParticipantName + " unknown."}; - throw SilKitError{errorMessage}; - } - auto it = _otherNextTasks.find(otherParticipantName); - if (it != _otherNextTasks.end()) + + if (duration == 0ns) { - _otherNextTasks.erase(it); + throw SilKitError("Attempted to set step duration to zero."); } + + _myNextTask.duration = duration; } -void TimeConfiguration::SetStepDuration(std::chrono::nanoseconds duration) + +auto TimeConfiguration::GetMinimalAlignedDuration() const -> std::chrono::nanoseconds { - Lock lock{_mx}; - _myNextTask.duration = duration; + if (_otherNextTasks.empty()) + { + return std::chrono::nanoseconds::max(); + } + + auto earliestOtherTimepoint = std::chrono::nanoseconds::max(); + for (const auto& entry : _otherNextTasks) + { + // 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) + { + earliestOtherTimepoint = nextStepStart; + } + else if (nextStepEnd < earliestOtherTimepoint) + { + earliestOtherTimepoint = nextStepEnd; + } + } + //Logging::Info(_logger, "Earliest next timepoint among other participants is {}ms", + // std::chrono::duration_cast(earliestOtherTimepoint).count()); + + auto minAlignedDuration = earliestOtherTimepoint - _currentTask.timePoint; + + if (minAlignedDuration < 0ns) + { + 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 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()); + _currentTask.duration = minAlignedDuration; + } + } + _myNextTask.timePoint = _currentTask.timePoint + _currentTask.duration; } diff --git a/SilKit/source/services/orchestration/TimeConfiguration.hpp b/SilKit/source/services/orchestration/TimeConfiguration.hpp index 59157c963..2e3f642da 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 GetMinimalAlignedDuration() 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,8 @@ 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..adb2ea6dc 100644 --- a/SilKit/source/services/orchestration/TimeSyncService.hpp +++ b/SilKit/source/services/orchestration/TimeSyncService.hpp @@ -56,7 +56,8 @@ 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) override; + void ReceiveMsg(const IServiceEndpoint* from, const NextSimTask& task) override; auto Now() const -> std::chrono::nanoseconds override; @@ -99,6 +100,8 @@ class TimeSyncService void RemoveOtherSimulationStepsCompletedHandler(HandlerId handlerId); void InvokeOtherSimulationStepsCompletedHandlers(); + void SetTimeAdvanceMode(TimeAdvanceMode timeAdvanceMode); + private: // ---------------------------------------- // private methods @@ -154,6 +157,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