diff --git a/score/mw/com/design/events_fields/NestedCallbacks.svg b/score/mw/com/design/events_fields/NestedCallbacks.svg old mode 100755 new mode 100644 diff --git a/score/mw/com/example/ipc_bridge/main.cpp b/score/mw/com/example/ipc_bridge/main.cpp index 8711a3cb..7c282c8f 100644 --- a/score/mw/com/example/ipc_bridge/main.cpp +++ b/score/mw/com/example/ipc_bridge/main.cpp @@ -51,7 +51,7 @@ Params ParseCommandLineArguments(const int argc, const char** argv) "Number of cycles that are executed before determining success or failure. 0 indicates no limit."); options.add_options()("mode,m", po::value(), - "Set to either send/skeleton or recv/proxy to determine the role of the process"); + "Set to: send/skeleton (typed skeleton), gen_skeleton (generic skeleton) or recv/proxy to determine the role of the process"); options.add_options()("cycle-time,t", po::value(), "Cycle time in milliseconds for sending/polling"); options.add_options()( "service_instance_manifest,s", po::value(), "Path to the com configuration file"); @@ -119,6 +119,10 @@ int main(const int argc, const char** argv) { return event_sender_receiver.RunAsSkeleton(instance_specifier, cycle_time, cycles); } + else if (mode == "gen_skeleton") + { + return event_sender_receiver.RunAsGenericSkeleton(instance_specifier, cycle_time, cycles); + } else if (mode == "recv" || mode == "proxy") { return event_sender_receiver.RunAsProxy(instance_specifier, cycle_time, cycles, false, check_sample_hash); diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp index db24972d..1445473a 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp @@ -13,6 +13,7 @@ #include "sample_sender_receiver.h" #include "score/mw/com/impl/generic_proxy.h" #include "score/mw/com/impl/generic_proxy_event.h" +#include "score/mw/com/impl/generic_skeleton.h" #include "score/mw/com/impl/handle_type.h" #include "score/concurrency/notification.h" @@ -268,6 +269,36 @@ Result> PrepareMapLaneSample(IpcBridgeSke return sample; } +Result> PrepareMapLaneSample(impl::GenericSkeletonEvent& event, + const std::size_t cycle) +{ + const std::default_random_engine::result_type seed{static_cast( + std::chrono::steady_clock::now().time_since_epoch().count())}; + std::default_random_engine rng{seed}; + + auto sample_result = event.Allocate(); + + if (!sample_result.has_value()) + { + return sample_result; + } + auto sample = std::move(sample_result).value(); + auto* typed_sample = static_cast(sample.Get()); + typed_sample->hash_value = START_HASH; + typed_sample->x = static_cast(cycle); + + std::cout << ToString("Sending sample: ", typed_sample->x, "\n"); + for (MapApiLaneData& lane : typed_sample->lanes) { + for (LaneIdType& successor : lane.successor_lanes) + { + successor = std::uniform_int_distribution()(rng); + } + + HashArray(lane.successor_lanes, typed_sample->hash_value); + } + return sample; +} + } // namespace template @@ -447,6 +478,61 @@ int EventSenderReceiver::RunAsSkeleton(const score::mw::com::InstanceSpecifier& return EXIT_SUCCESS; } +int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpecifier& instance_specifier, + const std::chrono::milliseconds cycle_time, + const std::size_t num_cycles) +{ + auto create_result = impl::GenericSkeleton::Create(instance_specifier); + if (!create_result.has_value()) + { + std::cerr << "Unable to construct skeleton: " << create_result.error() << ", bailing!\n"; + return EXIT_FAILURE; + } + auto& skeleton = create_result.value(); + + const auto event_name = "map_api_lanes_stamped"; + const SizeInfo size_info{sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped)}; + auto event_result = skeleton.AddEvent(event_name, size_info); + if (!event_result.has_value()) + { + std::cerr << "Unable to add event to skeleton: " << event_result.error() << ", bailing!\n"; + return EXIT_FAILURE; + } + auto& event = *event_result.value(); + + const auto offer_result = skeleton.OfferService(); + if (!offer_result.has_value()) + { + std::cerr << "Unable to offer service for skeleton: " << offer_result.error() << ", bailing!\n"; + return EXIT_FAILURE; + } + std::cout << "Starting to send data\n"; + + for (std::size_t cycle = 0U; cycle < num_cycles || num_cycles == 0U; ++cycle) + { + auto sample_result = PrepareMapLaneSample(event, cycle); + if (!sample_result.has_value()) + { + std::cerr << "No sample received. Exiting.\n"; + return EXIT_FAILURE; + } + auto sample = std::move(sample_result).value(); + + { + std::lock_guard lock{event_sending_mutex_}; + event.Send(std::move(sample)); + event_published_ = true; + } + std::this_thread::sleep_for(cycle_time); + } + + std::cout << "Stop offering service..."; + skeleton.StopOfferService(); + std::cout << "and terminating, bye bye\n"; + + return EXIT_SUCCESS; +} + template int EventSenderReceiver::RunAsProxy>( const score::mw::com::InstanceSpecifier&, const score::cpp::optional, diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.h b/score/mw/com/example/ipc_bridge/sample_sender_receiver.h index 541ad6de..6ce7fc04 100644 --- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.h +++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.h @@ -33,6 +33,10 @@ class EventSenderReceiver const std::chrono::milliseconds cycle_time, const std::size_t num_cycles); + int RunAsGenericSkeleton(const score::mw::com::InstanceSpecifier& instance_specifier, + const std::chrono::milliseconds cycle_time, + const std::size_t num_cycles); + template > int RunAsProxy(const score::mw::com::InstanceSpecifier& instance_specifier, diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 683d9dd6..6d94b56c 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -30,6 +30,7 @@ cc_library( ":generic_proxy_event", ":proxy_event", ":proxy_field", + ":generic_skeleton", ":skeleton_event", ":skeleton_field", ":traits", @@ -110,6 +111,82 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton", + srcs = ["generic_skeleton.cpp"], + hdrs = ["generic_skeleton.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + ":error", + "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory", + "//score/mw/com/impl/plumbing", + ], + tags = ["FFI"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = [ + ":generic_skeleton_event", + ":instance_identifier", + ":instance_specifier", + ":runtime", + ":skeleton_base", + ":skeleton_binding", + ":size_info", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "generic_skeleton_event", + srcs = ["generic_skeleton_event.cpp"], + hdrs = ["generic_skeleton_event.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = [ + "//score/mw/com/impl/bindings/lola:generic_skeleton_event", + ":generic_skeleton_event_binding", + ":skeleton_base", + ":skeleton_event_base", + ":skeleton_event_binding", + ":size_info", + "//score/mw/com/impl/plumbing:sample_allocatee_ptr", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "generic_skeleton_event_binding", + hdrs = [ + "generic_skeleton_event_binding.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl/bindings/lola:__pkg__", + "//score/mw/com/impl/bindings/mock_binding:__pkg__", + "//score/mw/com/impl/plumbing:__pkg__", + ], + deps = [ + ":skeleton_event_binding", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "size_info", + hdrs = ["size_info.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com:__pkg__", + "//score/mw/com/impl:__subpackages__", + ], +) + cc_library( name = "skeleton_event", srcs = ["skeleton_event.cpp"], @@ -381,6 +458,8 @@ cc_library( ], deps = [ ":binding_type", + ":generic_skeleton_event_binding", + ":size_info", "//score/mw/com/impl/configuration", "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/memory/shared:i_shared_memory_resource", diff --git a/score/mw/com/impl/bindings/lola/BUILD b/score/mw/com/impl/bindings/lola/BUILD index 02c5c3fe..5ec4eecc 100644 --- a/score/mw/com/impl/bindings/lola/BUILD +++ b/score/mw/com/impl/bindings/lola/BUILD @@ -240,6 +240,26 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton_event", + srcs = ["generic_skeleton_event.cpp"], + hdrs = ["generic_skeleton_event.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ + ":event", + ":skeleton", + "//score/mw/com/impl:error", + "//score/mw/com/impl:generic_skeleton_event_binding", + "//score/mw/com/impl:size_info", + "//score/mw/com/impl/tracing:skeleton_event_tracing", + ":type_erased_sample_ptrs_guard", + ], +) + cc_library( name = "skeleton", srcs = [ @@ -247,12 +267,14 @@ cc_library( "skeleton_event.cpp", "skeleton_event_properties.cpp", "skeleton_method.cpp", + "generic_skeleton_event.cpp", ], hdrs = [ "skeleton.h", "skeleton_event.h", "skeleton_event_properties.h", "skeleton_method.h", + "generic_skeleton_event.h", ], features = COMPILER_WARNING_FEATURES, tags = ["FFI"], @@ -282,6 +304,8 @@ cc_library( "//score/mw/com/impl/plumbing:sample_allocatee_ptr", "//score/mw/com/impl/tracing:skeleton_event_tracing", "//score/mw/com/impl/util:arithmetic_utils", + "//score/mw/com/impl:generic_skeleton_event_binding", + "//score/mw/com/impl:error", "@score_baselibs//score/filesystem", "@score_baselibs//score/language/futurecpp", "@score_baselibs//score/language/safecpp/safe_math", diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp new file mode 100644 index 00000000..0d22bde6 --- /dev/null +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -0,0 +1,125 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" +#include "score/mw/com/impl/bindings/lola/skeleton.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" +#include "score/mw/com/impl/runtime.h" +#include "score/mw/com/impl/tracing/skeleton_event_tracing.h" + +namespace score::mw::com::impl::lola +{ +GenericSkeletonEvent::GenericSkeletonEvent(Skeleton& parent, + const SkeletonEventProperties& event_properties, + const ElementFqId& event_fqn, + const SizeInfo& size_info) + : parent_(parent), size_info_(size_info), event_properties_(event_properties), event_fqn_(event_fqn) +{ + +} + +ResultBlank GenericSkeletonEvent::PrepareOffer() noexcept +{ + std::tie(data_storage_, control_) = + parent_.RegisterGeneric(event_fqn_, event_properties_, size_info_.size, size_info_.alignment); + PrepareOfferImpl(*this); + + return {}; +} + +Result GenericSkeletonEvent::Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value()); + control_.value().EventReady(control_slot_indicator, ++current_timestamp_); + + // Only call NotifyEvent if there are any registered receive handlers for each quality level. + // This avoids the expensive lock operation in the common case where no handlers are registered. + // Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent + // handler registration, the next Send() will pick it up. + if (qm_event_update_notifications_registered_.load() && !qm_disconnect_) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .NotifyEvent(QualityType::kASIL_QM, event_fqn_); + } + if (asil_b_event_update_notifications_registered_.load() && parent_.GetInstanceQualityType() == QualityType::kASIL_B) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .NotifyEvent(QualityType::kASIL_B, event_fqn_); + } + + return {}; +} + +Result> GenericSkeletonEvent::Allocate() noexcept +{ + if (!control_.has_value()) + { + ::score::mw::log::LogError("lola") << "Tried to allocate event, but the EventDataControl does not exist!"; + return MakeUnexpected(ComErrc::kBindingFailure); + } + const auto slot = control_.value().AllocateNextSlot(); + + if (!qm_disconnect_ && control_->GetAsilBEventDataControl().has_value() && !slot.IsValidQM()) + { + qm_disconnect_ = true; + score::mw::log::LogWarn("lola") + << __func__ << __LINE__ + << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_fqn_; + parent_.DisconnectQmConsumers(); + } + + if (slot.IsValidQM() || slot.IsValidAsilB()) + { + auto* data_storage = data_storage_.get>(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(data_storage != nullptr); + // The at() method on EventDataStorage (which is a DynamicArray) correctly handles + // the pointer arithmetic based on its template type (std::uint8_t). + // We multiply by size_info_.size here because the underlying storage is a byte array. + void* data_ptr = &data_storage->at(static_cast(slot.GetIndex()) * size_info_.size); + return lola::SampleAllocateePtr(data_ptr, control_.value(), slot); + } + else + { + if (!event_properties_.enforce_max_samples) + { + ::score::mw::log::LogError("lola") + << "GenericSkeletonEvent: Allocation of event slot failed. Hint: enforceMaxSamples was " + "disabled by config. Might be the root cause!"; + } + return MakeUnexpected(ComErrc::kBindingFailure); + } +} + +std::pair GenericSkeletonEvent::GetSizeInfo() const noexcept +{ + return {size_info_.size, size_info_.alignment}; +} + +void GenericSkeletonEvent::PrepareStopOffer() noexcept +{ + PrepareStopOfferImpl(*this); +} + +BindingType GenericSkeletonEvent::GetBindingType() const noexcept +{ + return BindingType::kLoLa; +} + +void GenericSkeletonEvent::SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept +{ + tracing_data_ = tracing_data; +} + +} // namespace score::mw::com::impl::lola \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h new file mode 100644 index 00000000..d5b9c33a --- /dev/null +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -0,0 +1,129 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/bindings/lola/element_fq_id.h" +#include "score/mw/com/impl/bindings/lola/event_data_storage.h" +#include "score/mw/com/impl/size_info.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" +#include "score/memory/shared/offset_ptr.h" +#include "score/mw/com/impl/bindings/lola/event_slot_status.h" + +#include "score/mw/com/impl/bindings/lola/transaction_log_registration_guard.h" +#include "score/mw/com/impl/bindings/lola/type_erased_sample_ptrs_guard.h" +#include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h" + +namespace score::mw::com::impl::lola +{ + +class TransactionLogRegistrationGuard; +class Skeleton; + +/// @brief The LoLa binding implementation for a generic skeleton event. +class GenericSkeletonEvent : public GenericSkeletonEventBinding +{ + public: + GenericSkeletonEvent(Skeleton& parent, + const SkeletonEventProperties& event_properties, + const ElementFqId& event_fqn, + const SizeInfo& size_info); + + Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept override; + + Result> Allocate() noexcept override; + + std::pair GetSizeInfo() const noexcept override; + + + ResultBlank PrepareOffer() noexcept override; + void PrepareStopOffer() noexcept override; + BindingType GetBindingType() const noexcept override; + void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override; + + std::size_t GetMaxSize() const noexcept override + { + return size_info_.size; + } + + private: + Skeleton& parent_; + SizeInfo size_info_; + const SkeletonEventProperties event_properties_; + const ElementFqId event_fqn_; + score::cpp::optional control_{}; + EventSlotStatus::EventTimeStamp current_timestamp_{0U}; + score::memory::shared::OffsetPtr data_storage_{nullptr}; + bool qm_disconnect_{false}; + impl::tracing::SkeletonEventTracingData tracing_data_{}; + std::atomic qm_event_update_notifications_registered_{false}; + std::atomic asil_b_event_update_notifications_registered_{false}; + std::optional transaction_log_registration_guard_{}; + std::optional type_erased_sample_ptrs_guard_{}; + + public: + // The following methods are public but intended for use by PrepareOfferImpl helper + impl::tracing::SkeletonEventTracingData& GetTracingData() + { + return tracing_data_; + } + + void EmplaceTransactionLogRegistrationGuard() + { + score::cpp::ignore = transaction_log_registration_guard_.emplace( + TransactionLogRegistrationGuard::Create(control_.value().GetQmEventDataControl())); + } + + void EmplaceTypeErasedSamplePtrsGuard() + { + score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(tracing_data_.service_element_tracing_data); + } + + void UpdateCurrentTimestamp() + { + current_timestamp_ = control_.value().GetLatestTimestamp(); + } + + Skeleton& GetParent() + { + return parent_; + } + + const ElementFqId& GetElementFQId() const + { + return event_fqn_; + } + + void SetQmNotificationsRegistered(bool value) + { + qm_event_update_notifications_registered_.store(value); + } + + void SetAsilBNotificationsRegistered(bool value) + { + asil_b_event_update_notifications_registered_.store(value); + } + + void ResetGuards() noexcept + { + type_erased_sample_ptrs_guard_.reset(); + if (control_.has_value()) + { + transaction_log_registration_guard_.reset(); + } + control_.reset(); + data_storage_ = nullptr; + } +}; + +} // namespace score::mw::com::impl::lola \ No newline at end of file diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index ccc61605..8b9a0cb3 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -26,6 +26,7 @@ #include "score/mw/com/impl/com_error.h" #include "score/mw/com/impl/configuration/lola_event_instance_deployment.h" #include "score/mw/com/impl/configuration/lola_service_instance_deployment.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" #include "score/mw/com/impl/configuration/lola_service_type_deployment.h" #include "score/mw/com/impl/configuration/quality_type.h" #include "score/mw/com/impl/runtime.h" @@ -111,6 +112,22 @@ ServiceDataStorage* GetServiceDataStorageSkeletonSide(const memory::shared::Mana return service_data_storage; } +std::optional CreateOrOpenServiceInstanceExistenceMarkerFile( + const LolaServiceInstanceId::InstanceId lola_instance_id, + const IPartialRestartPathBuilder& partial_restart_path_builder) +{ + auto service_instance_existence_marker_file_path = + partial_restart_path_builder.GetServiceInstanceExistenceMarkerFilePath(lola_instance_id); + + // The instance existence marker file can be opened in the case that another skeleton of the same service currently + // exists or that a skeleton of the same service previously crashed. We cannot determine which is true until we try + // to flock the file. Therefore, we do not take ownership on construction and take ownership later if we can + // exclusively flock the file. + bool take_ownership{false}; + return memory::shared::LockFile::CreateOrOpen(std::move(service_instance_existence_marker_file_path), + take_ownership); +} + enum class ShmObjectType : std::uint8_t { kControl_QM = 0x00, @@ -143,22 +160,6 @@ bool CreatePartialRestartDirectory(const score::filesystem::Filesystem& filesyst return true; } -std::optional CreateOrOpenServiceInstanceExistenceMarkerFile( - const LolaServiceInstanceId::InstanceId lola_instance_id, - const IPartialRestartPathBuilder& partial_restart_path_builder) -{ - auto service_instance_existence_marker_file_path = - partial_restart_path_builder.GetServiceInstanceExistenceMarkerFilePath(lola_instance_id); - - // The instance existence marker file can be opened in the case that another skeleton of the same service currently - // exists or that a skeleton of the same service previously crashed. We cannot determine which is true until we try - // to flock the file. Therefore, we do not take ownership on construction and take ownership later if we can - // exclusively flock the file. - bool take_ownership{false}; - return memory::shared::LockFile::CreateOrOpen(std::move(service_instance_existence_marker_file_path), - take_ownership); -} - std::optional CreateOrOpenServiceInstanceUsageMarkerFile( const LolaServiceInstanceId::InstanceId lola_instance_id, const IPartialRestartPathBuilder& partial_restart_path_builder) @@ -213,7 +214,7 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier, std::unique_ptr partial_restart_path_builder) { SCORE_LANGUAGE_FUTURECPP_PRECONDITION_PRD_MESSAGE(partial_restart_path_builder != nullptr, - "Skeleton::Create: partial restart path builder pointer is Null"); + "Skeleton::Create: partial restart path builder pointer is Null"); const auto partial_restart_dir_creation_result = CreatePartialRestartDirectory(filesystem, *partial_restart_path_builder); if (!partial_restart_dir_creation_result) @@ -226,12 +227,13 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier, const auto lola_instance_id = lola_service_instance_deployment.instance_id_.value().GetId(); auto service_instance_existence_marker_file = CreateOrOpenServiceInstanceExistenceMarkerFile(lola_instance_id, *partial_restart_path_builder); + if (!service_instance_existence_marker_file.has_value()) { score::mw::log::LogError("lola") << "Could not create or open service instance existence marker file."; return nullptr; } - + auto service_instance_existence_mutex_and_lock = std::make_unique>( *service_instance_existence_marker_file); @@ -242,7 +244,7 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier, "actively offering the same service instance."; return nullptr; } - + const auto& lola_service_type_deployment = GetLolaServiceTypeDeployment(identifier); // Since we were able to flock the existence marker file, it means that either we created it or the skeleton that // created it previously crashed. Either way, we take ownership of the LockFile so that it's destroyed when this @@ -919,6 +921,108 @@ void Skeleton::InitializeSharedMemoryForControl( control = memory->construct(memory->getMemoryResourceProxy()); } +EventDataControlComposite Skeleton::CreateEventControlComposite(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties) noexcept +{ + auto control_qm = control_qm_->event_controls_.emplace(std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(element_properties.number_of_slots, + element_properties.max_subscribers, + element_properties.enforce_max_samples, + control_qm_resource_->getMemoryResourceProxy())); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(control_qm.second, "Couldn't register/emplace event-meta-info in data-section."); + + EventDataControl* control_asil_result{nullptr}; + if (control_asil_resource_ != nullptr) + { + auto iterator = control_asil_b_->event_controls_.emplace( + std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(element_properties.number_of_slots, + element_properties.max_subscribers, + element_properties.enforce_max_samples, + control_asil_resource_->getMemoryResourceProxy())); + + // Suppress "AUTOSAR C++14 M7-5-1" rule. This rule declares: + // A function shall not return a reference or a pointer to an automatic variable (including parameters), defined + // within the function. + // Suppress "AUTOSAR C++14 M7-5-2": The address of an object with automatic storage shall not be assigned to + // another object that may persist after the first object has ceased to exist. + // The result pointer is still valid outside this method until Skeleton object (as a holder) is alive. + // coverity[autosar_cpp14_m7_5_1_violation] + // coverity[autosar_cpp14_m7_5_2_violation] + // coverity[autosar_cpp14_a3_8_1_violation] + control_asil_result = &iterator.first->second.data_control; + } + // clang-format off + // The lifetime of the "control_asil_result" object lasts as long as the Skeleton is alive. + // coverity[autosar_cpp14_m7_5_1_violation] + // coverity[autosar_cpp14_m7_5_2_violation] + // coverity[autosar_cpp14_a3_8_1_violation] + return EventDataControlComposite{&control_qm.first->second.data_control, control_asil_result}; +} + +std::pair, EventDataControlComposite> +Skeleton::CreateEventDataFromOpenedSharedMemory( + const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + size_t sample_size, + size_t sample_alignment) noexcept +{ + auto* data_storage = storage_resource_->construct>( + sample_size * element_properties.number_of_slots, + memory::shared::PolymorphicOffsetPtrAllocator(storage_resource_->getMemoryResourceProxy())); + + auto inserted_data_slots = storage_->events_.emplace(std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(data_storage)); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_data_slots.second, + "Couldn't register/emplace event-storage in data-section."); + + const DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)}; + void* const event_data_raw_array = data_storage->data(); + auto inserted_meta_info = storage_->events_metainfo_.emplace( + std::piecewise_construct, + std::forward_as_tuple(element_fq_id), + std::forward_as_tuple(sample_meta_info, event_data_raw_array)); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, + "Couldn't register/emplace event-meta-info in data-section."); + + return {data_storage, CreateEventControlComposite(element_fq_id, element_properties)}; +} + +std::pair, EventDataControlComposite> Skeleton::RegisterGeneric( + const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + const size_t sample_size, + const size_t sample_alignment) noexcept +{ + if (was_old_shm_region_reopened_) + { + auto [data_storage, control_composite] = OpenEventDataFromOpenedSharedMemory(element_fq_id); + + auto& event_data_control_qm = control_composite.GetQmEventDataControl(); + auto rollback_result = event_data_control_qm.GetTransactionLogSet().RollbackSkeletonTracingTransactions( + [&event_data_control_qm](const TransactionLog::SlotIndexType slot_index) { + event_data_control_qm.DereferenceEventWithoutTransactionLogging(slot_index); + }); + if (!rollback_result.has_value()) + { + ::score::mw::log::LogWarn("lola") + << "SkeletonEvent: PrepareOffer failed: Could not rollback tracing consumer after " + "crash. Disabling tracing."; + impl::Runtime::getInstance().GetTracingRuntime()->DisableTracing(); + } + + return {data_storage, control_composite}; + } + else + { + return CreateEventDataFromOpenedSharedMemory( + element_fq_id, element_properties, sample_size, sample_alignment); + } +} + ResultBlank Skeleton::OnServiceMethodsSubscribed(const ProxyInstanceIdentifier& proxy_instance_identifier, uid_t proxy_uid, const QualityType asil_level, diff --git a/score/mw/com/impl/bindings/lola/skeleton.h b/score/mw/com/impl/bindings/lola/skeleton.h index ecbc9e07..d1ba3f6e 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.h +++ b/score/mw/com/impl/bindings/lola/skeleton.h @@ -107,6 +107,12 @@ class Skeleton final : public SkeletonBinding return BindingType::kLoLa; }; + std::pair, EventDataControlComposite> RegisterGeneric( + const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + const size_t sample_size, + const size_t sample_alignment) noexcept; + /// \brief Enables dynamic registration of Events at the Skeleton. /// \tparam SampleType The type of the event /// \param element_fq_id The full qualified of the element (event or field) that shall be registered @@ -169,6 +175,15 @@ class Skeleton final : public SkeletonBinding const ElementFqId element_fq_id, const SkeletonEventProperties& element_properties); + EventDataControlComposite CreateEventControlComposite(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties) noexcept; + + std::pair, EventDataControlComposite> + CreateEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties, + size_t sample_size, + size_t sample_alignment) noexcept; + class ShmResourceStorageSizes { public: @@ -208,7 +223,7 @@ class Skeleton final : public SkeletonBinding pid_t proxy_pid); static MethodData& GetMethodData(const memory::shared::ManagedMemoryResource& resource); - /// \brief Checks whether the Proxy which sent a notification to the Skeleton that it subscribed to a method is in + /// \brief Checks whether the Proxy which sent a notification to the Skeleton that it subscribed to a method is in /// the allowed_consumers list in the configuration. bool IsProxyInAllowedConsumerList(const uid_t proxy_uid, const QualityType asil_level) const; @@ -385,44 +400,7 @@ auto Skeleton::CreateEventDataFromOpenedSharedMemory(const ElementFqId element_f std::forward_as_tuple(sample_meta_info, event_data_raw_array)); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, "Couldn't register/emplace event-meta-info in data-section."); - auto control_qm = - control_qm_->event_controls_.emplace(std::piecewise_construct, - std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(element_properties.number_of_slots, - element_properties.max_subscribers, - element_properties.enforce_max_samples, - control_qm_resource_->getMemoryResourceProxy())); - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(control_qm.second, "Couldn't register/emplace event-meta-info in data-section."); - - EventDataControl* control_asil_result{nullptr}; - if (control_asil_resource_ != nullptr) - { - auto iterator = control_asil_b_->event_controls_.emplace( - std::piecewise_construct, - std::forward_as_tuple(element_fq_id), - std::forward_as_tuple(element_properties.number_of_slots, - element_properties.max_subscribers, - element_properties.enforce_max_samples, - control_asil_resource_->getMemoryResourceProxy())); - - // Suppress "AUTOSAR C++14 M7-5-1" rule. This rule declares: - // A function shall not return a reference or a pointer to an automatic variable (including parameters), defined - // within the function. - // Suppress "AUTOSAR C++14 M7-5-2": The address of an object with automatic storage shall not be assigned to - // another object that may persist after the first object has ceased to exist. - // The result pointer is still valid outside this method until Skeleton object (as a holder) is alive. - // coverity[autosar_cpp14_m7_5_1_violation] - // coverity[autosar_cpp14_m7_5_2_violation] - // coverity[autosar_cpp14_a3_8_1_violation] - control_asil_result = &iterator.first->second.data_control; - } - // clang-format off - // The lifetime of the "control_asil_result" object lasts as long as the Skeleton is alive. - // coverity[autosar_cpp14_m7_5_1_violation] - // coverity[autosar_cpp14_m7_5_2_violation] - // coverity[autosar_cpp14_a3_8_1_violation] - return {typed_event_data_storage_ptr, EventDataControlComposite{&control_qm.first->second.data_control, control_asil_result}}; - // clang-format on + return {typed_event_data_storage_ptr, CreateEventControlComposite(element_fq_id, element_properties)}; } } // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.h b/score/mw/com/impl/bindings/lola/skeleton_event.h index cb9c035e..0ddac8ba 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event.h @@ -44,6 +44,82 @@ namespace score::mw::com::impl::lola { +namespace +{ + +template +void PrepareOfferImpl(EventType& event) +{ + const bool tracing_globally_enabled = ((impl::Runtime::getInstance().GetTracingRuntime() != nullptr) && + (impl::Runtime::getInstance().GetTracingRuntime()->IsTracingEnabled())); + if (!tracing_globally_enabled) + { + // in case tracing is globally disabled, this will never switch back to enable. Thus, we can directly disable + // all trace points for this event. This avoids any further lookups to the tracing runtime during Send() calls. + DisableAllTracePoints(event.GetTracingData()); + } + + const bool tracing_for_skeleton_event_enabled = + event.GetTracingData().enable_send || event.GetTracingData().enable_send_with_allocate; + // LCOV_EXCL_BR_START (Tool incorrectly marks the decision as "Decision couldn't be analyzed" despite all lines in + // both branches (true / false) being covered. "Decision couldn't be analyzed" only appeared after changing the code + // within the if statement (without changing the condition / tests). Suppression can be removed when bug is fixed in + // Ticket-188259). + if (tracing_for_skeleton_event_enabled) + { + // LCOV_EXCL_BR_STOP + event.EmplaceTransactionLogRegistrationGuard(); + event.EmplaceTypeErasedSamplePtrsGuard(); + } + + event.UpdateCurrentTimestamp(); + + // Register callbacks to be notified when event notification existence changes. + // This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered. + // Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access. + if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_QM) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .RegisterEventNotificationExistenceChangedCallback( + QualityType::kASIL_QM, event.GetElementFQId(), [&event](const bool has_handlers) noexcept { + event.SetQmNotificationsRegistered(has_handlers); + }); + } + if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_B) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .RegisterEventNotificationExistenceChangedCallback( + QualityType::kASIL_B, event.GetElementFQId(), [&event](const bool has_handlers) noexcept { + event.SetAsilBNotificationsRegistered(has_handlers); + }); + } +} + +template +void PrepareStopOfferImpl(EventType& event) noexcept +{ + // Unregister event notification existence changed callbacks + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, event.GetElementFQId()); + + if (event.GetParent().GetInstanceQualityType() == QualityType::kASIL_B) + { + GetBindingRuntime(BindingType::kLoLa) + .GetLolaMessaging() + .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, event.GetElementFQId()); + } + + // Reset the flags to indicate no handlers are registered + event.SetQmNotificationsRegistered(false); + event.SetAsilBNotificationsRegistered(false); + + event.ResetGuards(); +} + +} // namespace /// \brief Represents a binding specific instance (LoLa) of an event within a skeleton. It can be used to send events /// via Shared Memory. It will be created via a Factory Method, that will instantiate this class based on deployment @@ -110,6 +186,52 @@ class SkeletonEvent final : public SkeletonEventBinding return event_fqn_; }; + // The following methods are public but intended for use by PrepareOfferImpl helper + impl::tracing::SkeletonEventTracingData& GetTracingData() + { + return skeleton_event_tracing_data_; + } + + void EmplaceTransactionLogRegistrationGuard() + { + score::cpp::ignore = transaction_log_registration_guard_.emplace( + TransactionLogRegistrationGuard::Create(event_data_control_composite_->GetQmEventDataControl())); + } + + void EmplaceTypeErasedSamplePtrsGuard() + { + score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(skeleton_event_tracing_data_.service_element_tracing_data); + } + + void UpdateCurrentTimestamp() + { + current_timestamp_ = event_data_control_composite_.value().GetLatestTimestamp(); + } + + Skeleton& GetParent() + { + return parent_; + } + + void SetQmNotificationsRegistered(bool value) + { + qm_event_update_notifications_registered_.store(value); + } + + void SetAsilBNotificationsRegistered(bool value) + { + asil_b_event_update_notifications_registered_.store(value); + } + + void ResetGuards() noexcept + { + type_erased_sample_ptrs_guard_.reset(); + if (event_data_control_composite_.has_value()) + { + transaction_log_registration_guard_.reset(); + } + } + private: Skeleton& parent_; const ElementFqId event_fqn_; @@ -285,41 +407,8 @@ ResultBlank SkeletonEvent::PrepareOffer() noexcept SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_.has_value(), "Defensive programming as event_data_control_composite_ is set by Register above."); - current_timestamp_ = event_data_control_composite_.value().GetLatestTimestamp(); - const bool tracing_for_skeleton_event_enabled = - skeleton_event_tracing_data_.enable_send || skeleton_event_tracing_data_.enable_send_with_allocate; - // LCOV_EXCL_BR_START (Tool incorrectly marks the decision as "Decision couldn't be analyzed" despite all lines in - // both branches (true / false) being covered. "Decision couldn't be analyzed" only appeared after changing the code - // within the if statement (without changing the condition / tests). Suppression can be removed when bug is fixed in - // Ticket-188259). - if (tracing_for_skeleton_event_enabled) - { - // LCOV_EXCL_BR_STOP - score::cpp::ignore = transaction_log_registration_guard_.emplace( - TransactionLogRegistrationGuard::Create(event_data_control_composite_->GetQmEventDataControl())); - score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(skeleton_event_tracing_data_.service_element_tracing_data); - } - - // Register callbacks to be notified when event notification existence changes. - // This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered. - // Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access. - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .RegisterEventNotificationExistenceChangedCallback( - QualityType::kASIL_QM, event_fqn_, [this](const bool has_handlers) noexcept { - qm_event_update_notifications_registered_.store(has_handlers); - }); - - if (parent_.GetInstanceQualityType() == QualityType::kASIL_B) - { - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .RegisterEventNotificationExistenceChangedCallback( - QualityType::kASIL_B, event_fqn_, [this](const bool has_handlers) noexcept { - asil_b_event_update_notifications_registered_.store(has_handlers); - }); - } + PrepareOfferImpl(*this); return {}; } @@ -333,27 +422,7 @@ template // coverity[autosar_cpp14_a15_5_3_violation : FALSE] void SkeletonEvent::PrepareStopOffer() noexcept { - // Unregister event notification existence changed callbacks - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, event_fqn_); - - if (parent_.GetInstanceQualityType() == QualityType::kASIL_B) - { - GetBindingRuntime(BindingType::kLoLa) - .GetLolaMessaging() - .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, event_fqn_); - } - - // Reset the flags to indicate no handlers are registered - qm_event_update_notifications_registered_.store(false); - asil_b_event_update_notifications_registered_.store(false); - - type_erased_sample_ptrs_guard_.reset(); - if (event_data_control_composite_.has_value()) - { - transaction_log_registration_guard_.reset(); - } + PrepareStopOfferImpl(*this); } } // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/com_error.h b/score/mw/com/impl/com_error.h index 20bcf4e2..4c8aea5a 100644 --- a/score/mw/com/impl/com_error.h +++ b/score/mw/com/impl/com_error.h @@ -58,6 +58,7 @@ enum class ComErrc : score::result::ErrorCode kFindServiceHandlerFailure, kInvalidHandle, kCallQueueFull, + kServiceElementAlreadyExists, }; /** @@ -200,6 +201,9 @@ class ComErrorDomain final : public score::result::ErrorDomain case static_cast(ComErrc::kCallQueueFull): return "Call queue of service method is already full."; // coverity[autosar_cpp14_m6_4_5_violation] + case static_cast(ComErrc::kServiceElementAlreadyExists): + return "A service element (event, field, method) with the same name already exists."; + // coverity[autosar_cpp14_m6_4_5_violation] default: return "unknown future error"; } diff --git a/score/mw/com/impl/generic_skeleton.cpp b/score/mw/com/impl/generic_skeleton.cpp new file mode 100644 index 00000000..b1571fd7 --- /dev/null +++ b/score/mw/com/impl/generic_skeleton.cpp @@ -0,0 +1,97 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/generic_skeleton.h" + +#include "score/mw/com/impl/com_error.h" +#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h" +#include "score/mw/com/impl/plumbing/skeleton_binding_factory.h" +#include "score/mw/com/impl/runtime.h" +#include "score/mw/com/impl/size_info.h" +#include "score/mw/com/impl/skeleton_binding.h" + +namespace score::mw::com::impl +{ + +Result GenericSkeleton::Create( + const InstanceSpecifier& specifier, + MethodCallProcessingMode mode) noexcept +{ + const auto instance_identifier_result = GetInstanceIdentifier(specifier); + + if (!instance_identifier_result.has_value()) + { + score::mw::log::LogFatal("lola") << "Failed to resolve instance identifier from instance specifier"; + std::terminate(); + } + + return Create(instance_identifier_result.value(),mode); +} + +Result GenericSkeleton::Create( + const InstanceIdentifier& identifier, + MethodCallProcessingMode mode) noexcept +{ + auto binding = SkeletonBindingFactory::Create(identifier); + if (!binding) + { + return MakeUnexpected(ComErrc::kBindingFailure); + } + + return GenericSkeleton(identifier, std::move(binding),mode); +} + +Result GenericSkeleton::AddEvent(std::string_view name, const SizeInfo& size_info) noexcept +{ + auto skeleton_view = SkeletonBaseView{*this}; + if (skeleton_view.IsOffered()) + { + // It is not allowed to add events after the service has been offered. + return MakeUnexpected(ComErrc::kServiceInstanceAlreadyOffered); + } + + auto event_binding_result = GenericSkeletonEventBindingFactory::Create(*this, name, size_info); + if (!event_binding_result.has_value()) + { + return MakeUnexpected(ComErrc::kBindingFailure); + } + + auto emplace_result = owned_events_.emplace( + name, std::make_unique(*this, name, std::move(event_binding_result).value())); + if (!emplace_result.second) + { + // An event with this name has already been added. + return MakeUnexpected(ComErrc::kServiceElementAlreadyExists); + } + + return emplace_result.first->second.get(); +} + +Result GenericSkeleton::OfferService() noexcept +{ + return SkeletonBase::OfferService(); +} + +void GenericSkeleton::StopOfferService() noexcept +{ + SkeletonBase::StopOfferService(); +} + +GenericSkeleton::GenericSkeleton(const InstanceIdentifier& identifier, + std::unique_ptr binding, + MethodCallProcessingMode mode) + : SkeletonBase(std::move(binding), identifier,mode) +{ +} + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h new file mode 100644 index 00000000..4913b83b --- /dev/null +++ b/score/mw/com/impl/generic_skeleton.h @@ -0,0 +1,71 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/generic_skeleton_event.h" +#include "score/mw/com/impl/instance_identifier.h" +#include "score/mw/com/impl/instance_specifier.h" +#include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/size_info.h" + +#include "score/result/result.h" + +#include +#include + +namespace score::mw::com::impl +{ + +class SkeletonBinding; + + +class GenericSkeleton : public SkeletonBase +{ + public: + /// @brief Creates a GenericSkeleton for a given instance specifier. + /// @param specifier The instance specifier. + /// @return A GenericSkeleton or an error. + static Result Create(const InstanceSpecifier& specifier, + MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; + + /// @brief Creates a GenericSkeleton for a given instance identifier. + /// @param identifier The instance identifier. + /// @return A GenericSkeleton or an error. + static Result Create(const InstanceIdentifier& identifier, + MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept; + + /// @brief Adds a type-erased event to the skeleton. + /// + /// This must be called before OfferService(). + /// + /// @param name The name of the event. + /// @param size_info The size and alignment requirements for the event's sample data. + /// @return A reference to the created event or an error. + Result AddEvent(std::string_view name, const SizeInfo& size_info) noexcept; + + /// @brief Offers the service instance. + /// @return A blank result, or an error if offering fails. + Result OfferService() noexcept; + + /// @brief Stops offering the service instance. + void StopOfferService() noexcept; + + private: + GenericSkeleton(const InstanceIdentifier& identifier, + std::unique_ptr binding, + MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent); + + std::map> owned_events_; +}; + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp new file mode 100644 index 00000000..4fb1156d --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -0,0 +1,94 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/generic_skeleton_event.h" +#include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" +#include "score/mw/com/impl/tracing/skeleton_event_tracing.h" +#include "score/mw/com/impl/skeleton_base.h" + +#include + +namespace score::mw::com::impl +{ + +GenericSkeletonEvent::GenericSkeletonEvent(SkeletonBase& skeleton_base, + const std::string_view event_name, + std::unique_ptr binding) + : SkeletonEventBase(skeleton_base, event_name, std::move(binding)) +{ + SkeletonBaseView{skeleton_base}.RegisterEvent(event_name, *this); + + if (binding_ != nullptr) + { + const SkeletonBaseView skeleton_base_view{skeleton_base}; + const auto& instance_identifier = skeleton_base_view.GetAssociatedInstanceIdentifier(); + const auto binding_type = binding_->GetBindingType(); + auto tracing_data = + tracing::GenerateSkeletonTracingStructFromEventConfig(instance_identifier, binding_type, event_name); + binding_->SetSkeletonEventTracingData(tracing_data); + } +} + +Result GenericSkeletonEvent::Send(SampleAllocateePtr sample) noexcept +{ + if (!service_offered_flag_.IsSet()) + { + score::mw::log::LogError("lola") + << "GenericSkeletonEvent::Send failed as Event has not yet been offered or has been stop offered"; + return MakeUnexpected(ComErrc::kNotOffered); + } + + auto* const lola_sample_ptr = SampleAllocateePtrView{sample}.As>(); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(lola_sample_ptr != nullptr); + + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(binding_ != nullptr, "Binding is not initialized!"); + auto* const binding = static_cast(binding_.get()); + + const auto send_result = binding->Send(lola_sample_ptr->GetReferencedSlot()); + if (!send_result.has_value()) + { + score::mw::log::LogError("lola") << "GenericSkeletonEvent::Send failed: " << send_result.error().Message() + << ": " << send_result.error().UserMessage(); + return MakeUnexpected(ComErrc::kBindingFailure); + } + return send_result; +} + +Result> GenericSkeletonEvent::Allocate() noexcept +{ + if (!service_offered_flag_.IsSet()) + { + score::mw::log::LogError("lola") + << "GenericSkeletonEvent::Allocate failed as Event has not yet been offered or has been stop offered"; + return MakeUnexpected(ComErrc::kNotOffered); + } + auto* const binding = static_cast(binding_.get()); + auto result = binding->Allocate(); // This now returns a Result> + if (!result.has_value()) + { + score::mw::log::LogError("lola") << "SkeletonEvent::Allocate failed: " << result.error().Message() + << ": " << result.error().UserMessage(); + + return MakeUnexpected>(ComErrc::kSampleAllocationFailure); + } + return MakeSampleAllocateePtr(std::move(result.value())); +} + +SizeInfo GenericSkeletonEvent::GetSizeInfo() const noexcept +{ + const auto* const binding = static_cast(binding_.get()); + const auto size_info_pair = binding->GetSizeInfo(); + return {size_info_pair.first, size_info_pair.second}; +} + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event.h b/score/mw/com/impl/generic_skeleton_event.h new file mode 100644 index 00000000..a4a12af0 --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_event.h @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/skeleton_event_base.h" +#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" +#include "score/mw/com/impl/size_info.h" + +#include "score/result/result.h" + +namespace score::mw::com::impl +{ + +class GenericSkeletonEventBinding; + + +class GenericSkeletonEvent : public SkeletonEventBase +{ + public: + GenericSkeletonEvent(SkeletonBase& skeleton_base, + const std::string_view event_name, + std::unique_ptr binding); + + + Result Send(SampleAllocateePtr sample) noexcept; + + + Result> Allocate() noexcept; + + + SizeInfo GetSizeInfo() const noexcept; +}; + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_event_binding.h b/score/mw/com/impl/generic_skeleton_event_binding.h new file mode 100644 index 00000000..7372f91e --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_event_binding.h @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include "score/mw/com/impl/skeleton_event_binding.h" +#include "score/mw/com/impl/bindings/lola/sample_allocatee_ptr.h" +#include "score/mw/com/impl/bindings/lola/control_slot_composite_indicator.h" +#include "score/result/result.h" + +#include + +namespace score::mw::com::impl +{ + +class GenericSkeletonEventBinding : public SkeletonEventBindingBase +{ + public: + virtual Result Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0; + + virtual Result> Allocate() noexcept = 0; + + virtual std::pair GetSizeInfo() const noexcept = 0; +}; + +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_test.cpp b/score/mw/com/impl/generic_skeleton_test.cpp new file mode 100644 index 00000000..072717d9 --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_test.cpp @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/generic_skeleton.h" + +#include "score/mw/com/impl/bindings/mock_binding/skeleton.h" +#include "score/mw/com/impl/runtime_mock.h" +#include "score/mw/com/impl/test/dummy_instance_identifier_builder.h" +#include "score/mw/com/impl/test/runtime_mock_guard.h" +#include "score/mw/com/impl/test/skeleton_binding_factory_mock_guard.h" + +#include +#include + +namespace score::mw::com::impl +{ +namespace +{ + +using ::testing::_; +using ::testing::ByMove; +using ::testing::Return; + +class GenericSkeletonTest : public ::testing::Test +{ + public: + GenericSkeletonTest() + { + auto skeleton_binding_mock = std::make_unique(); + skeleton_binding_mock_ = skeleton_binding_mock.get(); + + ON_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)) + .WillByDefault(Return(ByMove(std::move(skeleton_binding_mock)))); + } + + RuntimeMockGuard runtime_mock_guard_{}; + SkeletonBindingFactoryMockGuard skeleton_binding_factory_mock_guard_{}; + mock_binding::Skeleton* skeleton_binding_mock_{nullptr}; + DummyInstanceIdentifierBuilder dummy_instance_identifier_builder_{}; +}; + +TEST_F(GenericSkeletonTest, CreateWithInstanceSpecifier) +{ + auto instance_specifier = InstanceSpecifier::Create("a/b/c").value(); + auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); + + EXPECT_CALL(runtime_mock_guard_.runtime_mock_, ResolveInstanceIdentifier(instance_specifier)) + .WillOnce(Return(instance_identifier)); + + auto skeleton_result = GenericSkeleton::Create(instance_specifier); + ASSERT_TRUE(skeleton_result.has_value()); +} + +TEST_F(GenericSkeletonTest, CreateWithInstanceIdentifier) +{ + auto instance_identifier = dummy_instance_identifier_builder_.CreateInstanceIdentifier(); + auto skeleton_result = GenericSkeleton::Create(instance_identifier); + ASSERT_TRUE(skeleton_result.has_value()); +} + +TEST_F(GenericSkeletonTest, AddEvent) +{ + auto skeleton = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()).value(); + const SizeInfo size_info{16, 8}; + auto event_result = skeleton.AddEvent("MyEvent", size_info); + ASSERT_TRUE(event_result.has_value()); + + // Adding the same event again fails + auto second_event_result = skeleton.AddEvent("MyEvent", size_info); + ASSERT_FALSE(second_event_result.has_value()); +} + +TEST_F(GenericSkeletonTest, OfferAndStopOfferService) +{ + auto skeleton = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()).value(); + + EXPECT_CALL(*skeleton_binding_mock_, OfferService()).WillOnce(Return(score::result::Blank{})); + auto offer_result = skeleton.OfferService(); + EXPECT_TRUE(offer_result.has_value()); + + EXPECT_CALL(*skeleton_binding_mock_, StopOfferService()); + skeleton.StopOfferService(); +} + +TEST_F(GenericSkeletonTest, CreateFailsIfBindingFails) +{ + EXPECT_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)).WillOnce(Return(ByMove(nullptr))); + auto skeleton_result = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateInstanceIdentifier()); + ASSERT_FALSE(skeleton_result.has_value()); + EXPECT_EQ(skeleton_result.error(), ComErrc::kBindingFailure); +} + +} // namespace +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/BUILD b/score/mw/com/impl/plumbing/BUILD index 67128aa3..4bd33d44 100644 --- a/score/mw/com/impl/plumbing/BUILD +++ b/score/mw/com/impl/plumbing/BUILD @@ -443,6 +443,21 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton_event_binding_factory", + hdrs = ["generic_skeleton_event_binding_factory.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ + "//score/mw/com/impl:generic_skeleton_event_binding", + "//score/mw/com/impl:skeleton_base", + "//score/mw/com/impl/plumbing:skeleton_service_element_binding_factory_impl", + ], +) + cc_library( name = "runtime", srcs = [ diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h new file mode 100644 index 00000000..438e08bc --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H +#define SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H + +#include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/size_info.h" +#include "score/mw/com/impl/service_element_type.h" // Added for ServiceElementType +#include "score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h" + +#include "score/result/result.h" + +#include +#include + +namespace score::mw::com::impl +{ + +class GenericSkeletonEventBindingFactory +{ + public: + static Result> Create(SkeletonBase& skeleton_base, + std::string_view event_name, + const SizeInfo& size_info) noexcept + { + const auto& instance_identifier = SkeletonBaseView{skeleton_base}.GetAssociatedInstanceIdentifier(); + return CreateSkeletonServiceElement( + instance_identifier, + skeleton_base, + event_name, + size_info); + } +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h index c12d712d..8205b0de 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -338,6 +339,108 @@ auto MakeSampleAllocateePtr(T ptr) noexcept -> SampleAllocateePtr{std::move(ptr)}; } +/// \brief Template specialization of SampleAllocateePtr for void. +template <> +class SampleAllocateePtr +{ + public: + using pointer = void*; + using element_type = void; + + constexpr SampleAllocateePtr() noexcept : SampleAllocateePtr(score::cpp::blank{}) {} + constexpr explicit SampleAllocateePtr(std::nullptr_t) noexcept : SampleAllocateePtr() {} + + SampleAllocateePtr(const SampleAllocateePtr&) = delete; + SampleAllocateePtr& operator=(const SampleAllocateePtr&) & = delete; + + SampleAllocateePtr(SampleAllocateePtr&& other) noexcept : SampleAllocateePtr() + { + this->Swap(other); + } + + SampleAllocateePtr& operator=(SampleAllocateePtr&& other) & noexcept + { + this->Swap(other); + return *this; + } + + SampleAllocateePtr& operator=(std::nullptr_t) noexcept + { + reset(); + return *this; + } + + ~SampleAllocateePtr() noexcept = default; + + void reset() noexcept + { + auto visitor = score::cpp::overload( + [](lola::SampleAllocateePtr& internal_ptr) noexcept -> void { internal_ptr.reset(); }, + [](const score::cpp::blank&) noexcept -> void {}); + std::visit(visitor, internal_); + } + + void Swap(SampleAllocateePtr& other) noexcept + { + using std::swap; + swap(internal_, other.internal_); + } + + pointer Get() const noexcept + { + auto visitor = score::cpp::overload( + [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, + [](const score::cpp::blank&) noexcept -> pointer { return nullptr; }); + return std::visit(visitor, internal_); + } + + explicit operator bool() const noexcept + { + auto visitor = score::cpp::overload( + [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> bool { + return static_cast(internal_ptr); + }, + [](const score::cpp::blank&) noexcept -> bool { return false; }); + return std::visit(visitor, internal_); + } + + // operator* is intentionally omitted for void specialization. + + pointer operator->() const noexcept + { + auto visitor = score::cpp::overload( + [](const lola::SampleAllocateePtr& internal_ptr) noexcept -> pointer { return internal_ptr.get(); }, + [](const score::cpp::blank&) noexcept -> pointer { + std::terminate(); + return nullptr; + }); + return std::visit(visitor, internal_); + } + + private: + template + constexpr explicit SampleAllocateePtr(T ptr) : internal_{std::move(ptr)} + { + } + + template + friend auto MakeSampleAllocateePtr(T ptr) noexcept -> SampleAllocateePtr; + + template + friend class SampleAllocateePtrView; + + template + friend class SampleAllocateePtrMutableView; + + std::variant> internal_; +}; + +template <> +inline void swap(SampleAllocateePtr& lhs, SampleAllocateePtr& rhs) noexcept +{ + lhs.Swap(rhs); +} + /// \brief SampleAllocateePtr is user facing, in order to interact with its internals we provide a view towards it template class SampleAllocateePtrView diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h index 060852bd..28dd05f9 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h @@ -15,6 +15,7 @@ #include "score/mw/com/impl/bindings/lola/element_fq_id.h" #include "score/mw/com/impl/bindings/lola/skeleton.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" #include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" #include "score/mw/com/impl/configuration/binding_service_type_deployment.h" #include "score/mw/com/impl/configuration/lola_service_instance_deployment.h" @@ -29,6 +30,7 @@ #include #include +#include #include #include #include @@ -65,9 +67,6 @@ lola::SkeletonEventProperties GetSkeletonEventProperties( lola_service_element_instance_deployment.max_subscribers_.value(), lola_service_element_instance_deployment.enforce_max_samples_}; } - -} // namespace detail - template // Suppress "AUTOSAR C++14 A15-5-3" rule finding. This rule states: "The std::terminate() function shall // not be called implicitly.". std::visit Throws std::bad_variant_access if @@ -77,9 +76,10 @@ template >& size_info) noexcept -> std::unique_ptr { static_assert(element_type != ServiceElementType::INVALID); @@ -88,7 +88,7 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, using ReturnType = std::unique_ptr; auto visitor = score::cpp::overload( - [identifier_view, &parent, &service_element_name]( + [identifier_view, &parent, &service_element_name, &size_info]( const LolaServiceTypeDeployment& lola_service_type_deployment) -> ReturnType { auto* const lola_parent = dynamic_cast(SkeletonBaseView{parent}.GetBinding()); if (lola_parent == nullptr) @@ -114,8 +114,18 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, lola_service_instance_deployment.instance_id_.value().GetId(), element_type}; - return std::make_unique( - *lola_parent, element_fq_id, service_element_name, skeleton_event_properties); + if constexpr (std::is_same_v) + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(size_info.has_value()); + return std::make_unique( + *lola_parent, skeleton_event_properties, element_fq_id, size_info.value().get()); + } + else + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(!size_info.has_value()); + return std::make_unique( + *lola_parent, element_fq_id, service_element_name, skeleton_event_properties); + } }, [](const SomeIpServiceInstanceDeployment&) noexcept -> ReturnType { return nullptr; @@ -132,6 +142,39 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, return std::visit(visitor, identifier_view.GetServiceTypeDeployment().binding_info_); } +} // namespace detail + + +/// @brief Overload for typed skeletons (which do not have a SizeInfo). +template +auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, + SkeletonBase& parent, + const std::string_view service_element_name) noexcept + -> std::unique_ptr +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE((!std::is_same_v), + "This overload is for typed skeletons only. Generic skeletons must provide a SizeInfo."); + return detail::CreateSkeletonServiceElementImpl( + identifier, parent, service_element_name, score::cpp::nullopt); +} + +/// @brief Overload for generic skeletons (which require a SizeInfo). +template +auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, + SkeletonBase& parent, + const std::string_view service_element_name, + const SizeInfo& size_info) noexcept + -> std::unique_ptr +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE((std::is_same_v), + "This overload is for generic skeletons only. Typed skeletons must not provide a SizeInfo."); + return detail::CreateSkeletonServiceElementImpl( + identifier, + parent, + service_element_name, + std::cref(size_info)); +} + } // namespace score::mw::com::impl #endif // SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H diff --git a/score/mw/com/impl/size_info.h b/score/mw/com/impl/size_info.h new file mode 100644 index 00000000..d69131fa --- /dev/null +++ b/score/mw/com/impl/size_info.h @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#pragma once + +#include + +namespace score::mw::com +{ +/// @brief A struct to hold size and alignment information for generic type-erased data. +struct SizeInfo +{ + size_t size; + size_t alignment; +}; + +} // namespace score::mw::com \ No newline at end of file diff --git a/score/mw/com/impl/skeleton_base.h b/score/mw/com/impl/skeleton_base.h index cdccdc44..10121c31 100644 --- a/score/mw/com/impl/skeleton_base.h +++ b/score/mw/com/impl/skeleton_base.h @@ -149,6 +149,11 @@ class SkeletonBaseView return skeleton_base_.binding_.get(); } + bool IsOffered() const + { + return skeleton_base_.service_offered_flag_.IsSet(); + } + void RegisterEvent(const std::string_view event_name, SkeletonEventBase& event) { const auto result = skeleton_base_.events_.emplace(event_name, event); diff --git a/score/mw/com/impl/skeleton_binding.h b/score/mw/com/impl/skeleton_binding.h index bc111ebe..f4e42079 100644 --- a/score/mw/com/impl/skeleton_binding.h +++ b/score/mw/com/impl/skeleton_binding.h @@ -16,7 +16,9 @@ #include "score/memory/shared/i_shared_memory_resource.h" #include "score/result/result.h" #include "score/mw/com/impl/binding_type.h" +#include "score/mw/com/impl/size_info.h" #include "score/mw/com/impl/service_element_type.h" +#include "score/mw/com/impl/generic_skeleton_event_binding.h" #include #include @@ -96,6 +98,7 @@ class SkeletonBinding /// \brief Gets the binding type of the binding virtual BindingType GetBindingType() const noexcept = 0; + }; } // namespace score::mw::com::impl