wfview/cachingqueue.cpp

492 wiersze
17 KiB
C++
Czysty Zwykły widok Historia

2023-05-11 23:24:01 +00:00
/*
* This is a singleton queue (only 1 instance can be created)
*
* Although subclassing QThread is often not recommended, I think that this is
* a valid use case for subclassing.
*
*/
#include "logcategories.h"
#include "cachingqueue.h"
cachingQueue* cachingQueue::instance{};
2023-05-15 11:34:24 +00:00
QMutex cachingQueue::instanceMutex;
2023-05-11 23:24:01 +00:00
cachingQueue *cachingQueue::getInstance(QObject* parent)
{
2023-05-15 11:34:24 +00:00
QMutexLocker locker(&instanceMutex);
2023-05-11 23:24:01 +00:00
if (instance == Q_NULLPTR)
{
instance = new cachingQueue(parent);
2024-06-08 22:06:34 +00:00
instance->setObjectName(("cachingQueue()"));
2023-05-11 23:24:01 +00:00
connect (instance, SIGNAL(finished()),instance, SLOT(deleteLater()));
2024-06-08 22:06:34 +00:00
instance->start(QThread::TimeCriticalPriority);
2023-05-11 23:24:01 +00:00
}
qDebug() << "Returning instance of cachingQueue() to calling process:" << ((parent != Q_NULLPTR) ? parent->objectName(): "<unknown>");
2023-05-11 23:24:01 +00:00
return instance;
}
cachingQueue::~cachingQueue()
{
aborted = true;
if (queue != Q_NULLPTR) {
waiting->wakeOne(); // wake the thread and signal it to exit.
}
2023-05-11 23:24:01 +00:00
qInfo() << "Destroying caching queue (parent closing)";
}
void cachingQueue::run()
{
// Setup queue.
qInfo() << "Starting caching queue handler thread (ThreadId:" << QThread::currentThreadId() << ")";
if (queue == Q_NULLPTR){
queue = new QMultiMap <queuePriority,queueItem>;
}
if (cache == Q_NULLPTR){
cache = new QMultiMap<funcs,cacheItem>;
}
if (items == Q_NULLPTR){
items = new QQueue<cacheItem>;
}
if (messages == Q_NULLPTR){
messages = new QQueue<QString>;
}
if (waiting == Q_NULLPTR) {
waiting = new QWaitCondition();
}
2023-05-11 23:24:01 +00:00
QElapsedTimer timer;
timer.start();
QDeadlineTimer deadline(queueInterval);
QMutexLocker locker(&mutex);
2023-05-15 09:53:52 +00:00
quint64 counter=0; // Will run for many years!
2023-05-11 23:24:01 +00:00
while (!aborted)
{
if (!waiting->wait(&mutex, deadline.remainingTime()))
2023-05-11 23:24:01 +00:00
{
// Time to process the queue - mutex is locked
queuePriority prio = priorityImmediate;
// priorityNone=0, priorityImmediate=1, priorityHighest=2, priorityHigh=3, priorityMediumHigh=5, priorityMedium=7, priorityMediumLow=11, priorityLow=19, priorityLowest=23
// If no immediate commands then process the rest of the queue
if (!queue->contains(prio)) {
2023-05-11 23:24:01 +00:00
if (counter % priorityHighest == 0)
prio = priorityHighest;
else if (counter % priorityHigh == 0)
prio = priorityHigh;
else if (counter % priorityMediumHigh == 0)
prio = priorityMediumHigh;
else if (counter % priorityMedium == 0)
prio = priorityMedium;
else if (counter % priorityMediumLow == 0)
prio = priorityMediumLow;
else if (counter % priorityLow == 0)
prio = priorityLow;
else if (counter % priorityLowest == 0)
prio = priorityLowest;
2024-05-20 13:35:36 +00:00
}
2023-05-11 23:24:01 +00:00
counter++;
2024-06-12 16:33:54 +00:00
//auto it = queue.upperBound(prio);
//it--; //upperBound returns the item immediately following the last key.
//if (it != queue.end() && it.key() == prio)
auto it = queue->find(prio);
2024-07-23 08:18:27 +00:00
if (it != queue->end()) {
while (it != queue->end() && it.key() == prio)
2024-06-12 16:33:54 +00:00
{
it++;
}
it--;
2023-05-15 09:53:52 +00:00
auto item = it.value();
emit haveCommand(item.command,item.param,item.receiver);
2024-06-12 16:33:54 +00:00
//it=queue.erase(it);
queue->remove(prio,it.value());
2023-09-24 09:32:59 +00:00
if (item.recurring && prio != priorityImmediate) {
queue->insert(prio,item);
2023-05-15 09:53:52 +00:00
}
updateCache(false,item.command,item.param,item.receiver);
2023-05-11 23:24:01 +00:00
}
deadline.setRemainingTime(queueInterval); // reset the deadline to the poll frequency
2024-05-20 13:35:36 +00:00
QCoreApplication::processEvents();
2023-05-11 23:24:01 +00:00
}
else if (!aborted) {
2023-05-15 09:53:52 +00:00
// Mutex is locked
while (!items->isEmpty()) {
emit sendValue(items->dequeue());
2023-05-15 09:53:52 +00:00
}
while (!messages->isEmpty()) {
emit sendMessage(messages->dequeue());
2024-04-30 08:42:54 +00:00
}
2024-05-20 13:35:36 +00:00
if (queueInterval != -1 && deadline.isForever())
deadline.setRemainingTime(queueInterval); // reset the deadline to the poll frequency
2023-05-11 23:24:01 +00:00
}
}
if (waiting != Q_NULLPTR) {
delete waiting;
waiting = Q_NULLPTR;
}
if (queue != Q_NULLPTR){
delete queue;
queue = Q_NULLPTR;
}
if (cache != Q_NULLPTR){
delete cache;
cache = Q_NULLPTR;
}
if (items != Q_NULLPTR){
delete items;
items = Q_NULLPTR;
}
if (messages != Q_NULLPTR){
delete messages;
messages = Q_NULLPTR;
}
2023-05-11 23:24:01 +00:00
}
2024-05-20 13:35:36 +00:00
void cachingQueue::interval(qint64 val)
2023-05-11 23:24:01 +00:00
{
this->queueInterval = val;
if (queue != Q_NULLPTR) {
waiting->wakeAll();
}
2023-05-11 23:24:01 +00:00
qInfo() << "Changing queue interval to" << val << "ms";
}
funcs cachingQueue::checkCommandAvailable(funcs cmd,bool set)
2023-05-11 23:24:01 +00:00
{
// If we don't have rigCaps yet, simply return the command.
if (rigCaps != Q_NULLPTR && cmd != funcNone && !rigCaps->commands.contains(cmd)) {
// We don't have the requested command, so lets see if we can change it to something we do have.
// WFVIEW functions should use funcMain/Sub commands by default,
if (cmd == funcMainFreq && rigCaps->commands.contains(funcSelectedFreq))
cmd = funcSelectedFreq;
else if (cmd == funcSubFreq && rigCaps->commands.contains(funcUnselectedFreq))
cmd = funcSelectedFreq;
else if (cmd == funcMainMode && rigCaps->commands.contains(funcSelectedMode))
cmd = funcSelectedMode;
else if (cmd == funcSubMode && rigCaps->commands.contains(funcUnselectedMode))
cmd = funcUnselectedFreq;
// These are fallback commands for older radios that don't have command 25/26
else if(cmd == funcMainMode)
{
if (set)
cmd = funcModeSet;
else
cmd = funcModeGet;
}
else if(cmd == funcMainFreq)
{
if (set)
cmd = funcFreqSet;
else
cmd = funcFreqGet;
}
else
cmd = funcNone;
}
return cmd;
}
void cachingQueue::add(queuePriority prio ,funcs func, bool recurring, uchar receiver)
{
if (queue != Q_NULLPTR) {
queueItem q(func,recurring,receiver);
add(prio,q);
}
2023-05-11 23:24:01 +00:00
}
void cachingQueue::add(queuePriority prio ,queueItem item)
{
if (queue != Q_NULLPTR) {
item.command=checkCommandAvailable(item.command,item.param.isValid());
if (item.command != funcNone)
2023-05-15 09:53:52 +00:00
{
QMutexLocker locker(&mutex);
if (!item.recurring || isRecurring(item.command,item.receiver) != prio)
{
if (item.recurring && prio == queuePriority::priorityImmediate) {
qWarning() << "Warning, cannot add recurring command with immediate priority!" << funcString[item.command];
} else {
if (item.recurring) {
// also insert an immediate command to get the current value "now" (removes the need to get rigstate)
queueItem it=item;
it.recurring=false;
2024-07-23 08:18:27 +00:00
it.param.clear();
//qDebug() << "adding" << funcString[item.command] << "recurring" << it.recurring << "priority" << prio << "receiver" << it.receiver;
queue->insert(queue->cend(),priorityImmediate, it);
2024-07-23 08:18:27 +00:00
}
queue->insert(prio, item);
2023-06-05 08:27:37 +00:00
}
2023-05-15 09:53:52 +00:00
}
}
}
2023-05-11 23:24:01 +00:00
}
void cachingQueue::addUnique(queuePriority prio ,funcs func, bool recurring, uchar receiver)
2023-05-15 09:53:52 +00:00
{
if (queue != Q_NULLPTR) {
queueItem q(func,recurring, receiver);
addUnique(prio,q);
}
2023-05-15 09:53:52 +00:00
}
2023-05-11 23:24:01 +00:00
void cachingQueue::addUnique(queuePriority prio ,queueItem item)
{
if (queue != Q_NULLPTR) {
item.command=checkCommandAvailable(item.command,item.param.isValid());
if (item.command != funcNone)
{
QMutexLocker locker(&mutex);
if (item.recurring && prio == queuePriority::priorityImmediate) {
qWarning() << "Warning, cannot add unique recurring command with immediate priority!" << funcString[item.command];
} else {
int count=queue->remove(prio,item);
if (count>0)
qDebug() << "cachingQueue()::addUnique deleted" << count << "entries from queue for" << funcString[item.command] << "on receiver" << item.receiver;
if (item.recurring) {
// also insert an immediate command to get the current value "now" (removes the need to get initial rigstate)
queueItem it = item;
it.recurring=false;
2024-07-23 08:18:27 +00:00
it.param.clear();
queue->insert(queue->cend(),priorityImmediate, it);
qDebug() << "adding unique" << funcString[item.command] << "recurring" << item.recurring << "priority" << prio << "receiver" << item.receiver;
}
queue->insert(prio, item);
2023-05-15 09:53:52 +00:00
}
}
}
2023-05-11 23:24:01 +00:00
}
void cachingQueue::del(funcs func, uchar receiver)
2023-05-11 23:24:01 +00:00
{
2023-06-05 08:27:37 +00:00
// This will immediately delete any matching commands.
if (queue != Q_NULLPTR && func != funcNone)
2023-05-26 18:11:36 +00:00
{
QMutexLocker locker(&mutex);
auto it = std::find_if(queue->begin(), queue->end(), [func,receiver](const queueItem& c) { return (c.command == func && c.receiver == receiver && c.recurring); });
//auto it(queue.begin());
if (it != queue->end()) {
int count = queue->remove(it.key(),it.value());
2024-07-10 11:01:21 +00:00
if (count>0)
qDebug() << "cachingQueue()::del" << count << "entries from queue for" << funcString[func] << "on receiver" << receiver;
2023-05-26 18:11:36 +00:00
}
}
2023-05-11 23:24:01 +00:00
}
queuePriority cachingQueue::isRecurring(funcs func, uchar receiver)
2023-05-15 09:53:52 +00:00
{
// Does NOT lock the mutex
if (queue != Q_NULLPTR) {
auto rec = std::find_if(queue->begin(), queue->end(), [func,receiver](const queueItem& c) { return (c.command == func && c.receiver == receiver && c.recurring); });
if (rec != queue->end())
{
return rec.key();
}
2023-05-15 09:53:52 +00:00
}
return queuePriority::priorityNone;
}
2023-05-11 23:24:01 +00:00
void cachingQueue::clear()
{
if (queue != Q_NULLPTR) {
QMutexLocker locker(&mutex);
queue->clear();
}
2023-05-11 23:24:01 +00:00
}
void cachingQueue::message(QString msg)
{
if (messages != Q_NULLPTR) {
QMutexLocker locker(&mutex);
messages->append(msg);
2024-07-24 19:16:15 +00:00
qDebug() << "Received:" << msg;
waiting->wakeOne();
}
2023-05-11 23:24:01 +00:00
}
void cachingQueue::receiveValue(funcs func, QVariant value, uchar receiver)
2023-05-15 09:53:52 +00:00
{
if (cache != Q_NULLPTR) {
QMutexLocker locker(&mutex);
cacheItem c = cacheItem(func,value,receiver);
items->enqueue(c);
updateCache(true,func,value,receiver);
waiting->wakeOne();
}
2023-05-15 09:53:52 +00:00
}
2023-05-11 23:24:01 +00:00
2023-06-05 08:27:37 +00:00
void cachingQueue::updateCache(bool reply, queueItem item)
{
2023-05-15 09:53:52 +00:00
// Mutex MUST be locked by the calling function.
if (cache != Q_NULLPTR) {
auto cv = cache->find(item.command);
while (cv != cache->end() && cv->command == item.command) {
if (cv->receiver == item.receiver) {
if (reply) {
cv->reply = QDateTime::currentDateTime();
} else {
cv->req = QDateTime::currentDateTime();
}
// If we are sending an actual value, update the cache with it
// Value will be replaced if invalid on next get()
if (compare(item.param,cv.value().value))
{
cv->value.clear();
cv->value.setValue(item.param);
2024-06-10 22:58:33 +00:00
emit cacheUpdated(cv.value());
}
return;
// We have found (and updated) a matching item so return
}
++cv;
2023-05-15 09:53:52 +00:00
}
cacheItem c;
c.command = item.command;
c.receiver = item.receiver;
if (reply) {
c.reply = QDateTime::currentDateTime();
} else {
c.req = QDateTime::currentDateTime();
}
// If we are sending an actual value, update the cache with it
// Value will be replaced if invalid on next get()
if (item.param.isValid()) {
c.value = item.param;
}
cache->insert(item.command,c);
2024-06-10 22:58:33 +00:00
}
2023-05-11 23:24:01 +00:00
}
void cachingQueue::updateCache(bool reply, funcs func, QVariant value, uchar receiver)
2023-06-05 08:27:37 +00:00
{
if (cache != Q_NULLPTR) {
queueItem q(func,value,false,receiver);
updateCache(reply,q);
}
2023-06-05 08:27:37 +00:00
}
cacheItem cachingQueue::getCache(funcs func, uchar receiver)
2023-05-11 23:24:01 +00:00
{
cacheItem ret;
if (cache != Q_NULLPTR && func != funcNone) {
QMutexLocker locker(&mutex);
auto it = cache->find(func);
while (it != cache->end() && it->command == func)
2023-06-05 08:27:37 +00:00
{
if (it->receiver == receiver)
2023-06-05 08:27:37 +00:00
ret = cacheItem(*it);
++it;
}
}
// If the cache is more than 5-20 seconds old, re-request it as it may be stale (maybe make this a config option?)
// Using priorityhighest WILL slow down the S-Meter when a command intensive client is connected to rigctl
if (func != funcNone && (!ret.value.isValid() || ret.reply.addSecs(QRandomGenerator::global()->bounded(5,20)) <= QDateTime::currentDateTime())) {
//qInfo() << "No (or expired) cache found for" << funcString[func] << "requesting";
add(priorityHighest,func,false,receiver);
2023-05-11 23:24:01 +00:00
}
return ret;
2023-05-11 23:24:01 +00:00
}
2023-05-15 09:53:52 +00:00
//Calling function MUST call unlockMutex() once finished with data
QMultiMap<funcs,cacheItem>* cachingQueue::getCacheItems()
2023-05-11 23:24:01 +00:00
{
2023-05-15 09:53:52 +00:00
mutex.lock();
return cache;
2023-05-11 23:24:01 +00:00
}
2023-05-15 09:53:52 +00:00
//Calling function MUST call unlockMutex() once finished with data
QMultiMap <queuePriority,queueItem>* cachingQueue::getQueueItems()
2023-05-11 23:24:01 +00:00
{
2023-05-15 09:53:52 +00:00
mutex.lock();
return queue;
2023-05-11 23:24:01 +00:00
}
bool cachingQueue::compare(QVariant a, QVariant b)
2023-05-11 23:24:01 +00:00
{
bool changed = false;
if (a.isValid() && b.isValid()) {
// Compare the details
2023-07-20 15:54:07 +00:00
if (!strcmp(a.typeName(),"bool")){
if (a.value<bool>() != b.value<bool>())
changed=true;
} else if (!strcmp(a.typeName(),"QString")) {
changed=true;
2023-07-20 15:54:07 +00:00
} else if (!strcmp(a.typeName(),"uchar")) {
if (a.value<uchar>() != b.value<uchar>())
changed=true;
} else if (!strcmp(a.typeName(),"ushort")) {
if (a.value<ushort>() != b.value<ushort>())
changed=true;
} else if (!strcmp(a.typeName(),"short")) {
if (a.value<short>() != a.value<short>())
changed=true;
} else if (!strcmp(a.typeName(),"uint")) {
if (a.value<uint>() != b.value<uint>())
2023-09-27 11:28:43 +00:00
changed=true;
} else if (!strcmp(a.typeName(),"int")) {
if (a.value<int>() != b.value<int>())
changed=true;
2023-07-20 15:54:07 +00:00
} else if (!strcmp(a.typeName(),"modeInfo")) {
if (a.value<modeInfo>().mk != b.value<modeInfo>().mk || a.value<modeInfo>().reg != b.value<modeInfo>().reg
2024-04-07 19:28:42 +00:00
|| a.value<modeInfo>().filter != b.value<modeInfo>().filter || a.value<modeInfo>().data != b.value<modeInfo>().data) {
2023-07-20 15:54:07 +00:00
changed=true;
}
} else if(!strcmp(a.typeName(),"freqt")) {
if (a.value<freqt>().Hz != b.value<freqt>().Hz)
changed=true;
} else if(!strcmp(a.typeName(),"antennaInfo")) {
if (a.value<antennaInfo>().antenna != b.value<antennaInfo>().antenna || a.value<antennaInfo>().rx != b.value<antennaInfo>().rx)
changed=true;
} else if(!strcmp(a.typeName(),"rigInput")) {
if (a.value<rigInput>().type != b.value<rigInput>().type)
changed=true;
} else if (!strcmp(a.typeName(),"duplexMode_t")) {
if (a.value<duplexMode_t>() != b.value<duplexMode_t>())
changed=true;
2024-02-12 21:36:39 +00:00
} else if (!strcmp(a.typeName(),"toneInfo")) {
if (a.value<toneInfo>().tone != b.value<toneInfo>().tone)
changed=true;
2023-07-20 15:54:07 +00:00
} else if (!strcmp(a.typeName(),"spectrumMode_t")) {
if (a.value<spectrumMode_t>() != b.value<spectrumMode_t>())
changed=true;
} else if (!strcmp(a.typeName(),"centerSpanData")) {
if (a.value<centerSpanData>().cstype != b.value<centerSpanData>().cstype || a.value<centerSpanData>().freq != b.value<centerSpanData>().freq )
changed=true;
2023-09-22 18:06:09 +00:00
} else if (!strcmp(a.typeName(),"scopeData") || !strcmp(a.typeName(),"memoryType") || !strcmp(a.typeName(),"bandStackType") ) {
2023-07-20 15:54:07 +00:00
changed=true; // Always different
} else {
// Maybe Try simple comparison?
qInfo () << "Unsupported cache value:" << a.typeName();
}
} else if (a.isValid()) {
changed = true;
}
return changed;
2023-05-11 23:24:01 +00:00
}