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 <QStringList>
|
||||
#include <QStringRef>
|
||||
#include <QVector>
|
||||
|
||||
namespace QStringUtils {
|
||||
|
||||
@ -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);
|
||||
#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
|
||||
|
@ -3,11 +3,13 @@
|
||||
#include <cassert>
|
||||
#include <iomanip>
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
|
||||
// stl includes
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iterator>
|
||||
#include <locale>
|
||||
|
||||
// Qt includes
|
||||
#include <QResource>
|
||||
@ -54,18 +56,19 @@ BoblightClientConnection::~BoblightClientConnection()
|
||||
|
||||
void BoblightClientConnection::readData()
|
||||
{
|
||||
_receiveBuffer += _socket->readAll();
|
||||
_receiveBuffer.append(_socket->readAll());
|
||||
|
||||
int bytes = _receiveBuffer.indexOf('\n') + 1;
|
||||
while(bytes > 0)
|
||||
{
|
||||
// create message string (strip the newline)
|
||||
QString message = QString::fromLatin1(_receiveBuffer.data(), bytes-1);
|
||||
// remove message data from buffer
|
||||
_receiveBuffer = _receiveBuffer.mid(bytes);
|
||||
const QString message = readMessage(_receiveBuffer.data(), bytes);
|
||||
|
||||
// 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
|
||||
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()
|
||||
{
|
||||
// clear the current channel
|
||||
@ -88,10 +116,11 @@ void BoblightClientConnection::socketClosed()
|
||||
emit connectionClosed(this);
|
||||
}
|
||||
|
||||
|
||||
void BoblightClientConnection::handleMessage(const QString & message)
|
||||
{
|
||||
//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[0] == "hello")
|
||||
@ -122,32 +151,18 @@ void BoblightClientConnection::handleMessage(const QString & message)
|
||||
if (messageParts.size() > 3 && messageParts[1] == "light")
|
||||
{
|
||||
bool rc;
|
||||
unsigned ledIndex = messageParts[2].toUInt(&rc);
|
||||
const unsigned ledIndex = parseUInt(messageParts[2], &rc);
|
||||
if (rc && ledIndex < _ledColors.size())
|
||||
{
|
||||
if (messageParts[3] == "rgb" && messageParts.size() == 7)
|
||||
{
|
||||
// replace decimal comma with decimal point
|
||||
messageParts[4].replace(',', '.');
|
||||
messageParts[5].replace(',', '.');
|
||||
messageParts[6].replace(',', '.');
|
||||
// custom parseByte accepts both ',' and '.' as decimal separator
|
||||
// no need to replace decimal comma with decimal point
|
||||
|
||||
bool rc1, rc2, rc3;
|
||||
uint8_t red = qMax(0, qMin(255, int(255 * messageParts[4].toFloat(&rc1))));
|
||||
|
||||
// check for correct locale should not be needed anymore - please check!
|
||||
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))));
|
||||
const uint8_t red = parseByte(messageParts[4], &rc1);
|
||||
const uint8_t green = parseByte(messageParts[5], &rc2);
|
||||
const uint8_t blue = parseByte(messageParts[6], &rc3);
|
||||
|
||||
if (rc1 && rc2 && rc3)
|
||||
{
|
||||
@ -181,7 +196,7 @@ void BoblightClientConnection::handleMessage(const QString & message)
|
||||
else if (messageParts.size() == 3 && messageParts[1] == "priority")
|
||||
{
|
||||
bool rc;
|
||||
int prio = messageParts[2].toInt(&rc);
|
||||
const int prio = static_cast<int>(parseUInt(messageParts[2], &rc));
|
||||
if (rc && prio != _priority)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
/// 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()
|
||||
{
|
||||
char buffer[256];
|
||||
|
@ -4,11 +4,15 @@
|
||||
#include <QByteArray>
|
||||
#include <QTcpSocket>
|
||||
#include <QLocale>
|
||||
#include <QString>
|
||||
|
||||
// utils includes
|
||||
#include <utils/Logger.h>
|
||||
#include <utils/ColorRgb.h>
|
||||
|
||||
/// Whether to parse floats with an eye on performance
|
||||
#define FAST_FLOAT_PARSE
|
||||
|
||||
class ImageProcessor;
|
||||
class Hyperion;
|
||||
|
||||
@ -70,6 +74,42 @@ private:
|
||||
///
|
||||
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:
|
||||
/// Locale used for parsing floating point values
|
||||
QLocale _locale;
|
||||
|
Loading…
Reference in New Issue
Block a user