Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Demos/api/Orchestration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

90 changes: 90 additions & 0 deletions Demos/api/Orchestration/DynSimStep.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: 2024 Vector Informatik GmbH
//
// SPDX-License-Identifier: MIT

#include <iostream>
#include <random>

#include "silkit/SilKit.hpp"

using namespace std::chrono_literals;

std::ostream& operator<<(std::ostream& out, std::chrono::nanoseconds timestamp)
{
out << std::chrono::duration_cast<std::chrono::milliseconds>(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] << " <ParticipantName>" << 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<unsigned> 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;
}
15 changes: 9 additions & 6 deletions Demos/api/Orchestration/SimStep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions SilKit/IntegrationTests/Hourglass/MockCapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions SilKit/IntegrationTests/Hourglass/MockCapi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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,
Expand Down
33 changes: 33 additions & 0 deletions SilKit/IntegrationTests/Hourglass/Test_HourglassOrchestration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(_, _))
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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::_;
Expand Down
32 changes: 32 additions & 0 deletions SilKit/include/silkit/capi/Orchestration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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.
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment has a typo: "lifecyle" should be "lifecycle".

Copilot uses AI. Check for mistakes.
* \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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -315,11 +318,22 @@ auto LifecycleService::Status() const -> const SilKit::Services::Orchestration::

auto LifecycleService::CreateTimeSyncService() -> SilKit::Services::Orchestration::ITimeSyncService*
{
_timeSyncService = std::make_unique<TimeSyncService>(_lifecycleService);
_timeSyncService = std::make_unique<TimeSyncService>(
_lifecycleService, SilKit::Services::Orchestration::TimeAdvanceMode::ByOwnDuration);

return _timeSyncService.get();
}

// TODO bkd: Needed or can I use a default in the function above?
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO comment should be resolved before merging. The comment asks whether a default parameter could be used instead of having two separate overloads. This is a valid architectural question that should be addressed.

Suggested change
// TODO bkd: Needed or can I use a default in the function above?
// Overload with explicit TimeAdvanceMode to allow callers to override the default used above.

Copilot uses AI. Check for mistakes.
auto LifecycleService::CreateTimeSyncService(SilKit::Services::Orchestration::TimeAdvanceMode timeAdvanceMode)
-> SilKit::Services::Orchestration::ITimeSyncService*
{
_timeSyncService = std::make_unique<TimeSyncService>(_lifecycleService, timeAdvanceMode);

return _timeSyncService.get();
}


Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary blank line added. Consider removing for consistency with the surrounding code style.

Suggested change

Copilot uses AI. Check for mistakes.
} // namespace Orchestration
} // namespace Services
} // namespace Impl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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<SilKit_TimeAdvanceMode>(timeAdvanceMode));
ThrowOnError(returnCode);
}

void TimeSyncService::SetSimulationStepHandler(SimulationStepHandler task, std::chrono::nanoseconds initialStepSize)
{
auto ownedHandlerPtr = std::make_unique<SimulationStepHandler>(std::move(task));
Expand Down Expand Up @@ -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<void()> handler)
-> SilKit::Util::HandlerId
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading