mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
boblight: reduce cpu time spent on memcopy and parsing rgb values (#1016)
This commit is contained in:
parent
aa465c018c
commit
c09061e5d8
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QStringRef>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
namespace QStringUtils {
|
namespace QStringUtils {
|
||||||
|
|
||||||
@ -13,11 +15,11 @@ enum class SplitBehavior {
|
|||||||
|
|
||||||
inline QStringList split (const QString &string, const QString &sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive)
|
inline QStringList split (const QString &string, const QString &sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive)
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
return behavior == SplitBehavior::SkipEmptyParts ? string.split(sep, Qt::SkipEmptyParts , cs) : string.split(sep, Qt::KeepEmptyParts , cs);
|
return behavior == SplitBehavior::SkipEmptyParts ? string.split(sep, Qt::SkipEmptyParts , cs) : string.split(sep, Qt::KeepEmptyParts , cs);
|
||||||
#else
|
#else
|
||||||
return behavior == SplitBehavior::SkipEmptyParts ? string.split(sep, QString::SkipEmptyParts , cs) : string.split(sep, QString::KeepEmptyParts , cs);
|
return behavior == SplitBehavior::SkipEmptyParts ? string.split(sep, QString::SkipEmptyParts , cs) : string.split(sep, QString::KeepEmptyParts , cs);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QStringList split (const QString &string, QChar sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive)
|
inline QStringList split (const QString &string, QChar sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive)
|
||||||
@ -37,6 +39,34 @@ inline QStringList split (const QString &string, const QRegExp &rx, SplitBehavio
|
|||||||
return behavior == SplitBehavior::SkipEmptyParts ? string.split(rx, QString::SkipEmptyParts) : string.split(rx, QString::KeepEmptyParts);
|
return behavior == SplitBehavior::SkipEmptyParts ? string.split(rx, QString::SkipEmptyParts) : string.split(rx, QString::KeepEmptyParts);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline QVector<QStringRef> splitRef(const QString &string, const QString &sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive)
|
||||||
|
{
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
|
return string.splitRef(sep, behavior == SplitBehavior::SkipEmptyParts ? Qt::SkipEmptyParts : Qt::KeepEmptyParts , cs);
|
||||||
|
#else
|
||||||
|
return string.splitRef(sep, behavior == SplitBehavior::SkipEmptyParts ? QString::SkipEmptyParts : QString::KeepEmptyParts, cs);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QVector<QStringRef> splitRef(const QString &string, QChar sep, SplitBehavior behavior = SplitBehavior::KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive)
|
||||||
|
{
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
|
return string.splitRef(sep, behavior == SplitBehavior::SkipEmptyParts ? Qt::SkipEmptyParts : Qt::KeepEmptyParts, cs);
|
||||||
|
#else
|
||||||
|
return string.splitRef(sep, behavior == SplitBehavior::SkipEmptyParts ? QString::SkipEmptyParts : QString::KeepEmptyParts, cs);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QVector<QStringRef> splitRef(const QString &string, const QRegExp &rx, SplitBehavior behavior = SplitBehavior::KeepEmptyParts)
|
||||||
|
{
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
|
return string.splitRef(rx,behavior == SplitBehavior::SkipEmptyParts ? Qt::SkipEmptyParts : Qt::KeepEmptyParts);
|
||||||
|
#else
|
||||||
|
return string.splitRef(rx, behavior == SplitBehavior::SkipEmptyParts ? QString::SkipEmptyParts : QString::KeepEmptyParts);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // QSTRINGUTILS_H
|
#endif // QSTRINGUTILS_H
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
// stl includes
|
// stl includes
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
// Qt includes
|
// Qt includes
|
||||||
#include <QResource>
|
#include <QResource>
|
||||||
@ -54,18 +56,19 @@ BoblightClientConnection::~BoblightClientConnection()
|
|||||||
|
|
||||||
void BoblightClientConnection::readData()
|
void BoblightClientConnection::readData()
|
||||||
{
|
{
|
||||||
_receiveBuffer += _socket->readAll();
|
_receiveBuffer.append(_socket->readAll());
|
||||||
|
|
||||||
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||||
while(bytes > 0)
|
while(bytes > 0)
|
||||||
{
|
{
|
||||||
// create message string (strip the newline)
|
// create message string (strip the newline)
|
||||||
QString message = QString::fromLatin1(_receiveBuffer.data(), bytes-1);
|
const QString message = readMessage(_receiveBuffer.data(), bytes);
|
||||||
// remove message data from buffer
|
|
||||||
_receiveBuffer = _receiveBuffer.mid(bytes);
|
|
||||||
|
|
||||||
// handle trimmed message
|
// handle trimmed message
|
||||||
handleMessage(message.trimmed());
|
handleMessage(message);
|
||||||
|
|
||||||
|
// remove message data from buffer
|
||||||
|
_receiveBuffer.remove(0, bytes);
|
||||||
|
|
||||||
// drop messages if the buffer is too full
|
// drop messages if the buffer is too full
|
||||||
if (_receiveBuffer.size() > 100*1024)
|
if (_receiveBuffer.size() > 100*1024)
|
||||||
@ -79,6 +82,31 @@ void BoblightClientConnection::readData()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString BoblightClientConnection::readMessage(const char *data, const size_t size) const
|
||||||
|
{
|
||||||
|
char *end = (char *)data + size - 1;
|
||||||
|
|
||||||
|
// Trim left
|
||||||
|
while (data < end && std::isspace(*data))
|
||||||
|
{
|
||||||
|
++data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim right
|
||||||
|
while (end > data && std::isspace(*end))
|
||||||
|
{
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create message string (strip the newline)
|
||||||
|
const int len = end - data + 1;
|
||||||
|
const QString message = QString::fromLatin1(data, len);
|
||||||
|
|
||||||
|
//std::cout << bytes << ": \"" << message.toUtf8().constData() << "\"" << std::endl;
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
void BoblightClientConnection::socketClosed()
|
void BoblightClientConnection::socketClosed()
|
||||||
{
|
{
|
||||||
// clear the current channel
|
// clear the current channel
|
||||||
@ -88,10 +116,11 @@ void BoblightClientConnection::socketClosed()
|
|||||||
emit connectionClosed(this);
|
emit connectionClosed(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void BoblightClientConnection::handleMessage(const QString & message)
|
void BoblightClientConnection::handleMessage(const QString & message)
|
||||||
{
|
{
|
||||||
//std::cout << "boblight message: " << message.toStdString() << std::endl;
|
//std::cout << "boblight message: " << message.toStdString() << std::endl;
|
||||||
QStringList messageParts = QStringUtils::split(message," ",QStringUtils::SplitBehavior::SkipEmptyParts);
|
const QVector<QStringRef> messageParts = QStringUtils::splitRef(message, ' ', QStringUtils::SplitBehavior::SkipEmptyParts);
|
||||||
if (messageParts.size() > 0)
|
if (messageParts.size() > 0)
|
||||||
{
|
{
|
||||||
if (messageParts[0] == "hello")
|
if (messageParts[0] == "hello")
|
||||||
@ -122,32 +151,18 @@ void BoblightClientConnection::handleMessage(const QString & message)
|
|||||||
if (messageParts.size() > 3 && messageParts[1] == "light")
|
if (messageParts.size() > 3 && messageParts[1] == "light")
|
||||||
{
|
{
|
||||||
bool rc;
|
bool rc;
|
||||||
unsigned ledIndex = messageParts[2].toUInt(&rc);
|
const unsigned ledIndex = parseUInt(messageParts[2], &rc);
|
||||||
if (rc && ledIndex < _ledColors.size())
|
if (rc && ledIndex < _ledColors.size())
|
||||||
{
|
{
|
||||||
if (messageParts[3] == "rgb" && messageParts.size() == 7)
|
if (messageParts[3] == "rgb" && messageParts.size() == 7)
|
||||||
{
|
{
|
||||||
// replace decimal comma with decimal point
|
// custom parseByte accepts both ',' and '.' as decimal separator
|
||||||
messageParts[4].replace(',', '.');
|
// no need to replace decimal comma with decimal point
|
||||||
messageParts[5].replace(',', '.');
|
|
||||||
messageParts[6].replace(',', '.');
|
|
||||||
|
|
||||||
bool rc1, rc2, rc3;
|
bool rc1, rc2, rc3;
|
||||||
uint8_t red = qMax(0, qMin(255, int(255 * messageParts[4].toFloat(&rc1))));
|
const uint8_t red = parseByte(messageParts[4], &rc1);
|
||||||
|
const uint8_t green = parseByte(messageParts[5], &rc2);
|
||||||
// check for correct locale should not be needed anymore - please check!
|
const uint8_t blue = parseByte(messageParts[6], &rc3);
|
||||||
if (!rc1)
|
|
||||||
{
|
|
||||||
// maybe a locale issue. switch to a locale with a comma instead of a dot as decimal seperator (or vice versa)
|
|
||||||
_locale = QLocale((_locale.decimalPoint() == QChar('.')) ? QLocale::Dutch : QLocale::C);
|
|
||||||
_locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
|
|
||||||
|
|
||||||
// try again
|
|
||||||
red = qMax(0, qMin(255, int(255 * messageParts[4].toFloat(&rc1))));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t green = qMax(0, qMin(255, int(255 * messageParts[5].toFloat(&rc2))));
|
|
||||||
uint8_t blue = qMax(0, qMin(255, int(255 * messageParts[6].toFloat(&rc3))));
|
|
||||||
|
|
||||||
if (rc1 && rc2 && rc3)
|
if (rc1 && rc2 && rc3)
|
||||||
{
|
{
|
||||||
@ -181,7 +196,7 @@ void BoblightClientConnection::handleMessage(const QString & message)
|
|||||||
else if (messageParts.size() == 3 && messageParts[1] == "priority")
|
else if (messageParts.size() == 3 && messageParts[1] == "priority")
|
||||||
{
|
{
|
||||||
bool rc;
|
bool rc;
|
||||||
int prio = messageParts[2].toInt(&rc);
|
const int prio = static_cast<int>(parseUInt(messageParts[2], &rc));
|
||||||
if (rc && prio != _priority)
|
if (rc && prio != _priority)
|
||||||
{
|
{
|
||||||
if (_priority != 0 && _hyperion->getPriorityInfo(_priority).componentId == hyperion::COMP_BOBLIGHTSERVER)
|
if (_priority != 0 && _hyperion->getPriorityInfo(_priority).componentId == hyperion::COMP_BOBLIGHTSERVER)
|
||||||
@ -223,6 +238,146 @@ void BoblightClientConnection::handleMessage(const QString & message)
|
|||||||
Debug(_log, "unknown boblight message: %s", QSTRING_CSTR(message));
|
Debug(_log, "unknown boblight message: %s", QSTRING_CSTR(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Float values 10 to the power of -p for p in 0 .. 8.
|
||||||
|
const float ipows[] = {
|
||||||
|
1,
|
||||||
|
1.0f / 10.0f,
|
||||||
|
1.0f / 100.0f,
|
||||||
|
1.0f / 1000.0f,
|
||||||
|
1.0f / 10000.0f,
|
||||||
|
1.0f / 100000.0f,
|
||||||
|
1.0f / 1000000.0f,
|
||||||
|
1.0f / 10000000.0f,
|
||||||
|
1.0f / 100000000.0f};
|
||||||
|
|
||||||
|
float BoblightClientConnection::parseFloat(const QStringRef& s, bool *ok) const
|
||||||
|
{
|
||||||
|
// We parse radix 10
|
||||||
|
const char MIN_DIGIT = '0';
|
||||||
|
const char MAX_DIGIT = '9';
|
||||||
|
const char SEP_POINT = '.';
|
||||||
|
const char SEP_COMMA = ',';
|
||||||
|
const int NUM_POWS = 9;
|
||||||
|
|
||||||
|
/// The maximum number of characters we want to process
|
||||||
|
const int MAX_LEN = 18; // Chosen randomly
|
||||||
|
|
||||||
|
/// The index of the current character
|
||||||
|
int q = 0;
|
||||||
|
|
||||||
|
/// The integer part of the number
|
||||||
|
int64_t n = 0;
|
||||||
|
|
||||||
|
auto it = s.begin();
|
||||||
|
|
||||||
|
#define STEP ((it != s.end()) && (q++ < MAX_LEN))
|
||||||
|
|
||||||
|
// parse the integer-part
|
||||||
|
while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && STEP)
|
||||||
|
{
|
||||||
|
n = (n * 10) + (it->unicode() - MIN_DIGIT);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The resulting float value
|
||||||
|
float f = static_cast<float>(n);
|
||||||
|
|
||||||
|
// parse decimal part
|
||||||
|
if ((it->unicode() == SEP_POINT || it->unicode() == SEP_COMMA) && STEP)
|
||||||
|
{
|
||||||
|
/// The decimal part of the number
|
||||||
|
int64_t d = 0;
|
||||||
|
|
||||||
|
/// The exponent for the scale-factor 10 to the power -e
|
||||||
|
int e = 0;
|
||||||
|
|
||||||
|
++it;
|
||||||
|
while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && STEP)
|
||||||
|
{
|
||||||
|
d = (d * 10) + (it->unicode() - MIN_DIGIT);
|
||||||
|
++e;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float h = static_cast<float>(d);
|
||||||
|
|
||||||
|
// We want to use pre-calculated power whenever possible
|
||||||
|
if (e < NUM_POWS)
|
||||||
|
{
|
||||||
|
f += h * ipows[e];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f += h / std::pow(10.0f, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q >= MAX_LEN || q < s.length())
|
||||||
|
{
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
//std::cout << "FAIL L " << q << ": " << s.toUtf8().constData() << std::endl;
|
||||||
|
*ok = false;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
//std::cout << "OK " << d << ": " << s.toUtf8().constData() << std::endl;
|
||||||
|
*ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned BoblightClientConnection::parseUInt(const QStringRef& s, bool *ok) const
|
||||||
|
{
|
||||||
|
// We parse radix 10
|
||||||
|
const char MIN_DIGIT = '0';
|
||||||
|
const char MAX_DIGIT = '9';
|
||||||
|
|
||||||
|
/// The maximum number of characters we want to process
|
||||||
|
const int MAX_LEN = 10;
|
||||||
|
|
||||||
|
/// The index of the current character
|
||||||
|
int q = 0;
|
||||||
|
|
||||||
|
/// The integer part of the number
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
auto it = s.begin();
|
||||||
|
|
||||||
|
// parse the integer-part
|
||||||
|
while (it->unicode() >= MIN_DIGIT && it->unicode() <= MAX_DIGIT && ((it != s.end()) && (q++ < MAX_LEN)))
|
||||||
|
{
|
||||||
|
n = (n * 10) + (it->unicode() - MIN_DIGIT);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
*ok = !(q >= MAX_LEN || q < s.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t BoblightClientConnection::parseByte(const QStringRef& s, bool *ok) const
|
||||||
|
{
|
||||||
|
const int LO = 0;
|
||||||
|
const int HI = 255;
|
||||||
|
|
||||||
|
#if defined(FAST_FLOAT_PARSE)
|
||||||
|
const float d = parseFloat(s, ok);
|
||||||
|
#else
|
||||||
|
const float d = s.toFloat(ok);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Clamp to byte range 0 to 255
|
||||||
|
return static_cast<uint8_t>(qBound(LO, int(HI * d), HI)); // qBound args are in order min, value, max; see: https://doc.qt.io/qt-5/qtglobal.html#qBound
|
||||||
|
}
|
||||||
|
|
||||||
void BoblightClientConnection::sendLightMessage()
|
void BoblightClientConnection::sendLightMessage()
|
||||||
{
|
{
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
|
@ -4,11 +4,15 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
// utils includes
|
// utils includes
|
||||||
#include <utils/Logger.h>
|
#include <utils/Logger.h>
|
||||||
#include <utils/ColorRgb.h>
|
#include <utils/ColorRgb.h>
|
||||||
|
|
||||||
|
/// Whether to parse floats with an eye on performance
|
||||||
|
#define FAST_FLOAT_PARSE
|
||||||
|
|
||||||
class ImageProcessor;
|
class ImageProcessor;
|
||||||
class Hyperion;
|
class Hyperion;
|
||||||
|
|
||||||
@ -70,6 +74,42 @@ private:
|
|||||||
///
|
///
|
||||||
void sendLightMessage();
|
void sendLightMessage();
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Interpret the float value "0.0" to "1.0" of the QString byte values 0 .. 255
|
||||||
|
///
|
||||||
|
/// @param s the string to parse
|
||||||
|
/// @param ok whether the result is ok
|
||||||
|
/// @return the parsed byte value in range 0 to 255, or 0
|
||||||
|
///
|
||||||
|
uint8_t parseByte(const QStringRef& s, bool *ok = nullptr) const;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Parse the given QString as unsigned int value.
|
||||||
|
///
|
||||||
|
/// @param s the string to parse
|
||||||
|
/// @param ok whether the result is ok
|
||||||
|
/// @return the parsed unsigned int value
|
||||||
|
///
|
||||||
|
unsigned parseUInt(const QStringRef& s, bool *ok = nullptr) const;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Parse the given QString as float value, e.g. the 16-bit (wide char) QString "1" shall represent 1, "0.5" is 0.5 and so on.
|
||||||
|
///
|
||||||
|
/// @param s the string to parse
|
||||||
|
/// @param ok whether the result is ok
|
||||||
|
/// @return the parsed float value, or 0
|
||||||
|
///
|
||||||
|
float parseFloat(const QStringRef& s, bool *ok = nullptr) const;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Read an incoming boblight message as QString
|
||||||
|
///
|
||||||
|
/// @param data the char data buffer of the incoming message
|
||||||
|
/// @param size the length of the buffer buffer
|
||||||
|
/// @returns the incoming boblight message as QString
|
||||||
|
///
|
||||||
|
QString readMessage(const char *data, const size_t size) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Locale used for parsing floating point values
|
/// Locale used for parsing floating point values
|
||||||
QLocale _locale;
|
QLocale _locale;
|
||||||
|
Loading…
Reference in New Issue
Block a user