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: .. literalinclude:: //home/runner/work/Unpadded/Unpadded/test/snippet/callee1 :language: cpp Basics : fulfiling incoming request with dispatchers ---------------------------------------------------- .. warning Dispatchers are NOT thread-safe. 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: .. literalinclude:: //home/runner/work/Unpadded/Unpadded/test/snippet/callee2 :language: cpp 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``. .. literalinclude:: //home/runner/work/Unpadded/Unpadded/test/snippet/motion_keyring1.hpp :language: cpp :caption: motion_keyring.hpp .. literalinclude:: //home/runner/work/Unpadded/Unpadded/test/snippet/callee1.cpp :language: cpp :caption: callee.cpp 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`` ~~~~~~~~~~~~~~ .. doxygenclass:: upd::dispatcher :members: ``buffered_dispatcher`` ~~~~~~~~~~~~~~~~~~~~~~~ .. doxygenclass:: upd::buffered_dispatcher :members: ``single_buffered_dispatcher`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. doxygenclass:: upd::single_buffered_dispatcher :members: ``double_buffered_dispatcher`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. doxygenclass:: upd::double_buffered_dispatcher :members: ``packet_status`` ~~~~~~~~~~~~~~~~~ .. doxygenenum:: upd::packet_status Policies ~~~~~~~~ .. doxygenvariable:: upd::policy::any_action .. doxygenvariable:: upd::policy::static_storage_duration_only