File indexing completed on 2024-07-14 14:25:32

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "TirexBackend.h"
0008 
0009 #include <QDir>
0010 #include <QScopedValueRollback>
0011 #include <QSettings>
0012 #include <QSocketNotifier>
0013 
0014 #include <cstdlib>
0015 #include <cstring>
0016 #include <unistd.h>
0017 
0018 TirexBackend::TirexBackend(QObject *parent)
0019     : QObject(parent)
0020 {
0021     // setup command socket
0022     const auto socketFd = getenv("TIREX_BACKEND_SOCKET_FILENO");
0023     if (socketFd) {
0024         m_commandSocketFd = std::atoi(socketFd);
0025         m_socketNotifier = new QSocketNotifier(m_commandSocketFd, QSocketNotifier::Read, this);
0026         connect(m_socketNotifier, &QSocketNotifier::activated, this, &TirexBackend::commandReadyRead);
0027     } else {
0028         qFatal("TIREX_BACKEND_SOCKET_FILENO not set!");
0029     }
0030 
0031     // setup heartbeat pipe and timer
0032     const auto pipeFd = getenv("TIREX_BACKEND_PIPE_FILENO");
0033     const auto aliveTimeout = getenv("TIREX_BACKEND_ALIVE_TIMEOUT");
0034     if (pipeFd && aliveTimeout) {
0035         m_heartbeatFd = std::atoi(pipeFd);
0036         m_heartbeatTimer.setInterval(std::chrono::seconds(std::atoi(aliveTimeout)));
0037         m_heartbeatTimer.setSingleShot(false);
0038         connect(&m_heartbeatTimer, &QTimer::timeout, this, [this]() {
0039             write(m_heartbeatFd, "alive", 5);
0040         });
0041         m_heartbeatTimer.start();
0042     } else {
0043         qWarning() << "not using heartbeat timer";
0044     }
0045 
0046     // read map configuration
0047     m_tileDir = configValue(QStringLiteral("tiledir")).toString();
0048     if (m_tileDir.isEmpty()) {
0049         m_tileDir = QStringLiteral("output/");
0050     }
0051 }
0052 
0053 TirexBackend::~TirexBackend() = default;
0054 
0055 void TirexBackend::commandReadyRead()
0056 {
0057     if (m_recursionLock) {
0058         return;
0059     }
0060     QScopedValueRollback<bool> lock(m_recursionLock, true);
0061 
0062     while (true) {
0063         m_renderTime.restart();
0064         TirexMetatileRequest req;
0065         req.addrSize = sizeof(req.addr);
0066         bzero(&req.addr, req.addrSize);
0067         QByteArray data(0xffff, 0);
0068         auto n = recvfrom(m_commandSocketFd, data.data(), 0xffff, MSG_DONTWAIT, reinterpret_cast<sockaddr*>(&req.addr), &req.addrSize);
0069         if (n <= 0) {
0070             break;
0071         }
0072         data.resize(n);
0073 
0074         int nextIdx = 0;
0075         const char *type = nullptr;
0076         int typeLen = 0;
0077 
0078         while (nextIdx < data.size() && nextIdx >= 0) {
0079             const auto endIdx = data.indexOf('\n', nextIdx);
0080             if (endIdx < 0) {
0081                 break;
0082             }
0083             const auto midIdx = data.indexOf('=', nextIdx);
0084             if (midIdx < 0 || midIdx >= endIdx) {
0085                 break;
0086             }
0087 
0088             const auto key = data.constData() + nextIdx;
0089             const auto keyLen = midIdx - nextIdx;
0090             const auto value = data.constData() + midIdx + 1;
0091             const auto valueLen = endIdx - midIdx - 1;
0092 
0093             if (keyLen == 4 && std::strncmp(key, "type", 4) == 0) {
0094                 type = value;
0095                 typeLen = valueLen;
0096             } else if (keyLen == 2 && std::strncmp(key, "id", 2) == 0) {
0097                 req.id = QByteArray(value, valueLen);
0098             } else if (keyLen == 1 && std::strncmp(key, "x", 1) == 0) {
0099                 req.tile.x = std::atoi(value);
0100             } else if (keyLen == 1 && std::strncmp(key, "y", 1) == 0) {
0101                 req.tile.y = std::atoi(value);
0102             } else if (keyLen == 1 && std::strncmp(key, "z", 1) == 0) {
0103                 req.tile.z = std::atoi(value);
0104             } else if (keyLen == 3 && std::strncmp(key, "map", 3) == 0) {
0105                 req.map = QByteArray(value, valueLen);
0106             }
0107 
0108             nextIdx = endIdx + 1;
0109         }
0110 
0111         if (std::strncmp(type, "metatile_render_request\n", 24) != 0) {
0112             QByteArray errorMsg;
0113             errorMsg += "id=" + req.id + "\n";
0114             errorMsg += "type=" + QByteArray(type, typeLen) + "\n";
0115             errorMsg += "result=error\nerrormsg=unsupported requested\n";
0116             sendto(m_commandSocketFd, errorMsg.constData(), errorMsg.size(), 0, reinterpret_cast<const sockaddr*>(&req.addr), req.addrSize);
0117             continue;
0118         }
0119 
0120         emit tileRequested(req);
0121     }
0122 }
0123 
0124 void TirexBackend::tileDone(const TirexMetatileRequest &req)
0125 {
0126     QByteArray msg = "id=" + req.id
0127         + "\ntype=metatile_render_request\nresult=ok\nx=" + QByteArray::number(req.tile.x)
0128         + "\ny=" + QByteArray::number(req.tile.y)
0129         + "\nz=" + QByteArray::number(req.tile.z)
0130         + "\nmetatile=" + metatileFileName(req).toUtf8()
0131         + "\nrender_time=" + QByteArray::number(m_renderTime.elapsed()) + "\n";
0132     sendto(m_commandSocketFd, msg.constData(), msg.size(), 0, reinterpret_cast<const sockaddr*>(&req.addr), req.addrSize);
0133 }
0134 
0135 void TirexBackend::tileError(const TirexMetatileRequest &req, const QString &errMsg)
0136 {
0137     QByteArray msg = "id=" + req.id + "\ntype=metatile_render_request\nresult=error\nerrmsg=" + errMsg.toUtf8() + "\n";
0138     sendto(m_commandSocketFd, msg.constData(), msg.size(), 0, reinterpret_cast<const sockaddr*>(&req.addr), req.addrSize);
0139 }
0140 
0141 QVariant TirexBackend::configValue(const QString &key) const
0142 {
0143     const auto configFiles = getenv("TIREX_BACKEND_MAP_CONFIGS");
0144     if (configFiles) {
0145         QSettings settings(QString::fromUtf8(configFiles), QSettings::IniFormat);
0146         return settings.value(key);
0147     }
0148     return {};
0149 }
0150 
0151 QString TirexBackend::metatileFileName(const TirexMetatileRequest &req)
0152 {
0153     auto x = req.tile.x;
0154     auto y = req.tile.y;
0155     uint8_t hash[5];
0156     for (auto i = 0; i < 5; i++) {
0157         hash[i] = ((x & 0x0f) << 4) | (y & 0x0f);
0158         x >>= 4;
0159         y >>= 4;
0160     }
0161 
0162     QString path = m_tileDir + QLatin1Char('/') +
0163         QString::number(req.tile.z) + QLatin1Char('/') +
0164         QString::number(hash[4]) + QLatin1Char('/') +
0165         QString::number(hash[3]) + QLatin1Char('/') +
0166         QString::number(hash[2]) + QLatin1Char('/') +
0167         QString::number(hash[1]) + QLatin1Char('/');
0168     QDir().mkpath(path);
0169     path += QString::number(hash[0]) + QLatin1String(".meta");
0170     return path;
0171 }
0172 
0173 int TirexBackend::metatileColumns() const
0174 {
0175     return m_metatileCols;
0176 }
0177 
0178 int TirexBackend::metatileRows() const
0179 {
0180     return m_metatileRows;
0181 }
0182 
0183 struct TirexMetatileHeader {
0184     char magic[4];
0185     int count;
0186     int x;
0187     int y;
0188     int z;
0189 };
0190 
0191 struct TirexMetatileEntry {
0192     int offset;
0193     int size;
0194 };
0195 
0196 void TirexBackend::writeMetatileHeader(QIODevice *io, const TirexMetatile &tile) const
0197 {
0198     TirexMetatileHeader header;
0199     header.magic[0] = 'M';
0200     header.magic[1] = 'E';
0201     header.magic[2] = 'T';
0202     header.magic[3] = 'A';
0203     header.count = m_metatileRows * m_metatileCols;
0204     header.x = tile.x;
0205     header.y = tile.y;
0206     header.z = tile.z;
0207     io->write(reinterpret_cast<const char*>(&header), sizeof(header));
0208 
0209     TirexMetatileEntry entry;
0210     entry.offset = 0;
0211     entry.size = 0;
0212     for (int i = 0; i < header.count; ++i) {
0213         io->write(reinterpret_cast<const char*>(&entry), sizeof(entry));
0214     }
0215 }
0216 
0217 void TirexBackend::writeMetatileEntry(QIODevice *io, int entryIdx, int offset, int size) const
0218 {
0219     const auto seekPos = io->pos();
0220 
0221     io->seek(sizeof(TirexMetatileHeader) + entryIdx * sizeof(TirexMetatileEntry));
0222     TirexMetatileEntry entry;
0223     entry.offset = offset;
0224     entry.size = size;
0225     io->write(reinterpret_cast<const char*>(&entry), sizeof(TirexMetatileEntry));
0226 
0227     io->seek(seekPos);
0228 }
0229 
0230 #include "moc_TirexBackend.cpp"