Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,24 @@ class "mw::com::impl::GenericSkeletonEvent" #yellow {
+Send(SampleAllocateePtr<void> sample): Result<void>
+Allocate(): Result<SampleAllocateePtr<void>>
+GetSizeInfo() const : DataTypeMetaInfo
+SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback<void(bool)> callback): Result<void>
+UnsetReceiveHandlerRegistrationChangedHandler(): Result<void>
}

abstract class "GenericSkeletonEventBinding" #yellow {
+{abstract} Send(SampleAllocateePtr<void> sample) = 0: Result<void>
+{abstract} Allocate() = 0: Result<SampleAllocateePtr<void>>
+{abstract} GetSizeInfo() const = 0: std::pair<size_t, size_t>
+{abstract} SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback<void(bool)> callback) = 0: Result<void>
+{abstract} UnsetReceiveHandlerRegistrationChangedHandler() = 0: Result<void>
}

class "lola::SkeletonEventCommon" #yellow {
-parent_: lola::Skeleton&
-element_fq_id_: ElementFqId
-control_: score::cpp::optional<EventDataControlComposite>&
-current_timestamp_: EventSlotStatus::EventTimeStamp&
-receive_handler_registration_changed_callback_: std::optional<lola::IMessagePassingService::HandlerStatusChangeCallback>
+SkeletonEventCommon(lola::Skeleton&, const ElementFqId&, score::cpp::optional<EventDataControlComposite>&, EventSlotStatus::EventTimeStamp&, impl::tracing::SkeletonEventTracingData)
+PrepareOfferCommon(): void
+PrepareStopOfferCommon(): void
Expand All @@ -132,6 +137,8 @@ class "lola::SkeletonEventCommon" #yellow {
+IsQmNotificationsRegistered(): bool
+IsAsilBNotificationsRegistered(): bool
+GetTracingData(): impl::tracing::SkeletonEventTracingData&
+SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback<void(bool)>): Result<void>
+UnsetReceiveHandlerRegistrationChangedHandler(): Result<void>
}

class "lola::GenericSkeletonEvent" #yellow {
Expand All @@ -144,6 +151,8 @@ class "lola::GenericSkeletonEvent" #yellow {
+GetBindingType(): BindingType
+SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data): void
+GetMaxSize() const : std::size_t
+SetReceiveHandlerNotificationCallback(score::cpp::callback<void(bool)>): Result<void>
+UnsetReceiveHandlerRegistrationChangedHandler(): Result<void>
-skeleton_event_common_ : lola::SkeletonEventCommon
}

Expand Down
13 changes: 13 additions & 0 deletions score/mw/com/impl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ cc_library(
"//score/mw/com/impl/plumbing:__pkg__",
],
deps = [
":receive_handler_registration_changed_handler",
":skeleton_event_binding",
"@score_baselibs//score/result",
],
Expand Down Expand Up @@ -835,6 +836,18 @@ cc_library(
deps = ["@score_baselibs//score/language/futurecpp"],
)

cc_library(
name = "receive_handler_registration_changed_handler",
srcs = ["receive_handler_registration_changed_handler.cpp"],
hdrs = ["receive_handler_registration_changed_handler.h"],
features = COMPILER_WARNING_FEATURES,
tags = ["FFI"],
visibility = [
"//score/mw/com:__subpackages__",
],
deps = ["@score_baselibs//score/language/futurecpp"],
)

cc_library(
name = "subscription_state_change_handler",
srcs = ["subscription_state_change_handler.cpp"],
Expand Down
10 changes: 10 additions & 0 deletions score/mw/com/impl/bindings/lola/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,16 @@ cc_unit_test(
],
)

cc_unit_test(
name = "skeleton_event_common_test",
srcs = ["skeleton_event_common_test.cpp"],
features = COMPILER_WARNING_FEATURES,
deps = [
":skeleton",
"//score/mw/com/impl/bindings/lola/test:skeleton_event_test_resources",
],
)

cc_unit_test(
name = "proxy_test",
srcs = ["proxy_test.cpp"],
Expand Down
15 changes: 15 additions & 0 deletions score/mw/com/impl/bindings/lola/generic_skeleton_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding
return size_info_.size;
}

/// \brief Set callback, to get notified, when either the 1st event-notification has been registered or the last
/// event-notification has been unregistered.
/// \detail This extension has been added to GenericSkeletonEvent, because we are only using it so far in the LoLa
/// gateway use case.
Result<void> SetReceiveHandlerRegistrationChangedHandler(
ReceiveHandlerRegistrationChangedCallback callback) noexcept override
{
return skeleton_event_common_.SetReceiveHandlerRegistrationChangedHandler(std::move(callback));
}

Result<void> UnsetReceiveHandlerRegistrationChangedHandler() noexcept override
{
return skeleton_event_common_.UnsetReceiveHandlerRegistrationChangedHandler();
}

private:
DataTypeMetaInfo size_info_;
std::uint8_t* event_data_storage_;
Expand Down
34 changes: 34 additions & 0 deletions score/mw/com/impl/bindings/lola/skeleton_event_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#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/configuration/quality_type.h"
#include "score/mw/com/impl/generic_skeleton_event_binding.h"
#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h"
#include "score/mw/com/impl/runtime.h"
#include "score/mw/com/impl/skeleton_event_binding.h"
Expand Down Expand Up @@ -56,6 +57,8 @@ class SkeletonEventCommon
// private members and used for testing purposes only.
friend class SkeletonEventAttorney<SampleType>;

using ReceiveHandlerRegistrationChangedCallback = lola::IMessagePassingService::HandlerStatusChangeCallback;

public:
SkeletonEventCommon(Skeleton& parent,
const std::string_view event_name,
Expand Down Expand Up @@ -109,6 +112,25 @@ class SkeletonEventCommon
return consumer_control_local_view_qm_.value();
}

/// \brief Set callback, to get notified, when either the 1st event-notification has been registered or the last
/// event-notification has been unregistered.
/// \detail This extension has been added to GenericSkeletonEvent, because we are only using it so far in the LoLa
/// gateway use case.
Result<void> SetReceiveHandlerRegistrationChangedHandler(
ReceiveHandlerRegistrationChangedCallback callback) noexcept
{
static_assert(std::is_same_v<decltype(callback), ReceiveHandlerRegistrationChangedCallback>,
"Callback type mismatch between GenericSkeletonEvent and lola::GenericSkeletonEvent");
receive_handler_registration_changed_callback_ = std::move(callback);
return {};
}

Result<void> UnsetReceiveHandlerRegistrationChangedHandler()
{
receive_handler_registration_changed_callback_.reset();
return {};
}

private:
Skeleton& parent_;
std::string_view event_name_;
Expand Down Expand Up @@ -142,6 +164,8 @@ class SkeletonEventCommon
/// PrepareStopOfferCommon().
std::optional<TransactionLogRegistrationGuard> transaction_log_registration_guard_{};
std::optional<tracing::TypeErasedSamplePtrsGuard> type_erased_sample_ptrs_guard_{};
// ReceiveHandlerRegistrationChangedCallback
std::optional<ReceiveHandlerRegistrationChangedCallback> receive_handler_registration_changed_callback_;

void EmplaceTransactionLogRegistrationGuard(TransactionLogSet& transaction_log_set);
void EmplaceTypeErasedSamplePtrsGuard();
Expand Down Expand Up @@ -214,6 +238,11 @@ void SkeletonEventCommon<SampleType>::PrepareOfferCommon(EventControl& event_con
.RegisterEventNotificationExistenceChangedCallback(
QualityType::kASIL_QM, element_fq_id_, [this](const bool has_handlers) noexcept {
SetQmNotificationsRegistered(has_handlers);
if (receive_handler_registration_changed_callback_.has_value())
{
const bool qm_registered = qm_event_update_notifications_registered_.load();
receive_handler_registration_changed_callback_.value()(qm_registered);
}
});

if (parent_.GetInstanceQualityType() == QualityType::kASIL_B)
Expand All @@ -223,6 +252,11 @@ void SkeletonEventCommon<SampleType>::PrepareOfferCommon(EventControl& event_con
.RegisterEventNotificationExistenceChangedCallback(
QualityType::kASIL_B, element_fq_id_, [this](const bool has_handlers) noexcept {
SetAsilBNotificationsRegistered(has_handlers);
if (receive_handler_registration_changed_callback_.has_value())
{
const bool asil_b_registered = asil_b_event_update_notifications_registered_.load();
receive_handler_registration_changed_callback_.value()(asil_b_registered);
}
});
}
}
Expand Down
157 changes: 157 additions & 0 deletions score/mw/com/impl/bindings/lola/skeleton_event_common_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/********************************************************************************
* Copyright (c) 2026 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/skeleton_event_common.h"
#include "score/mw/com/impl/bindings/lola/test/skeleton_event_test_resources.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

namespace score::mw::com::impl::lola
{
namespace
{

using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Return;
using ::testing::ReturnRef;

class SkeletonEventCommonFixture : public SkeletonEventFixture
{
public:
SkeletonEventCommonFixture()
{
ON_CALL(runtime_mock_, GetServiceDiscovery()).WillByDefault(ReturnRef(service_discovery_mock_));
}

void InitialiseSkeletonEventCommon(const ElementFqId element_fq_id,
const std::string& service_element_name,
const std::size_t max_samples,
const std::uint8_t max_subscribers,
const bool enforce_max_samples,
const QualityType quality_type,
impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data)
{
// We defer initialisation of the Skeleton to InitialiseSkeletonEventCommon to allow test fixtures to set any
// mocked expectations before creating the skeleton.
InitialiseSkeleton(GetValidInstanceIdentifierForEventCommon(quality_type));

SkeletonBinding::SkeletonEventBindings events{};
SkeletonBinding::SkeletonFieldBindings fields{};
std::optional<SkeletonBinding::RegisterShmObjectTraceCallback> register_shm_object_trace_callback{};

std::ignore = skeleton_->PrepareOffer(events, fields, std::move(register_shm_object_trace_callback));

skeleton_event_ = std::make_unique<SkeletonEvent<test::TestSampleType>>(
*skeleton_,
element_fq_id,
service_element_name,
SkeletonEventProperties{max_samples, max_subscribers, enforce_max_samples},
skeleton_event_tracing_data);

// Call PrepareOffer on the skeleton event to trigger Skeleton::Register, which populates
// the event_controls_ maps in ServiceDataControl for both QM and (if ASIL-B) ASIL-B.
std::ignore = skeleton_event_->PrepareOffer();

skeleton_event_common_ =
std::make_unique<score::mw::com::impl::lola::SkeletonEventCommon<test::TestSampleType>>(
*skeleton_,
event_name_,
SkeletonEventProperties{max_samples, max_subscribers, enforce_max_samples},
element_fq_id,
skeleton_event_tracing_data);
}

protected:
std::string_view event_name_{"test_event"};
std::unique_ptr<SkeletonEventCommon<test::TestSampleType>> skeleton_event_common_;

InstanceIdentifier GetValidInstanceIdentifierForEventCommon(QualityType quality_type) const
{
if (quality_type == QualityType::kASIL_B)
{
return make_InstanceIdentifier(valid_asil_instance_deployment_, valid_type_deployment_);
}
else
{
return make_InstanceIdentifier(valid_qm_instance_deployment_, valid_type_deployment_);
}
}

private:
ServiceInstanceDeployment valid_qm_instance_deployment_{make_ServiceIdentifierType(service_type_name_),
binding_info_,
QualityType::kASIL_QM,
instance_specifier_};
};

TEST_F(SkeletonEventCommonFixture, RegisterEventNotificationCallbacksForAsilBTriggersMessagePassingRegistration)
{
const bool enforce_max_samples{true};
InitialiseSkeletonEventCommon(fake_element_fq_id_,
fake_event_name_,
max_samples_,
max_subscribers_,
enforce_max_samples,
QualityType::kASIL_B,
{});

auto* const event_control_qm = GetEventControl(fake_element_fq_id_, QualityType::kASIL_QM);
auto* const event_control_asil_b = GetEventControl(fake_element_fq_id_, QualityType::kASIL_B);
ASSERT_NE(event_control_qm, nullptr);
ASSERT_NE(event_control_asil_b, nullptr);

// Expect that RegisterEventNotificationExistenceChangedCallback is called once per quality type with correct ASIL
// level and element ID
EXPECT_CALL(message_passing_mock_,
RegisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_B, fake_element_fq_id_, _))
.Times(1);
EXPECT_CALL(message_passing_mock_,
RegisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_QM, fake_element_fq_id_, _))
.Times(1);
// ... when PrepareOfferCommon is called
skeleton_event_common_->PrepareOfferCommon(*event_control_qm, event_control_asil_b);
}

TEST_F(SkeletonEventCommonFixture, UnregisterEventNotificationCallbacksForAsilBTriggersMessagePassingUnregistration)
{
const bool enforce_max_samples{true};
InitialiseSkeletonEventCommon(fake_element_fq_id_,
fake_event_name_,
max_samples_,
max_subscribers_,
enforce_max_samples,
QualityType::kASIL_B,
{});

auto* const event_control_qm = GetEventControl(fake_element_fq_id_, QualityType::kASIL_QM);
auto* const event_control_asil_b = GetEventControl(fake_element_fq_id_, QualityType::kASIL_B);
ASSERT_NE(event_control_qm, nullptr);
ASSERT_NE(event_control_asil_b, nullptr);

// Expect that RegisterEventNotificationExistenceChangedCallback is called once per quality type with correct ASIL
// level and element ID
EXPECT_CALL(message_passing_mock_,
UnregisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_B, fake_element_fq_id_))
.Times(1);
EXPECT_CALL(message_passing_mock_,
UnregisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_QM, fake_element_fq_id_))
.Times(1);
// ... when PrepareStopOfferCommon is called
skeleton_event_common_->PrepareStopOfferCommon();
}

} // namespace

} // namespace score::mw::com::impl::lola
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding
MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override));
MOCK_METHOD(void, SetSkeletonEventTracingData, (impl::tracing::SkeletonEventTracingData), (noexcept, override));
MOCK_METHOD(std::size_t, GetMaxSize, (), (const, noexcept, override));
MOCK_METHOD(Result<void>,
SetReceiveHandlerRegistrationChangedHandler,
(ReceiveHandlerRegistrationChangedCallback),
(noexcept, override));
MOCK_METHOD(Result<void>, UnsetReceiveHandlerRegistrationChangedHandler, (), (noexcept, override));
};

} // namespace score::mw::com::impl::mock_binding
Expand Down
22 changes: 22 additions & 0 deletions score/mw/com/impl/generic_skeleton_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,26 @@ DataTypeMetaInfo GenericSkeletonEvent::GetSizeInfo() const noexcept
return {size_info_pair.first, size_info_pair.second};
}

Result<void> GenericSkeletonEvent::SetReceiveHandlerRegistrationChangedHandler(
ReceiveHandlerRegistrationChangedCallback callback) noexcept
{
auto* const binding = dynamic_cast<GenericSkeletonEventBinding*>(binding_.get());
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(
binding != nullptr,
"Cast to GenericSkeletonEventBinding failed, but ReceiveHandlerNotification is only supported for "
"GenericSkeletonEvents.");

return binding->SetReceiveHandlerRegistrationChangedHandler(std::move(callback));
}

Result<void> GenericSkeletonEvent::UnsetReceiveHandlerRegistrationChangedHandler() noexcept
{
auto* const binding = dynamic_cast<GenericSkeletonEventBinding*>(binding_.get());
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(
binding != nullptr,
"Cast to GenericSkeletonEventBinding failed, but ReceiveHandlerNotification is only supported for "
"GenericSkeletonEvents.");

return binding->UnsetReceiveHandlerRegistrationChangedHandler();
}
} // namespace score::mw::com::impl
Loading