mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-04-18 22:18:38 -04:00
* Adding document describing how to use logger * Adjusting logs to follow a new guide * Change order in log header: line number is now before function name
273 lines
10 KiB
C++
273 lines
10 KiB
C++
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
|
|
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
|
|
|
|
#include "ServiceGUI.hpp"
|
|
#include "WorkerGUI.hpp"
|
|
|
|
#include "messages/DrawMessage.hpp"
|
|
#include "messages/ChangeColorScheme.hpp"
|
|
|
|
#include <DrawCommand.hpp>
|
|
#include <FontManager.hpp>
|
|
#include <gui/core/ImageManager.hpp>
|
|
#include <log/log.hpp>
|
|
#include <service-eink/ServiceEinkName.hpp>
|
|
#include <service-eink/messages/ImageMessage.hpp>
|
|
#include <service-eink/messages/EinkMessage.hpp>
|
|
#include <service-eink/messages/PrepareDisplayEarlyRequest.hpp>
|
|
#include <Timers/TimerFactory.hpp>
|
|
#include <SystemManager/SystemManagerCommon.hpp>
|
|
|
|
#include <gsl/util>
|
|
#include <purefs/filesystem_paths.hpp>
|
|
|
|
namespace service::gui
|
|
{
|
|
namespace
|
|
{
|
|
constexpr auto ServiceGuiStackDepth = 1536U;
|
|
constexpr auto ContextsCount = 2;
|
|
constexpr auto CommandsQueueCapacity = 3;
|
|
constexpr std::chrono::milliseconds BSPEinkBusyTimeout{3000}; ///< sync with \ref BSP_EinkBusyTimeout
|
|
constexpr std::chrono::milliseconds RTOSMessageRoundtripTimeout{1000};
|
|
constexpr std::chrono::milliseconds ContextReleaseTimeout{BSPEinkBusyTimeout + RTOSMessageRoundtripTimeout};
|
|
|
|
::gui::RefreshModes getMaxRefreshMode(::gui::RefreshModes lhs, ::gui::RefreshModes rhs) noexcept
|
|
{
|
|
if (lhs == ::gui::RefreshModes::GUI_REFRESH_DEEP) {
|
|
return lhs;
|
|
}
|
|
return rhs;
|
|
}
|
|
} // namespace
|
|
|
|
ServiceGUI::ServiceGUI(::gui::Size displaySize, const std::string &name, std::string parent)
|
|
: sys::Service(name, parent, ServiceGuiStackDepth), displaySize{displaySize},
|
|
commandsQueue{std::make_unique<DrawCommandsQueue>(CommandsQueueCapacity)}
|
|
{
|
|
initAssetManagers();
|
|
registerMessageHandlers();
|
|
}
|
|
|
|
ServiceGUI::~ServiceGUI() noexcept = default;
|
|
|
|
void ServiceGUI::initAssetManagers()
|
|
{
|
|
::gui::FontManager::getInstance().init(purefs::dir::getAssetsDirPath());
|
|
::gui::ImageManager::getInstance().init(purefs::dir::getAssetsDirPath());
|
|
}
|
|
|
|
void ServiceGUI::registerMessageHandlers()
|
|
{
|
|
connect(typeid(DrawMessage),
|
|
[this](sys::Message *request) -> sys::MessagePointer { return handleDrawMessage(request); });
|
|
|
|
connect(typeid(RenderingFinished),
|
|
[this](sys::Message *request) -> sys::MessagePointer { return handleGUIRenderingFinished(request); });
|
|
|
|
connect(typeid(ChangeColorScheme),
|
|
[this](sys::Message *request) -> sys::MessagePointer { return handleChangeColorScheme(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 sys::MessageNone{};
|
|
}
|
|
|
|
sys::ReturnCodes ServiceGUI::InitHandler()
|
|
{
|
|
contextPool = std::make_unique<ContextPool>(displaySize, ContextsCount);
|
|
|
|
std::list<sys::WorkerQueueInfo> queueInfo{
|
|
{WorkerGUI::SignallingQueueName, WorkerGUI::SignalSize, WorkerGUI::SignallingQueueCapacity}};
|
|
worker = std::make_unique<WorkerGUI>(this);
|
|
worker->init(queueInfo);
|
|
worker->run();
|
|
stateManager.setState(ServiceGUIState::Running);
|
|
|
|
LOG_INFO("Initialized");
|
|
return sys::ReturnCodes::Success;
|
|
}
|
|
|
|
sys::ReturnCodes ServiceGUI::DeinitHandler()
|
|
{
|
|
worker->close();
|
|
|
|
LOG_INFO("Deinitialized");
|
|
return sys::ReturnCodes::Success;
|
|
}
|
|
|
|
void ServiceGUI::ProcessCloseReason(sys::CloseReason closeReason)
|
|
{
|
|
if (stateManager.isInState(RenderingState::Idle) and stateManager.isInState(DisplayingState::Idle)) {
|
|
sendCloseReadyMessage(this);
|
|
stateManager.setState(ServiceGUIState::Closed);
|
|
return;
|
|
}
|
|
else {
|
|
stateManager.setState(ServiceGUIState::Closing);
|
|
}
|
|
}
|
|
|
|
void ServiceGUI::ProcessCloseReasonHandler(sys::CloseReason closeReason)
|
|
{
|
|
ProcessCloseReason(closeReason);
|
|
}
|
|
|
|
sys::ReturnCodes ServiceGUI::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
|
|
{
|
|
return sys::ReturnCodes::Success;
|
|
}
|
|
|
|
sys::MessagePointer ServiceGUI::handleDrawMessage(sys::Message *message)
|
|
{
|
|
if (const auto drawMsg = static_cast<DrawMessage *>(message); !drawMsg->commands.empty()) {
|
|
if (!isAnyFrameBeingRenderedOrDisplayed()) {
|
|
prepareDisplayEarly(drawMsg->mode);
|
|
}
|
|
notifyRenderer(std::move(drawMsg->commands), drawMsg->mode);
|
|
}
|
|
return std::make_shared<sys::ResponseMessage>();
|
|
}
|
|
|
|
sys::MessagePointer ServiceGUI::handleChangeColorScheme(sys::Message *message)
|
|
{
|
|
const auto msg = static_cast<ChangeColorScheme *>(message);
|
|
notifyRenderColorSchemeChange(msg->getColorScheme());
|
|
return sys::MessageNone{};
|
|
}
|
|
|
|
void ServiceGUI::prepareDisplayEarly(::gui::RefreshModes refreshMode)
|
|
{
|
|
auto msg = std::make_shared<service::eink::PrepareDisplayEarlyRequest>(refreshMode);
|
|
bus.sendUnicast(msg, service::name::eink);
|
|
}
|
|
|
|
void ServiceGUI::notifyRenderer(std::list<std::unique_ptr<::gui::DrawCommand>> &&commands,
|
|
::gui::RefreshModes refreshMode)
|
|
{
|
|
stateManager.setState(RenderingState::Rendering);
|
|
enqueueDrawCommands(DrawCommandsQueue::QueueItem{std::move(commands), refreshMode});
|
|
worker->notify(WorkerGUI::Signal::Render);
|
|
}
|
|
|
|
void ServiceGUI::notifyRenderColorSchemeChange(::gui::ColorScheme &&scheme)
|
|
{
|
|
colorSchemeUpdate = std::make_unique<::gui::ColorScheme>(scheme);
|
|
worker->notify(WorkerGUI::Signal::ChangeColorScheme);
|
|
}
|
|
|
|
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;
|
|
}
|
|
commandsQueue->enqueue(std::move(item)); // 3 consecutive deep refreshes after leaving messages? :/
|
|
}
|
|
|
|
sys::MessagePointer ServiceGUI::handleGUIRenderingFinished(sys::Message *message)
|
|
{
|
|
stateManager.setState(RenderingState::Idle);
|
|
auto finishedMsg = static_cast<service::gui::RenderingFinished *>(message);
|
|
const auto contextId = finishedMsg->getContextId();
|
|
auto refreshMode = finishedMsg->getRefreshMode();
|
|
|
|
if (stateManager.isInState(DisplayingState::Idle)) {
|
|
if (cache.isRenderCached()) {
|
|
refreshMode = getMaxRefreshMode(cache.getCachedRender()->refreshMode, refreshMode);
|
|
cache.invalidate();
|
|
}
|
|
const auto context = contextPool->peekContext(contextId);
|
|
#if DEBUG_EINK_REFRESH == 1
|
|
LOG_INFO("Rendering finished, send, contextId: %d, mode: %d", contextId, (int)refreshMode);
|
|
#endif
|
|
sendOnDisplay(context, contextId, refreshMode);
|
|
}
|
|
else {
|
|
cache.cache({contextId, refreshMode});
|
|
contextPool->returnContext(contextId);
|
|
#if DEBUG_EINK_REFRESH == 1
|
|
LOG_INFO("Rendering finished, cancel, contextId: %d, mode: %d", contextId, (int)refreshMode);
|
|
#endif
|
|
sendCancelRefresh();
|
|
}
|
|
return sys::MessageNone{};
|
|
}
|
|
|
|
void ServiceGUI::sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode)
|
|
{
|
|
stateManager.setState(DisplayingState::Displaying);
|
|
auto msg = std::make_shared<service::eink::ImageMessage>(contextId, context, refreshMode);
|
|
bus.sendUnicast(std::move(msg), service::name::eink);
|
|
scheduleContextRelease(contextId);
|
|
}
|
|
|
|
void ServiceGUI::sendCancelRefresh()
|
|
{
|
|
auto msg = std::make_shared<service::eink::CancelRefreshMessage>();
|
|
bus.sendUnicast(std::move(msg), service::name::eink);
|
|
}
|
|
|
|
void ServiceGUI::scheduleContextRelease(int contextId)
|
|
{
|
|
// Whenever the response from ServiceEink doesn't come, the context has to be released automatically after a
|
|
// timeout.
|
|
contextReleaseTimer = sys::TimerFactory::createSingleShotTimer(
|
|
this, "contextRelease", ContextReleaseTimeout, [this, contextId](sys::Timer &it) {
|
|
eink::ImageDisplayedNotification notification{contextId};
|
|
handleImageDisplayedNotification(¬ification);
|
|
LOG_WARN("Context #%d released after timeout. Does ServiceEink respond properly?", contextId);
|
|
});
|
|
contextReleaseTimer.start();
|
|
}
|
|
|
|
sys::MessagePointer ServiceGUI::handleImageDisplayedNotification(sys::Message *message)
|
|
{
|
|
stateManager.setState(DisplayingState::Idle);
|
|
const auto msg = static_cast<eink::ImageDisplayedNotification *>(message);
|
|
const auto contextId = msg->getContextId();
|
|
contextPool->returnContext(contextId);
|
|
contextReleaseTimer.stop();
|
|
|
|
// Even if the next render is already cached, if any context in the pool is currently being processed, then
|
|
// we better wait for it.
|
|
if (isNextFrameReady() and not isAnyFrameBeingRenderedOrDisplayed()) {
|
|
trySendNextFrame();
|
|
}
|
|
else if (stateManager.isInState(ServiceGUIState::Closing)) {
|
|
sendCloseReadyMessage(this);
|
|
stateManager.setState(ServiceGUIState::Closed);
|
|
}
|
|
return sys::MessageNone{};
|
|
}
|
|
|
|
bool ServiceGUI::isNextFrameReady() const noexcept
|
|
{
|
|
return cache.isRenderCached();
|
|
}
|
|
|
|
bool ServiceGUI::isAnyFrameBeingRenderedOrDisplayed() const noexcept
|
|
{
|
|
return contextPool->isAnyContextLocked();
|
|
}
|
|
|
|
void ServiceGUI::trySendNextFrame()
|
|
{
|
|
const auto contextId = cache.getCachedRender()->contextId;
|
|
if (const auto context = contextPool->borrowContext(contextId); context != nullptr) {
|
|
sendOnDisplay(context, contextId, cache.getCachedRender()->refreshMode);
|
|
}
|
|
cache.invalidate();
|
|
}
|
|
} // namespace service::gui
|