65816-llvm-mos/runtime/include/c++/etl/message_router.h
2026-05-30 19:40:29 -05:00

897 lines
32 KiB
C++

/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2017 John Wellbelove
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#ifndef ETL_MESSAGE_ROUTER_INCLUDED
#define ETL_MESSAGE_ROUTER_INCLUDED
#include "platform.h"
#include "alignment.h"
#include "array.h"
#include "error_handler.h"
#include "exception.h"
#include "largest.h"
#include "message.h"
#include "message_packet.h"
#include "message_types.h"
#include "nullptr.h"
#include "placement_new.h"
#include "shared_message.h"
#include "successor.h"
#include "type_list.h"
#include "type_traits.h"
#include <stdint.h>
namespace etl
{
//***************************************************************************
/// Base exception class for message router
//***************************************************************************
class message_router_exception : public etl::exception
{
public:
message_router_exception(string_type reason_, string_type file_name_, numeric_type line_number_)
: etl::exception(reason_, file_name_, line_number_)
{
}
};
//***************************************************************************
/// Router id is out of the legal range.
//***************************************************************************
class message_router_illegal_id : public etl::message_router_exception
{
public:
message_router_illegal_id(string_type file_name_, numeric_type line_number_)
: message_router_exception(ETL_ERROR_TEXT("message router:illegal id", ETL_MESSAGE_ROUTER_FILE_ID"A"), file_name_, line_number_)
{
}
};
namespace private_message_router
{
//***************************************************************************
// Traits for a message router.
// message packet type
// message type_list
// sorted message type_list.
//***************************************************************************
template <typename... TMessageTypes>
class traits
{
#if ETL_USING_CPP11
private:
using message_id_sequence = etl::index_sequence<TMessageTypes::ID...>;
public:
using message_packet = etl::message_packet<TMessageTypes...>;
using message_types = etl::type_list<TMessageTypes...>;
using sorted_message_types = etl::type_list_sort_t<message_types, etl::compare_message_id_less>;
static_assert(etl::type_list_is_unique<message_types>::value, "All TMessageTypes must be unique");
static_assert(etl::type_list_all_of<message_types, etl::is_message_type>::value,
"All TMessageTypes must satisfy the condition etl::is_message_type");
static_assert(etl::index_sequence_is_unique<message_id_sequence>::value, "All message IDs must be unique");
#endif
};
//***************************************************************************
// Specialisation of traits for no message types.
// message packet type
// message type_list
// sorted message type_list.
//***************************************************************************
template <>
class traits<>
{
public:
#if ETL_USING_CPP11
using message_packet = etl::message_packet<>;
using message_types = etl::type_list<>;
using sorted_message_types = etl::type_list<>;
#endif
};
} // namespace private_message_router
//***************************************************************************
/// Forward declare null message router functionality.
//***************************************************************************
class imessage_router;
etl::imessage_router& get_null_message_router();
//***************************************************************************
/// This is the base of all message routers.
//***************************************************************************
class imessage_router : public etl::successor<imessage_router>
{
public:
virtual ~imessage_router() {}
virtual void receive(const etl::imessage&) = 0;
virtual bool accepts(etl::message_id_t) const = 0;
virtual bool is_null_router() const = 0;
virtual bool is_producer() const = 0;
virtual bool is_consumer() const = 0;
//********************************************
virtual void receive(etl::message_router_id_t destination_router_id, const etl::imessage& message)
{
if ((destination_router_id == get_message_router_id()) || (destination_router_id == imessage_router::ALL_MESSAGE_ROUTERS))
{
receive(message);
}
}
//********************************************
virtual void receive(etl::shared_message shared_msg)
{
receive(shared_msg.get_message());
}
//********************************************
virtual void receive(etl::message_router_id_t destination_router_id, etl::shared_message shared_msg)
{
if ((destination_router_id == get_message_router_id()) || (destination_router_id == imessage_router::ALL_MESSAGE_ROUTERS))
{
receive(shared_msg);
}
}
//********************************************
bool accepts(const etl::imessage& msg) const
{
return accepts(msg.get_message_id());
}
//********************************************
etl::message_router_id_t get_message_router_id() const
{
return message_router_id;
}
enum
{
NULL_MESSAGE_ROUTER = 255,
MESSAGE_BUS = 254,
ALL_MESSAGE_ROUTERS = 253,
MESSAGE_BROKER = 252,
MESSAGE_ROUTER = 251,
MAX_MESSAGE_ROUTER = 249
};
protected:
imessage_router(etl::message_router_id_t id_)
: message_router_id(id_)
{
}
imessage_router(etl::message_router_id_t id_, imessage_router& successor_)
: successor(successor_)
, message_router_id(id_)
{
}
private:
// Disabled.
imessage_router(const imessage_router&);
imessage_router& operator=(const imessage_router&);
etl::message_router_id_t message_router_id;
};
//***************************************************************************
/// This router can be used as a sink for messages or a 'null source' router.
//***************************************************************************
class null_message_router
: public imessage_router
, public private_message_router::traits<>
{
public:
//********************************************
null_message_router()
: imessage_router(imessage_router::NULL_MESSAGE_ROUTER)
{
}
//********************************************
null_message_router(etl::imessage_router& successor_)
: imessage_router(imessage_router::NULL_MESSAGE_ROUTER, successor_)
{
}
//********************************************
using etl::imessage_router::receive;
void receive(const etl::imessage& msg) ETL_OVERRIDE
{
if (has_successor())
{
get_successor().receive(msg);
}
}
//********************************************
using etl::imessage_router::accepts;
bool accepts(etl::message_id_t id) const ETL_OVERRIDE
{
if (has_successor())
{
return get_successor().accepts(id);
}
else
{
return false;
}
}
//********************************************
ETL_DEPRECATED
bool is_null_router() const ETL_OVERRIDE
{
return true;
}
//********************************************
bool is_producer() const ETL_OVERRIDE
{
return false;
}
//********************************************
bool is_consumer() const ETL_OVERRIDE
{
return false;
}
//********************************************
static null_message_router& instance()
{
static null_message_router nmr;
return nmr;
}
};
//***********************************************
/// null message router functionality.
inline etl::imessage_router& get_null_message_router()
{
return etl::null_message_router::instance();
}
//***************************************************************************
/// This router can be used as a producer-only of messages, such an interrupt
/// routine.
//***************************************************************************
class message_producer
: public imessage_router
, public private_message_router::traits<>
{
public:
//********************************************
message_producer()
: imessage_router(etl::imessage_router::MESSAGE_ROUTER)
{
}
//********************************************
message_producer(etl::imessage_router& successor_)
: imessage_router(imessage_router::NULL_MESSAGE_ROUTER, successor_)
{
}
//********************************************
message_producer(etl::message_router_id_t id_)
: imessage_router(id_)
{
ETL_ASSERT(id_ <= etl::imessage_router::MAX_MESSAGE_ROUTER, ETL_ERROR(etl::message_router_illegal_id));
}
//********************************************
message_producer(etl::message_router_id_t id_, etl::imessage_router& successor_)
: imessage_router(id_, successor_)
{
ETL_ASSERT(id_ <= etl::imessage_router::MAX_MESSAGE_ROUTER, ETL_ERROR(etl::message_router_illegal_id));
}
//********************************************
using etl::imessage_router::receive;
void receive(const etl::imessage& msg) ETL_OVERRIDE
{
if (has_successor())
{
get_successor().receive(msg);
}
}
//********************************************
using etl::imessage_router::accepts;
bool accepts(etl::message_id_t id) const ETL_OVERRIDE
{
if (has_successor())
{
return get_successor().accepts(id);
}
else
{
return false;
}
}
//********************************************
ETL_DEPRECATED
bool is_null_router() const ETL_OVERRIDE
{
return false;
}
//********************************************
bool is_producer() const ETL_OVERRIDE
{
return true;
}
//********************************************
bool is_consumer() const ETL_OVERRIDE
{
return false;
}
};
//***************************************************************************
/// Is T ultimately derived from etl::imessage_router?
//***************************************************************************
template <typename T>
struct is_message_router : public etl::bool_constant< etl::is_base_of< etl::imessage_router, typename etl::remove_cvref<T>::type>::value>
{
};
//***************************************************************************
/// Send a message to a router.
//***************************************************************************
template <typename TRouter, typename TMessage>
static typename etl::enable_if< etl::is_message_router<TRouter>::value && etl::is_message<TMessage>::value, void>::type
send_message(TRouter& destination, const TMessage& message)
{
destination.receive(message);
}
//***************************************************************************
/// Send a shared message to a router.
//***************************************************************************
template <typename TRouter>
static typename etl::enable_if<etl::is_message_router<TRouter>::value, void>::type send_message(TRouter& destination, etl::shared_message message)
{
destination.receive(message);
}
//***************************************************************************
/// Send a message to a router with a particular id.
//***************************************************************************
template <typename TRouter, typename TMessage>
static typename etl::enable_if< etl::is_message_router<TRouter>::value && etl::is_message<TMessage>::value, void>::type
send_message(TRouter& destination, etl::message_router_id_t id, const TMessage& message)
{
destination.receive(id, message);
}
//***************************************************************************
/// Send a shared message to a router with a particular id.
//***************************************************************************
template <typename TRouter>
static typename etl::enable_if<etl::is_message_router<TRouter>::value, void>::type send_message(TRouter& destination, etl::message_router_id_t id,
etl::shared_message message)
{
destination.receive(id, message);
}
//*************************************************************************************************
// For C++11 and above.
//*************************************************************************************************
#if ETL_USING_CPP11 && !defined(ETL_MESSAGE_ROUTER_FORCE_CPP03_IMPLEMENTATION)
//***************************************************************************
// The definition for all message types.
//***************************************************************************
template <typename TDerived, typename... TMessageTypes>
class message_router
: public imessage_router
, public private_message_router::traits<TMessageTypes...>
{
public:
using typename private_message_router::traits<TMessageTypes...>::message_packet;
using typename private_message_router::traits<TMessageTypes...>::message_types;
using typename private_message_router::traits< TMessageTypes...>::sorted_message_types;
//**********************************************
/// Default constructor. The message router id will be MESSAGE_ROUTER.
//**********************************************
message_router()
: imessage_router(etl::imessage_router::MESSAGE_ROUTER)
{
}
//**********************************************
/// Constructor with successor. The message router id will be
/// MESSAGE_ROUTER.
//**********************************************
message_router(etl::imessage_router& successor_)
: imessage_router(etl::imessage_router::MESSAGE_ROUTER, successor_)
{
}
//**********************************************
/// Constructor with message router id.
//**********************************************
message_router(etl::message_router_id_t id_)
: imessage_router(id_)
{
ETL_ASSERT(id_ <= etl::imessage_router::MAX_MESSAGE_ROUTER, ETL_ERROR(etl::message_router_illegal_id));
}
//**********************************************
/// Constructor with message router id and successor.
//**********************************************
message_router(etl::message_router_id_t id_, etl::imessage_router& successor_)
: imessage_router(id_, successor_)
{
ETL_ASSERT(id_ <= etl::imessage_router::MAX_MESSAGE_ROUTER, ETL_ERROR(etl::message_router_illegal_id));
}
//**********************************************
/// Allow visibility of base class receive() methods.
//**********************************************
using etl::imessage_router::receive;
//**********************************************
/// This will be called for all messages passed as an etl::imessage.
/// It will dispatch the message to the correct handler based on the message
/// id, or pass it to a successor if there is no handler for the message id.
/// \param msg The message.
//***********************************************
void receive(const etl::imessage& msg) ETL_OVERRIDE
{
const etl::message_id_t id = msg.get_message_id();
// The IDs are sorted, so an ID less than the first is not handled by this
// router.
if (id >= Message_Id_Start)
{
const size_t index = get_dispatch_index_from_message_id(id);
// If the index is less than Number_Of_Messages, then we have a handler
// for this message type, so dispatch it.
if (index < Number_Of_Messages)
{
dispatch(msg, index);
return;
}
}
// We don't have a handler for this message type, so pass it to a
// successor if there is one, or call on_receive_unknown() if there isn't.
if (has_successor())
{
get_successor().receive(msg);
}
else
{
#include "etl/private/diagnostic_array_bounds_push.h"
static_cast<TDerived*>(this)->on_receive_unknown(msg);
#include "etl/private/diagnostic_pop.h"
}
}
//**********************************************
/// This will be called for messages where TMessage is in the message type
/// list. \tparam TMessage The message type. \param msg The message. Enabled
/// if TMessage is in the message type list.
//**********************************************
template < typename TMessage, typename etl::enable_if<etl::is_one_of<TMessage, TMessageTypes...>::value, int>::type = 0>
void receive(const TMessage& msg)
{
#include "etl/private/diagnostic_array_bounds_push.h"
static_cast<TDerived*>(this)->on_receive(msg);
#include "etl/private/diagnostic_pop.h"
}
//**********************************************
/// This will be called for messages where TMessage is a message type, but
/// not in the message type list. \tparam TMessage The message type. \param
/// msg The message. Enabled if TMessage is a message type, but not in the
/// message type list.
//**********************************************
template <typename TMessage,
typename etl::enable_if< etl::is_message<TMessage>::value && !etl::is_one_of<TMessage, TMessageTypes...>::value, int>::type = 0>
void receive(const TMessage& msg)
{
if (has_successor())
{
get_successor().receive(msg);
}
else
{
#include "etl/private/diagnostic_array_bounds_push.h"
static_cast<TDerived*>(this)->on_receive_unknown(msg);
#include "etl/private/diagnostic_pop.h"
}
}
//**********************************************
/// Allow visibility of base class accepts() methods.
//**********************************************
using imessage_router::accepts;
//**********************************************
/// This will return true if the message id is in the message type list, or
/// if a successor accepts the message id.
//***********************************************
bool accepts(etl::message_id_t id) const ETL_OVERRIDE
{
if (id >= Message_Id_Start)
{
const size_t index = get_dispatch_index_from_message_id(id);
if (index < Number_Of_Messages)
{
return true;
}
}
return has_successor() ? get_successor().accepts(id) : false;
}
//********************************************
ETL_DEPRECATED
bool is_null_router() const ETL_OVERRIDE
{
return false;
}
//********************************************
bool is_producer() const ETL_OVERRIDE
{
return true;
}
//********************************************
bool is_consumer() const ETL_OVERRIDE
{
return true;
}
private:
static constexpr size_t Number_Of_Messages = sizeof...(TMessageTypes);
static constexpr etl::message_id_t Message_Id_Start = etl::type_list_type_at_index_t<sorted_message_types, 0>::ID;
//**********************************************
// Checks that the message ids are contiguous.
//**********************************************
template <size_t Index, bool Last = (Index + 1U >= Number_Of_Messages)>
struct contiguous_impl;
template <size_t Index>
struct contiguous_impl<Index, true> : etl::true_type
{
};
template <size_t Index>
struct contiguous_impl<Index, false>
: etl::bool_constant< (etl::type_list_type_at_index_t<sorted_message_types, Index>::ID + 1U
== etl::type_list_type_at_index_t<sorted_message_types, Index + 1U>::ID)
&& contiguous_impl<Index + 1U>::value>
{
};
// The message ids are contiguous if there are 0 or 1 message types, or if
// each message id is one greater than the previous message id.
static constexpr bool Message_Ids_Are_Contiguous = (Number_Of_Messages <= 1U) ? true : contiguous_impl<0U>::value;
using handler_ptr = void (*)(TDerived&,
const etl::imessage&); ///< Pointer to a handler function that
///< takes a reference to the derived
///< class and a reference to the message.
using message_dispatch_table_t = etl::array<handler_ptr,
Number_Of_Messages>; ///< The dispatch table type. An array of
///< handler pointers, one for each
///< message type.
using message_id_table_t = etl::array<etl::message_id_t,
Number_Of_Messages>; ///< The message id table type. An
///< array of message ids, one for
///< each message type.
//**********************************************
// Call for a single message type
//**********************************************
template <typename TMessage>
static void call_on_receive(TDerived& derived, const imessage& msg)
{
derived.on_receive(static_cast<const TMessage&>(msg));
}
//**********************************************
// Get the handler for a single message type at the index in the sorted
// type_list. This will be called for each message type to generate the
// dispatch table.
//**********************************************
template <size_t Index>
static constexpr handler_ptr get_message_handler()
{
return &call_on_receive< etl::type_list_type_at_index_t<sorted_message_types, Index>>;
}
//**********************************************
// Generate the dispatch table at compile time.
// This will create an array of handler pointers, one for each message type.
//**********************************************
template <size_t... Indices>
static constexpr message_dispatch_table_t make_message_dispatch_table(etl::index_sequence<Indices...>)
{
return message_dispatch_table_t{{get_message_handler<Indices>()...}};
}
//**********************************************
// Get the message id for a single message type at an index in the sorted
// type_list. This will be called for each message type to generate the
// message id table.
//**********************************************
template <size_t Index>
static constexpr etl::message_id_t get_message_id_from_index()
{
return etl::type_list_type_at_index_t<sorted_message_types, Index>::ID;
}
//**********************************************
// Generate the message id table at compile time.
// This will create an array of message ids, one for each message type.
//**********************************************
template <size_t... Indices>
static constexpr message_id_table_t make_message_id_table(etl::index_sequence<Indices...>)
{
return message_id_table_t{{get_message_id_from_index<Indices>()...}};
}
//**********************************************
// Get the dispatch index for a message id.
// This will be used at runtime to find the handler for a message id.
// If the message ids are contiguous, we can calculate the index directly.
// If they are not contiguous, we need to do a binary search. This will
// return Number_Of_Messages if the message id is not found, which indicates
// that the message should be passed to the successor.
//**********************************************
static size_t get_dispatch_index_from_message_id(etl::message_id_t id)
{
if ETL_IF_CONSTEXPR (Message_Ids_Are_Contiguous)
{
// The IDs are contiguous, so we can calculate the index directly.
return static_cast<size_t>(id - Message_Id_Start);
}
else
{
// The IDs are not contiguous, so we need to do a binary search.
size_t left = 0;
size_t right = Number_Of_Messages;
while (left < right)
{
size_t mid = (left + right) / 2;
if (message_id_table[mid] == id)
{
return mid;
}
else if (message_id_table[mid] < id)
{
left = mid + 1;
}
else
{
right = mid;
}
}
}
return Number_Of_Messages; // Not found
}
//**********************************************
// Dispatch the message to the appropriate handler based on the index in the
// dispatch table.
//**********************************************
void dispatch(const etl::imessage& msg, size_t index)
{
message_dispatch_table[index](static_cast<TDerived&>(*this), msg);
}
//**********************************************
// The dispatch table is generated at compile time. The dispatch table
// contains pointers to the on_receive handlers for each message type.
//**********************************************
static ETL_INLINE_VAR constexpr message_dispatch_table_t message_dispatch_table =
etl::message_router<TDerived, TMessageTypes...>::make_message_dispatch_table(
etl::make_index_sequence< etl::message_router< TDerived, TMessageTypes...>::Number_Of_Messages>{});
//**********************************************
// The message id table is generated at compile time. The message id table
// contains the corresponding message ids for each message type.
//**********************************************
static ETL_INLINE_VAR constexpr message_id_table_t message_id_table = etl::message_router<TDerived, TMessageTypes...>::make_message_id_table(
etl::make_index_sequence< etl::message_router< TDerived, TMessageTypes...>::Number_Of_Messages>{});
};
#if ETL_USING_CPP11 && !ETL_USING_CPP17
template <typename TDerived, typename... TMessageTypes>
constexpr const typename etl::message_router< TDerived, TMessageTypes...>::message_dispatch_table_t
etl::message_router<TDerived, TMessageTypes...>::message_dispatch_table;
template <typename TDerived, typename... TMessageTypes>
constexpr const typename etl::message_router< TDerived, TMessageTypes...>::message_id_table_t
etl::message_router<TDerived, TMessageTypes...>::message_id_table;
#endif
//***************************************************************************
// The definition of a message_router for zero message types.
//***************************************************************************
template <typename TDerived>
class message_router<TDerived>
: public imessage_router
, public private_message_router::traits<>
{
public:
//**********************************************
/// Default constructor. The message router id will be MESSAGE_ROUTER.
//**********************************************
message_router()
: imessage_router(etl::imessage_router::MESSAGE_ROUTER)
{
}
//**********************************************
/// Constructor with successor. The message router id will be
/// MESSAGE_ROUTER.
//**********************************************
message_router(etl::imessage_router& successor_)
: imessage_router(etl::imessage_router::MESSAGE_ROUTER, successor_)
{
}
//**********************************************
/// Constructor with message router id.
//**********************************************
message_router(etl::message_router_id_t id_)
: imessage_router(id_)
{
ETL_ASSERT(id_ <= etl::imessage_router::MAX_MESSAGE_ROUTER, ETL_ERROR(etl::message_router_illegal_id));
}
//**********************************************
/// Constructor with message router id and successor.
//**********************************************
message_router(etl::message_router_id_t id_, etl::imessage_router& successor_)
: imessage_router(id_, successor_)
{
ETL_ASSERT(id_ <= etl::imessage_router::MAX_MESSAGE_ROUTER, ETL_ERROR(etl::message_router_illegal_id));
}
//**********************************************
/// Allow visibility of base class receive() methods.
using etl::imessage_router::receive;
//**********************************************
/// This will be called for all messages passed as an etl::imessage.
/// Since there are no message types, this will just pass the message to a
/// successor if there is one, or call on_receive_unknown() if there isn't.
/// \param msg The message.
//***********************************************
void receive(const etl::imessage& msg) ETL_OVERRIDE
{
#include "etl/private/diagnostic_array_bounds_push.h"
if (has_successor())
{
get_successor().receive(msg);
}
#include "etl/private/diagnostic_pop.h"
}
//**********************************************
/// Allow visibility of base class accepts() methods.
//**********************************************
using imessage_router::accepts;
//**********************************************
/// This will return true if a successor accepts the message id.
//***********************************************
bool accepts(etl::message_id_t id) const ETL_OVERRIDE
{
if (has_successor())
{
return get_successor().accepts(id);
}
else
{
return false;
}
}
//********************************************
ETL_DEPRECATED
bool is_null_router() const ETL_OVERRIDE
{
return false;
}
//********************************************
bool is_producer() const ETL_OVERRIDE
{
return true;
}
//********************************************
bool is_consumer() const ETL_OVERRIDE
{
return true;
}
};
//***************************************************************************
/// Helper to turn etl::type_list<TTypes...> into
/// etl::message_router<TTypes...>
//***************************************************************************
template <typename TDerived, typename TList>
struct message_router_from_type_list;
template <typename TDerived, typename... TMessageTypes>
struct message_router_from_type_list<TDerived, etl::type_list<TMessageTypes...>>
{
using type = etl::message_router<TDerived, TMessageTypes...>;
};
template <typename TDerived, typename TTypeList>
using message_router_from_type_list_t = typename message_router_from_type_list<TDerived, TTypeList>::type;
#else
#include "private/message_router_cpp03.h"
#endif
} // namespace etl
#endif