ETLCPP / etl

Embedded Template Library
https://www.etlcpp.com
MIT License
2.05k stars 372 forks source link

Add support for in-place instantiation of a shared message in the message pool #854

Closed SanderSmeenkInspiro closed 3 months ago

SanderSmeenkInspiro commented 4 months ago

First of all, thank you so much for writing the ETL!

With the current implementation of the reference counted message pool and the shared message, the message must first be created (e.g. on the stack) and is then copied into the message pool. However, sometimes a message needs to be instantiated in the message pool that is expensive to copy on an embedded platform.

If the message could be instantiated in-place in the memory pool (without a copy), this could save significant memory for certain use-cases.

This pull request is an example of what an implementation of in-place instantiation could look like. The test suite is extended to demonstrate the usage ( message_pool.create_message<Message1>(1); ) In the test suite, the data element in the message is an int, but you can imagine this to be a type that is more expensive to copy.

Note: allocate(const TMessage*, Args&&... args) passes a pointer to TMessage, but it is only used to pass the type. There is undoubtedly a better way to achieve this, that I have not found to work yet.

Feel free to comment or request changes, I'd be happy to incorporate those.

semanticdiff-com[bot] commented 4 months ago

Review changes with SemanticDiff.

jwellbelove commented 3 months ago

I looked at the code and I've experimentally modified the code.

I added a utility class etl::type_tag<T> that can replace the dummy pointer.

//*************************************************************************
/// Creator for in-place instantiation
//*************************************************************************
template <typename TMessage, typename TPool, typename... Args>
static shared_message create(TPool& owner, Args&&... args)
{
  return shared_message(owner, etl::type_tag<TMessage>(), etl::forward<Args>(args)...);
}

//*************************************************************************
/// Constructor
//*************************************************************************
template <typename TPool, typename TMessage, typename... TArgs>
shared_message(TPool& owner, etl::type_tag<TMessage>, TArgs&&... args)
{
  ETL_STATIC_ASSERT((etl::is_base_of<etl::ireference_counted_message_pool, TPool>::value), "TPool not derived from etl::ireference_counted_message_pool");
  ETL_STATIC_ASSERT((etl::is_base_of<etl::imessage, TMessage>::value), "TMessage not derived from etl::imessage");

  p_rcmessage = owner.template allocate<TMessage>(etl::forward<TArgs>(args)...);

  if (p_rcmessage != ETL_NULLPTR)
  {
    p_rcmessage->get_reference_counter().set_reference_count(1U);
  }
}

You'll notice that the emplace allocate function now takes the message type as a template parameter, rather than as a dummy pointer argument.

I'll merge your code and add my changes.

SanderSmeenkInspiro commented 3 months ago

Great!

jwellbelove commented 3 months ago

I removed the etl::type_tag<T> and used the already existing STL compatible class etl::in_place_type_t<T>.