From 5ef1c468ce226d8c376c1e4fd4cf63b5c2a61f1b Mon Sep 17 00:00:00 2001 From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com> Date: Wed, 9 Aug 2023 07:52:48 +0200 Subject: [PATCH] Doc: Added documentation for 5.12 release (#1182) Co-authored-by: Kerstin Keller Co-authored-by: Rex Schilasky <49162693+rex-schilasky@users.noreply.github.com> --- doc/rst/advanced/layers/shm.rst | 75 ++-- doc/rst/advanced/layers/shm_zerocopy.rst | 374 ++++++++++++++++++ doc/rst/versions/5.10/whats_new.rst | 3 +- doc/rst/versions/5.11/whats_new.rst | 2 +- doc/rst/versions/5.12/compatibility_table.txt | 42 ++ doc/rst/versions/5.12/whats_new.rst | 58 +++ doc/rst/versions/5.8/whats_new.rst | 2 +- doc/rst/versions/5.9/whats_new.rst | 3 +- doc/rst/versions/compatibility.rst | 7 + doc/rst/versions/ecal_versions.rst | 1 + 10 files changed, 510 insertions(+), 57 deletions(-) create mode 100644 doc/rst/advanced/layers/shm_zerocopy.rst create mode 100644 doc/rst/versions/5.12/compatibility_table.txt create mode 100644 doc/rst/versions/5.12/whats_new.rst diff --git a/doc/rst/advanced/layers/shm.rst b/doc/rst/advanced/layers/shm.rst index 24987315b4..b43f0f8169 100644 --- a/doc/rst/advanced/layers/shm.rst +++ b/doc/rst/advanced/layers/shm.rst @@ -40,7 +40,7 @@ Communication phase (default configuration): * The subscribers close the memory file and release the access-mutex. -To support one to many publisher/subscriber connections the publisher creates in fact one named update event per connection. +To support one to many publisher/subscriber connections, the publisher creates one named update event per connection. .. note:: @@ -92,73 +92,41 @@ Finally that means the publishers ``CPublisher::Send`` API function call is now Zero Copy mode (optional) ------------------------- -*Zero-copy has been added in eCAL 5.10. It is turned off by default. When turned on, old eCAL Version can still receive the data but will not use zero-copy.** - -The “normal” eCAL Shared memory communication results in the payload being copied at least twice: - -1. Into the SHM file by the publisher - -2. From the SHM file the private memory of each subscriber - -Usually there is no issue with that. -Copying the payload from the memory file before executing the subscriber callback results in better decoupling, so the publisher can update the memory file with the next message while the subscriber is still processing the last one. -Small messages will be transmitted in a few microseconds and will not benefit from zero-copy. - -If it comes to very large messages (e.g. high resolution images) however, copying really matters and it can make sense to activate eCAL's zero-copy mode. -With zero-copy, the communication would look like this: - -1. The publisher still has to copy the data into the memory file. - -2. The subscriber executes its callback directly on the memory file. - The memory file is blocked, while being used. - - .. warning:: - - The memory file is blocked for new publications as long as the user’s callback is processing its content. - It will also block other subscribers from reading the same SHM file. - -3. The subscriber releases the memory file, so the publisher can update it again. - .. note:: - Even though it is called zero-copy, only the subscribers are zero-copy. - Publishers still have to copy the data into the memory file, as they have to also support other layers like UDP or TCP and therefore cannot directly work on the memory file. + Zero-copy has been added in eCAL 5.10 for subscription and in 5.12 for publishing. + It is turned off by default. + When turned on, old eCAL Versions can still receive the data but will not use zero-copy. -Zero-copy can be enabled in the following ways: +The "normal" eCAL Shared memory communication results in the payload being copied at least twice: -- **Use zero-copy as system-default (not recommended!):** - - Activating zero copy system-wide is not recommended because of the mentioned disadvantages for small payloads. - But if this is wanted for reasons it can be done by adapting your :file:`ecal.ini` like this. - - .. code-block:: ini - - [publisher] - memfile_zero_copy = 1 +1. Into the SHM file by the publisher -- **Use zero-copy for a single publisher (from your code):** +2. From the SHM file the private memory of each subscriber - Zero copy could be activated either per connection or for a complete system using the eCAL configuration file. - To activate it for a specific publisher this ``CPublisher`` `API function `_ needs to be called. +Copying the payload from the memory file before executing the subscriber callback results in **better decoupling**, so the publisher can update the memory file with the next message while the subscriber is still processing the last one. +**Small messages** will be transmitted in a few microseconds and will not benefit from zero-copy. - .. code-block:: cpp +If it comes to very **large messages** (e.g. high resolution images) however, copying really matters and it can make sense to activate eCAL's **zero-copy mode**. - // Create a publisher (topic name "person") - eCAL::protobuf::CPublisher pub("person"); +.. seealso:: - // Enable zero-copy for this publisher - pub.ShmEnableZeroCopy(true); + Check out the following Chapter to learn how to enable zero-copy. + The chapter will also teach you about advantages and disadvantages of zero-copy: -.. note:: + .. toctree:: + :maxdepth: 1 - In general, it is advisable to combine zero-copy with multi-buffering to reduce the impact on the publisher. + shm_zerocopy.rst Multi-buffering mode (optional) ------------------------------- -*Multi-buffering has been added in eCAL 5.10. -Multi-buffered topics cannot be received by older eCAL versions. -The feature is turned off by default.* +.. note:: + + Multi-buffering has been added in eCAL 5.10. + Multi-buffered topics cannot be received by older eCAL versions. + The feature is turned off by default. As described in the previous sections, eCAL uses one shared memory file per publisher. This can lead to performance reduction if @@ -199,3 +167,4 @@ You can activate the feature in the following ways. pub.ShmSetBufferCount(3); Combining the zero-copy feature with an increased number of memory buffer files (like 2 or 3) could be a nice setup allowing the subscriber to work on the memory file content without copying its content and nevertheless not blocking the publisher to write new data. +Using Multibuffering however will force each Send operation to re-write the entire memory file and disable partial updates. \ No newline at end of file diff --git a/doc/rst/advanced/layers/shm_zerocopy.rst b/doc/rst/advanced/layers/shm_zerocopy.rst new file mode 100644 index 0000000000..1ffba04ffc --- /dev/null +++ b/doc/rst/advanced/layers/shm_zerocopy.rst @@ -0,0 +1,374 @@ +.. include:: /include.txt + +.. _transport_layer_shm_zerocopy: + +============== +eCAL Zero Copy +============== + +.. note:: + + The eCAL Zero Copy mode was introduced in two steps: + + - eCAL 5.10 (zero-copy on subscriber side only, still one memory copy on the publisher side) + - eCAL 5.12 (zero-copy on publisher and subscriber side, see Full Zero Copy Behavior) + + In all versions it is turned off by default. + +Enabling eCAL Zero Copy +======================= + +- **Use zero-copy as system-default:** + + Zero Copy can be enabled as system default from the :file:`ecal.ini` file like follows: + + .. code-block:: ini + + [publisher] + memfile_zero_copy = 1 + +- **Use zero-copy for a single publisher (from your code):** + + Zero-copy can be activated (or deactivated) for a single publisher from the eCAL API: + + .. code-block:: cpp + + // Create a publisher (topic name "person") + eCAL::protobuf::CPublisher pub("person"); + + // Enable zero-copy for this publisher + pub.ShmEnableZeroCopy(true); + + Keep in mind, that using protobuf for serialization will still: + + #. Require to set the data in the protobuf object + #. Later cause a copy by serializing into the SHM buffer. + + If you want to avoid this copy, you can use the :ref:`low-level API ` to directly operate on the SHM buffer. + +Full Zero Copy behavior +======================= + +The Full eCAL Zero Copy mechanism is working for local (inner-host) publish-subscribe connections only. Sending data over a network connection will not benefit from that feature. + +Shared-Memory-only connection +----------------------------- + +This describes the case, where a publisher publishes it's data **only via shared memory** to 1 or more subscribers. + +**Publisher**: + +- Protobuf API Level: + + #. The user sets the data in the **protobuf object** + + #. The publisher **locks** the SHM buffer. + + *This operation may take some time, as the publisher needs to wait for the subscriber to release the buffer.* + *This can be relaxed by using the multi-buffering feature.* + + #. The publisher **serializes** the protobuf object **directly into the SHM** buffer + + *Due to the technical implementation of protobuf, this will cause the entire message to be serialized and re-written* + + #. The publisher **unlocks** the SHM buffer + +- Binary API Level: + + #. The publisher **locks** the SHM buffer + + #. The user **directly** writes data to the **SHM buffer**. + + #. The publisher **unlocks** the SHM buffer + + #. The publisher **informs** all connected subscriber + +**Subscriber**: + +#. The subscriber **locks** the SHM buffer + + *The subscriber will need to wait for any publisher and subscriber to unlock the buffer.* + *Currently, there is no parallel read access to the SHM buffer.* + +#. The subscriber calls the **callback** function directly with the **SHM buffer** as parameter + +#. After the callback has **finished**, the subscriber **unlocks** the SHM buffer + +Mixed Layer connection +---------------------- + +This describes the case where a publisher publishes its data parallel via **shared memory and network** (tcp or udp). So we have at least one local subscription and one external (network) subscription on the provided topic. + +**Publisher**: + +Regardless of whether the data is generated by a Low Level Binary Publisher or by a Protobuf Publisher, it is always written to an process internal cache first. This memory cache is then passed sequentially to the connected transport layers "shared memory", "innerprocess", "udp" and "tcp" in this order. + +Compared to the Full Zero Copy behavior described above with only local (shm) connections, we have a copy of the user payload on the publisher side again. + +This leads to the following publication sequence for a local connection: + +- Protobuf API Level: + + #. The user sets the data in the **protobuf object** + + #. The publisher **serializes** the protobuf object into a process internal data cache + + #. The publisher **locks** the SHM buffer. + + #. The publisher **copies** the process internal data cache to the SHM buffer. + + #. The publisher **unlocks** the SHM buffer + +- Binary API Level: + + #. The publisher **copies** the binary user data into a process internal data cache + + #. The publisher **locks** the SHM buffer + + #. The publisher **copies** the process internal data cache to the SHM buffer. + + #. The publisher **unlocks** the SHM buffer + + #. The publisher **informs** all connected subscriber + +**Subscriber**: + +Subscribers will always use Zero Copy, if enabled. +So they will **directly** read from the SHM buffer. + +#. The subscriber **locks** the SHM buffer + +#. The subscriber calls the **callback** function directly with the **SHM buffer** as parameter + +#. After the callback has **finished**, the subscriber **unlocks** the SHM buffer + +.. _transport_layer_shm_zerocopy_low_level: + +Low Level Memory Access +======================= + +For unleashing the full power of Full eCAL Zero Copy, the user needs to directly work on the eCAL Shared Memory via the ``CPayloadWriter`` API. The idea behind the new ``CPayloadWriter`` API is to give the user the possibility to modify only the data in the memory that has changed since the last time the date was sent. The aim is to avoid writing the complete memory and thus save computing time and reduce the latency of data transmission. + +The new payload type ``CPayloadWriter`` looks like this (all functions unnecessary for the explanation have been omitted): + +.. code-block:: cpp + + /** + * @brief Base payload writer class to allow zero copy memory operations. + * + * This class serves as the base class for payload writers, allowing zero-copy memory + * operations. The `WriteFull` and `WriteModified` calls may operate on the target + * memory file directly in zero-copy mode. + * + * A partial writing / modification of the memory file is only possible when zero-copy mode + * is activated. If zero-copy is not enabled, the `WriteModified` method is ignored and the + * `WriteFull` method is always executed (see CPublisher::ShmEnableZeroCopy) + * + */ + class CPayloadWriter + { + public: + /** + * @brief Perform a full write operation on uninitialized memory. + * + * This virtual function allows derived classes to perform a full write operation + * when the provisioned memory is uninitialized. Typically, this is the case when a + * memory file had to be recreated or its size had to be changed. + * + * @param buffer_ Pointer to the buffer containing the data to be written. + * @param size_ Size of the data to be written. + * + * @return True if the write operation is successful, false otherwise. + */ + virtual bool WriteFull(void* buffer_, size_t size_) = 0; + + /** + * @brief Perform a partial write operation to modify existing data. + * + * This virtual function allows derived classes to modify existing data when the provisioned + * memory is already initialized by a WriteFull call (i.e. contains the data from that full write operation). + * + * The memory can be partially modified and does not have to be completely rewritten, which leads to significantly + * higher performance (lower latency). + * + * If not implemented (by default), this operation will just call the `WriteFull` function. + * + * @param buffer_ Pointer to the buffer containing the data to be modified. + * @param size_ Size of the data to be modified. + * + * @return True if the write/update operation is successful, false otherwise. + */ + virtual bool WriteModified(void* buffer_, size_t size_) { return WriteFull(buffer_, size_); }; + + /** + * @brief Get the size of the required memory. + * + * This virtual function allows derived classes to provide the size of the memory + * that eCAL needs to allocate. + * + * @return The size of the required memory. + */ + virtual size_t GetSize() = 0; + }; + +The user must derive his own playload data class and implement at least the ``WriteFull`` function. This ``WriteFull`` function will be called by the low level eCAL SHM layer when finally the shared memory file needs to be written the first time (initial full write action). + +For writing partial content (modifying the memory content) the user may define a second function called ``WriteModified``. This function is called by the eCAL SHM layer if the shared memory file is in an initialized state i.e. if it was written with the previously mentioned ``WriteFull`` method. As you can see, the ``WriteModified`` function simply calls the ``WriteFull`` function by default if it is not overwritten. + +The implementation of the ``GetSize`` method is mandatory. This method is used by the eCAL SHM layer to obtain the size of the memory file that needs to be allocated. + +**Example**: + +The following primitive example shows the usage of the ``CPayloadWriter`` API to send a simple binary struct efficient by implementing a full ``WriteFull`` and an ``WriteModified`` method that is modifying a few struct elements without memcopying the whole structure again into memory. Note the in case of the none Full Zero Copy Mode only the ``WriteFull`` function will be called by eCAL. + +This is the customized new payload writer class. The ``WriteFull`` method is creating a new ``SSimpleStruct`` struct, updating its content and copying the whole structure into the opened shared memory file buffer. The ``WriteModified`` method gets a view of the opened shared memory file, and applies modifications on the struct elements ``clock`` and ``bytes`` by just apllying ``UpdateStruct``. + +.. code-block:: cpp + + // a simple struct to demonstrate + // zero copy modifications + struct alignas(4) SSimpleStruct + { + uint32_t version = 1; + uint16_t rows = 5; + uint16_t cols = 3; + uint32_t clock = 0; + uint8_t bytes[5 * 3] = { 0 }; + }; + + // a binary payload object that handles + // SSimpleStruct WriteFull and WriteModified functionality + class CStructPayload : public eCAL::CPayloadWriter + { + public: + // Write the complete SSimpleStruct to the shared memory + bool WriteFull(void* buf_, size_t len_) override + { + // check available size and pointer + if (len_ < GetSize() || buf_ == nullptr) return false; + + // create a new struct and update its content + SSimpleStruct simple_struct; + UpdateStruct(&simple_struct); + + // copy complete struct into the memory + *static_cast(buf_) = simple_struct; + + return true; + }; + + // Modify the SSimpleStruct in the shared memory + bool WriteModified(void* buf_, size_t len_) override + { + // check available size and pointer + if (len_ < GetSize() || buf_ == nullptr) return false; + + // update the struct in memory + UpdateStruct(static_cast(buf_)); + + return true; + }; + + size_t GetSize() override { return sizeof(SSimpleStruct); }; + + private: + void UpdateStruct(SSimpleStruct* simple_struct) + { + // modify the simple_struct + simple_struct->clock = clock; + for (auto i = 0; i < (simple_struct->rows * simple_struct->cols); ++i) + { + simple_struct->bytes[i] = static_cast(simple_struct->clock); + } + + // increase internal state clock + clock++; + }; + + uint32_t clock = 0; + }; + +To send this payload you just need a few lines of code: + +.. code-block:: cpp + + int main(int argc, char** argv) + { + // initialize eCAL API + eCAL::Initialize(argc, argv, "binary_payload_snd"); + + // publisher for topic "simple_struct" + eCAL::CPublisher pub("simple_struct"); + + // turn zero copy mode on + pub.ShmEnableZeroCopy(true); + + // create the simple struct payload + CStructPayload struct_payload; + + // send updates every 100 ms + while (eCAL::Ok()) + { + pub.Send(struct_payload); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // finalize eCAL API + eCAL::Finalize(); + + return(0); + } + +Default eCAL SHM vs. Full Zero Copy SHM +============================= + +.. list-table:: Default eCAL SHM vs. Full Zero Copy SHM + :widths: 10 45 45 + :header-rows: 1 + :stub-columns: 1 + + * - + + - Default eCAL SHM + + - Full Zero Copy SHM + + * - Memcopies + + - ❌ 2 additional memcpy (1 for publishing, 1 for each subscriber) + + - ✅ No memcpy (if Low Level API is used) + + * - Partial changes + + - ❌ Changing only 1 byte causes the entire updated message to be copied to the buffer, again + + - ✅ Changing only 1 byte only costs as much as changing that 1 byte in the target memory, independent from the message size + + * - Subscriber decoupling + + - ✅ Good decoupling between subscribers. + Subscribers only block each other for the duration of that 1 memcpy + + - ❌ Subscribers need to wait for each other to finish their callbacks + + * - Pub/Sub decoupling + + - ✅ Good decoupling between publisher and subscribers. + + - If the serialization takes a long time, this can be done beforehand without having a lock on the SHM buffer + + - Publishers don't have to wait for the subscribers to finish their callbacks, only for them to copy the data to their own process memory + + - ❌ Subscribers may block publishers + + - Publishers need to wait for all subscriber callbacks to finish + + - Publishers need to keep the the SHM buffer locked while performing the message serialization + +Combining Zero Copy and Multibuffering +====================================== + +For technical reasons the Full Zero Copy mode described above is turned of if the Multibuffering option ``CPublisher::ShmSetBufferCount`` is activated. + +Default (subscriber side) Zero Copy is working in combination with Multibuffering as described. diff --git a/doc/rst/versions/5.10/whats_new.rst b/doc/rst/versions/5.10/whats_new.rst index 6ff845b870..46c7734fdc 100644 --- a/doc/rst/versions/5.10/whats_new.rst +++ b/doc/rst/versions/5.10/whats_new.rst @@ -10,7 +10,8 @@ eCAL 5.10 was released in May 2022. The release notably brings eCAL Core improvements (TCP and Shared Memory) - **Release**: May 2022 -- **End of life**: June 2023 (planned) +- +- **End of life**: July 2023 (after 14 months) New features ============ diff --git a/doc/rst/versions/5.11/whats_new.rst b/doc/rst/versions/5.11/whats_new.rst index abf6e6983b..2fda4a65e8 100644 --- a/doc/rst/versions/5.11/whats_new.rst +++ b/doc/rst/versions/5.11/whats_new.rst @@ -10,7 +10,7 @@ eCAL 5.11 was released in Nobember 2022. The release contains the eCAL Mon TUI, the eCAL Meas Cutter and the SHM Monitoring Layer. - **Release**: December 2022 -- **End of life**: November 2023 (planned) +- **End of life**: March 2024 (planned) New features ============ diff --git a/doc/rst/versions/5.12/compatibility_table.txt b/doc/rst/versions/5.12/compatibility_table.txt new file mode 100644 index 0000000000..c76e57f91c --- /dev/null +++ b/doc/rst/versions/5.12/compatibility_table.txt @@ -0,0 +1,42 @@ +.. list-table:: eCAL 5.12 vs. 5.11 + :widths: 20 80 + + * - Wire compatibility + + - * **eCAL UDP**: 100% compatible in default settings + + A proper Topic-Name -> UDP-Multicast-Group computation has been added. + By default, the old version (``multicast_config_version = v1``) is enabled in the :file:`ecal.ini`, which makes the UDP Layer 100% compatible. + If the new version (``v2``) is enabled, UDP communication between eCAL 5.12 and older versions of eCAL will fail. + + * **eCAL TCP**: 100% compatible + + * **Services**: 100% compatible + + * **eCAL Shared Memory**: 100% compatible + + * **eCAL Registration Layer**: 100% compatible + + New fields have been added in the internal protobuf format. + + * - API / ABI + + - * **API**: downwards compatible. + + * **ABI**: not compatible + + From now on, the official eCAL installer for Windows is built with Visual Studio 2019 / v142. + This means, that you need VS 2019 or newer to link against the included SDK. + + * - Tools + + - * **Rec**: Downwards compatible + + The ``SetConfig`` RPC Call has been added, which was not available in eCAL 5.11 + + * **Sys**: 100% compatible + + * **Measurements**: 100% compatible + + * **Meas Cutter**: The structure of the output directory has changed. + It is now aligned to the output structure of eCAL Rec. \ No newline at end of file diff --git a/doc/rst/versions/5.12/whats_new.rst b/doc/rst/versions/5.12/whats_new.rst new file mode 100644 index 0000000000..c52df9a31b --- /dev/null +++ b/doc/rst/versions/5.12/whats_new.rst @@ -0,0 +1,58 @@ +.. include:: /include.txt + +.. _ecal_version_5_12: + +========= +eCAL 5.12 +========= + +eCAL 5.12 was released in August 2023. + +- **Release**: August 2023 +- **End of life**: August 2024 (planned) + +New features +============ + +- **True Zero Copy!** + + eCAL had Zero Copy in the past. + But now it's even better! + With the new **low level API** you can precisely control which part of your Shared Memory message you actually have to change and therefore speed up your system even more! + + Check out the :ref:`Zero Copy documentation` for explanations and examples on how to use it! + +- **Container support through host-groups** + + You want to containerize your eCAL Apps? + Well, then this is the release for you. + In the past, you had to choose between enabling support for eCAL services or utilizing the shared memory transport layer, now you can have both simultaneously. + + Check out the :ref:`eCAL in Docker tutorial` to learn more! + + This also enables you to manage apps in your docker containers with eCAL Sys! + +- **Improved eCAL Monitor for Debugging** + + We enriched the eCAL Monitor with even more information, that allow you to debug your system. + You can now see the size and hash of your publishers' and subscribers' **descriptors**. + And - if that isn't enough for you - the eCAL Monitor got an entirely new **Raw Data panel** for very deep (but easy!) inspections of the registration layer. + +- **Improved support for C#** + + The C# language binding has seen some mayor updates. Most notably, the Client / Server API is now available in C#, so you can use eCAL's RPC features! + + +- Check out the `entire 5.12.0 changelog `_!. + +Compatibility table +=================== + +.. include:: compatibility_table.txt + +Where to download +================= + +- |fa-windows| Windows: Head to the :ref:`Download Archive ` and pick your version from there! +- |fa-ubuntu| Ubuntu: Install eCAL from :ref:`our PPA ` with ``apt-get`` *(or pick a Version from the download archive as well)* + diff --git a/doc/rst/versions/5.8/whats_new.rst b/doc/rst/versions/5.8/whats_new.rst index cf259f6b14..ff9f4ad3e2 100644 --- a/doc/rst/versions/5.8/whats_new.rst +++ b/doc/rst/versions/5.8/whats_new.rst @@ -11,7 +11,7 @@ The release notably contains the new application eCAL Sys. - **Release**: December 2020. -- **End of life**: May 2022 (after 17 Month). +- **End of life**: May 2022 (after 17 months). New features ============ diff --git a/doc/rst/versions/5.9/whats_new.rst b/doc/rst/versions/5.9/whats_new.rst index 4b4d2088f2..457bb9a86b 100644 --- a/doc/rst/versions/5.9/whats_new.rst +++ b/doc/rst/versions/5.9/whats_new.rst @@ -10,7 +10,8 @@ eCAL 5.9 was released in August 2021. The release notably contains the eCAL Rec command line Application. - **Release**: August 2021 -- **End of life**: December 2022 +- +- **End of life**: December 2022 (After 16 months) New features ============ diff --git a/doc/rst/versions/compatibility.rst b/doc/rst/versions/compatibility.rst index 07e29b9f15..62fbc615eb 100644 --- a/doc/rst/versions/compatibility.rst +++ b/doc/rst/versions/compatibility.rst @@ -33,6 +33,13 @@ Depending on what we changed in eCAL, we increment the appropriate number: You can upgrade through patch versions without having to expect any problems. Usually, you just want the latest release to profit from all bugfixes. +eCAL 5.12 vs. 5.11 +================== + +Compatibility table when upgrading from eCAL 5.11: + +.. include:: 5.12/compatibility_table.txt + eCAL 5.11 vs. 5.10 ================== diff --git a/doc/rst/versions/ecal_versions.rst b/doc/rst/versions/ecal_versions.rst index c2a46099e7..49faf15020 100644 --- a/doc/rst/versions/ecal_versions.rst +++ b/doc/rst/versions/ecal_versions.rst @@ -9,6 +9,7 @@ eCAL Versions compatibility + 5.12/whats_new 5.11/whats_new 5.10/whats_new 5.9/whats_new