Skip to content

Conversation

@Abhishek2581
Copy link
Contributor

@Abhishek2581 Abhishek2581 commented Jan 15, 2026

Hi @crimson11
As discussed earler Rasing a pull request containing the Event with black-box tests through Example Application.

Rest will done in second pull request(First week of Feb) which includes the complete Event functionality documentation along with tests for both Event and Field

@mina-hamdi mina-hamdi force-pushed the generic_skeleton_implementation branch 2 times, most recently from 7e7c369 to 809cecd Compare January 15, 2026 12:54
@LittleHuba
Copy link
Contributor

@mina-hamdi it looks like you have conflicts that require manual resolution. Could you please fix them?

Signed-off-by: Abhishek GOYAL <abhishek.goyal@valeo.com>
@Abhishek2581
Copy link
Contributor Author

@mina-hamdi it looks like you have conflicts that require manual resolution. Could you please fix them?

hi @LittleHuba : confilct resolved

@Abhishek2581
Copy link
Contributor Author

hi @LittleHuba , @hoe-jo , @castler
As discussed in the ticket, this pull request represents the first iteration of the generic Skeleton for Events, including black-box tests via the example application. Kindly review and provide feedback.

A follow-up pull request is planned for the beginning of Feb to add Field support, tests, and Event documentation, incorporating feedback from this review


/// @brief Adds a type-erased event to the skeleton.
///
/// This must be called before OfferService().
Copy link
Contributor

Choose a reason for hiding this comment

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

is it possible to avoid this statefulness?

For example Create() or OfferService() could get an additional argument Span<tuple<string_view, SizeInfo>> events which is a complete list of events to be used.

Then there is no need for the user to keep an eye on what is allowed in the documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with your point — avoiding this statefulness would be an improvement. Passing the complete list of events in a single call removes the need for users to reason about call order or rely on documentation to understand what is allowed at each stage.

We are planning to move event configuration into Create() and pass the full set of events upfront via a std::span. This allows the service instance and its events to be defined atomically and validated in one place, while keeping OfferService() as a pure activation step.

Regarding the event descriptor type, we currently lean slightly towards using a small named struct (e.g. EventInfo { std::string_view name; SizeInfo size_info; }) rather than a tuple. The main reason is that this models the event description as a single logical unit and makes the intent explicit in the type, rather than relying on positional semantics. That said, a tuple would also work mechanically.

Before finalizing this, we’d like to check with you whether you prefer the struct-based descriptor or the tuple<string_view, SizeInfo> approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

structs are completely fine. Actually I usually avoid tuples, because you cannot give its members names.

Copy link
Contributor Author

@Abhishek2581 Abhishek2581 Jan 22, 2026

Choose a reason for hiding this comment

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

Thanks as per our discussion i see this new interface for GenericSkeleton

`/// @brief Describes the configuration for a single event.
struct EventInfo
{
std::string_view name;
SizeInfo size_info;
};

/// @brief An opaque handle to an event within a GenericSkeleton.
struct EventHandle
{
std::uint16_t index;
};

class GenericSkeleton : public SkeletonBase
{
public:
/// @brief Creates a GenericSkeleton and all its events atomically.
/// @param id The instance identifier.
/// @param events A span of EventInfo structs describing all events to be created.
/// @param out_handles A span that will be populated with handles to the created events.
/// The caller must ensure its size is equal to events.size().
/// @param mode The method call processing mode.
/// @return A GenericSkeleton or an error if creation fails.
static Result Create(
const InstanceIdentifier& id,
score::cpp::span events,
score::cpp::span out_handles,
MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept;

/// @brief Retrieves an event using its handle.
/// @param h The handle to the event.
/// @return A reference to the GenericSkeletonEvent.
GenericSkeletonEvent& GetEvent(EventHandle h) noexcept;

/// @brief Offers the service instance.
/// @return A blank result, or an error if offering fails.
Result<score::Blank> OfferService() noexcept;

/// @brief Stops offering the service instance.
void StopOfferService() noexcept;

private:
// Private constructor and other members...
GenericSkeleton(const InstanceIdentifier& identifier,
std::unique_ptr binding,
MethodCallProcessingMode mode);

// Internal storage is now a vector for O(1) access via EventHandle.
std::vector<std::unique_ptr<GenericSkeletonEvent>> owned_events_;

};`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as support of fileds also required so i updated interface Please provide your feedback

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

/// @brief An opaque handle to an event within a GenericSkeleton.
/// @note This handle is only valid for the GenericSkeleton instance that produced it.
struct EventHandle {
std::uint16_t index{};
};

/// @brief An opaque handle to a field within a GenericSkeleton.
/// @note This handle is only valid for the GenericSkeleton instance that produced it.
struct FieldHandle {
std::uint16_t index{};
};

struct EventInfo {
std::string_view name;
SizeInfo size_info; // SizeInfo is used to pre-allocate event sample storage.
};

struct FieldInfo {
std::string_view name;
SizeInfo size_info;
/// @brief The initial value for the field.
/// @note Must be non-empty.
/// @note The data must remain valid only for the duration of the Create() call.
/// initial_value_bytes.size() must be less than or equal to size_info.size.
/// The bytes are in the middleware’s “generic” field representation.
std::span initial_value_bytes;
};

struct GenericSkeletonCreateParams {
std::span events{};
std::span fields{};
};

struct GenericSkeletonCreateOut {
/// @note The caller must provide pre-allocated spans of the correct size.
/// event_handles.size() must be equal to in.events.size().
/// field_handles.size() must be equal to in.fields.size().
/// To indicate no events or fields, pass empty spans (e.g., std::span<EventHandle>{}).
std::span event_handles;
std::span field_handles;
};

class GenericSkeleton : public SkeletonBase
{
public:
/// @brief Creates a GenericSkeleton and all its service elements (events + fields) atomically.
///
/// @contract
/// - out.event_handles[i] corresponds to in.events[i] (same order); same for fields.
/// - out.event_handles.size() must be equal to in.events.size().
/// - out.field_handles.size() must be equal to in.fields.size().
/// - Empty spans are allowed for in.events or in.fields (and the corresponding out.*_handles must then also be empty).
/// - in.events.size() must be less than or equal to std::numeric_limits<std::uint16_t>::max().
/// - in.fields.size() must be less than or equal to std::numeric_limits<std::uint16_t>::max().
/// - All element names (from in.events and in.fields) must be unique across all element kinds within this skeleton.
/// Name uniqueness is case-sensitive and based on std::string_view content comparison.
/// - For elements in in.fields, initial_value_bytes must be non-empty and its size must be <= size_info.size.
/// - The Create() function will copy the initial_value_bytes for fields into internal storage immediately.
/// - On error, no partially-created elements are left behind. Contents of out.* are unspecified.
///
/// @param identifier The instance identifier.
/// @param in Input parameters for creation, including event and field descriptors.
/// @param out Output parameters, where handles to created events and fields will be stored.
/// @param mode The method call processing mode.
/// @return A GenericSkeleton or an error if creation fails.
static Result Create(
const InstanceIdentifier& identifier,
const GenericSkeletonCreateParams& in,
const GenericSkeletonCreateOut& out,
MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept;

/// @brief Creates a GenericSkeleton and all its service elements (events + fields) atomically.
///
/// This overload resolves the InstanceSpecifier to an InstanceIdentifier internally.
/// Same contract as the `Create` overload taking `InstanceIdentifier`.
///
/// @param specifier The instance specifier.
/// @param in Input parameters for creation, including event and field descriptors.
/// @param out Output parameters, where handles to created events and fields will be stored.
/// @param mode The method call processing mode.
/// @return A GenericSkeleton or an error if creation fails.
static Result<GenericSkeleton> Create(
    const InstanceSpecifier& specifier,
    const GenericSkeletonCreateParams& in,
    const GenericSkeletonCreateOut& out,
    MethodCallProcessingMode mode = MethodCallProcessingMode::kEvent) noexcept;


/// @brief Retrieves a mutable event using its handle.
/// @param h The handle to the event.
/// @return A mutable reference to the GenericSkeletonEvent or an error if the handle is invalid.
/// @note Handles are only valid for the GenericSkeleton instance that produced them.
Result<std::reference_wrapper<GenericSkeletonEvent>> TryGetEvent(EventHandle h) noexcept;

/// @brief Retrieves a const event using its handle.
/// @param h The handle to the event.
/// @return A const reference to the GenericSkeletonEvent or an error if the handle is invalid.
/// @note Handles are only valid for the GenericSkeleton instance that produced them.
Result<std::reference_wrapper<const GenericSkeletonEvent>> TryGetEvent(EventHandle h) const noexcept;

/// @brief Retrieves a mutable field using its handle.
/// @param h The handle to the field.
/// @return A mutable reference to the GenericSkeletonField or an error if the handle is invalid.
/// @note Handles are only valid for the GenericSkeleton instance that produced them.
Result<std::reference_wrapper<GenericSkeletonField>> TryGetField(FieldHandle h) noexcept;

/// @brief Retrieves a const field using its handle.
/// @param h The handle to the field.
/// @return A const reference to the GenericSkeletonField or an error if the handle is invalid.
/// @note Handles are only valid for the GenericSkeleton instance that produced them.
Result<std::reference_wrapper<const GenericSkeletonField>> TryGetField(FieldHandle h) const noexcept;

/// @brief Offers the service instance.
/// @return A blank result, or an error if offering fails.
Result<score::Blank> OfferService() noexcept;

/// @brief Stops offering the service instance.
void StopOfferService() noexcept;

private:
// Private constructor, only callable by static Create methods
GenericSkeleton(const InstanceIdentifier& identifier,
std::unique_ptr binding,
MethodCallProcessingMode mode);

// Internal storage with O(1) lookup via handles.
std::vector<std::unique_ptr<GenericSkeletonEvent>> events_;
std::vector<std::unique_ptr<GenericSkeletonField>> fields_;

};

} // namespace score::mw::com::impl

Copy link
Contributor

@crimson11 crimson11 Jan 23, 2026

Choose a reason for hiding this comment

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

Short question:
I'm fine with getting rid of AddEvent() method and require the user to hand over all events/fields/in future methods at creation time!
But I don't get/feel this GenericSkeletonCreateOut ... this looks clumsy and I also do not see, what a EventHandleshall be ...
This also went further away from the GenericProxy...
Imho: Leave out this whole GenericSkeletonCreateOut thing. After successful creation of a GenericSkeleton, the user shall call ServiceElementMap<GenericSkeletonEvent> impl::GenericSkeleton::GetEvents() -> look at GenericProxy


if (!instance_identifier_result.has_value())
{
score::mw::log::LogFatal("lola") << "Failed to resolve instance identifier from instance specifier";
Copy link
Contributor

Choose a reason for hiding this comment

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

This is code, which is like this in SkeletonWrapperClass, but it is soon to be changed! Because it makes no sense to terminate here as it is a simple failure, the user handing over an `InstanceSpecifier' for which no mapping is in the config!
Instead of terminating an error code 'kInstanceIDCouldNotBeResolved' (this code already exists!) shall be returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree with you


/// \brief Template specialization of SampleAllocateePtr for void.
template <>
class SampleAllocateePtr<void>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is a "full" specialization really needed? I did not check in-depth ... but in case of SamplePtr we also did only "SFINAE remove" via std::enable_if the operator* in case of the void-case ... so couldn't we have done it here symmetrically?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good observation, and the comparison with SamplePtr is valid.
However, for SampleAllocateePtr the need for a full specialization is not an interface-level issue (such as disabling operator*), but a type-instantiation and object-lifetime issue caused by the internal representation.

The primary template of SampleAllocateePtr contains the following member:
std::variant< score::cpp::blank, lola::SampleAllocateePtr<SampleType>, std::unique_ptr<SampleType>> internal_;

For non-void SampleType, the std::unique_ptr alternative is required to support non-LoLa/test bindings and provides correct ownership semantics.

When SampleType = void, instantiating the primary template would require instantiating std::unique_ptr. This is fundamentally problematic:

  • std::unique_ptr assumes ownership of a complete object type and performs destruction via its deleter.
  • For T = void, there is no complete object type, and deletion requires a complete object type.
  • As a consequence, std::unique_ptr does not represent a valid owning abstraction in this context.

This failure occurs during class template instantiation, not during overload resolution. Even if all dereferencing operators were conditionally removed via SFINAE, the class would already be ill-formed because std::variant requires all alternative types to be well-formed and destructible.

SFINAE cannot be applied here because:

  • it only applies to substitution in dependent contexts (e.g. function templates),
  • the std::unique_ptr alternative is part of the class layout itself,
  • and std::variant must be fully instantiated, including its destructor and move operations, regardless of which alternative is active at runtime.

In contrast, SamplePtr only needed SFINAE because its internal representation does not include an owning type that becomes invalid for void; the issue there is limited to disabling invalid operations such as dereferencing.

Therefore, a full specialization for SampleAllocateePtr is required to:

  • remove the invalid std::unique_ptr alternative at the type level,
  • ensure that all variant alternatives are well-formed and destructible,
  • maintain correct ownership semantics,
  • and clearly express the reduced contract for the void case (no dereference, no owning test pointer).

While it would technically be possible to avoid a full specialization by conditionally replacing std::unique_ptr inside the std::variant, this would introduce additional dummy alternatives and require extra visitor handling to preserve invariants. That approach would obscure the ownership model and rely on subtle template indirection rather than expressing the constraints explicitly at the type level.

By using a dedicated specialization for SampleAllocateePtr, the invalid owning alternative is removed entirely, the variant remains minimal and well-formed, and the reduced contract for void is enforced directly by the type system. This makes the design more robust and easier to reason about in the long term.

Please let me know if you see a viable alternative that avoids the specialization without introducing an invalid variant alternative or weakening the ownership semantics.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. I guess, we were a bit "lazy" in the context of SampleAllocateePtr ... with adding just std::unique_ptr<T> to the variant to care for test cases ... which now falls on our feet, when introducing a void-specialization!
Please now follow the same approach as for the SamplePtr, which for testing purposes adds a specific mock-binding SampleAllocateePtr to the variant. This would be a mock_binding::SampleAllocateePtr<SampleType>

You can implement is according to mock_binding::SamplePtr<SampleType>, which adds a distinct deleter to the underlying std::unique_ptr, so that class template instantiation (impl::SampleAllocateePtr) works.

The thing is, that in case you are then implementing tests on a mock/mock-binding base, you need to be concise and hand over the "right" deleter! Example, where/how it is done, you find here.
I.e. in the testing case for a GenericProxyEvent or GenericSkeletonEvent, you exactly know the type (being non-void), with which you want to set-up/populate your test. There you can then instantiate the mock_binding::SamplePtr<SampleType> resp. mock_binding::SampleAllocateePtr<SampleType> (being aliases to std::unique_ptr) with the correct deleter as shown in my link!

Copy link
Contributor Author

@Abhishek2581 Abhishek2581 Jan 23, 2026

Choose a reason for hiding this comment

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

I came across this comment today — kindly excuse me for the follow-up.

While reviewing a suggestion to use SFINAE on operator* / operator->, I noticed a more fundamental issue beyond the usual std::unique_ptr<T> analogy when we do NOT provide a full specialization for SampleAllocateePtr<void>.

SampleAllocateePtr<void> is a valid type in the public API , but for void there is no object to dereference. Therefore, operator* and operator-> must not exist for this instantiation.

with typical SFINAE-based approachm we would do like this:

template <typename = std::enable_if_t<!std::is_void_v<SampleType>>>
SampleType& operator*() const;

However, in C++17 this does not solve the problem.

As soon as SampleAllocateePtr<void> appears as a parameter or return type, the compiler must instantiate the full class template. During that instantiation, it attempts to form all member declarations. For SampleType = void, the SFINAE condition becomes std::enable_if_t<false>, which has no ::type.

This failure occurs while forming the class definition itself, not during overload resolution, and therefore results in a hard compilation error — even though operator* / operator-> are never called.

In other words, instead of the operators being “removed”, the compiler rejects SampleAllocateePtr<void> entirely. This makes it impossible to use SampleAllocateePtr<void> in function signatures without an explicit specialization.

However this alternative SFINAE techniques in C++17

It is technically possible in C++17 to make this work using more subtle techniques (e.g. SFINAE in return types with defaulted template parameters). However, these approaches rely on delicate template instantiation rules:

  • return types participating in substitution

  • substitution happening later than class formation

- enable_if in return types triggering SFINAE

  • default template parameters delaying instantiation

All of these conditions must remain true for the code to stay correct.

This makes such solutions fragile under maintenance. Seemingly harmless refactorings — for example:

  • moving the function out of the class definition

  • changing the return type

  • replacing enable_if with a type alias

  • introducing auto or constexpr

  • later adding concepts inconsistently

From my point of view , core middleware , this level of subtlety is undesirable. An explicit specialization is clearer, more robust, and communicates intent directly.

PS: With C++20 this could be solved without specialization by using requires clauses orconstrained memberfunctions, which do not break class instantiation. However, Eclipse SCORE currently targets C++17, so
this approach is not available.

@crimson11 whats your view i go with alternative SFINAE techniques or explicit specialization for void?


#include <cstddef>

namespace score::mw::com
Copy link
Contributor

Choose a reason for hiding this comment

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

should be score::mw::com::impl? Most likely it will later also be used on the public interface ... then you would also introduce an alias in types.h

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree with you

SizeInfo struct should be moved into the score::mw::com::impl namespace to accurately reflect its location and intended usage as an internal detail

namespace score::mw::com
{
/// @brief A struct to hold size and alignment information for generic type-erased data.
struct SizeInfo
Copy link
Contributor

Choose a reason for hiding this comment

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

We already have DataTypeMetaInfo ... this seems to be identical/redundant? E.g. you should think about re-using it ... in this case it is also fine imho, if you move the existing DataTypeMetaInfo from impl::lola to just impl.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out — there is overlap and we should avoid redundant “size + alignment” carriers where possible.

That said, lola::DataTypeMetaInfo is currently not a drop-in replacement for score::mw::com::SizeInfo for two reasons:

Different scope/semantics: DataTypeMetaInfo lives in score::mw::com::impl::lola and is treated as LoLa-binding internal metadata. SizeInfo is already used as a general-purpose representation (e.g. DynamicType::mwcom_size_info_) and is not LoLa-specific.

Different representation: DataTypeMetaInfo::align_of_ is std::uint8_t, while SizeInfo::alignment is size_t. In valid C++ the alignment requirement can exceed 255 (e.g. via alignas(N)), so storing alignment in uint8_t risks truncation. Even if our current platforms don’t hit this, size_t is the safer generic type and matches how we treat alignment elsewhere.

Given that, I agree we should consolidate. Two options:

Option A (preferred): keep SizeInfo as the canonical type for generic size/alignment and update LoLa code to use it (or an impl::SizeInfo), then deprecate/remove lola::DataTypeMetaInfo.

Option B: move/rename DataTypeMetaInfo to impl and change align_of_ to size_t (and potentially rename fields for consistency), then use that everywhere.

I leaned toward Option A since SizeInfo is already used by DynamicType and uses the robust size_t representation. Let me know which consolidation direction you prefer and I’ll align the changes accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think what Manuel meant was that we have DataTypeSizeInfo (probably we need to improve the naming of these two) which you can use: https://github.com/eclipse-score/baselibs/blob/main/score/memory/data_type_size_info.h

size_t sample_size,
size_t sample_alignment) noexcept
{
auto* data_storage = storage_resource_->construct<EventDataStorage<std::uint8_t>>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Makes this EventDataStorage (which is an alias for DynamicArray) really sense here .... I mean, what is the added value of the DynamicArray here, if the slots/elements within the DynamicArray do not even represent the "real" events ... and you have to do pointer-arithmetic anyhow?
Apart from this, when creating a DynamicArray<uint8_t>, you get potential WRONG alignment! Because it will allocate starting on a one-byte aligned address, which might be wrong, when your sampl_alignmentis larger!

I.e. why not simply:
void* data_storage = storage_resource_->allocate(sample_size * element_properties.number_of_slots, sample_alignment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally Agree with you

// 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<std::uint64_t>(slot.GetIndex()) * size_info_.size);
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this cast to std::uint64_t necessary? However, if you follow my comment for Skeleton::CreateEventDataFromOpenedSharedMemory(), then this code here will change ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice point and for sure after change in Skeleton::CreateEventDataFromOpenedSharedMemory() as per your feedback this code will change to something like this

void* base_ptr = data_storage_.get();
void* data_ptr = static_caststd::uint8_t*(base_ptr) + (static_caststd::uint64_t(slot.GetIndex()) * size_info_.size);

but still i am thinking we need type casting in uint64_t because

slot.GetIndex() returns SlotIndexType which is uint16_t. In C++, when we do arithmetic with small integer types like uint16_t, the compiler first promotes them to a larger “normal” integer type (typically int or unsigned int) before doing the multiplication. Then, because size_info_.size is size_t, the final multiplication ends up being done after a sequence of implicit conversions. Which exact intermediate type is used can depend on the platform (32-bit vs 64-bit), and it’s not obvious from reading the code.

By explicitly casting the index first, we force the multiplication to happen in 64-bit arithmetic from the start. This makes the intent clearer and avoids any risk of unexpected overflow due to intermediate type promotions.

Whats your view?

return skeleton_base_.binding_.get();
}

bool IsOffered() const
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not 100% convinced to use the SkeletonBaseView pattern here:
You want the GenericSkeleton to be able to access this member service_offered_flag_ of its base class.
So I would rather make it protected then.
The usage of SkeletonBaseView in the existing code is to "allow" classes outside of SkeletonBase class hierarchy to access non-public elements ... but here it is a bit different ... a subclass should be able to access this member (if it needs) without the SkeletonBaseView "trick"....

Copy link
Contributor

@bemerybmw bemerybmw left a comment

Choose a reason for hiding this comment

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

I haven't done a full review, just left a few comments after glancing over the PR.

class SkeletonBinding;


class GenericSkeleton : public SkeletonBase
Copy link
Contributor

Choose a reason for hiding this comment

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

Class should have a docstring.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes i'll do this

auto binding = SkeletonBindingFactory::Create(identifier);
if (!binding)
{
return MakeUnexpected(ComErrc::kBindingFailure);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should add an error log message.

Copy link
Contributor Author

@Abhishek2581 Abhishek2581 Jan 21, 2026

Choose a reason for hiding this comment

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

Nice point, I'll do this

Copy link
Contributor

Choose a reason for hiding this comment

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

As a general point, all header files should have a corresponding .cpp file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_Agree with point, We will update this


/// @brief Overload for generic skeletons (which require a SizeInfo).
template <typename SkeletonServiceElementBinding, typename SkeletonServiceElement, ServiceElementType element_type>
auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just have CreateGenericSkeletonServiceElement rather than overloading CreateSkeletonServiceElement?

Copy link
Contributor Author

@Abhishek2581 Abhishek2581 Jan 22, 2026

Choose a reason for hiding this comment

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

I agree with your point, instead of overloading i create new function named CreateGenericSkeletonServiceElement

const std::string_view service_element_name) noexcept
-> std::unique_ptr<SkeletonServiceElementBinding>
{
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE((!std::is_same_v<SkeletonServiceElement, lola::GenericSkeletonEvent>),
Copy link
Contributor

Choose a reason for hiding this comment

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

static_assert so that it's checked at compile time?

SkeletonBase& parent,
const std::string_view service_element_name) noexcept
const std::string_view service_element_name,
const score::cpp::optional<std::reference_wrapper<const SizeInfo>>& size_info) noexcept
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const score::cpp::optional<std::reference_wrapper<const SizeInfo>>& size_info) noexcept
const score::cpp::optional<SizeInfo> size_info) noexcept

SizeInfo is small so I wouldn't worry about passing by reference (to keep the signature more readable).


TEST_F(GenericSkeletonTest, CreateWithInstanceSpecifier)
{
auto instance_specifier = InstanceSpecifier::Create("a/b/c").value();
Copy link
Contributor

Choose a reason for hiding this comment

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

Unit tests should follow the Given, when, then structure. (E.g. https://martinfowler.com/bliki/GivenWhenThen.html)

public:
virtual Result<score::Blank> Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0;

virtual Result<lola::SampleAllocateePtr<void>> Allocate() noexcept = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

The code within mw::com::impl is binding independent. So we should pass SampleAllocateePtr (from impl/plumbing) instead of lola::SampleAllocateePtr.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You have correctly identified a violation of the architectural layering in code. I completely agree with assessment. I 'll do necessary change

return service_data_storage;
}

std::optional<memory::shared::LockFile> CreateOrOpenServiceInstanceExistenceMarkerFile(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this moved?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanksm
It was a mistake from my side , i will revert this

return BindingType::kLoLa;
};

std::pair<score::memory::shared::OffsetPtr<void>, EventDataControlComposite> RegisterGeneric(
Copy link
Contributor

Choose a reason for hiding this comment

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

All these new functions should have docstrings

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing out ,i will do

class GenericSkeletonEventBinding : public SkeletonEventBindingBase
{
public:
virtual Result<score::Blank> Send(lola::ControlSlotCompositeIndicator control_slot_indicator) noexcept = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

This signature makes no sense. GenericSkeletonEventBinding is the interface every binding shall support. It can't have a lola-binding specific argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You've made a great point, thank you for catching that. I completely agree that the binding-independent layer shouldn't have alola-binding specific argument..

I will correct it.

{

template <typename EventType>
void PrepareOfferImpl(EventType& event)
Copy link
Contributor

Choose a reason for hiding this comment

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

So you are trying to re-use impl. where possible between SkeletonEvent and GenericSkeletonEvent ...
I would propose to achieve this "symmetrically" to the proxy-side, by introducing a SkeletonEventCommon ... which should then be aggregated by SkeletonEvent and GenericSkeletonEvent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great Point i;ll do necessary changes

return MakeUnexpected(ComErrc::kNotOffered);
}

auto* const lola_sample_ptr = SampleAllocateePtrView<void>{sample}.As<lola::SampleAllocateePtr<void>>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this hardcoded expectation, that SampleAllocateePtr is a lola variant? We are on the binding independent layer here, which needs to deal with all potential variants of SampleAllocateePtr

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You've made a great point, thank you for catching that. I completely agree that the binding-independent layer shouldn't have a hardcoded dependency.

I will correct it.


/// @brief Adds a type-erased event to the skeleton.
///
/// This must be called before OfferService().
Copy link
Contributor

@crimson11 crimson11 Jan 23, 2026

Choose a reason for hiding this comment

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

Short question:
I'm fine with getting rid of AddEvent() method and require the user to hand over all events/fields/in future methods at creation time!
But I don't get/feel this GenericSkeletonCreateOut ... this looks clumsy and I also do not see, what a EventHandleshall be ...
This also went further away from the GenericProxy...
Imho: Leave out this whole GenericSkeletonCreateOut thing. After successful creation of a GenericSkeleton, the user shall call ServiceElementMap<GenericSkeletonEvent> impl::GenericSkeleton::GetEvents() -> look at GenericProxy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants