~aleteoryx/muditaos

3f29c30bed7884c6b4a10a0a0d13fc2cb2cccc7d — Piotr Tański 5 years ago 402a741
[EGD-4968] Change GUI service to optimize flow and memory management

Gui and Eink services refactor.
Displaying frames performance optimization.
Critical resources management.
47 files changed, 1194 insertions(+), 643 deletions(-)

M module-apps/Application.cpp
M module-apps/application-desktop/windows/PowerOffWindow.cpp
M module-apps/application-desktop/windows/PowerOffWindow.hpp
M module-gui/gui/core/Context.hpp
M module-gui/gui/core/RawFont.cpp
M module-gui/gui/core/Renderer.cpp
M module-gui/gui/core/Renderer.hpp
M module-os/RTOSWrapper/cqueue.cpp
M module-os/RTOSWrapper/include/queue.hpp
M module-services/service-appmgr/model/ApplicationManager.cpp
M module-services/service-eink/CMakeLists.txt
M module-services/service-eink/Common.hpp
R module-services/service-eink/{EinkScreen => EinkDisplay}.cpp
R module-services/service-eink/{EinkScreen => EinkDisplay}.hpp
M module-services/service-eink/ServiceEink.cpp
M module-services/service-eink/ServiceEink.hpp
D module-services/service-eink/messages/EinkDMATransfer.hpp
M module-services/service-eink/messages/EinkMessage.hpp
M module-services/service-eink/messages/EinkModeMessage.hpp
M module-services/service-eink/messages/ImageMessage.cpp
M module-services/service-eink/messages/ImageMessage.hpp
A module-services/service-eink/messages/PrepareDisplayRequest.cpp
A module-services/service-eink/messages/PrepareDisplayRequest.hpp
D module-services/service-eink/messages/StateRequest.hpp
D module-services/service-eink/messages/TemperatureUpdate.hpp
M module-services/service-gui/CMakeLists.txt
M module-services/service-gui/Common.hpp
A module-services/service-gui/ContextPool.cpp
A module-services/service-gui/ContextPool.hpp
A module-services/service-gui/DrawCommandsQueue.cpp
A module-services/service-gui/DrawCommandsQueue.hpp
M module-services/service-gui/ServiceGUI.cpp
M module-services/service-gui/ServiceGUI.hpp
A module-services/service-gui/SynchronizationMechanism.cpp
A module-services/service-gui/SynchronizationMechanism.hpp
M module-services/service-gui/WorkerGUI.cpp
M module-services/service-gui/WorkerGUI.hpp
M module-services/service-gui/messages/DrawMessage.cpp
M module-services/service-gui/messages/DrawMessage.hpp
R module-services/service-gui/messages/{GUIDisplayReady => EinkReady}.hpp
M module-services/service-gui/messages/GUIMessage.hpp
M module-services/service-gui/messages/RenderingFinished.hpp
A module-services/service-gui/tests/CMakeLists.txt
A module-services/service-gui/tests/MockedSynchronizationMechanism.hpp
A module-services/service-gui/tests/test-ContextPool.cpp
A module-services/service-gui/tests/test-DrawCommandsQueue.cpp
A module-services/service-gui/tests/tests-main.cpp
M module-apps/Application.cpp => module-apps/Application.cpp +3 -3
@@ 135,12 135,12 @@ namespace app
            window->updateSignalStrength();
            window->updateNetworkAccessTechnology();

            auto message = std::make_shared<sgui::DrawMessage>(window->buildDrawList(), mode);
            auto message = std::make_shared<service::gui::DrawMessage>(window->buildDrawList(), mode);
            if (shutdownInProgress) {
                message->setCommandType(sgui::DrawMessage::Type::SHUTDOWN);
                message->setCommandType(service::gui::DrawMessage::Type::SHUTDOWN);
            }
            else if (suspendInProgress) {
                message->setCommandType(sgui::DrawMessage::Type::SUSPEND);
                message->setCommandType(service::gui::DrawMessage::Type::SUSPEND);
            }
            sys::Bus::SendUnicast(message, service::name::gui, this);
        }

M module-apps/application-desktop/windows/PowerOffWindow.cpp => module-apps/application-desktop/windows/PowerOffWindow.cpp +18 -1
@@ 4,6 4,7 @@
#include "InputEvent.hpp"
#include "gui/widgets/BottomBar.hpp"
#include "gui/widgets/TopBar.hpp"
#include "GuiTimer.hpp"
#include "log/log.hpp"

// module-utils


@@ 141,7 142,7 @@ namespace gui
            infoLabel->setVisible(false);

            application->refreshWindow(RefreshModes::GUI_REFRESH_DEEP);

            scheduleSystemShutdown();
            return true;
        };



@@ 159,6 160,22 @@ namespace gui
        };
    }

    // Temporary solution for shutting down the system.
    // The former solution removed from service-gui during its refactor.
    // To be reimplemented in SystemManager in upcoming commits.
    void PowerOffWindow::scheduleSystemShutdown()
    {
        constexpr auto SystemShutdownDelayInMs = 500;
        auto powerOffTimer                     = std::make_unique<app::GuiTimer>("PowerOffTimer", application);
        powerOffTimer->setInterval(SystemShutdownDelayInMs);
        timerCallback = [this](Item &, Timer &timer) {
            detachTimer(timer);
            sys::SystemManager::CloseSystem(application);
            return true;
        };
        application->connect(std::move(powerOffTimer), this);
    }

    void PowerOffWindow::destroyInterface()
    {
        erase();

M module-apps/application-desktop/windows/PowerOffWindow.hpp => module-apps/application-desktop/windows/PowerOffWindow.hpp +2 -1
@@ 13,7 13,6 @@ namespace gui
{
    class PowerOffWindow : public AppWindow
    {

        enum class State
        {
            PowerDown,


@@ 29,6 28,8 @@ namespace gui
        gui::Image *powerDownImage = nullptr;
        State state                = State::Return;

        void scheduleSystemShutdown();

      public:
        PowerOffWindow(app::Application *app);
        void onBeforeShow(ShowMode mode, SwitchData *data) override;

M module-gui/gui/core/Context.hpp => module-gui/gui/core/Context.hpp +13 -9
@@ 51,26 51,30 @@ namespace gui
        /**
         * @brief returns pointer to context's data;
         */
        inline const uint8_t *getData() const
        {
            return data;
        }
        inline uint8_t *getData()
        {
            return data;
        };
        inline int16_t getX()
        }
        inline int16_t getX() const
        {
            return x;
        };
        inline int16_t getY()
        }
        inline int16_t getY() const
        {
            return y;
        };
        inline uint16_t getW()
        }
        inline uint16_t getW() const
        {
            return w;
        };
        inline uint16_t getH()
        }
        inline uint16_t getH() const
        {
            return h;
        };
        }

        inline bool addressInData(const uint8_t *ptr) const
        {

M module-gui/gui/core/RawFont.cpp => module-gui/gui/core/RawFont.cpp +1 -1
@@ 306,7 306,7 @@ namespace gui
        commandRect->penWidth = unsupported->xoffset;

        auto renderCtx                           = std::make_unique<Context>(unsupported->width, unsupported->height);
        std::list<Command> commands;
        std::list<std::unique_ptr<gui::DrawCommand>> commands;
        commands.emplace_back(std::move(commandRect));
        Renderer().render(renderCtx.get(), commands);


M module-gui/gui/core/Renderer.cpp => module-gui/gui/core/Renderer.cpp +1 -1
@@ 288,7 288,7 @@ namespace gui
        delete drawCtx;
    }

    void Renderer::render(Context *ctx, std::list<Command> &commands)
    void Renderer::render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands)
    {
        if (ctx == nullptr) {
            return;

M module-gui/gui/core/Renderer.hpp => module-gui/gui/core/Renderer.hpp +1 -1
@@ 50,7 50,7 @@ namespace gui
      public:
        virtual ~Renderer() = default;

        void render(Context *ctx, std::list<Command> &commands);
        void render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands);
    };

} /* namespace gui */

M module-os/RTOSWrapper/cqueue.cpp => module-os/RTOSWrapper/cqueue.cpp +8 -0
@@ 62,6 62,14 @@ Queue::~Queue()
    vQueueDelete(handle);
}

bool Queue::Overwrite(void *item)
{
    BaseType_t success;

    success = xQueueOverwrite(handle, item);

    return success == pdTRUE ? true : false;
}

bool Queue::Enqueue(void *item)
{

M module-os/RTOSWrapper/include/queue.hpp => module-os/RTOSWrapper/include/queue.hpp +8 -0
@@ 136,6 136,14 @@ class Queue {
        virtual ~Queue();

        /**
         * Add an item to the queue, or overwrites the existing one.
         * Works only for queues 1-element long.
         * @param item  The item to be added.
         * @return true if the item was added, false otherwise.
         */
        bool Overwrite(void *item);

        /**
         *  Add an item to the back of the queue.
         *
         *  @param item The item you are adding.

M module-services/service-appmgr/model/ApplicationManager.cpp => module-services/service-appmgr/model/ApplicationManager.cpp +2 -5
@@ 6,7 6,6 @@

#include <module-apps/messages/AppMessage.hpp>
#include <Common.hpp>
#include <Common/Common.hpp>
#include <Service/Bus.hpp>
#include <Service/Message.hpp>
#include <Service/Timer.hpp>


@@ 16,10 15,8 @@
#include <i18n/i18n.hpp>
#include <log/log.hpp>
#include <service-appmgr/messages/Message.hpp>
#include <service-db/DBServiceAPI.hpp>
#include <service-evtmgr/EventManager.hpp>
#include <service-gui/ServiceGUI.hpp>
#include <service-eink/Common.hpp>
#include <service-eink/ServiceEink.hpp>
#include <service-gui/Common.hpp>



@@ 135,12 132,12 @@ namespace app::manager
    void ApplicationManager::startSystemServices()
    {
        if (bool ret = sys::SystemManager::CreateService(
                std::make_shared<sgui::ServiceGUI>(service::name::gui, GetName(), 480, 600), this);
                std::make_shared<service::gui::ServiceGUI>(service::name::gui, GetName()), this);
            !ret) {
            LOG_ERROR("Failed to initialize GUI service");
        }
        if (bool ret = sys::SystemManager::CreateService(
                std::make_shared<eink::ServiceEink>(service::name::eink, GetName()), this);
                std::make_shared<service::eink::ServiceEink>(service::name::eink, GetName()), this);
            !ret) {
            LOG_ERROR("Failed to initialize EInk service");
        }

M module-services/service-eink/CMakeLists.txt => module-services/service-eink/CMakeLists.txt +2 -1
@@ 14,8 14,9 @@ message( "EINK BOARD PATH: ${CMAKE_CURRENT_LIST_DIR}/${EINK_BOARD_PATH}" )

set(SOURCES
    ServiceEink.cpp
    EinkScreen.cpp
    EinkDisplay.cpp
    messages/ImageMessage.cpp
    messages/PrepareDisplayRequest.cpp
)

add_library(${PROJECT_NAME} STATIC ${SOURCES})

M module-services/service-eink/Common.hpp => module-services/service-eink/Common.hpp +12 -3
@@ 3,7 3,16 @@

#pragma once

namespace service::name
namespace service
{
    constexpr char eink[] = "ServiceEink";
};
    namespace name
    {
        constexpr inline auto eink = "ServiceEink";
    } // namespace name

    namespace eink
    {
        constexpr inline auto DefaultScreenWidth  = 480;
        constexpr inline auto DefaultScreenHeight = 600;
    } // namespace eink
} // namespace service

R module-services/service-eink/EinkScreen.cpp => module-services/service-eink/EinkDisplay.cpp +45 -71
@@ 1,7 1,7 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "EinkScreen.hpp"
#include "EinkDisplay.hpp"

#include <gui/core/Color.hpp>
#include <gsl/gsl_util>


@@ 9,7 9,7 @@
#include <cstdio>
#include <cstring>

namespace eink
namespace service::eink
{
    namespace
    {


@@ 19,6 19,9 @@ namespace eink
        constexpr auto LUTCSize                      = 64;
        constexpr auto LUTRSize                      = 256; ///< Needed due to \ref EINK_LUTS_FILE_PATH structure
        constexpr auto LUTSTotalSize                 = LUTDSize + LUTCSize + LUTRSize;
        constexpr auto LUTVersionInterval            = 3;
        constexpr auto LUTSubcritical                = 12;
        constexpr auto LUTCritical                   = 13;

        EinkWaveFormSettings_t createDefaultWaveFormSettings(EinkWaveforms_e waveformMode)
        {


@@ 31,102 34,69 @@ namespace eink
            settings.LUTDSize    = 0;
            return settings;
        }

        std::unique_ptr<std::uint8_t[]> allocateScreenBuffer(gui::Size screenSize)
        {
            return std::make_unique<std::uint8_t[]>(screenSize.width * screenSize.height);
        }
    } // namespace

    EinkScreen::EinkScreen(gui::Size screenSize)
        : size{screenSize}, screenBuffer{allocateScreenBuffer(screenSize)},
          waveformSettings{createDefaultWaveFormSettings(EinkWaveformGC16)},
    EinkDisplay::EinkDisplay(::gui::Size screenSize)
        : size{screenSize}, waveformSettings{createDefaultWaveFormSettings(EinkWaveformGC16)},
          displayMode{EinkDisplayColorMode_e::EinkDisplayColorModeStandard}
    {}

    EinkScreen::~EinkScreen() noexcept
    EinkDisplay::~EinkDisplay() noexcept
    {
        delete[] waveformSettings.LUTCData;
        delete[] waveformSettings.LUTDData;
    }

    EinkStatus_e EinkScreen::resetAndInit()
    EinkStatus_e EinkDisplay::resetAndInit()
    {
        return EinkResetAndInitialize();
    }

    void EinkScreen::dither()
    void EinkDisplay::dither()
    {
        EinkDitherDisplay();
    }

    void EinkScreen::powerOn()
    void EinkDisplay::powerOn()
    {
        EinkPowerOn();
    }

    void EinkScreen::powerOff()
    void EinkDisplay::powerOff()
    {
        EinkPowerOff();
    }

    void EinkScreen::shutdown()
    void EinkDisplay::shutdown()
    {
        EinkPowerDown();
    }

    void EinkScreen::setScreenBuffer(const std::uint8_t *buffer, std::uint32_t bufferSize)
    {
        std::memcpy(screenBuffer.get(), buffer, bufferSize);
    }

    void EinkScreen::setScreenBuffer(std::uint8_t value, std::uint32_t bufferSize)
    EinkStatus_e EinkDisplay::update(std::uint8_t *displayBuffer)
    {
        std::memset(screenBuffer.get(), value, bufferSize);
        return EinkUpdateFrame(pointTopLeft.x,
                               pointTopLeft.y,
                               size.width,
                               size.height,
                               displayBuffer,
                               getCurrentBitsPerPixelFormat(),
                               displayMode);
    }

    EinkStatus_e EinkScreen::update()
    EinkBpp_e EinkDisplay::getCurrentBitsPerPixelFormat() const noexcept
    {
        return EinkUpdateFrame(
            pointTopLeft.x, pointTopLeft.y, size.width, size.height, screenBuffer.get(), Eink4Bpp, displayMode);
    }

    EinkStatus_e EinkScreen::refresh(EinkDisplayTimingsMode_e refreshMode)
    {
        return EinkRefreshImage(pointTopLeft.x, pointTopLeft.y, size.width, size.height, refreshMode);
    }

    bool EinkScreen::deepClear(std::int32_t temperature)
    {
        const auto waveformMode = waveformSettings.mode;

        powerOn();
        changeWaveform(EinkWaveforms_e::EinkWaveformA2, temperature);

        fillScreen(gui::Color::White);
        for (auto i = 0; i < 2; ++i) {
            fillScreen(gui::Color::Black);
            fillScreen(gui::Color::White);
        if (waveformSettings.mode == EinkWaveformDU2) {
            return Eink1Bpp;
        }

        changeWaveform(waveformMode, temperature);
        powerOff();
        return true;
        return Eink4Bpp;
    }

    void EinkScreen::fillScreen(std::uint8_t colorIntensity)
    EinkStatus_e EinkDisplay::refresh(EinkDisplayTimingsMode_e refreshMode)
    {
        const auto screenBufferSize = size.width * size.height;
        setScreenBuffer(colorIntensity, screenBufferSize);
        if (const auto status = update(); status != EinkOK) {
            LOG_FATAL("Failed to update frame");
        }
        if (const auto status = refresh(EinkDisplayTimingsFastRefreshMode); status != EinkOK) {
            LOG_FATAL("Failed to refresh frame");
        }
        return EinkRefreshImage(pointTopLeft.x, pointTopLeft.y, size.width, size.height, refreshMode);
    }

    bool EinkScreen::changeWaveform(EinkWaveforms_e mode, std::int32_t temperature)
    bool EinkDisplay::changeWaveform(EinkWaveforms_e mode, std::int32_t temperature)
    {
        if (temperature == waveformSettings.temperature && mode == waveformSettings.mode) {
            return EinkOK;


@@ 161,18 131,18 @@ namespace eink
        return true;
    }

    unsigned int EinkScreen::calculateWaveFormSegment(std::int32_t temperature) const
    unsigned int EinkDisplay::calculateWaveFormSegment(std::int32_t temperature) const
    {
        if (temperature < 38) {
            return temperature / 3;
            return temperature / LUTVersionInterval;
        }
        if (temperature < 43) {
            return 12;
            return LUTSubcritical;
        }
        return 13;
        return LUTCritical;
    }

    unsigned int EinkScreen::calculateWaveFormOffset(EinkWaveforms_e mode, unsigned int segment) const
    unsigned int EinkDisplay::calculateWaveFormOffset(EinkWaveforms_e mode, unsigned int segment) const
    {
        switch (mode) {
        case EinkWaveformINIT:


@@ 184,27 154,31 @@ namespace eink
        case EinkWaveformGLD16:
            return LUTSTotalSize * (42 + segment);
        case EinkWaveformGC16:
            [[fallthrough]];
        default:
            return LUTSTotalSize * (56 + segment);
        }
        throw std::invalid_argument{"Invalid waveform mode."};
    }

    void EinkScreen::resetWaveFormSettings()
    void EinkDisplay::resetWaveFormSettings()
    {
        delete[] waveformSettings.LUTDData;
        waveformSettings.LUTDSize    = 0;
        waveformSettings.LUTDData    = new uint8_t[LUTDSize + 1];
        waveformSettings.LUTDData    = new std::uint8_t[LUTDSize + 1];
        waveformSettings.LUTDData[0] = EinkLUTD;

        delete[] waveformSettings.LUTCData;
        waveformSettings.LUTCSize    = LUTCSize;
        waveformSettings.LUTCData    = new uint8_t[LUTCSize + 1];
        waveformSettings.LUTCData    = new std::uint8_t[LUTCSize + 1];
        waveformSettings.LUTCData[0] = EinkLUTC;
    }

    void EinkScreen::setDisplayMode(EinkDisplayColorMode_e mode) noexcept
    void EinkDisplay::setMode(EinkDisplayColorMode_e mode) noexcept
    {
        displayMode = mode;
    }
} // namespace eink

    ::gui::Size EinkDisplay::getSize() const noexcept
    {
        return size;
    }
} // namespace service::eink

R module-services/service-eink/EinkScreen.hpp => module-services/service-eink/EinkDisplay.hpp +18 -16
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 6,44 6,46 @@
#include <gui/Common.hpp>

#include <EinkIncludes.hpp>
#include "Common.hpp"

#include <cstdint>
#include <memory>

namespace eink
namespace service::eink
{
    class EinkScreen
    /**
     * Specifies the Eink display.
     * Responsible for handling low-level Eink display operations, e.g. switching power modes, updating, refreshing,
     * etc.
     */
    class EinkDisplay
    {
      public:
        explicit EinkScreen(gui::Size screenSize = {480, 600});
        ~EinkScreen() noexcept;
        explicit EinkDisplay(::gui::Size screenSize = {DefaultScreenWidth, DefaultScreenHeight});
        ~EinkDisplay() noexcept;

        EinkStatus_e resetAndInit();
        EinkStatus_e update();
        EinkStatus_e update(std::uint8_t *displayBuffer);
        EinkStatus_e refresh(EinkDisplayTimingsMode_e refreshMode);
        void dither();
        void powerOn();
        void powerOff();
        void shutdown();

        bool deepClear(std::int32_t temperature);
        bool changeWaveform(EinkWaveforms_e mode, std::int32_t temperature);
        void setMode(EinkDisplayColorMode_e mode) noexcept;

        void setScreenBuffer(const std::uint8_t *buffer, std::uint32_t bufferSize);
        void setDisplayMode(EinkDisplayColorMode_e mode) noexcept;
        ::gui::Size getSize() const noexcept;

      private:
        void fillScreen(std::uint8_t colorIntensity);
        void setScreenBuffer(std::uint8_t value, std::uint32_t bufferSize);
        unsigned int calculateWaveFormSegment(std::int32_t temperature) const;
        unsigned int calculateWaveFormOffset(EinkWaveforms_e mode, unsigned int segment) const;
        void resetWaveFormSettings();
        EinkBpp_e getCurrentBitsPerPixelFormat() const noexcept;

        static constexpr gui::Point pointTopLeft{0, 0};

        const gui::Size size;
        std::unique_ptr<std::uint8_t[]> screenBuffer;
        static constexpr ::gui::Point pointTopLeft{0, 0};
        const ::gui::Size size;
        EinkWaveFormSettings_t waveformSettings;
        EinkDisplayColorMode_e displayMode;
    };
} // namespace eink
} // namespace service::eink

M module-services/service-eink/ServiceEink.cpp => module-services/service-eink/ServiceEink.cpp +61 -84
@@ 3,11 3,9 @@

#include "ServiceEink.hpp"
#include "messages/EinkModeMessage.hpp"
#include "messages/EinkDMATransfer.hpp"
#include "messages/StateRequest.hpp"
#include "messages/TemperatureUpdate.hpp"
#include "messages/PrepareDisplayRequest.hpp"
#include <service-gui/Common.hpp>
#include <service-gui/messages/GUIDisplayReady.hpp>
#include <service-gui/messages/EinkReady.hpp>
#include <time/ScopedTime.hpp>

#include <log/log.hpp>


@@ 18,7 16,7 @@
#include <cstring>
#include <memory>

namespace eink
namespace service::eink
{
    namespace
    {


@@ 26,55 24,48 @@ namespace eink
    } // namespace

    ServiceEink::ServiceEink(const std::string &name, std::string parent)
        : sys::Service(name, parent, ServceEinkStackDepth), selfRefereshTriggerCount{0},
          temperatureMeasurementTriggerCount{0}, powerOffTriggerCount{0},
          powerOffTimer("PwrOffTimer", this, 3000, sys::Timer::Type::SingleShot)
        : sys::Service(name, parent, ServceEinkStackDepth), currentState{State::Running}
    {
        connect(typeid(service::eink::EinkModeMessage),
        connect(typeid(EinkModeMessage),
                [this](sys::Message *message) -> sys::MessagePointer { return handleEinkModeChangedMessage(message); });

        connect(typeid(service::eink::EinkDMATransfer),
                [this](sys::Message *request) -> sys::MessagePointer { return handleEinkDMATransfer(request); });

        connect(typeid(service::eink::ImageMessage),
        connect(typeid(ImageMessage),
                [this](sys::Message *request) -> sys::MessagePointer { return handleImageMessage(request); });

        connect(typeid(service::eink::StateRequest),
                [this](sys::Message *request) -> sys::MessagePointer { return handleStateRequest(request); });

        connect(typeid(service::eink::TemperatureUpdate),
                [this](sys::Message *request) -> sys::MessagePointer { return handleTemperatureUpdate(request); });
        connect(typeid(PrepareDisplayRequest),
                [this](sys::Message *request) -> sys::MessagePointer { return handlePrepareRequest(request); });
    }

    sys::MessagePointer ServiceEink::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
    sys::MessagePointer ServiceEink::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *response)
    {
        return std::make_shared<sys::ResponseMessage>();
    }

    sys::ReturnCodes ServiceEink::InitHandler()
    {
        LOG_INFO("[ServiceEink] Initializing");
        if (const auto status = screen.resetAndInit(); status != EinkOK) {
            LOG_FATAL("Error: Could not initialize Eink display!\n");
        LOG_INFO("Initializing");
        if (const auto status = display.resetAndInit(); status != EinkOK) {
            LOG_FATAL("Error: Could not initialize Eink display!");
            return sys::ReturnCodes::Failure;
        }
        screen.powerOn();

        auto msg = std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
        display.powerOn();

        auto msg = std::make_shared<service::gui::EinkReady>(display.getSize());
        sys::Bus::SendUnicast(msg, service::name::gui, this);

        return sys::ReturnCodes::Success;
    }

    sys::ReturnCodes ServiceEink::DeinitHandler()
    {
        screen.shutdown();
        display.shutdown();
        return sys::ReturnCodes::Success;
    }

    sys::ReturnCodes ServiceEink::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
    {
        LOG_FATAL("[ServiceEink] PowerModeHandler: %s", c_str(mode));

        LOG_INFO("PowerModeHandler: %s", c_str(mode));
        switch (mode) {
        case sys::ServicePowerMode::Active:
            enterActiveMode();


@@ 90,21 81,19 @@ namespace eink

    void ServiceEink::enterActiveMode()
    {
        suspended = false;
        setState(State::Running);

        if (const auto status = screen.resetAndInit(); status != EinkOK) {
            LOG_FATAL("Error: Could not initialize Eink display!\n");
        if (const auto status = display.resetAndInit(); status != EinkOK) {
            LOG_FATAL("Error: Could not initialize Eink display!");
        }
        screen.powerOn();
        screen.powerOff();
        display.powerOn();
        display.powerOff();
    }

    void ServiceEink::suspend()
    {
        suspended = true;

        powerOffTimer.stop();
        screen.shutdown();
        setState(State::Suspended);
        display.shutdown();
    }

    sys::MessagePointer ServiceEink::handleEinkModeChangedMessage(sys::Message *message)


@@ 113,79 102,67 @@ namespace eink
        const auto displayMode = msg->getMode() == service::eink::EinkModeMessage::Mode::Normal
                                     ? EinkDisplayColorMode_e::EinkDisplayColorModeStandard
                                     : EinkDisplayColorMode_e::EinkDisplayColorModeInverted;
        screen.setDisplayMode(displayMode);
        display.setMode(displayMode);
        return sys::MessageNone{};
    }

    sys::MessagePointer ServiceEink::handleEinkDMATransfer(sys::Message *message)
    sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request)
    {
        utils::time::Scoped measurement("EinkDMATransfer");

        if (suspended) {
            if (suspendInProgress) {
                LOG_ERROR("drawing before suspend failed");
                suspendInProgress = false;
            }
            LOG_INFO("[ServiceEink] Received image while suspended, ignoring");
        const auto message = static_cast<service::eink::ImageMessage *>(request);
        if (isInState(State::Suspended)) {
            LOG_WARN("Received image while suspended, ignoring");
            return sys::MessageNone{};
        }
        utils::time::Scoped measurement("ImageMessage");

        screen.powerOn();
        if (const auto temperature = EinkGetTemperatureInternal(); deepRefresh) {
            screen.changeWaveform(EinkWaveforms_e::EinkWaveformGC16, temperature);
            screen.dither();
        }
        else {
            screen.changeWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
        }
        updateDisplay(message->getData(), message->getRefreshMode());
        return std::make_shared<service::eink::ImageDisplayedNotification>(message->getContextId());
    }

    void ServiceEink::updateDisplay(std::uint8_t *frameBuffer, ::gui::RefreshModes refreshMode)
    {
        prepareDisplay(refreshMode);

        if (const auto status = screen.update(); status != EinkOK) {
        if (const auto status = display.update(frameBuffer); status != EinkOK) {
            LOG_FATAL("Failed to update frame");
        }

        const auto isDeepRefresh = refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
        if (const auto status =
                screen.refresh(deepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode);
                display.refresh(isDeepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode);
            status != EinkOK) {
            LOG_FATAL("Failed to refresh frame");
        }

        powerOffTimer.reload();

        auto msg           = std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
        suspendInProgress  = false;
        shutdownInProgress = false;
        sys::Bus::SendUnicast(msg, service::name::gui, this);

        return sys::MessageNone{};
    }

    sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request)
    void ServiceEink::prepareDisplay(::gui::RefreshModes refreshMode)
    {
        auto message = static_cast<service::eink::ImageMessage *>(request);
        display.powerOn();

        powerOffTimer.stop();
        screen.setScreenBuffer(message->getData(), message->getSize());
        deepRefresh = message->getDeepRefresh();

        shutdownInProgress = message->getShutdown();
        if (shutdownInProgress) {
            LOG_DEBUG("Shutdown In Progress");
        const auto isDeepRefresh = refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
        if (const auto temperature = EinkGetTemperatureInternal(); isDeepRefresh) {
            display.changeWaveform(EinkWaveforms_e::EinkWaveformGC16, temperature);
            display.dither();
        }
        suspendInProgress = message->getSuspend();
        if (suspendInProgress) {
            LOG_DEBUG("Suspend In Progress");
        else {
            display.changeWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
        }
    }

        sys::Bus::SendUnicast(std::make_shared<service::eink::EinkDMATransfer>(), GetName(), this);
        return std::make_shared<sys::ResponseMessage>();
    sys::MessagePointer ServiceEink::handlePrepareRequest(sys::Message *message)
    {
        const auto waveformUpdateMsg = static_cast<service::eink::PrepareDisplayRequest *>(message);
        prepareDisplay(waveformUpdateMsg->getRefreshMode());
        return sys::MessageNone{};
    }

    sys::MessagePointer ServiceEink::handleStateRequest(sys::Message *)
    void ServiceEink::setState(State state) noexcept
    {
        return std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
        currentState = state;
    }

    sys::MessagePointer ServiceEink::handleTemperatureUpdate(sys::Message *)
    bool ServiceEink::isInState(State state) const noexcept
    {
        return nullptr;
        return currentState == state;
    }
} // namespace eink
} // namespace service::eink

M module-services/service-eink/ServiceEink.hpp => module-services/service-eink/ServiceEink.hpp +26 -38
@@ 3,64 3,52 @@

#pragma once

#include <gui/Common.hpp>
#include <EinkIncludes.hpp>
#include <Service/Common.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
#include <Service/Timer.hpp>

#include "EinkDisplay.hpp"

#include <chrono>
#include <cstdint>
#include <string>

#include "EinkScreen.hpp"

namespace eink
namespace service::eink
{
    class ServiceEink : public sys::Service
    {
      protected:
        EinkScreen screen;

        // counts timer triggers from last self refresh
        uint32_t selfRefereshTriggerCount;
        // counts timer events from last temperature measurement
        uint32_t temperatureMeasurementTriggerCount;
        // counts trigger counts from last action that required eink to be powered on
        uint32_t powerOffTriggerCount;

        // number of timer triggers required to execute self refresh handler
        const uint32_t selfRefereshTriggerValue = 60;
        // number of timer triggers required to execute temperature measurement handler
        const uint32_t temperatureMeasurementTriggerValue = 5 * 60;
        // number of timer triggers from last action requiring power on eink to power down eink.
        const uint32_t powerOffTriggerValue = 3;

        bool suspended = false;

        bool suspendInProgress  = false;
        bool shutdownInProgress = false;
        bool deepRefresh        = false;

        sys::ms powerOffTime = 3000;
        sys::Timer powerOffTimer;

      public:
        explicit ServiceEink(const std::string &name, std::string parent = {});

        sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
        sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *response) override;
        sys::ReturnCodes InitHandler() override;
        sys::ReturnCodes DeinitHandler() override;
        sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override final;
        sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override;

      private:
        enum class State
        {
            Running,
            Suspended
        };
        void setState(State state) noexcept;
        bool isInState(State state) const noexcept;

        void enterActiveMode();
        void suspend();
        void updateDisplay(std::uint8_t *frameBuffer, ::gui::RefreshModes refreshMode);
        void prepareDisplay(::gui::RefreshModes refreshMode);

        sys::MessagePointer handleEinkModeChangedMessage(sys::Message *message);
        sys::MessagePointer handleEinkDMATransfer(sys::Message *message);
        sys::MessagePointer handleImageMessage(sys::Message *message);
        sys::MessagePointer handleStateRequest(sys::Message *messge);
        sys::MessagePointer handleTemperatureUpdate(sys::Message *);
        sys::MessagePointer handlePrepareRequest(sys::Message *message);

        EinkDisplay display;
        State currentState;

        /*
         * PowerOffTimer to be implemented when needed.
         * It should power off the display when not used for 3000ms.
         */
    };
} // namespace eink
} // namespace service::eink

D module-services/service-eink/messages/EinkDMATransfer.hpp => module-services/service-eink/messages/EinkDMATransfer.hpp +0 -12
@@ 1,12 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "EinkMessage.hpp"

namespace service::eink
{
    class EinkDMATransfer : public service::eink::EinkMessage
    {};
}; // namespace service::eink

M module-services/service-eink/messages/EinkMessage.hpp => module-services/service-eink/messages/EinkMessage.hpp +2 -3
@@ 8,11 8,10 @@

namespace service::eink
{

    class EinkMessage : public sys::DataMessage
    {
      public:
        EinkMessage() : sys::DataMessage(MessageType::EinkMessage){};
        EinkMessage() : sys::DataMessage(MessageType::EinkMessage)
        {}
    };

} /* namespace seink */

M module-services/service-eink/messages/EinkModeMessage.hpp => module-services/service-eink/messages/EinkModeMessage.hpp +1 -1
@@ 16,7 16,7 @@ namespace service::eink
            Invert
        };

        EinkModeMessage(Mode mode) : EinkMessage(), mode(mode)
        explicit EinkModeMessage(Mode mode) : mode(mode)
        {}

        [[nodiscard]] auto getMode() const noexcept

M module-services/service-eink/messages/ImageMessage.cpp => module-services/service-eink/messages/ImageMessage.cpp +24 -6
@@ 4,14 4,32 @@
#include "EinkMessage.hpp"
#include "ImageMessage.hpp"

#include <MessageType.hpp>

namespace service::eink
{
    ImageMessage::ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode)
        : contextId{contextId}, context{context}, refreshMode{refreshMode}
    {}

    auto ImageMessage::getData() noexcept -> std::uint8_t *
    {
        return context->getData();
    }

    ImageMessage::ImageMessage(
        uint32_t x, uint32_t y, uint32_t w, uint32_t h, bool deepRefresh, uint8_t *data, bool suspend, bool shutdown)
        : EinkMessage(), x{x}, y{y}, w{w}, h{h}, deepRefresh{deepRefresh}, data{data}, suspend{suspend}, shutdown{
                                                                                                             shutdown}
    auto ImageMessage::getRefreshMode() const noexcept -> ::gui::RefreshModes
    {
        return refreshMode;
    }

    auto ImageMessage::getContextId() const noexcept -> int
    {
        return contextId;
    }

    ImageDisplayedNotification::ImageDisplayedNotification(int contextId) : contextId{contextId}
    {}

    auto ImageDisplayedNotification::getContextId() const noexcept -> int
    {
        return contextId;
    }
} /* namespace seink */

M module-services/service-eink/messages/ImageMessage.hpp => module-services/service-eink/messages/ImageMessage.hpp +22 -37
@@ 6,49 6,34 @@
#include "EinkMessage.hpp"

#include <cstdint>
#include <module-gui/gui/core/Context.hpp>
#include <module-gui/gui/Common.hpp>

namespace service::eink
{

    class ImageMessage : public EinkMessage
    {
      protected:
        uint32_t x, y, w, h;
        bool deepRefresh;
        uint8_t *data;
        bool suspend  = false;
        bool shutdown = false;

      public:
        ImageMessage(uint32_t x,
                     uint32_t y,
                     uint32_t w,
                     uint32_t h,
                     bool deepRefresh,
                     uint8_t *data,
                     bool suspend,
                     bool shutdown);

        uint8_t *getData()
        {
            return data;
        };
        uint32_t getSize()
        {
            return w * h;
        };
        bool getDeepRefresh()
        {
            return deepRefresh;
        };
        bool getSuspend()
        {
            return suspend;
        };
        bool getShutdown()
        {
            return shutdown;
        };
        ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode);

        [[nodiscard]] auto getContextId() const noexcept -> int;
        [[nodiscard]] auto getData() noexcept -> std::uint8_t *;
        [[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes;

      private:
        int contextId;
        ::gui::Context *context;
        ::gui::RefreshModes refreshMode;
    };

    class ImageDisplayedNotification : public EinkMessage
    {
      public:
        explicit ImageDisplayedNotification(int contextId);

        [[nodiscard]] auto getContextId() const noexcept -> int;

      private:
        int contextId;
    };
} /* namespace seink */

A module-services/service-eink/messages/PrepareDisplayRequest.cpp => module-services/service-eink/messages/PrepareDisplayRequest.cpp +15 -0
@@ 0,0 1,15 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "PrepareDisplayRequest.hpp"

namespace service::eink
{
    PrepareDisplayRequest::PrepareDisplayRequest(::gui::RefreshModes refreshMode) : refreshMode{refreshMode}
    {}

    auto PrepareDisplayRequest::getRefreshMode() const noexcept -> ::gui::RefreshModes
    {
        return refreshMode;
    }
} // namespace service::eink

A module-services/service-eink/messages/PrepareDisplayRequest.hpp => module-services/service-eink/messages/PrepareDisplayRequest.hpp +22 -0
@@ 0,0 1,22 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "EinkMessage.hpp"

#include <gui/Common.hpp>

namespace service::eink
{
    class PrepareDisplayRequest : public EinkMessage
    {
      public:
        explicit PrepareDisplayRequest(::gui::RefreshModes refreshMode);

        [[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes;

      private:
        ::gui::RefreshModes refreshMode;
    };
} // namespace service::eink

D module-services/service-eink/messages/StateRequest.hpp => module-services/service-eink/messages/StateRequest.hpp +0 -12
@@ 1,12 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "EinkMessage.hpp"

namespace service::eink
{
    class StateRequest : public EinkMessage
    {};
} // namespace service::eink

D module-services/service-eink/messages/TemperatureUpdate.hpp => module-services/service-eink/messages/TemperatureUpdate.hpp +0 -12
@@ 1,12 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "EinkMessage.hpp"

namespace service::eink
{
    class TemperatureUpdate : public EinkMessage
    {};
} // namespace service::eink

M module-services/service-gui/CMakeLists.txt => module-services/service-gui/CMakeLists.txt +7 -2
@@ 4,17 4,19 @@ message( "${PROJECT_NAME}  ${CMAKE_CURRENT_LIST_DIR}" )
set(SOURCES
    "${CMAKE_CURRENT_LIST_DIR}/ServiceGUI.cpp"
    "${CMAKE_CURRENT_LIST_DIR}/WorkerGUI.cpp"
    "${CMAKE_CURRENT_LIST_DIR}/ContextPool.cpp"
    "${CMAKE_CURRENT_LIST_DIR}/DrawCommandsQueue.cpp"
    "${CMAKE_CURRENT_LIST_DIR}/SynchronizationMechanism.cpp"
    "${CMAKE_CURRENT_LIST_DIR}/messages/DrawMessage.cpp"
)


add_library(${PROJECT_NAME} STATIC ${SOURCES})

target_link_libraries(${PROJECT_NAME}
    service-eink
    module-gui
    module-apps
    )
)

target_include_directories(${PROJECT_NAME}
    PUBLIC


@@ 22,3 24,6 @@ target_include_directories(${PROJECT_NAME}
        "${CMAKE_CURRENT_LIST_DIR}/messages"
)

if (${ENABLE_TESTS})
    add_subdirectory(tests)
endif()

M module-services/service-gui/Common.hpp => module-services/service-gui/Common.hpp +1 -1
@@ 6,4 6,4 @@
namespace service::name
{
    constexpr inline auto gui = "ServiceGUI";
}
} // namespace service::name

A module-services/service-gui/ContextPool.cpp => module-services/service-gui/ContextPool.cpp +109 -0
@@ 0,0 1,109 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ContextPool.hpp"

#include <mutex.hpp>

#include <algorithm>

namespace service::gui
{
    using namespace std::literals::chrono_literals;

    namespace
    {
        std::unique_ptr<::gui::Context> allocateContext(::gui::Size screenSize)
        {
            return std::make_unique<::gui::Context>(screenSize.width, screenSize.height);
        }

        constexpr auto DefaultWaitTime = 5000ms;
    } // namespace

    ContextPool::ContextPool(::gui::Size screenSize,
                             std::size_t capacity,
                             std::unique_ptr<SynchronizationMechanism> &&synchronization)
        : synchronization{std::move(synchronization)}
    {
        contexts.reserve(capacity);
        freeContextIds.reserve(capacity);
        lockedContextIds.reserve(capacity);

        for (std::size_t i = 0; i < capacity; ++i) {
            contexts.push_back(allocateContext(screenSize));
            freeContextIds.push_back(i);
        }
    }

    auto ContextPool::peekContext(int id) noexcept -> ::gui::Context *
    {
        return contexts[id].get();
    }

    auto ContextPool::borrowContext() -> std::pair<int, ::gui::Context *>
    {
        cpp_freertos::LockGuard lock(mutex);
        synchronization->wait(mutex, DefaultWaitTime, [this]() { return isAvailable(); });

        const auto contextId = freeContextIds.front();
        lockContext(contextId);
        return std::make_pair(contextId, contexts[contextId].get());
    }

    auto ContextPool::borrowContext(int id) -> ::gui::Context *
    {
        cpp_freertos::LockGuard lock(mutex);
        const auto it = std::find(freeContextIds.begin(), freeContextIds.end(), id);
        if (it == freeContextIds.end()) {
            return nullptr;
        }

        lockContext(id);
        return contexts[id].get();
    }

    auto ContextPool::isAnyContextLocked() const -> bool
    {
        cpp_freertos::LockGuard lock(mutex);
        return !lockedContextIds.empty();
    }

    auto ContextPool::isAvailable() const noexcept -> bool
    {
        return !freeContextIds.empty();
    }

    void ContextPool::lockContext(int contextId)
    {
        removeContextId(freeContextIds, contextId);
        addContextId(lockedContextIds, contextId);
    }

    void ContextPool::returnContext(int id)
    {
        cpp_freertos::LockGuard lock(mutex);
        freeContext(id);

        synchronization->notify();
    }

    void ContextPool::freeContext(int contextId)
    {
        removeContextId(lockedContextIds, contextId);
        addContextId(freeContextIds, contextId);
    }

    void ContextPool::removeContextId(std::vector<int> &sequence, int contextId)
    {
        sequence.erase(std::remove(sequence.begin(), sequence.end(), contextId), sequence.end());
    }

    void ContextPool::addContextId(std::vector<int> &sequence, int contextId)
    {
        if (const auto it = std::find(sequence.begin(), sequence.end(), contextId); it == sequence.end()) {
            // Add if not found.
            sequence.push_back(contextId);
        }
    }
} // namespace service::gui

A module-services/service-gui/ContextPool.hpp => module-services/service-gui/ContextPool.hpp +74 -0
@@ 0,0 1,74 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "SynchronizationMechanism.hpp"

#include <gui/Common.hpp>
#include <gui/core/Context.hpp>

#include <mutex.hpp>

#include <chrono>
#include <functional>
#include <memory>
#include <utility>
#include <vector>

namespace service::gui
{
    class ContextPool
    {
      public:
        ContextPool(
            ::gui::Size screenSize,
            std::size_t capacity,
            std::unique_ptr<SynchronizationMechanism> &&synchronization = getFreeRtosSynchronizationMechanism());

        /**
         * Peeks the context under specific id
         * @param id    Context id
         * @return Context data
         */
        [[nodiscard]] auto peekContext(int id) noexcept -> ::gui::Context *;
        /**
         * Locks the first available context
         * @return A context and its id
         */
        [[nodiscard]] auto borrowContext() -> std::pair<int, ::gui::Context *>;
        /**
         * Locks the context
         * @param id    Context id
         * @return Context data
         */
        [[nodiscard]] auto borrowContext(int id) -> ::gui::Context *;
        /**
         * Checks whether any context is currently locked
         * @return True if any context is locked, false otherwise
         */
        [[nodiscard]] auto isAnyContextLocked() const -> bool;
        /**
         * Makes a context (specified by its id) available again.
         * @param id    Context id
         */
        void returnContext(int id);

      private:
        using ContextIdContainer = std::vector<int>;

        [[nodiscard]] auto isAvailable() const noexcept -> bool;

        void freeContext(int contextId);
        void lockContext(int contextId);
        static void removeContextId(std::vector<int> &sequence, int contextId);
        static void addContextId(std::vector<int> &sequence, int contextId);

        std::vector<std::unique_ptr<::gui::Context>> contexts;
        ContextIdContainer lockedContextIds;
        ContextIdContainer freeContextIds;

        mutable cpp_freertos::MutexStandard mutex;
        std::unique_ptr<SynchronizationMechanism> synchronization;
    };
} // namespace service::gui

A module-services/service-gui/DrawCommandsQueue.cpp => module-services/service-gui/DrawCommandsQueue.cpp +62 -0
@@ 0,0 1,62 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "DrawCommandsQueue.hpp"

namespace service::gui
{
    namespace
    {
        constexpr std::chrono::milliseconds WaitTimeout{5000};
    } // namespace

    DrawCommandsQueue::DrawCommandsQueue(std::size_t expectedSize,
                                         std::unique_ptr<SynchronizationMechanism> &&synchronization)
        : synchronization{std::move(synchronization)}
    {
        queue.reserve(expectedSize);
    }

    void DrawCommandsQueue::enqueue(QueueItem &&item)
    {
        cpp_freertos::LockGuard lock{queueMutex};
        queue.push_back(std::move(item));

        synchronization->notify();
    }

    auto DrawCommandsQueue::dequeue() -> QueueItem
    {
        cpp_freertos::LockGuard lock{queueMutex};
        synchronization->wait(queueMutex, WaitTimeout, [this]() { return !queue.empty(); });

        auto item = std::move(queue.front());
        queue.erase(queue.begin());
        return item;
    }

    auto DrawCommandsQueue::getMaxRefreshModeAndClear() -> ::gui::RefreshModes
    {
        cpp_freertos::LockGuard lock{queueMutex};
        const auto deepRefreshRequested = std::any_of(queue.begin(), queue.end(), [](const auto &item) {
            return item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
        });
        const auto maxRefreshMode =
            deepRefreshRequested ? ::gui::RefreshModes::GUI_REFRESH_DEEP : ::gui::RefreshModes::GUI_REFRESH_FAST;

        queue.clear();
        return maxRefreshMode;
    }

    void DrawCommandsQueue::clear()
    {
        cpp_freertos::LockGuard lock{queueMutex};
        queue.clear();
    }

    auto DrawCommandsQueue::size() const noexcept -> QueueContainer::size_type
    {
        cpp_freertos::LockGuard lock{queueMutex};
        return queue.size();
    }
} // namespace service::gui

A module-services/service-gui/DrawCommandsQueue.hpp => module-services/service-gui/DrawCommandsQueue.hpp +44 -0
@@ 0,0 1,44 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "SynchronizationMechanism.hpp"

#include <gui/core/DrawCommand.hpp>

#include <cstdint>
#include <list>
#include <memory>
#include <vector>

namespace service::gui
{
    class DrawCommandsQueue
    {
      public:
        using CommandList = std::list<std::unique_ptr<::gui::DrawCommand>>;
        struct QueueItem
        {
            CommandList commands;
            ::gui::RefreshModes refreshMode = ::gui::RefreshModes::GUI_REFRESH_FAST;
        };
        using QueueContainer = std::vector<QueueItem>;

        explicit DrawCommandsQueue(
            std::size_t expectedSize,
            std::unique_ptr<SynchronizationMechanism> &&synchronization = getFreeRtosSynchronizationMechanism());

        void enqueue(QueueItem &&item);
        [[nodiscard]] auto dequeue() -> QueueItem;
        [[nodiscard]] auto getMaxRefreshModeAndClear() -> ::gui::RefreshModes;
        void clear();
        [[nodiscard]] auto size() const noexcept -> QueueContainer::size_type;

      private:
        QueueContainer queue;

        mutable cpp_freertos::MutexStandard queueMutex;
        std::unique_ptr<SynchronizationMechanism> synchronization;
    };
} // namespace service::gui

M module-services/service-gui/ServiceGUI.cpp => module-services/service-gui/ServiceGUI.cpp +167 -166
@@ 4,245 4,246 @@
#include "ServiceGUI.hpp"
#include "WorkerGUI.hpp"

#include "messages/GUIMessage.hpp"
#include "messages/DrawMessage.hpp"
#include "messages/RenderingFinished.hpp"
#include "messages/GUIDisplayReady.hpp"
#include "messages/EinkReady.hpp"

#include <DrawCommand.hpp>
#include <FontManager.hpp>
#include <MessageType.hpp>
#include <gui/core/Context.hpp>
#include <gui/core/ImageManager.hpp>
#include <log/log.hpp>
#include <projdefs.h>
#include <service-appmgr/Controller.hpp>
#include <service-eink/Common.hpp>
#include <service-eink/messages/ImageMessage.hpp>
#include <service-eink/messages/EinkMessage.hpp>
#include <service-eink/messages/StateRequest.hpp>
#include <Service/Bus.hpp>
#include <Service/Worker.hpp>
#include <service-eink/messages/PrepareDisplayRequest.hpp>
#include <SystemManager/SystemManager.hpp>
#include <service-eink/Common.hpp>

#include <task.h>

extern "C"
{
#include <FreeRTOS.h>
#include <semphr.h>
}

#include <cstddef>
#include <list>
#include <memory>
#include <utility>
#include <gsl/gsl_util>
#include <purefs/filesystem_paths.hpp>

namespace sgui
namespace service::gui
{
    ServiceGUI::ServiceGUI(const std::string &name, std::string parent, uint32_t screenWidth, uint32_t screenHeight)
        : sys::Service(name, parent, 4096, sys::ServicePriority::Idle), renderContext{nullptr},
          transferContext{nullptr}, renderFrameCounter{1}, transferedFrameCounter{0}, screenWidth{screenWidth},
          screenHeight{screenHeight}, semCommands{NULL}
    namespace
    {

        LOG_INFO("[ServiceGUI] Initializing");

        renderContext   = new gui::Context(screenWidth, screenHeight);
        transferContext = new gui::Context(screenWidth, screenHeight);

        gui::FontManager &fontManager = gui::FontManager::getInstance();
        fontManager.init(purefs::dir::getCurrentOSPath() / "assets");

        gui::ImageManager &imageManager = gui::ImageManager::getInstance();
        imageManager.init(purefs::dir::getCurrentOSPath() / "assets");

        connect(typeid(sgui::DrawMessage),
                [&](sys::Message *request) -> sys::MessagePointer { return handleDrawMessage(request); });

        connect(typeid(service::gui::RenderingFinished),
                [&](sys::Message *request) -> sys::MessagePointer { return handleGUIRenderingFinished(request); });

        connect(typeid(service::gui::GUIDisplayReady),
                [&](sys::Message *request) -> sys::MessagePointer { return handleGUIDisplayReady(request); });
        constexpr auto ServiceGuiStackDepth  = 4096U;
        constexpr auto ContextsCount         = 2;
        constexpr auto CommandsQueueCapacity = 3;
        constexpr std::chrono::milliseconds ContextReleaseTimeout{1000};
    } // namespace

    ServiceGUI::ServiceGUI(const std::string &name, std::string parent)
        : sys::Service(name, parent, ServiceGuiStackDepth), commandsQueue{std::make_unique<DrawCommandsQueue>(
                                                                CommandsQueueCapacity)},
          contextReleaseTimer{
              std::make_unique<sys::Timer>(this, ContextReleaseTimeout.count(), sys::Timer::Type::SingleShot)},
          currentState{State::NotInitialised}
    {
        initAssetManagers();
        registerMessageHandlers();
    }

    ServiceGUI::~ServiceGUI()
    ServiceGUI::~ServiceGUI() noexcept = default;

    void ServiceGUI::initAssetManagers()
    {
        LOG_INFO("[ServiceGUI] Cleaning resources");
        if (renderContext)
            delete renderContext;
        if (transferContext)
            delete transferContext;
        const auto assetsPath = purefs::dir::getCurrentOSPath() / "assets";
        ::gui::FontManager::getInstance().init(assetsPath);
        ::gui::ImageManager::getInstance().init(assetsPath);
    }

    void ServiceGUI::sendBuffer()
    void ServiceGUI::registerMessageHandlers()
    {
        transferContext->insert(0, 0, renderContext);
        connect(typeid(EinkReady),
                [this](sys::Message *request) -> sys::MessagePointer { return handleEinkReady(request); });

        auto msg =
            std::make_shared<service::eink::ImageMessage>(0,
                                                          0,
                                                          transferContext->getW(),
                                                          transferContext->getH(),
                                                          (mode == gui::RefreshModes::GUI_REFRESH_DEEP ? true : false),
                                                          transferContext->getData(),
                                                          suspendInProgress,
                                                          shutdownInProgress);
        einkReady = false;
        auto ret  = sys::Bus::SendUnicast(msg, service::name::eink, this, 2000);
        if (ret.first == sys::ReturnCodes::Success) {
            transferedFrameCounter = renderFrameCounter;
        }
        mode = gui::RefreshModes::GUI_REFRESH_FAST;
    }
        connect(typeid(DrawMessage),
                [this](sys::Message *request) -> sys::MessagePointer { return handleDrawMessage(request); });

    void ServiceGUI::sendToRender()
    {
        rendering = true;
        worker->send(static_cast<uint32_t>(WorkerGUICommands::Render), NULL);
        connect(typeid(RenderingFinished),
                [this](sys::Message *request) -> sys::MessagePointer { return handleGUIRenderingFinished(request); });

        connect(typeid(eink::ImageDisplayedNotification), [this](sys::Message *request) -> sys::MessagePointer {
            return handleImageDisplayedNotification(request);
        });
    }

    sys::MessagePointer ServiceGUI::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
    {
        return std::make_shared<sys::ResponseMessage>();
        return sys::MessageNone{};
    }

    sys::ReturnCodes ServiceGUI::InitHandler()
    {
        semCommands = xSemaphoreCreateBinary();
        if (semCommands == NULL) {
            LOG_FATAL("Failed to create commands semaphore.");
            return sys::ReturnCodes::Failure;
        }
        xSemaphoreGive(semCommands);

        std::list<sys::WorkerQueueInfo> queueInfo{
            {WorkerGUI::SignallingQueueName, WorkerGUI::SignalSize, WorkerGUI::SignallingQueueCapacity}};
        worker = std::make_unique<WorkerGUI>(this);
        std::list<sys::WorkerQueueInfo> list;
        worker->init(list);
        worker->init(queueInfo);
        worker->run();

        if (einkReady == false) {
            requestSent = true;
            sys::Bus::SendUnicast(std::make_shared<service::eink::StateRequest>(), service::name::eink, this);
        }
        return sys::ReturnCodes::Success;
    }

    sys::ReturnCodes ServiceGUI::DeinitHandler()
    {

        if (semCommands != NULL)
            vSemaphoreDelete(semCommands);
        semCommands = NULL;

        worker->stop();
        worker->join();
        worker->deinit();

        worker->close();
        return sys::ReturnCodes::Success;
    }

    sys::ReturnCodes ServiceGUI::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
    {
        LOG_FATAL("[ServiceGUI] PowerModeHandler: %s", c_str(mode));

        LOG_INFO("PowerModeHandler: %s", c_str(mode));
        switch (mode) {
        case sys::ServicePowerMode ::Active:
        case sys::ServicePowerMode::Active:
            setState(contextPool != nullptr ? State::Idle : State::NotInitialised);
            break;
        case sys::ServicePowerMode ::SuspendToRAM:
        case sys::ServicePowerMode ::SuspendToNVM:
        case sys::ServicePowerMode::SuspendToRAM:
            [[fallthrough]];
        case sys::ServicePowerMode::SuspendToNVM:
            setState(State::Suspended);
            break;
        }

        return sys::ReturnCodes::Success;
    }

    sys::MessagePointer ServiceGUI::handleDrawMessage(sys::Message *message)
    {
        auto dmsg = static_cast<sgui::DrawMessage *>(message);
        if (!dmsg->commands.empty()) {

            if (!suspendInProgress) {
        if (isInState(State::NotInitialised)) {
            LOG_WARN("Service not yet initialised - ignoring draw commands");
            return sys::MessageNone{};
        }
        if (isInState(State::Suspended)) {
            LOG_WARN("Suspended - ignoring draw commands");
            return sys::MessageNone{};
        }

                if (dmsg->isType(sgui::DrawMessage::Type::SHUTDOWN)) {
                    LOG_WARN("Shutdown - received shutdown draw commands");
                    shutdownInProgress = true;
                }
        if (const auto drawMsg = static_cast<DrawMessage *>(message); !drawMsg->commands.empty()) {
            if (drawMsg->isType(DrawMessage::Type::SHUTDOWN) || drawMsg->isType(DrawMessage::Type::SUSPEND)) {
                setState(State::Suspended);
            }

                if (dmsg->isType(sgui::DrawMessage::Type::SUSPEND)) {
                    LOG_WARN("Suspended - received suspend draw commands");
                    suspendInProgress = true;
                }
            prepareDisplay(drawMsg->mode);
            notifyRenderer(std::move(drawMsg->commands), drawMsg->mode);
        }
        return sys::MessageNone{};
    }

                if (dmsg->mode == gui::RefreshModes::GUI_REFRESH_DEEP) {
                    mode = dmsg->mode;
                }
    void ServiceGUI::prepareDisplay(::gui::RefreshModes refreshMode)
    {
        auto msg = std::make_shared<service::eink::PrepareDisplayRequest>(refreshMode);
        sys::Bus::SendUnicast(msg, service::name::eink, this);
    }

                if (xSemaphoreTake(semCommands, pdMS_TO_TICKS(1000)) == pdTRUE) {
                    commands = std::move(dmsg->commands);
                    xSemaphoreGive(semCommands);
                }
                else {
                    LOG_ERROR("Failed to acquire semaphore");
                }
    void ServiceGUI::notifyRenderer(std::list<std::unique_ptr<::gui::DrawCommand>> &&commands,
                                    ::gui::RefreshModes refreshMode)
    {
        enqueueDrawCommands(DrawCommandsQueue::QueueItem{std::move(commands), refreshMode});
        worker->notify(WorkerGUI::Signal::Render);
    }

                if (!rendering) {
                    sendToRender();
                }
            }
            else {
                LOG_WARN("Suspended - ignoring draw commands");
            }
    void ServiceGUI::enqueueDrawCommands(DrawCommandsQueue::QueueItem &&item)
    {
        // Clear all queue elements for now to keep only the latest command in the queue.
        // In the future, we'll need to implement more sophisticated algorithm for partially refresh the display.
        if (item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP) {
            commandsQueue->clear();
        }
        else if (const auto maxRefreshMode = commandsQueue->getMaxRefreshModeAndClear();
                 maxRefreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP) {
            item.refreshMode = maxRefreshMode;
        }
        return nullptr;
        commandsQueue->enqueue(std::move(item)); // 3 consecutive deep refreshes after leaving messages? :/
    }

    sys::MessagePointer ServiceGUI::handleGUIRenderingFinished(sys::Message *message)
    {
        rendering = false;
        renderFrameCounter++;

        if (einkReady) {
            sendBuffer();
        auto finishedMsg       = static_cast<service::gui::RenderingFinished *>(message);
        const auto contextId   = finishedMsg->getContextId();
        const auto refreshMode = finishedMsg->getRefreshMode();
        if (isInState(State::Idle)) {
            const auto context = contextPool->peekContext(contextId);
            sendOnDisplay(context, contextId, refreshMode);
            invalidateCache();
        }
        else if (!requestSent) {
            requestSent = true;
            sys::Bus::SendUnicast(std::make_shared<service::eink::StateRequest>(), service::name::eink, this);
        else {
            cacheRender(contextId, refreshMode);
            contextPool->returnContext(contextId);
        }
        return nullptr;
        return sys::MessageNone{};
    }

    void ServiceGUI::sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode)
    {
        setState(State::Busy);
        auto imageMsg = std::make_shared<service::eink::ImageMessage>(contextId, context, refreshMode);
        sys::Bus::SendUnicast(imageMsg, service::name::eink, this);
        scheduleContextRelease(contextId);
    }

    sys::MessagePointer ServiceGUI::handleGUIDisplayReady(sys::Message *message)
    void ServiceGUI::scheduleContextRelease(int contextId)
    {
        auto msg    = static_cast<service::gui::GUIDisplayReady *>(message);
        einkReady   = true;
        requestSent = false;
        // Whenever the response from ServiceEink doesn't come, the context has to be released automatically after a
        // timeout.
        contextReleaseTimer->connect([this, contextId](sys::Timer &it) {
            eink::ImageDisplayedNotification notification{contextId};
            handleImageDisplayedNotification(&notification);
            LOG_WARN("Context # %d released after timeout. Does ServiceEink respond properly?", contextId);
        });
        contextReleaseTimer->start();
    }

        if (msg->getShutdownInProgress()) {
            einkReady         = false;
            suspendInProgress = false;
            LOG_DEBUG("last rendering before shutdown finished.");
    void ServiceGUI::cacheRender(int contextId, ::gui::RefreshModes refreshMode)
    {
        cachedRender = CachedRender{contextId, refreshMode};
    }

            sys::SystemManager::CloseSystem(this);
        }
    void ServiceGUI::invalidateCache()
    {
        cachedRender = std::nullopt;
    }

        if (msg->getSuspendInProgress()) {
            einkReady         = false;
            suspendInProgress = false;
            LOG_DEBUG("last rendering before suspend is finished.");
    sys::MessagePointer ServiceGUI::handleEinkReady(sys::Message *message)
    {
        const auto msg = static_cast<service::gui::EinkReady *>(message);
        contextPool    = std::make_unique<ContextPool>(msg->getDisplaySize(), ContextsCount);
        setState(State::Idle);
        return sys::MessageNone{};
    }

            app::manager::Controller::changePowerSaveMode(this);
        }
        if ((renderFrameCounter != transferedFrameCounter) && (!rendering)) {
            sendBuffer();
    sys::MessagePointer ServiceGUI::handleImageDisplayedNotification(sys::Message *message)
    {
        const auto msg       = static_cast<eink::ImageDisplayedNotification *>(message);
        const auto contextId = msg->getContextId();
        contextPool->returnContext(contextId);
        contextReleaseTimer->stop();
        setState(State::Idle);

        if (isNextFrameReady()) {
            trySendNextFrame();
        }
        return sys::MessageNone{};
    }

    bool ServiceGUI::isNextFrameReady() const noexcept
    {
        // Even if the next render is already cached, if any context in the pool is currently being processed, then we
        // better wait for it.
        return cachedRender.has_value() && !contextPool->isAnyContextLocked();
    }

        if (commands.empty() == false) {
            sendToRender();
    void ServiceGUI::trySendNextFrame()
    {
        const auto contextId = cachedRender->contextId;
        if (const auto context = contextPool->borrowContext(contextId); context != nullptr) {
            sendOnDisplay(context, contextId, cachedRender->refreshMode);
        }
        return nullptr;
        invalidateCache();
    }

    void ServiceGUI::setState(State state) noexcept
    {
        currentState = state;
    }

    bool ServiceGUI::isInState(State state) const noexcept
    {
        return currentState == state;
    }
} /* namespace sgui */

M module-services/service-gui/ServiceGUI.hpp => module-services/service-gui/ServiceGUI.hpp +51 -63
@@ 3,20 3,20 @@

#pragma once

// module-gui

#include <Common.hpp>
#include <gui/core/Context.hpp>
#include <gui/core/Renderer.hpp>
#include <gui/input/Translator.hpp>
#include <Service/Common.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
#include <Service/Timer.hpp>

#include "messages/RenderingFinished.hpp"

#include "ContextPool.hpp"
#include "DrawCommandsQueue.hpp"

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace gui
{


@@ 24,76 24,64 @@ namespace gui
    class DrawCommand;
} // namespace gui

namespace sgui
namespace service::gui
{

    class WorkerGUI;

    class ServiceGUI : public sys::Service
    {
        friend WorkerGUI;

      protected:
        // this is where every incomming frame is painted.
        gui::Context *renderContext;
        // this buffer is provided to eink
        gui::Context *transferContext;
        // ID of the last rendered frame
        uint32_t renderFrameCounter;
        // ID of the last frame sent to eink for rendering
        uint32_t transferedFrameCounter;
        // Horizontal size of the screen in pixels
        uint32_t screenWidth;
        // vertical size of the screen in pixels
        uint32_t screenHeight;
        // object responsible for rendering images to context
        gui::Renderer renderer;
        // flag that defines whether eink is ready for new frame buffer
        volatile bool einkReady   = false;
        volatile bool requestSent = false;
        volatile bool rendering   = false;
        // set of commands recently received. If this vector is not empty and new set of commands is received
        // previous commands are removed.
        std::list<std::unique_ptr<gui::DrawCommand>> commands;
        //	uint32_t timer_id= 0;
        gui::RefreshModes mode = gui::RefreshModes::GUI_REFRESH_DEEP;

        // semaphore used to protect commands vector while commands are taken from service to worker.
        SemaphoreHandle_t semCommands;

        std::unique_ptr<WorkerGUI> worker;

        /**
         * Flag controls process of redrawing screen when suspend is in progress.
         */
        bool suspendInProgress = false;
        /**
         * Flag controls process of redrawing screen when phone is shutting down.
         */
        bool shutdownInProgress = false;

        void sendBuffer();
        void sendToRender();

      public:
        ServiceGUI(const std::string &name,
                   std::string parent    = "",
                   uint32_t screenWidth  = 480,
                   uint32_t screenHeight = 600);
        ~ServiceGUI();

        sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
        explicit ServiceGUI(const std::string &name, std::string parent = {});
        ~ServiceGUI() noexcept override;

        sys::ReturnCodes InitHandler() override;

        sys::ReturnCodes DeinitHandler() override;

        sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override final;
        sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
        sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override;

      private:
        struct CachedRender
        {
            int contextId;
            ::gui::RefreshModes refreshMode;
        };
        enum class State
        {
            NotInitialised,
            Idle,
            Busy,
            Suspended
        };

        static void initAssetManagers();
        void registerMessageHandlers();

        void cacheRender(int contextId, ::gui::RefreshModes refreshMode);
        void invalidateCache();

        void prepareDisplay(::gui::RefreshModes refreshMode);
        void notifyRenderer(std::list<std::unique_ptr<::gui::DrawCommand>> &&commands, ::gui::RefreshModes refreshMode);
        void enqueueDrawCommands(DrawCommandsQueue::QueueItem &&item);
        void sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode);
        void scheduleContextRelease(int contextId);
        bool isNextFrameReady() const noexcept;
        void trySendNextFrame();

        void setState(State state) noexcept;
        bool isInState(State state) const noexcept;

        sys::MessagePointer handleDrawMessage(sys::Message *message);
        sys::MessagePointer handleGUIRenderingFinished(sys::Message *message);
        sys::MessagePointer handleGUIDisplayReady(sys::Message *message);
    };
        sys::MessagePointer handleEinkReady(sys::Message *message);
        sys::MessagePointer handleImageDisplayedNotification(sys::Message *message);

} /* namespace sgui */
        std::unique_ptr<ContextPool> contextPool;
        std::unique_ptr<WorkerGUI> worker;
        std::unique_ptr<DrawCommandsQueue> commandsQueue;
        std::optional<CachedRender> cachedRender;
        std::unique_ptr<sys::Timer> contextReleaseTimer;
        State currentState;
    };
} // namespace service::gui

A module-services/service-gui/SynchronizationMechanism.cpp => module-services/service-gui/SynchronizationMechanism.cpp +58 -0
@@ 0,0 1,58 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "SynchronizationMechanism.hpp"

namespace service::gui
{
    class FreeRtosSynchronization : public SynchronizationMechanism
    {
      public:
        FreeRtosSynchronization();
        FreeRtosSynchronization(const FreeRtosSynchronization &)     = delete;
        FreeRtosSynchronization(FreeRtosSynchronization &&) noexcept = default;
        FreeRtosSynchronization &operator=(const FreeRtosSynchronization &) = delete;
        FreeRtosSynchronization &operator=(FreeRtosSynchronization &&) noexcept = default;
        ~FreeRtosSynchronization() noexcept override;

        void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) override;
        void notify() override;

      private:
        SemaphoreHandle_t semaphore;
    };

    FreeRtosSynchronization::FreeRtosSynchronization() : semaphore{xSemaphoreCreateBinary()}
    {}

    FreeRtosSynchronization::~FreeRtosSynchronization() noexcept
    {
        vSemaphoreDelete(semaphore);
    }

    void FreeRtosSynchronization::wait(cpp_freertos::Mutex &lock,
                                       std::chrono::milliseconds timeout,
                                       Predicate predicate)
    {
        if (!predicate) {
            // Predicate needed to cover spurious wake-ups.
            throw std::invalid_argument{"Null predicate passed."};
        }

        while (!predicate()) {
            lock.Unlock();
            xSemaphoreTake(semaphore, timeout.count());
            lock.Lock();
        }
    }

    void FreeRtosSynchronization::notify()
    {
        xSemaphoreGive(semaphore);
    }

    std::unique_ptr<SynchronizationMechanism> getFreeRtosSynchronizationMechanism()
    {
        return std::make_unique<FreeRtosSynchronization>();
    }
} // namespace service::gui

A module-services/service-gui/SynchronizationMechanism.hpp => module-services/service-gui/SynchronizationMechanism.hpp +26 -0
@@ 0,0 1,26 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <mutex.hpp>

#include <chrono>
#include <functional>
#include <memory>

namespace service::gui
{
    /// Interface providing platform-independent synchronization strategy.
    class SynchronizationMechanism
    {
      public:
        using Predicate = std::function<bool()>;

        virtual ~SynchronizationMechanism() noexcept                                                         = default;
        virtual void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) = 0;
        virtual void notify()                                                                                = 0;
    };

    std::unique_ptr<SynchronizationMechanism> getFreeRtosSynchronizationMechanism();
} // namespace service::gui

M module-services/service-gui/WorkerGUI.cpp => module-services/service-gui/WorkerGUI.cpp +34 -31
@@ 5,52 5,55 @@

#include <DrawCommand.hpp>
#include <log/log.hpp>
#include <MessageType.hpp>
#include <projdefs.h>
#include <queue.h>
#include <Renderer.hpp>
#include <semphr.h>
#include <Service/Bus.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
#include <Service/Worker.hpp>
#include <service-gui/ServiceGUI.hpp>

#include <memory>
#include <utility>
#include <vector>
#include <module-utils/time/ScopedTime.hpp>
#include "messages/RenderingFinished.hpp"

namespace sgui
namespace service::gui
{

    WorkerGUI::WorkerGUI(ServiceGUI *service) : Worker(service), service(service)
    WorkerGUI::WorkerGUI(ServiceGUI *service) : Worker(service), guiService{service}
    {}

    bool WorkerGUI::handleMessage(uint32_t queueID)
    void WorkerGUI::notify(Signal command)
    {
        auto &queue = queues[queueID];

        auto serviceGUI = static_cast<sgui::ServiceGUI *>(service);

        if (queueID == 0) {
            sys::WorkerCommand received;
            queue->Dequeue(&received, 0);
        if (auto queue = getQueueByName(SignallingQueueName); !queue->Overwrite(&command)) {
            LOG_ERROR("Unable to overwrite the command in the commands queue.");
        }
    }

            std::list<std::unique_ptr<gui::DrawCommand>> uniqueCommands;
            if (xSemaphoreTake(serviceGUI->semCommands, pdMS_TO_TICKS(1000)) == pdTRUE) {
                uniqueCommands = std::move(serviceGUI->commands);
                xSemaphoreGive(serviceGUI->semCommands);
            }
            else {
                LOG_ERROR("Failed to acquire semaphore");
    bool WorkerGUI::handleMessage(uint32_t queueID)
    {
        if (const auto queue = queues[queueID]; queue->GetQueueName() == SignallingQueueName) {
            if (sys::WorkerCommand command; queue->Dequeue(&command, 0)) {
                handleCommand(static_cast<Signal>(command.command));
            }

            serviceGUI->renderer.render(serviceGUI->renderContext, uniqueCommands);

            sys::Bus::SendUnicast(std::make_shared<service::gui::RenderingFinished>(), service->GetName(), service);
        }
        return true;
    }

} /* namespace sgui */
    void WorkerGUI::handleCommand(Signal command)
    {
        if (command == Signal::Render) {
            auto item = guiService->commandsQueue->dequeue();
            render(item.commands, item.refreshMode);
        }
    }

    void WorkerGUI::render(std::list<std::unique_ptr<::gui::DrawCommand>> &commands, ::gui::RefreshModes refreshMode)
    {
        const auto [contextId, context] = guiService->contextPool->borrowContext(); // Waits for the context.
        renderer.render(context, commands);
        onRenderingFinished(contextId, refreshMode);
    }

    void WorkerGUI::onRenderingFinished(int contextId, ::gui::RefreshModes refreshMode)
    {
        auto msg = std::make_shared<service::gui::RenderingFinished>(contextId, refreshMode);
        sys::Bus::SendUnicast(std::move(msg), guiService->GetName(), guiService);
    }
} // namespace service::gui

M module-services/service-gui/WorkerGUI.hpp => module-services/service-gui/WorkerGUI.hpp +22 -24
@@ 12,34 12,32 @@

#include <cstdint>

namespace sgui
namespace service::gui
{

    class ServiceGUI;

    enum class WorkerGUICommands
    {
        Finish,
        Render,
        //	RenderSuspend
    };

    /*
     *
     */
    class WorkerGUI : public sys::Worker
    {
        // object responsible for rendering images to context
        gui::Renderer renderer;
        sys::Service *service;

      public:
        WorkerGUI(ServiceGUI *service);

        /**
         * virtual method responsible for finishing the worker and handling rendering commands
         */
        bool handleMessage(uint32_t queueID) override;
        enum class Signal
        {
            Render
        };
        static constexpr auto SignallingQueueName     = "SignallingQueue";
        static constexpr auto SignallingQueueCapacity = 1;
        static constexpr auto SignalSize              = sizeof(Signal);

        explicit WorkerGUI(ServiceGUI *service);

        void notify(Signal command);
        auto handleMessage(std::uint32_t queueID) -> bool override;

      private:
        void handleCommand(Signal command);
        void render(std::list<std::unique_ptr<::gui::DrawCommand>> &commands, ::gui::RefreshModes refreshMode);
        void onRenderingFinished(int contextId, ::gui::RefreshModes refreshMode);

        ServiceGUI *guiService;
        ::gui::Renderer renderer;
    };

} /* namespace sgui */
} // namespace service::gui

M module-services/service-gui/messages/DrawMessage.cpp => module-services/service-gui/messages/DrawMessage.cpp +4 -6
@@ 6,11 6,9 @@
#include <Common.hpp>
#include <DrawCommand.hpp>

namespace sgui
namespace service::gui
{

    DrawMessage::DrawMessage(std::list<gui::Command> commands, gui::RefreshModes mode)
    DrawMessage::DrawMessage(std::list<::gui::Command> commands, ::gui::RefreshModes mode)
        : GUIMessage(), mode(mode), commands(std::move(commands))
    {
    }
} /* namespace sgui */
    {}
} // namespace service::gui

M module-services/service-gui/messages/DrawMessage.hpp => module-services/service-gui/messages/DrawMessage.hpp +9 -11
@@ 16,9 16,8 @@
#include "GUIMessage.hpp"
#include "gui/Common.hpp"

namespace sgui
namespace service::gui
{

    class DrawMessage : public GUIMessage
    {
      public:


@@ 30,20 29,19 @@ namespace sgui
        } type = Type::NORMAL;

      public:
        gui::RefreshModes mode;
        std::list<gui::Command> commands;
        ::gui::RefreshModes mode;
        std::list<::gui::Command> commands;

        DrawMessage(std::list<gui::Command> commandsList, gui::RefreshModes mode);
        DrawMessage(std::list<::gui::Command> commandsList, ::gui::RefreshModes mode);

        void setCommandType(Type type) noexcept
        void setCommandType(Type value) noexcept
        {
            this->type = type;
            type = value;
        }

        bool isType(Type type) const noexcept
        bool isType(Type value) const noexcept
        {
            return this->type == type;
            return type == value;
        }
    };

} // namespace sgui
} // namespace service::gui

R module-services/service-gui/messages/GUIDisplayReady.hpp => module-services/service-gui/messages/EinkReady.hpp +9 -14
@@ 1,31 1,26 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "GUIMessage.hpp"

#include <gui/Common.hpp>

namespace service::gui
{
    class GUIDisplayReady : public sgui::GUIMessage
    class EinkReady : public GUIMessage
    {

        bool suspendInProgress  = false;
        bool shutdownInProgress = false;

      public:
        GUIDisplayReady(bool suspendInProgress, bool shutdownInProgress)
            : suspendInProgress(suspendInProgress), shutdownInProgress(shutdownInProgress)
        explicit EinkReady(::gui::Size displaySize) : einkDisplaySize{displaySize}
        {}

        [[nodiscard]] auto getSuspendInProgress() const noexcept
        [[nodiscard]] auto getDisplaySize() const noexcept -> ::gui::Size
        {
            return suspendInProgress;
            return einkDisplaySize;
        }

        [[nodiscard]] auto getShutdownInProgress() const noexcept
        {
            return shutdownInProgress;
        }
      private:
        ::gui::Size einkDisplaySize;
    };
} // namespace service::gui

M module-services/service-gui/messages/GUIMessage.hpp => module-services/service-gui/messages/GUIMessage.hpp +4 -5
@@ 7,13 7,12 @@
#include <Service/Message.hpp>
#include <core/DrawCommand.hpp>

namespace sgui
namespace service::gui
{

    class GUIMessage : public sys::DataMessage
    {
      public:
        GUIMessage() : sys::DataMessage(MessageType::GUIMessage){};
        GUIMessage() : sys::DataMessage(MessageType::GUIMessage)
        {}
    };

} /* namespace sgui */
} // namespace service::gui

M module-services/service-gui/messages/RenderingFinished.hpp => module-services/service-gui/messages/RenderingFinished.hpp +23 -2
@@ 5,8 5,29 @@

#include "GUIMessage.hpp"

#include <gui/Common.hpp>

namespace service::gui
{
    class RenderingFinished : public sgui::GUIMessage
    {};
    class RenderingFinished : public GUIMessage
    {
      public:
        RenderingFinished(int contextId, ::gui::RefreshModes refreshMode)
            : contextId{contextId}, refreshMode{refreshMode}
        {}

        [[nodiscard]] int getContextId() const noexcept
        {
            return contextId;
        }

        [[nodiscard]] ::gui::RefreshModes getRefreshMode() const noexcept
        {
            return refreshMode;
        }

      private:
        int contextId;
        ::gui::RefreshModes refreshMode;
    };
} // namespace service::gui

A module-services/service-gui/tests/CMakeLists.txt => module-services/service-gui/tests/CMakeLists.txt +21 -0
@@ 0,0 1,21 @@
add_catch2_executable(
    NAME
        context-pool-tests
    SRCS
        tests-main.cpp
        test-ContextPool.cpp
        MockedSynchronizationMechanism.hpp
    LIBS
        service-gui
)

add_catch2_executable(
    NAME
        commands-queue-tests
    SRCS
        tests-main.cpp
        test-DrawCommandsQueue.cpp
        MockedSynchronizationMechanism.hpp
    LIBS
        service-gui
)

A module-services/service-gui/tests/MockedSynchronizationMechanism.hpp => module-services/service-gui/tests/MockedSynchronizationMechanism.hpp +31 -0
@@ 0,0 1,31 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <SynchronizationMechanism.hpp>

#include "mutex.hpp"

#include <chrono>
#include <thread>

class MockedSynchronizationMechanism : public service::gui::SynchronizationMechanism
{
  public:
    void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) override;
    void notify() override;
};

void MockedSynchronizationMechanism::wait(cpp_freertos::Mutex &lock,
                                          std::chrono::milliseconds timeout,
                                          Predicate predicate)
{
    using namespace std::chrono_literals;
    while (!predicate()) {
        lock.Unlock();
        std::this_thread::sleep_for(10ms);
        lock.Lock();
    }
}

void MockedSynchronizationMechanism::notify()
{}

A module-services/service-gui/tests/test-ContextPool.cpp => module-services/service-gui/tests/test-ContextPool.cpp +53 -0
@@ 0,0 1,53 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include "ContextPool.hpp"
#include "MockedSynchronizationMechanism.hpp"

#include <chrono>
#include <thread>

using namespace service::gui;

TEST_CASE("ContextPoolTests")
{
    const ::gui::Size displaySize{1, 1};
    constexpr auto ContextPoolCapacity = 1;
    constexpr auto ContextPoolFirstId  = 0;
    ContextPool contextPool{displaySize, ContextPoolCapacity, std::make_unique<MockedSynchronizationMechanism>()};
    REQUIRE(!contextPool.isAnyContextLocked());

    SECTION("Borrow context when all are used")
    {
        const auto [contextId, context] = contextPool.borrowContext();
        REQUIRE(contextId == ContextPoolFirstId);
        REQUIRE(contextPool.isAnyContextLocked());

        std::thread thr{[&contextPool, id = contextId]() {
            std::this_thread::sleep_for(std::chrono::milliseconds{500});
            contextPool.returnContext(id);
        }};
        const auto [nextContextId, nextContext] = contextPool.borrowContext();
        REQUIRE(contextId == nextContextId);

        if (thr.joinable()) {
            thr.join();
        }
        contextPool.returnContext(nextContextId);
        REQUIRE(!contextPool.isAnyContextLocked());
    }

    SECTION("Return unused context")
    {
        REQUIRE(!contextPool.isAnyContextLocked());
        REQUIRE_NOTHROW(contextPool.returnContext(0));
    }

    SECTION("Return invalid context id")
    {
        REQUIRE(!contextPool.isAnyContextLocked());
        REQUIRE_NOTHROW(contextPool.returnContext(10));
    }
}

A module-services/service-gui/tests/test-DrawCommandsQueue.cpp => module-services/service-gui/tests/test-DrawCommandsQueue.cpp +73 -0
@@ 0,0 1,73 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include "DrawCommandsQueue.hpp"
#include "MockedSynchronizationMechanism.hpp"

#include <chrono>
#include <thread>

using namespace service::gui;

TEST_CASE("DrawCommandsQueueTests")
{
    constexpr auto ExpectedQueueSize = 4U;
    DrawCommandsQueue queue{ExpectedQueueSize, std::make_unique<MockedSynchronizationMechanism>()};

    SECTION("Enqueue")
    {
        queue.enqueue(DrawCommandsQueue::QueueItem{});
        REQUIRE(queue.size() == 1U);
    }

    SECTION("Single-thread dequeue")
    {
        queue.enqueue(DrawCommandsQueue::QueueItem{});
        REQUIRE(queue.size() == 1U);

        const auto item = queue.dequeue();
        REQUIRE(item.commands.empty());
        REQUIRE(item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_FAST);
        REQUIRE(queue.size() == 0);
    }

    SECTION("Multi-thread dequeue")
    {
        std::thread thr{[&queue]() {
            std::this_thread::sleep_for(std::chrono::milliseconds{100});
            queue.enqueue(DrawCommandsQueue::QueueItem{});
        }};

        const auto item = queue.dequeue();
        if (thr.joinable()) {
            thr.join();
        }

        REQUIRE(item.commands.empty());
        REQUIRE(item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_FAST);
        REQUIRE(queue.size() == 0U);
    }

    SECTION("Clear")
    {
        queue.enqueue(DrawCommandsQueue::QueueItem{});
        queue.enqueue(DrawCommandsQueue::QueueItem{});
        REQUIRE(queue.size() == 2U);

        queue.clear();
        REQUIRE(queue.size() == 0);
    }

    SECTION("Clear and get max refresh mode")
    {
        queue.enqueue(DrawCommandsQueue::QueueItem{});
        queue.enqueue(DrawCommandsQueue::QueueItem{{}, ::gui::RefreshModes::GUI_REFRESH_DEEP});
        REQUIRE(queue.size() == 2U);

        const auto maxRefreshMode = queue.getMaxRefreshModeAndClear();
        REQUIRE(queue.size() == 0);
        REQUIRE(maxRefreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP);
    }
}

A module-services/service-gui/tests/tests-main.cpp => module-services/service-gui/tests/tests-main.cpp +5 -0
@@ 0,0 1,5 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include <catch2/catch.hpp>