Implementing callee side¶
Unpadded provides tools for receiving and processing incoming packet from the caller. Depending on your needs, there are several options available, especially for embedded platforms.
The following sections will assume these functions to be defined by the callee device:
void write_byte_to_master(upd::byte_t);
upd::byte_t read_byte_from_master();
Basics : fulfiling incoming request with dispatchers¶
Processing incoming invocation requests and sending back the result of this invocation to the caller is done with the dispatcher classes. There are four of them:
dispatcher
, the most basic one, which provide no internal buffer;buffered_dispatcher
, which use user-provided buffers;single_buffered_dispatcher
, which use a single internal buffer for both receiving and sending;double_buffered_dispatcher
, which use two separate internal buffers: one for receiving and one for sending;
Receiving a packet and sending back a response packet is pretty straightforward:
// We assume that a `keyring` object has been defined and
// both the caller and the callee use this keyring.
// We create a dispatcher which can handle request for
// the invocables managed by `keyring`. The second
// argument will be discussed later.
static upd::dispatcher dispatcher{keyring, upd::policy::any_callback};
// Let's assume this function is invoked when the input
// buffer contains a full request.
void processing_request() {
// And, that's it ! With this single line, the dispatcher
// will automatically extract the bytes from the input
// buffer and write the response to the output buffer.
dispatcher(read_byte_from_master, write_byte_to_master);
}
Buffered dispatchers vs. non-buffered dispatchers¶
Both kind of dispatchers use callbacks to read and write the bytes of the packets. However:
Non-buffered dispatchers will immediately read all the bytes needed for a full packet, regardless of whether these bytes have actually all been receive at this point. In other words, the provided invocable which is used to get the bytes one by one will be called reapetedly, and it is up to you to implement the correct behavior. For example, you may block until at least one byte is available. The most sensible solution would be to call the dispatcher only when enough bytes have been received or to use synchronization primitives in order to wait for new bytes to come without blocking the device.
Buffered dispatchers use buffers which allow you to store incomplete packets. That means that you may receive a part of the packet, then do something else to finally receive the rest later.
Non-buffered dispatchers may be a viable option when you can afford to block the execuction of your program or can use an RTOS. It may be also be useful if you have specific needs and want to implement your own dispatcher, since it is the most basic form of dispatcher.
Buffered dispatchers are best used when you can’t afford to waste machine cycles. Most often, communication peripherals of microcontrollers can only receive incoming data in a fixed-width FIFO and same goes with sending. In that case, buffered dispatchers are suitable, since they provide a buffer big enough to receive an action request or response.
Single buffered dispatchers vs. double buffered dispatchers¶
Single buffered dispatchers only have a single internal buffer, which is used to store both incoming packets and packets to send back. However, you can’t receive any packet as long as you have a packet to send in the buffer.
Double buffered dispatchers, on the other hand, use separate buffers for input and output. That means that you can receive an incoming request and sending a response packet at the same time. Only one packet can fit in both buffers.
You can implement your owm kind of buffered dispatcher using the buffered_dispatcher
class. On the other hand, single_buffered_dispatcher
and double_buffered_dispatcher
come with their own internal buffers, whose sizes are optimized to handle packets no larger than what you should receive or send. These sizes are deduced at compile-time with CTAD (or template argument deduction is you are using make_single_buffered_dispatcher
or make_double_buffered_dispatcher
) using the provided keyring.
Example¶
In the follwing example, we assume that on_byte_received
is invoked whenever a byte has been received from callee (which can be retrieved using read_byte_from_callee
) and on_byte_sent
is invoked whenever a byte has been fully sent to callee with the function write_byte_to_callee
.
#pragma once
#include <cstdint>
#include <upd/format.hpp>
#include <upd/keyring.hpp>
#include <upd/typelist.hpp>
void set_forward_speed(std::uint32_t);
void set_left_steering_speed(std::uint32_t);
void set_right_steering_speed(std::uint32_t);
constexpr upd::keyring motion_keyring{upd::flist<set_forward_speed, set_left_steering_speed, set_right_steering_speed>,
upd::little_endian,
upd::twos_complement};
Hot swapping callbacks¶
You can replace what callback will invoked after your dispatcher has been created with the replace
member function. However, it must be invocable in the same way (ie. it must be invocable on the same parameter as the replaced callback, and it must return a value of the same type). Depending on the storage policy, you can even replace the callbacks with callback of any storage duration.
Storage policy¶
Depending on what you can afford to do, you will want your invocables to be stored differently. There is two available policies at the moment:
Allowing any kind of callback to be stored with the
any_action
policy. It means that the dispatcher is responsible for the life cycle of the callbacks, and it therefore uses dynamic allocation.Allowing callback with static storage duration only with the
weak_reference
policy. In that case, the dispatcher doesn’t need to manage the life cycle of the callbacks because it restricts the callbacks to be allocated statically. In that case, the dispatcher merely refers to the callbacks without keeping it alive, hence the name of the policy. This policy works well with plain functions, since they exist: in program memory which is usually not modified. It can also work with function objects, but in that case, the object cannot live on the heap or on the stack. It must be alive during the whole execution of the program.
The latter policy is more appropriate for microcontrollers.
API References¶
dispatcher
¶
-
template<typename Keyring, action_features Action_Features>
class dispatcher : public upd::detail::immediate_process<dispatcher<Keyring, Action_Features>, Keyring::index_t>¶ Action container able to accept and process action requests.
A dispatcher is constructed from a keyring instance and is able to unserialize a payload serialized by a key created from the same keyring. When it happens, the dispatcher calls the callback associated with the key, forwards the arguments from the payload to the callback. The functions are internally held as action instances.
- Template Parameters:
Keyring – Keyring describing the actions to manage
Action_Features – Restriction on stored actions
Public Types
Public Functions
-
inline explicit dispatcher(Keyring, action_features_h<Action_Features>)¶
Construct the object from the provided keyring.
-
inline dispatcher()¶
Construct the object from the provided keyring.
-
template<typename Src, typename Dest>
inline index_t operator()(Src &&src, Dest &&dest)¶ Extract an index from a byte sequence then invoke the action with that index.
The parameters for the action call are extracted from
src
and the return value is inserted intodest
.The following member functions are also defined through CRTP.
All these functions work the same way asauto operator()(Input &&, Output &&); auto operator()(Input &&, Output &&) const;
operator()(Src &&, Dest &&)
.- Template Parameters:
Input – Input byte stream type of any kind
Output – Output byte stream type of any kind
- Parameters:
src – Byte getter
dest – Byte putter
- Returns:
the index of the called action
-
template<typename Src>
inline action_t *get_action(Src &&src)¶ Extract an index from a byte sequence and get the action with that index.
- Parameters:
src – Byte getter
- Returns:
Either a reference to the action if it exists or
nullptr
-
template<typename Src>
inline index_t get_index(Src &&src) const¶ Extract an index from a byte sequence.
- Parameters:
src – Byte getter
- Returns:
The extracted index
-
template<index_t Index, typename F, F Ftor>
inline void replace(unevaluated<F, Ftor>)¶ Replace with a free function or a callback with static storage duration.
- Template Parameters:
Index – Index of the action to replace
Ftor – Free function or callback with static storage duration
-
template<index_t Index, auto &Ftor>
inline void replace()¶ (C++17) Replace with a free function or a callback with static storage duration
- Template Parameters:
Index – Index of the action to replace
Ftor – Free function or callback with static storage duration
-
template<index_t Index, typename F>
inline void replace(F &&ftor)¶ Replace with a callback of any kind.
- Template Parameters:
Index – Index of the action to replace
- Parameters:
ftor – Callback of any kind
Public Static Attributes
-
template<typename Keyring, action_features Action_Features>
dispatcher<Keyring, Action_Features> make_dispatcher(Keyring, action_features_h<Action_Features>)¶ Make a dispatcher.
buffered_dispatcher
¶
-
template<typename D, typename Dispatcher>
class buffered_dispatcher : public upd::detail::immediate_reader<buffered_dispatcher<D, Dispatcher>, packet_status>, public upd::detail::immediate_writer<buffered_dispatcher<D, Dispatcher>>, public upd::detail::immediate_process<buffered_dispatcher<D, Dispatcher>, packet_status>¶ Dispatcher with input / output storage.
Instances of this class may store input and output byte streams while they are received or sent. This allows the user to load and unload the dispatcher byte after byte, whereas plain dispatchers cannot buffer their input and output. A buffered dispatcher goes through the following states:
The input buffer is empty, ready to accept an action request.
Once a full action request has been received, it is immediately fulfilled and the result is written to the output buffer. The input buffer is reset, thus it may receive a new request while the output buffer is unloaded.
Once the output buffer is empty, it may be written again.
This class is not self-sufficient and must be derived from according to the CRTP idiom.
Note
It is possible to use a single buffer as input and output as long as the reading and the writing does not occur at the same time. For that purpose, is_loaded() will indicate whether the output buffer is empty or not.
- Template Parameters:
D – Derived class
Dispatcher – Type of the underlying dispatcher
Public Types
-
using index_t = typename Dispatcher::index_t¶
Type of the index prepended to the payload when sending a packet to the callee.
Public Functions
-
template<typename Keyring, action_features Action_Features>
inline explicit buffered_dispatcher(Keyring, action_features_h<Action_Features>)¶ Initialize the underlying plain dispatcher with a keyring.
- Template Parameters:
Keyring – keyring template instance
Action_Features – Allowed action features for the managed actions
-
inline buffered_dispatcher()¶
Initialize the underlying plain dispatcher with a keyring.
- Template Parameters:
Keyring – keyring template instance
Action_Features – Allowed action features for the managed actions
-
inline bool is_loaded() const¶
Indicates whether the output buffer contains data to send.
- Returns:
true
if and only if the next call to put() or write_to() will have a visible effect
-
template<typename Src>
inline packet_status read_from(Src &&src)¶ Put bytes into the input buffer until a full action request is stored.
The following member functions are also defined through CRTP.
All these functions work the same way asR operator<<(Src &&); R operator<<(Src &&) const; R read_from(It); R read_from(It) const; R operator<<(It); R operator<<(It) const;
read_from(Src &&)
.- Template Parameters:
Src – Byte getter type
It – Input iterator type
- Parameters:
src – Byte getter
- Returns:
one of the following :
packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.
packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).
-
inline packet_status put(byte_t byte)¶
Put one byte into the input buffer.
- Parameters:
byte – Byte to put
- Returns:
one of the following :
packet_status::LOADING_PACKET: The packet is not yet fully loaded.
packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.
packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).
-
template<typename Dest>
inline void write_to(Dest &&dest)¶ Completely output the output buffer content.
The following member functions are also defined through CRTP.
All these functions work the same way asvoid operator>>(Dest &&); void operator>>(Dest &&) const; void write_to(It); void write_to(It) const; void operator>>(It); void operator>>(It) const;
write_to(Dest &&)
.- Template Parameters:
Dest – Byte putter type
It – Output iterator type
- Parameters:
dest – Byte putter
-
inline byte_t get()¶
Output one byte from the output buffer.
If the output buffer is empty, the function will return an arbitrary value.
- Returns:
the next byte in the output buffer (if it is not empty) or an arbitrary value
-
template<index_t Index, typename F, F Ftor>
inline void replace(unevaluated<F, Ftor>)¶ Replace with a free function or a callback with static storage duration.
- Template Parameters:
Index – Index of the action to replace
Ftor – Free function or callback with static storage duration
-
template<typename Src, typename Dest>
inline packet_status operator()(Src &&src, Dest &&dest)¶ Call read_from() then write_to()
write_to() is called if and only the output buffer has been populated by read_from().
The following member functions are also defined through CRTP.
All these functions work the same way asauto operator()(Input &&, Output &&); auto operator()(Input &&, Output &&) const;
operator()(Src &&, Dest &&)
.- Template Parameters:
Input – Input byte stream type of any kind
Output – Output byte stream type of any kind
- Parameters:
src – Byte getter
dest – Byte putter
- Returns:
the enumerator instance resulting from the read_from() call
-
template<index_t Index, auto &Ftor>
inline void replace()¶ (C++17) Replace with a free function or a callback with static storage duration
- Template Parameters:
Index – Index of the action to replace
Ftor – Free function or callback with static storage duration
-
template<index_t Index, typename F>
inline void replace(F &&ftor)¶ Replace with a callback of any kind.
- Template Parameters:
Index – Index of the action to replace
- Parameters:
ftor – Callback of any kind
-
template<typename Output, typename Key>
inline bool reply(Output &&output, Key k)¶ Forward the content of the output buffer to another dispatcher as an action request.
This function send an action request to the other dispatcher, which is supposed to process directly the content of the output buffer. The requested action must accept a single byte buffer as sole argument (the size of the buffer can be choosen at compile time). The content of the received byte sequence is the same as if it was written by write_to(), so it can be deserialized inside the requested action by using the key that was used to populate the output buffer.
This function can only be used if the data in the output buffer is complete (i.e. if no call to get() has been made since the last packet resolution) and if the requested action buffer is large enough to hold all the data to send. When this function has finished executing, the output buffer is empty.
- Parameters:
output – Output byte stream of any kind
k – Key of the action to request on the other dispatcher
- Returns:
true
if and only if the content of the output buffer has been written to the output byte stream
single_buffered_dispatcher
¶
-
template<typename Dispatcher>
class single_buffered_dispatcher : public upd::buffered_dispatcher<single_buffered_dispatcher<Dispatcher>, Dispatcher>¶ Implements a dispatcher using a single buffer for input and output.
The buffer is allocated statically as a plain array. Its size is as small as possible for holding any action request and any action response.
Warning
It is not possible to read a request and write a response at the same time. If you need to do that, use double_buffered_dispatcher instead.
- Template Parameters:
Dispatcher – Underlying dispatcher type
Public Functions
-
template<typename Keyring, action_features Action_Features>
inline explicit single_buffered_dispatcher(Keyring, action_features_h<Action_Features>)¶ Initialize the underlying dispatcher.
- Template Parameters:
Keyring – Keyring which holds the actions to be managed by the dispatcher
Action_Features – Features of the actions managed by the dispatcher
-
single_buffered_dispatcher() = default¶
Initialize the underlying dispatcher.
-
inline bool is_loaded() const¶
Indicates whether the output buffer contains data to send.
- Returns:
true
if and only if the next call to put() or write_to() will have a visible effect
-
inline packet_status read_from(Src &&src)¶
Put bytes into the input buffer until a full action request is stored.
The following member functions are also defined through CRTP.
All these functions work the same way asR operator<<(Src &&); R operator<<(Src &&) const; R read_from(It); R read_from(It) const; R operator<<(It); R operator<<(It) const;
read_from(Src &&)
.- Template Parameters:
Src – Byte getter type
It – Input iterator type
- Parameters:
src – Byte getter
- Returns:
one of the following :
packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.
packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).
-
inline packet_status put(byte_t byte)¶
Put one byte into the input buffer.
- Parameters:
byte – Byte to put
- Returns:
one of the following :
packet_status::LOADING_PACKET: The packet is not yet fully loaded.
packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.
packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).
-
inline void write_to(Dest &&dest)¶
Completely output the output buffer content.
The following member functions are also defined through CRTP.
All these functions work the same way asvoid operator>>(Dest &&); void operator>>(Dest &&) const; void write_to(It); void write_to(It) const; void operator>>(It); void operator>>(It) const;
write_to(Dest &&)
.- Template Parameters:
Dest – Byte putter type
It – Output iterator type
- Parameters:
dest – Byte putter
-
inline byte_t get()¶
Output one byte from the output buffer.
If the output buffer is empty, the function will return an arbitrary value.
- Returns:
the next byte in the output buffer (if it is not empty) or an arbitrary value
-
inline packet_status operator()(Src &&src, Dest &&dest)¶
Call read_from() then write_to()
write_to() is called if and only the output buffer has been populated by read_from().
The following member functions are also defined through CRTP.
All these functions work the same way asauto operator()(Input &&, Output &&); auto operator()(Input &&, Output &&) const;
operator()(Src &&, Dest &&)
.- Template Parameters:
Input – Input byte stream type of any kind
Output – Output byte stream type of any kind
- Parameters:
src – Byte getter
dest – Byte putter
- Returns:
the enumerator instance resulting from the read_from() call
-
inline bool reply(Output &&output, Key k)¶
Forward the content of the output buffer to another dispatcher as an action request.
This function send an action request to the other dispatcher, which is supposed to process directly the content of the output buffer. The requested action must accept a single byte buffer as sole argument (the size of the buffer can be choosen at compile time). The content of the received byte sequence is the same as if it was written by write_to(), so it can be deserialized inside the requested action by using the key that was used to populate the output buffer.
This function can only be used if the data in the output buffer is complete (i.e. if no call to get() has been made since the last packet resolution) and if the requested action buffer is large enough to hold all the data to send. When this function has finished executing, the output buffer is empty.
- Parameters:
output – Output byte stream of any kind
k – Key of the action to request on the other dispatcher
- Returns:
true
if and only if the content of the output buffer has been written to the output byte stream
Public Static Attributes
-
static constexpr auto buffer_size = detail::max_p<detail::needed_input_buffer_size<keyring_t>, detail::needed_output_buffer_size<keyring_t>>::value¶
Equals the size of the buffer.
-
template<typename Keyring, action_features Action_Features>
auto make_single_buffered_dispatcher(Keyring, action_features_h<Action_Features>)¶ Make a single buffered dispatcher.
double_buffered_dispatcher
¶
-
template<typename Dispatcher>
class double_buffered_dispatcher : public upd::buffered_dispatcher<double_buffered_dispatcher<Dispatcher>, Dispatcher>¶ Implements a dispatcher using separate buffers for input and output.
The buffers are allocated statically as plain arrays. Their sizes are as small as possible for holding any action request and any action response.
Note
If you would rather having a single buffer and do not mind reading and writing at different moment, consider using single_buffered_dispatcher instead.
- Template Parameters:
Dispatcher – Underlying dispatcher type
Public Functions
-
template<typename Keyring, action_features Action_Features>
inline explicit double_buffered_dispatcher(Keyring, action_features_h<Action_Features>)¶ Initialize the underlying dispatcher.
- Template Parameters:
Keyring – Keyring which holds the actions to be managed by the dispatcher
Action_Features – Features of the actions managed by the dispatcher
-
double_buffered_dispatcher() = default¶
Initialize the underlying dispatcher.
-
inline bool is_loaded() const¶
Indicates whether the output buffer contains data to send.
- Returns:
true
if and only if the next call to put() or write_to() will have a visible effect
-
inline packet_status read_from(Src &&src)¶
Put bytes into the input buffer until a full action request is stored.
The following member functions are also defined through CRTP.
All these functions work the same way asR operator<<(Src &&); R operator<<(Src &&) const; R read_from(It); R read_from(It) const; R operator<<(It); R operator<<(It) const;
read_from(Src &&)
.- Template Parameters:
Src – Byte getter type
It – Input iterator type
- Parameters:
src – Byte getter
- Returns:
one of the following :
packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.
packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).
-
inline packet_status put(byte_t byte)¶
Put one byte into the input buffer.
- Parameters:
byte – Byte to put
- Returns:
one of the following :
packet_status::LOADING_PACKET: The packet is not yet fully loaded.
packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.
packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).
-
inline void write_to(Dest &&dest)¶
Completely output the output buffer content.
The following member functions are also defined through CRTP.
All these functions work the same way asvoid operator>>(Dest &&); void operator>>(Dest &&) const; void write_to(It); void write_to(It) const; void operator>>(It); void operator>>(It) const;
write_to(Dest &&)
.- Template Parameters:
Dest – Byte putter type
It – Output iterator type
- Parameters:
dest – Byte putter
-
inline byte_t get()¶
Output one byte from the output buffer.
If the output buffer is empty, the function will return an arbitrary value.
- Returns:
the next byte in the output buffer (if it is not empty) or an arbitrary value
-
inline packet_status operator()(Src &&src, Dest &&dest)¶
Call read_from() then write_to()
write_to() is called if and only the output buffer has been populated by read_from().
The following member functions are also defined through CRTP.
All these functions work the same way asauto operator()(Input &&, Output &&); auto operator()(Input &&, Output &&) const;
operator()(Src &&, Dest &&)
.- Template Parameters:
Input – Input byte stream type of any kind
Output – Output byte stream type of any kind
- Parameters:
src – Byte getter
dest – Byte putter
- Returns:
the enumerator instance resulting from the read_from() call
-
inline bool reply(Output &&output, Key k)¶
Forward the content of the output buffer to another dispatcher as an action request.
This function send an action request to the other dispatcher, which is supposed to process directly the content of the output buffer. The requested action must accept a single byte buffer as sole argument (the size of the buffer can be choosen at compile time). The content of the received byte sequence is the same as if it was written by write_to(), so it can be deserialized inside the requested action by using the key that was used to populate the output buffer.
This function can only be used if the data in the output buffer is complete (i.e. if no call to get() has been made since the last packet resolution) and if the requested action buffer is large enough to hold all the data to send. When this function has finished executing, the output buffer is empty.
- Parameters:
output – Output byte stream of any kind
k – Key of the action to request on the other dispatcher
- Returns:
true
if and only if the content of the output buffer has been written to the output byte stream
Public Static Attributes
-
static constexpr auto input_buffer_size = detail::needed_input_buffer_size<keyring_t>::value¶
Equals the size of the input buffer.
-
static constexpr auto output_buffer_size = detail::needed_output_buffer_size<keyring_t>::value¶
Equals the size of the output buffer.
-
template<typename Keyring, action_features Action_Features>
auto make_double_buffered_dispatcher(Keyring, action_features_h<Action_Features>)¶ Make a double buffered dispatcher.
packet_status
¶
-
enum class upd::packet_status¶
Enumerates the possible status of a loading packet.
LOADING_PACKET
: The packet is currently being loaded and is not yet completeDROPPED_PACKET
: The packet loading has been canceled before completionRESOLVED_PACKET
: The packet loading has been completed and the corresponding action has been called
Values:
-
enumerator LOADING_PACKET¶
-
enumerator DROPPED_PACKET¶
-
enumerator RESOLVED_PACKET¶
Policies¶
Warning
doxygenvariable: Cannot find variable “upd::policy::any_action” in doxygen xml output for project “Unpadded” from directory: /home/runner/work/Unpadded/Unpadded/build/doc/xml
Warning
doxygenvariable: Cannot find variable “upd::policy::static_storage_duration_only” in doxygen xml output for project “Unpadded” from directory: /home/runner/work/Unpadded/Unpadded/build/doc/xml