Skip to content
5 changes: 4 additions & 1 deletion score/mw/com/impl/skeleton_base_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,9 @@ class DummyField : public SkeletonFieldBase
{
public:
using SkeletonFieldBase::SkeletonFieldBase;

void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override {}

bool IsInitialValueSaved() const noexcept override
{
return false;
Expand All @@ -668,7 +671,7 @@ class DummyField : public SkeletonFieldBase
return Result<void>{};
};

bool IsSetHandlerRegistered() const noexcept override
bool IsSetHandlerMissing() const noexcept override
{
return false;
}
Expand Down
154 changes: 73 additions & 81 deletions score/mw/com/impl/skeleton_field.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,19 @@ class SkeletonField : public SkeletonFieldBase
//
// \tparam CallableType Any callable (std::function, score::cpp::callback, lambda, ...) with the signature:
// void(FieldType& new_value)
// - new_value : the value requested by the proxy.
// - new_value : the value requested by the proxy. This value will be modified in place by the registered handler
// and the new value will be used to update the field.
template <bool ES = EnableSet, typename std::enable_if<ES, int>::type = 0, typename CallableType>
Result<void> RegisterSetHandler(CallableType&& handler)
Result<void> RegisterSetHandler(CallableType&& set_handler)
{
static_assert(std::is_invocable_v<CallableType, FieldType&>,
"RegisterSetHandler: handler must be callable as void(FieldType& value). "
"The argument initially holds the proxy-requested value and may be modified in-place.");
set_handler_ = std::forward<CallableType>(handler);

auto wrapped_callback = [this](FieldType& new_value) -> FieldType {
auto wrapped_callback =
[this, set_handler = std::forward<CallableType>(set_handler)](FieldType& new_value) -> FieldType {
// Allow user to validate/modify the value in-place
set_handler_(new_value);
set_handler(new_value);

// Store the (possibly modified) value as the latest field value
auto update_result = this->Update(new_value);
Expand All @@ -143,10 +144,41 @@ class SkeletonField : public SkeletonFieldBase
};

is_set_handler_registered_ = true;
return set_method_.get()->RegisterHandler(std::move(wrapped_callback));
return set_method_->RegisterHandler(std::move(wrapped_callback));
}

/// \brief Updates the reference to SkeletonBase held by this SkeletonField and also the owned methods.
///
/// This is necessary when a Skeleton (which owns its events, fields and methods) is moved to a new address. When
/// this happens, the references to the SkeletonBase are pointing to the old address and must be updated. This must
/// be done also for the get and set method since they call a SkeletonMethod constructor which does not register
/// them with the SkeletonBase. Rather, they're considered as part of the SkeletonField and it's the field's
/// responsibility to update their SkeletonBase reference when it's moved.
void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override
{
skeleton_base_ = skeleton_base;

if (set_method_ != nullptr)
{
set_method_->UpdateSkeletonReference(skeleton_base);
}
if (get_method_ != nullptr)
{
get_method_->UpdateSkeletonReference(skeleton_base);
}
}

private:
using SetMethodSignature = FieldType(FieldType);
using GetMethodSignature = FieldType();

/// \brief Private delegating constructor used by the no-setter public ctor and testing ctor.
SkeletonField(SkeletonBase& parent,
std::unique_ptr<SkeletonEvent<FieldType>> skeleton_event_dispatch,
std::unique_ptr<SkeletonMethod<SetMethodSignature>> skeleton_set_method_dispatch,
std::unique_ptr<SkeletonMethod<GetMethodSignature>> skeleton_get_method_dispatch,
const std::string_view field_name);

bool IsInitialValueSaved() const noexcept override
{
return initial_field_value_ != nullptr;
Expand All @@ -159,59 +191,23 @@ class SkeletonField : public SkeletonFieldBase

SkeletonEvent<FieldType>* GetTypedEvent() const noexcept;

std::unique_ptr<FieldType> initial_field_value_;
ISkeletonField<FieldType>* skeleton_field_mock_;

// Zero-cost conditional storage: unique_ptr when EnableSet=true, zero-size tag when false.
using SetMethodSignature = FieldType(FieldType);
using SetMethodType =
std::conditional_t<EnableSet, std::unique_ptr<SkeletonMethod<SetMethodSignature>>, detail::EnableSetOnlyTag>;
SetMethodType set_method_;

// Stores the user-provided set handler. Kept as a member so that the wrapped
// callback can invoke it via this->set_handler_. The concrete storage type is
// score::cpp::callback with the expected signature so that any callable provided
// to RegisterSetHandler is type-erased here.
// Zero-cost when EnableSet=false.
using SetHandlerStorageType =
std::conditional_t<EnableSet, score::cpp::callback<void(FieldType&)>, detail::EnableSetOnlyTag>;
SetHandlerStorageType set_handler_{};

// Tracks whether RegisterSetHandler() has been called. Zero-cost when EnableSet=false.
using IsSetHandlerRegisteredType = std::conditional_t<EnableSet, bool, detail::EnableSetOnlyTag>;
IsSetHandlerRegisteredType is_set_handler_registered_{};

// EnableSet=true: checks the flag; EnableSet=false: no setter, no handler required.
bool IsSetHandlerRegistered() const noexcept override
bool IsSetHandlerMissing() const noexcept override
{
if constexpr (EnableSet)
if constexpr (!EnableSet)
{
return is_set_handler_registered_;
return false;
}
return true;
return !is_set_handler_registered_;
}

/// \brief Private delegating constructor used by the setter-enabled public ctor.
template <bool ES = EnableSet, typename = std::enable_if_t<ES>>
SkeletonField(SkeletonBase& parent,
std::unique_ptr<SkeletonEvent<FieldType>> skeleton_event_dispatch,
const std::string_view field_name,
detail::EnableSetOnlyTag);
std::unique_ptr<FieldType> initial_field_value_;
ISkeletonField<FieldType>* skeleton_field_mock_;

/// \brief Private delegating constructor used by the no-setter public ctor and testing ctor.
SkeletonField(SkeletonBase& parent,
std::unique_ptr<SkeletonEvent<FieldType>> skeleton_event_dispatch,
const std::string_view field_name);
// Tracks whether RegisterSetHandler() has been called.
bool is_set_handler_registered_;

// TODO: Move get_method_ initialization into the delegating constructors (like set_method_) once the
// Get handler is implemented.
using GetMethodSignature = FieldType();
std::unique_ptr<SkeletonMethod<GetMethodSignature>> get_method_{
std::make_unique<SkeletonMethod<GetMethodSignature>>(
skeleton_base_.get(),
field_name_,
::score::mw::com::impl::MethodType::kGet,
typename SkeletonMethod<GetMethodSignature>::FieldOnlyConstructorEnabler{})};
std::unique_ptr<SkeletonMethod<SetMethodSignature>> set_method_;
std::unique_ptr<SkeletonMethod<GetMethodSignature>> get_method_;
};

/// \brief Public ctor — EnableSet=true: delegates to the private ctor that also creates the set method.
Expand All @@ -229,8 +225,19 @@ SkeletonField<SampleDataType, EnableSet, EnableNotifier>::SkeletonField(Skeleton
parent,
field_name),
typename SkeletonEvent<FieldType>::FieldOnlyConstructorEnabler{}),
field_name,
detail::EnableSetOnlyTag{}}
std::make_unique<SkeletonMethod<SetMethodSignature>>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm maybe missing some context here ... and it is not an issue with your PR ... But does this ctor call of SkeletonMethod work? I just looked at the code in SkeletonMethod and it looks like it would register now this getter/setter under wrong name/type in the SkeletonBase?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As discussed, they won't be registered as methods but rather the field registers itself with the parent and it takes care of the event/method dispatch objects.

parent,
field_name,
::score::mw::com::impl::MethodType::kSet,
typename SkeletonMethod<SetMethodSignature>::FieldOnlyConstructorEnabler{}),
// TODO: Move get_method_ initialization into the delegating constructors (like set_method_) once the
// Get handler is implemented.
std::make_unique<SkeletonMethod<GetMethodSignature>>(
parent,
field_name,
::score::mw::com::impl::MethodType::kGet,
typename SkeletonMethod<GetMethodSignature>::FieldOnlyConstructorEnabler{}),
field_name}
{
}

Expand All @@ -249,6 +256,8 @@ SkeletonField<SampleDataType, EnableSet, EnableNotifier>::SkeletonField(Skeleton
parent,
field_name),
typename SkeletonEvent<FieldType>::FieldOnlyConstructorEnabler{}),
nullptr,
nullptr,
field_name}
{
}
Expand All @@ -261,40 +270,25 @@ SkeletonField<SampleDataType, EnableSet, EnableNotifier>::SkeletonField(
std::unique_ptr<SkeletonEventBinding<FieldType>> binding)
: SkeletonField{skeleton_base,
std::make_unique<SkeletonEvent<FieldType>>(skeleton_base, field_name, std::move(binding)),
nullptr,
nullptr,
field_name}
{
}

/// \brief Private delegating ctor — setter enabled.
template <typename SampleDataType, bool EnableSet, bool EnableNotifier>
template <bool ES, typename>
SkeletonField<SampleDataType, EnableSet, EnableNotifier>::SkeletonField(
SkeletonBase& parent,
std::unique_ptr<SkeletonEvent<FieldType>> skeleton_event_dispatch,
const std::string_view field_name,
detail::EnableSetOnlyTag)
: SkeletonFieldBase{parent, field_name, std::move(skeleton_event_dispatch)},
initial_field_value_{nullptr},
skeleton_field_mock_{nullptr}
{
set_method_ = std::make_unique<SkeletonMethod<SetMethodSignature>>(
parent,
field_name_,
::score::mw::com::impl::MethodType::kSet,
typename SkeletonMethod<SetMethodSignature>::FieldOnlyConstructorEnabler{});
SkeletonBaseView skeleton_base_view{parent};
skeleton_base_view.RegisterField(field_name, *this);
}

/// \brief Private delegating ctor — no setter. Receives the already-constructed event.
template <typename SampleDataType, bool EnableSet, bool EnableNotifier>
SkeletonField<SampleDataType, EnableSet, EnableNotifier>::SkeletonField(
SkeletonBase& parent,
std::unique_ptr<SkeletonEvent<FieldType>> skeleton_event_dispatch,
std::unique_ptr<SkeletonMethod<SetMethodSignature>> skeleton_set_method_dispatch,
std::unique_ptr<SkeletonMethod<GetMethodSignature>> skeleton_get_method_dispatch,
const std::string_view field_name)
: SkeletonFieldBase{parent, field_name, std::move(skeleton_event_dispatch)},
initial_field_value_{nullptr},
skeleton_field_mock_{nullptr}
skeleton_field_mock_{nullptr},
is_set_handler_registered_{false},
set_method_{std::move(skeleton_set_method_dispatch)},
get_method_{std::move(skeleton_get_method_dispatch)}
{
SkeletonBaseView skeleton_base_view{parent};
skeleton_base_view.RegisterField(field_name, *this);
Expand All @@ -310,9 +304,8 @@ SkeletonField<SampleDataType, EnableSet, EnableNotifier>::SkeletonField(Skeleton
// coverity[autosar_cpp14_a12_8_3_violation] This is a false-positive.
initial_field_value_{std::move(other.initial_field_value_)},
skeleton_field_mock_{other.skeleton_field_mock_},
set_method_{std::move(other.set_method_)},
set_handler_{std::move(other.set_handler_)},
is_set_handler_registered_{std::move(other.is_set_handler_registered_)},
set_method_{std::move(other.set_method_)},
get_method_{std::move(other.get_method_)}
{
SkeletonBaseView skeleton_base_view{skeleton_base_.get()};
Expand All @@ -329,9 +322,8 @@ auto SkeletonField<SampleDataType, EnableSet, EnableNotifier>::operator=(Skeleto

initial_field_value_ = std::move(other.initial_field_value_);
skeleton_field_mock_ = std::move(other.skeleton_field_mock_);
set_method_ = std::move(other.set_method_);
set_handler_ = std::move(other.set_handler_);
is_set_handler_registered_ = std::move(other.is_set_handler_registered_);
set_method_ = std::move(other.set_method_);
get_method_ = std::move(other.get_method_);
SkeletonBaseView skeleton_base_view{skeleton_base_.get()};
skeleton_base_view.UpdateField(field_name_, *this);
Expand Down
18 changes: 10 additions & 8 deletions score/mw/com/impl/skeleton_field_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ class SkeletonFieldBase

virtual ~SkeletonFieldBase() = default;

void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept
{
skeleton_base_ = skeleton_base;
}
/// \brief Updates the reference to SkeletonBase held by the SkeletonField and also the owned methods.
///
/// This must happen in the derived class since the derived class owns the methods (this is required since they are
/// templated with the FieldType, which SkeletonFieldBase doesn't know).
virtual void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept = 0;

/// \brief Used to indicate that the field shall be available to consumer (e.g. binding specific preparation)
Result<void> PrepareOffer() noexcept
Expand All @@ -63,8 +64,8 @@ class SkeletonFieldBase
if (!was_prepare_offer_called_)
{
// If the field is configured with a setter, the application must register
// a set handler before calling OfferService(), otherwise Offer() shall fail.
if (!IsSetHandlerRegistered())
// a set handler via RegisterSetHandler before calling OfferService(), otherwise Offer() shall fail.
if (IsSetHandlerMissing())
{
score::mw::log::LogWarn("lola")
<< "Set handler must be registered before offering field: " << field_name_;
Expand Down Expand Up @@ -131,8 +132,9 @@ class SkeletonFieldBase
/// \brief Returns whether the initial value has been saved by the user to be used by DoDeferredUpdate
virtual bool IsInitialValueSaved() const noexcept = 0;

/// \brief Returns whether a set handler has been registered.
virtual bool IsSetHandlerRegistered() const noexcept = 0;
/// \brief Returns true if a setter has been enabled in the interface and a set handler was not registered via
/// RegisterSetHandler. Otherwise, returns false.
virtual bool IsSetHandlerMissing() const noexcept = 0;

/// \brief Sets the initial value of the field.
///
Expand Down
12 changes: 8 additions & 4 deletions score/mw/com/impl/skeleton_field_base_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class MyDummyField : public SkeletonFieldBase
{
}

void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override {}

StrictMock<mock_binding::SkeletonEventBase>* GetMockEventBinding() noexcept
{
auto* const skeleton_field_base_binding = SkeletonFieldBaseView{*this}.GetEventBinding();
Expand All @@ -83,9 +85,9 @@ class MyDummyField : public SkeletonFieldBase
return {};
}

bool IsSetHandlerRegistered() const noexcept override
bool IsSetHandlerMissing() const noexcept override
{
return true;
return false;
}

bool was_deferred_update_called_{false};
Expand All @@ -95,14 +97,16 @@ class MyDummyField : public SkeletonFieldBase
class MyDummyFieldFailingDeferredUpdate final : public MyDummyField
{
public:
void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override {}

Result<void> DoDeferredUpdate() noexcept override
{
return MakeUnexpected(ComErrc::kCommunicationLinkError);
}

bool IsSetHandlerRegistered() const noexcept override
bool IsSetHandlerMissing() const noexcept override
{
return true;
return false;
}
};

Expand Down
Loading
Loading