mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
That flag caused a DLL to be loaded before Application was instanced. This triggers a Qt bug inside Q_COREAPP_STARTUP_FUNC that causes the previous registration pointing the startup function in the main executable to be overridden with the address of the function in the DLL (Since they both link the same static library) This leads to the correct function running in the wrong address space (the DLLs), hence not initializing some global variables correctly.
1234 lines
42 KiB
C++
1234 lines
42 KiB
C++
//
|
|
// SharedUtil.cpp
|
|
// libraries/shared/src
|
|
//
|
|
// Created by Stephen Birarda on 2/22/13.
|
|
// Copyright 2013 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
#include "SharedUtil.h"
|
|
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cctype>
|
|
#include <time.h>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <set>
|
|
#include <unordered_map>
|
|
|
|
#include <glm/glm.hpp>
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <windows.h>
|
|
#include "CPUIdent.h"
|
|
#include <Psapi.h>
|
|
|
|
#if _MSC_VER >= 1900
|
|
#pragma comment(lib, "legacy_stdio_definitions.lib")
|
|
FILE _iob[] = {*stdin, *stdout, *stderr};
|
|
extern "C" FILE * __cdecl __iob_func(void) {
|
|
return _iob;
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef __APPLE__
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#endif
|
|
|
|
|
|
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
|
|
#include <signal.h>
|
|
#include <cerrno>
|
|
#endif
|
|
|
|
#include <QtCore/QDebug>
|
|
#include <QDateTime>
|
|
#include <QElapsedTimer>
|
|
#include <QTimer>
|
|
#include <QProcess>
|
|
#include <QSysInfo>
|
|
#include <QThread>
|
|
|
|
#include "NumericalConstants.h"
|
|
#include "OctalCode.h"
|
|
#include "SharedLogging.h"
|
|
|
|
static std::mutex stagedGlobalInstancesMutex;
|
|
static std::unordered_map<std::string, QVariant> stagedGlobalInstances;
|
|
|
|
|
|
std::mutex& globalInstancesMutex() {
|
|
return stagedGlobalInstancesMutex;
|
|
}
|
|
|
|
static void commitGlobalInstances() {
|
|
std::unique_lock<std::mutex> lock(globalInstancesMutex());
|
|
for (const auto& it : stagedGlobalInstances) {
|
|
qApp->setProperty(it.first.c_str(), it.second);
|
|
}
|
|
stagedGlobalInstances.clear();
|
|
}
|
|
|
|
void setupGlobalInstances() {
|
|
qAddPreRoutine(commitGlobalInstances);
|
|
}
|
|
|
|
QVariant getGlobalInstance(const char* propertyName) {
|
|
if (qApp) {
|
|
return qApp->property(propertyName);
|
|
} else {
|
|
auto it = stagedGlobalInstances.find(propertyName);
|
|
if (it != stagedGlobalInstances.end()) {
|
|
return it->second;
|
|
}
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
void setGlobalInstance(const char* propertyName, const QVariant& variant) {
|
|
if (qApp) {
|
|
qApp->setProperty(propertyName, variant);
|
|
} else {
|
|
stagedGlobalInstances[propertyName] = variant;
|
|
}
|
|
}
|
|
|
|
static qint64 usecTimestampNowAdjust = 0; // in usec
|
|
void usecTimestampNowForceClockSkew(qint64 clockSkew) {
|
|
::usecTimestampNowAdjust = clockSkew;
|
|
}
|
|
|
|
static qint64 TIME_REFERENCE = 0; // in usec
|
|
static std::once_flag usecTimestampNowIsInitialized;
|
|
static QElapsedTimer timestampTimer;
|
|
|
|
quint64 usecTimestampNow(bool wantDebug) {
|
|
std::call_once(usecTimestampNowIsInitialized, [&] {
|
|
TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec
|
|
timestampTimer.start();
|
|
});
|
|
|
|
quint64 now;
|
|
quint64 nsecsElapsed = timestampTimer.nsecsElapsed();
|
|
quint64 usecsElapsed = nsecsElapsed / NSECS_PER_USEC; // nsec to usec
|
|
|
|
// QElapsedTimer may not advance if the CPU has gone to sleep. In which case it
|
|
// will begin to deviate from real time. We detect that here, and reset if necessary
|
|
quint64 msecsCurrentTime = QDateTime::currentMSecsSinceEpoch();
|
|
quint64 msecsEstimate = (TIME_REFERENCE + usecsElapsed) / USECS_PER_MSEC; // usecs to msecs
|
|
int possibleSkew = msecsEstimate - msecsCurrentTime;
|
|
const int TOLERANCE = 10 * MSECS_PER_SECOND; // up to 10 seconds of skew is tolerated
|
|
if (abs(possibleSkew) > TOLERANCE) {
|
|
// reset our TIME_REFERENCE and timer
|
|
TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec
|
|
timestampTimer.restart();
|
|
now = TIME_REFERENCE + ::usecTimestampNowAdjust;
|
|
|
|
if (wantDebug) {
|
|
qCDebug(shared) << "usecTimestampNow() - resetting QElapsedTimer. ";
|
|
qCDebug(shared) << " msecsCurrentTime:" << msecsCurrentTime;
|
|
qCDebug(shared) << " msecsEstimate:" << msecsEstimate;
|
|
qCDebug(shared) << " possibleSkew:" << possibleSkew;
|
|
qCDebug(shared) << " TOLERANCE:" << TOLERANCE;
|
|
|
|
qCDebug(shared) << " nsecsElapsed:" << nsecsElapsed;
|
|
qCDebug(shared) << " usecsElapsed:" << usecsElapsed;
|
|
|
|
QDateTime currentLocalTime = QDateTime::currentDateTime();
|
|
|
|
quint64 msecsNow = now / 1000; // usecs to msecs
|
|
QDateTime nowAsString;
|
|
nowAsString.setMSecsSinceEpoch(msecsNow);
|
|
|
|
qCDebug(shared) << " now:" << now;
|
|
qCDebug(shared) << " msecsNow:" << msecsNow;
|
|
|
|
qCDebug(shared) << " nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
qCDebug(shared) << " currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
}
|
|
} else {
|
|
now = TIME_REFERENCE + usecsElapsed + ::usecTimestampNowAdjust;
|
|
}
|
|
|
|
if (wantDebug) {
|
|
QDateTime currentLocalTime = QDateTime::currentDateTime();
|
|
|
|
quint64 msecsNow = now / 1000; // usecs to msecs
|
|
QDateTime nowAsString;
|
|
nowAsString.setMSecsSinceEpoch(msecsNow);
|
|
|
|
quint64 msecsTimeReference = TIME_REFERENCE / 1000; // usecs to msecs
|
|
QDateTime timeReferenceAsString;
|
|
timeReferenceAsString.setMSecsSinceEpoch(msecsTimeReference);
|
|
|
|
qCDebug(shared) << "usecTimestampNow() - details... ";
|
|
qCDebug(shared) << " TIME_REFERENCE:" << TIME_REFERENCE;
|
|
qCDebug(shared) << " timeReferenceAsString:" << timeReferenceAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
qCDebug(shared) << " usecTimestampNowAdjust:" << usecTimestampNowAdjust;
|
|
qCDebug(shared) << " nsecsElapsed:" << nsecsElapsed;
|
|
qCDebug(shared) << " usecsElapsed:" << usecsElapsed;
|
|
qCDebug(shared) << " now:" << now;
|
|
qCDebug(shared) << " msecsNow:" << msecsNow;
|
|
qCDebug(shared) << " nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
qCDebug(shared) << " currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
}
|
|
|
|
return now;
|
|
}
|
|
|
|
float secTimestampNow() {
|
|
static const auto START_TIME = usecTimestampNow();
|
|
const auto nowUsecs = usecTimestampNow() - START_TIME;
|
|
const auto nowMsecs = nowUsecs / USECS_PER_MSEC;
|
|
return (float)nowMsecs / MSECS_PER_SECOND;
|
|
}
|
|
|
|
float randFloat() {
|
|
return (rand() % 10000)/10000.0f;
|
|
}
|
|
|
|
int randIntInRange (int min, int max) {
|
|
return min + (rand() % ((max + 1) - min));
|
|
}
|
|
|
|
float randFloatInRange (float min,float max) {
|
|
return min + ((rand() % 10000)/10000.0f * (max-min));
|
|
}
|
|
|
|
float randomSign() {
|
|
return randomBoolean() ? -1.0 : 1.0;
|
|
}
|
|
|
|
unsigned char randomColorValue(int miniumum) {
|
|
return miniumum + (rand() % (256 - miniumum));
|
|
}
|
|
|
|
bool randomBoolean() {
|
|
return rand() % 2;
|
|
}
|
|
|
|
bool shouldDo(float desiredInterval, float deltaTime) {
|
|
return randFloat() < deltaTime / desiredInterval;
|
|
}
|
|
|
|
void outputBufferBits(const unsigned char* buffer, int length, QDebug* continuedDebug) {
|
|
for (int i = 0; i < length; i++) {
|
|
outputBits(buffer[i], continuedDebug);
|
|
}
|
|
}
|
|
|
|
void outputBits(unsigned char byte, QDebug* continuedDebug) {
|
|
QDebug debug = qDebug().nospace();
|
|
|
|
if (continuedDebug) {
|
|
debug = *continuedDebug;
|
|
debug.nospace();
|
|
}
|
|
|
|
QString resultString;
|
|
|
|
if (isalnum(byte)) {
|
|
resultString.sprintf("[ %d (%c): ", byte, byte);
|
|
} else {
|
|
resultString.sprintf("[ %d (0x%x): ", byte, byte);
|
|
}
|
|
debug << qPrintable(resultString);
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
resultString.sprintf("%d", byte >> (7 - i) & 1);
|
|
debug << qPrintable(resultString);
|
|
}
|
|
debug << " ]";
|
|
}
|
|
|
|
int numberOfOnes(unsigned char byte) {
|
|
|
|
static const int nbits[256] = {
|
|
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,
|
|
4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,
|
|
4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,
|
|
3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,
|
|
4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,
|
|
4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,
|
|
3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,
|
|
6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,
|
|
4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,
|
|
6,5,6,6,7,5,6,6,7,6,7,7,8
|
|
};
|
|
|
|
return nbits[(unsigned char) byte];
|
|
|
|
}
|
|
|
|
bool oneAtBit(unsigned char byte, int bitIndex) {
|
|
return (byte >> (7 - bitIndex) & 1);
|
|
}
|
|
|
|
void setAtBit(unsigned char& byte, int bitIndex) {
|
|
byte |= (1 << (7 - bitIndex));
|
|
}
|
|
|
|
void clearAtBit(unsigned char& byte, int bitIndex) {
|
|
if (oneAtBit(byte, bitIndex)) {
|
|
byte -= (1 << (7 - bitIndex));
|
|
}
|
|
}
|
|
|
|
int getSemiNibbleAt(unsigned char byte, int bitIndex) {
|
|
return (byte >> (6 - bitIndex) & 3); // semi-nibbles store 00, 01, 10, or 11
|
|
}
|
|
|
|
int getNthBit(unsigned char byte, int ordinal) {
|
|
const int ERROR_RESULT = -1;
|
|
const int MIN_ORDINAL = 1;
|
|
const int MAX_ORDINAL = 8;
|
|
if (ordinal < MIN_ORDINAL || ordinal > MAX_ORDINAL) {
|
|
return ERROR_RESULT;
|
|
}
|
|
int bitsSet = 0;
|
|
for (int bitIndex = 0; bitIndex < MAX_ORDINAL; bitIndex++) {
|
|
if (oneAtBit(byte, bitIndex)) {
|
|
bitsSet++;
|
|
}
|
|
if (bitsSet == ordinal) {
|
|
return bitIndex;
|
|
}
|
|
}
|
|
return ERROR_RESULT;
|
|
}
|
|
|
|
void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value) {
|
|
//assert(value <= 3 && value >= 0);
|
|
byte |= ((value & 3) << (6 - bitIndex)); // semi-nibbles store 00, 01, 10, or 11
|
|
}
|
|
|
|
bool isInEnvironment(const char* environment) {
|
|
char* environmentString = getenv("HIFI_ENVIRONMENT");
|
|
return (environmentString && strcmp(environmentString, environment) == 0);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Function: getCmdOption()
|
|
// Description: Handy little function to tell you if a command line flag and option was
|
|
// included while launching the application, and to get the option value
|
|
// immediately following the flag. For example if you ran:
|
|
// ./app -i filename.txt
|
|
// then you're using the "-i" flag to set the input file name.
|
|
// Usage: char * inputFilename = getCmdOption(argc, argv, "-i");
|
|
// Complaints: Brad :)
|
|
const char* getCmdOption(int argc, const char * argv[],const char* option) {
|
|
// check each arg
|
|
for (int i=0; i < argc; i++) {
|
|
// if the arg matches the desired option
|
|
if (strcmp(option,argv[i])==0 && i+1 < argc) {
|
|
// then return the next option
|
|
return argv[i+1];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Function: getCmdOption()
|
|
// Description: Handy little function to tell you if a command line option flag was
|
|
// included while launching the application. Returns bool true/false
|
|
// Usage: bool wantDump = cmdOptionExists(argc, argv, "-d");
|
|
// Complaints: Brad :)
|
|
|
|
bool cmdOptionExists(int argc, const char * argv[],const char* option) {
|
|
// check each arg
|
|
for (int i=0; i < argc; i++) {
|
|
// if the arg matches the desired option
|
|
if (strcmp(option,argv[i])==0) {
|
|
// then return the next option
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void sharedMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message) {
|
|
fprintf(stdout, "%s", message.toLocal8Bit().constData());
|
|
}
|
|
|
|
unsigned char* pointToOctalCode(float x, float y, float z, float s) {
|
|
return pointToVoxel(x, y, z, s);
|
|
}
|
|
|
|
/// Given a universal point with location x,y,z this will return the voxel
|
|
/// voxel code corresponding to the closest voxel which encloses a cube with
|
|
/// lower corners at x,y,z, having side of length S.
|
|
/// The input values x,y,z range 0.0 <= v < 1.0
|
|
/// IMPORTANT: The voxel is returned to you a buffer which you MUST delete when you are
|
|
/// done with it.
|
|
unsigned char* pointToVoxel(float x, float y, float z, float s, unsigned char r, unsigned char g, unsigned char b ) {
|
|
|
|
// special case for size 1, the root node
|
|
if (s >= 1.0f) {
|
|
unsigned char* voxelOut = new unsigned char;
|
|
*voxelOut = 0;
|
|
return voxelOut;
|
|
}
|
|
|
|
float xTest, yTest, zTest, sTest;
|
|
xTest = yTest = zTest = sTest = 0.5f;
|
|
|
|
// First determine the voxelSize that will properly encode a
|
|
// voxel of size S.
|
|
unsigned int voxelSizeInOctets = 1;
|
|
while (sTest > s) {
|
|
sTest /= 2.0f;
|
|
voxelSizeInOctets++;
|
|
}
|
|
|
|
auto voxelSizeInBytes = bytesRequiredForCodeLength(voxelSizeInOctets); // (voxelSizeInBits/8)+1;
|
|
auto voxelBufferSize = voxelSizeInBytes + sizeof(rgbColor); // 3 for color
|
|
|
|
// allocate our resulting buffer
|
|
unsigned char* voxelOut = new unsigned char[voxelBufferSize];
|
|
|
|
// first byte of buffer is always our size in octets
|
|
voxelOut[0]=voxelSizeInOctets;
|
|
|
|
sTest = 0.5f; // reset sTest so we can do this again.
|
|
|
|
unsigned char byte = 0; // we will be adding coding bits here
|
|
int bitInByteNDX = 0; // keep track of where we are in byte as we go
|
|
int byteNDX = 1; // keep track of where we are in buffer of bytes as we go
|
|
unsigned int octetsDone = 0;
|
|
|
|
// Now we actually fill out the voxel code
|
|
while (octetsDone < voxelSizeInOctets) {
|
|
if (x >= xTest) {
|
|
//<write 1 bit>
|
|
byte = (byte << 1) | true;
|
|
xTest += sTest/2.0f;
|
|
} else {
|
|
//<write 0 bit;>
|
|
byte = (byte << 1) | false;
|
|
xTest -= sTest/2.0f;
|
|
}
|
|
bitInByteNDX++;
|
|
// If we've reached the last bit of the byte, then we want to copy this byte
|
|
// into our buffer. And get ready to start on a new byte
|
|
if (bitInByteNDX == 8) {
|
|
voxelOut[byteNDX]=byte;
|
|
byteNDX++;
|
|
bitInByteNDX=0;
|
|
byte=0;
|
|
}
|
|
|
|
if (y >= yTest) {
|
|
//<write 1 bit>
|
|
byte = (byte << 1) | true;
|
|
yTest += sTest/2.0f;
|
|
} else {
|
|
//<write 0 bit;>
|
|
byte = (byte << 1) | false;
|
|
yTest -= sTest/2.0f;
|
|
}
|
|
bitInByteNDX++;
|
|
// If we've reached the last bit of the byte, then we want to copy this byte
|
|
// into our buffer. And get ready to start on a new byte
|
|
if (bitInByteNDX == 8) {
|
|
voxelOut[byteNDX]=byte;
|
|
byteNDX++;
|
|
bitInByteNDX=0;
|
|
byte=0;
|
|
}
|
|
|
|
if (z >= zTest) {
|
|
//<write 1 bit>
|
|
byte = (byte << 1) | true;
|
|
zTest += sTest/2.0f;
|
|
} else {
|
|
//<write 0 bit;>
|
|
byte = (byte << 1) | false;
|
|
zTest -= sTest/2.0f;
|
|
}
|
|
bitInByteNDX++;
|
|
// If we've reached the last bit of the byte, then we want to copy this byte
|
|
// into our buffer. And get ready to start on a new byte
|
|
if (bitInByteNDX == 8) {
|
|
voxelOut[byteNDX]=byte;
|
|
byteNDX++;
|
|
bitInByteNDX=0;
|
|
byte=0;
|
|
}
|
|
|
|
octetsDone++;
|
|
sTest /= 2.0f;
|
|
}
|
|
|
|
// If we've got here, and we didn't fill the last byte, we need to zero pad this
|
|
// byte before we copy it into our buffer.
|
|
if (bitInByteNDX > 0 && bitInByteNDX < 8) {
|
|
// Pad the last byte
|
|
while (bitInByteNDX < 8) {
|
|
byte = (byte << 1) | false;
|
|
bitInByteNDX++;
|
|
}
|
|
|
|
// Copy it into our output buffer
|
|
voxelOut[byteNDX]=byte;
|
|
byteNDX++;
|
|
}
|
|
// copy color data
|
|
voxelOut[byteNDX]=r;
|
|
voxelOut[byteNDX+1]=g;
|
|
voxelOut[byteNDX+2]=b;
|
|
|
|
return voxelOut;
|
|
}
|
|
|
|
void printVoxelCode(unsigned char* voxelCode) {
|
|
unsigned char octets = voxelCode[0];
|
|
unsigned int voxelSizeInBits = octets*3;
|
|
unsigned int voxelSizeInBytes = (voxelSizeInBits/8)+1;
|
|
unsigned int voxelSizeInOctets = (voxelSizeInBits/3);
|
|
unsigned int voxelBufferSize = voxelSizeInBytes+1+3; // 1 for size, 3 for color
|
|
|
|
qCDebug(shared, "octets=%d",octets);
|
|
qCDebug(shared, "voxelSizeInBits=%d",voxelSizeInBits);
|
|
qCDebug(shared, "voxelSizeInBytes=%d",voxelSizeInBytes);
|
|
qCDebug(shared, "voxelSizeInOctets=%d",voxelSizeInOctets);
|
|
qCDebug(shared, "voxelBufferSize=%d",voxelBufferSize);
|
|
|
|
for(unsigned int i=0; i < voxelBufferSize; i++) {
|
|
QDebug voxelBufferDebug = qDebug();
|
|
voxelBufferDebug << "i =" << i;
|
|
outputBits(voxelCode[i], &voxelBufferDebug);
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
void usleep(int waitTime) {
|
|
// Use QueryPerformanceCounter for least overhead
|
|
LARGE_INTEGER now; // ticks
|
|
QueryPerformanceCounter(&now);
|
|
|
|
static int64_t ticksPerSec = 0;
|
|
if (ticksPerSec == 0) {
|
|
LARGE_INTEGER frequency;
|
|
QueryPerformanceFrequency(&frequency);
|
|
ticksPerSec = frequency.QuadPart;
|
|
}
|
|
|
|
// order ops to avoid loss in precision
|
|
int64_t waitTicks = (ticksPerSec * waitTime) / USECS_PER_SECOND;
|
|
int64_t sleepTicks = now.QuadPart + waitTicks;
|
|
|
|
// Busy wait with sleep/yield where possible
|
|
while (true) {
|
|
QueryPerformanceCounter(&now);
|
|
if (now.QuadPart >= sleepTicks) {
|
|
break;
|
|
}
|
|
|
|
// Sleep if we have at least 1ms to spare
|
|
const int64_t MIN_SLEEP_USECS = 1000;
|
|
// msleep is allowed to overshoot, so give it a 100us berth
|
|
const int64_t MIN_SLEEP_USECS_BERTH = 100;
|
|
// order ops to avoid loss in precision
|
|
int64_t sleepFor = ((sleepTicks - now.QuadPart) * USECS_PER_SECOND) / ticksPerSec - MIN_SLEEP_USECS_BERTH;
|
|
if (sleepFor > MIN_SLEEP_USECS) {
|
|
Sleep((DWORD)(sleepFor / USECS_PER_MSEC));
|
|
// Yield otherwise
|
|
} else {
|
|
// Use Qt to delegate, as SwitchToThread is only supported starting with XP
|
|
QThread::yieldCurrentThread();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Inserts the value and key into three arrays sorted by the key array, the first array is the value,
|
|
// the second array is a sorted key for the value, the third array is the index for the value in it original
|
|
// non-sorted array
|
|
// returns -1 if size exceeded
|
|
// originalIndexArray is optional
|
|
int insertIntoSortedArrays(void* value, float key, int originalIndex,
|
|
void** valueArray, float* keyArray, int* originalIndexArray,
|
|
int currentCount, int maxCount) {
|
|
|
|
if (currentCount < maxCount) {
|
|
int i = 0;
|
|
if (currentCount > 0) {
|
|
while (i < currentCount && key > keyArray[i]) {
|
|
i++;
|
|
}
|
|
// i is our desired location
|
|
// shift array elements to the right
|
|
if (i < currentCount && i+1 < maxCount) {
|
|
memmove(&valueArray[i + 1], &valueArray[i], sizeof(void*) * (currentCount - i));
|
|
memmove(&keyArray[i + 1], &keyArray[i], sizeof(float) * (currentCount - i));
|
|
if (originalIndexArray) {
|
|
memmove(&originalIndexArray[i + 1], &originalIndexArray[i], sizeof(int) * (currentCount - i));
|
|
}
|
|
}
|
|
}
|
|
// place new element at i
|
|
valueArray[i] = value;
|
|
keyArray[i] = key;
|
|
if (originalIndexArray) {
|
|
originalIndexArray[i] = originalIndex;
|
|
}
|
|
return currentCount + 1;
|
|
}
|
|
return -1; // error case
|
|
}
|
|
|
|
int removeFromSortedArrays(void* value, void** valueArray, float* keyArray, int* originalIndexArray,
|
|
int currentCount, int maxCount) {
|
|
|
|
int i = 0;
|
|
if (currentCount > 0) {
|
|
while (i < currentCount && value != valueArray[i]) {
|
|
i++;
|
|
}
|
|
|
|
if (value == valueArray[i] && i < currentCount) {
|
|
// i is the location of the item we were looking for
|
|
// shift array elements to the left
|
|
memmove(&valueArray[i], &valueArray[i + 1], sizeof(void*) * ((currentCount-1) - i));
|
|
memmove(&keyArray[i], &keyArray[i + 1], sizeof(float) * ((currentCount-1) - i));
|
|
if (originalIndexArray) {
|
|
memmove(&originalIndexArray[i], &originalIndexArray[i + 1], sizeof(int) * ((currentCount-1) - i));
|
|
}
|
|
return currentCount-1;
|
|
}
|
|
}
|
|
return -1; // error case
|
|
}
|
|
|
|
float SMALL_LIMIT = 10.0f;
|
|
float LARGE_LIMIT = 1000.0f;
|
|
|
|
int packFloatRatioToTwoByte(unsigned char* buffer, float ratio) {
|
|
// if the ratio is less than 10, then encode it as a positive number scaled from 0 to int16::max()
|
|
int16_t ratioHolder;
|
|
|
|
if (ratio < SMALL_LIMIT) {
|
|
const float SMALL_RATIO_CONVERSION_RATIO = (std::numeric_limits<int16_t>::max() / SMALL_LIMIT);
|
|
ratioHolder = floorf(ratio * SMALL_RATIO_CONVERSION_RATIO);
|
|
} else {
|
|
const float LARGE_RATIO_CONVERSION_RATIO = std::numeric_limits<int16_t>::min() / LARGE_LIMIT;
|
|
ratioHolder = floorf((std::min(ratio,LARGE_LIMIT) - SMALL_LIMIT) * LARGE_RATIO_CONVERSION_RATIO);
|
|
}
|
|
memcpy(buffer, &ratioHolder, sizeof(ratioHolder));
|
|
return sizeof(ratioHolder);
|
|
}
|
|
|
|
int unpackFloatRatioFromTwoByte(const unsigned char* buffer, float& ratio) {
|
|
int16_t ratioHolder;
|
|
memcpy(&ratioHolder, buffer, sizeof(ratioHolder));
|
|
|
|
// If it's positive, than the original ratio was less than SMALL_LIMIT
|
|
if (ratioHolder > 0) {
|
|
ratio = (ratioHolder / (float) std::numeric_limits<int16_t>::max()) * SMALL_LIMIT;
|
|
} else {
|
|
// If it's negative, than the original ratio was between SMALL_LIMIT and LARGE_LIMIT
|
|
ratio = ((ratioHolder / (float) std::numeric_limits<int16_t>::min()) * LARGE_LIMIT) + SMALL_LIMIT;
|
|
}
|
|
return sizeof(ratioHolder);
|
|
}
|
|
|
|
int packClipValueToTwoByte(unsigned char* buffer, float clipValue) {
|
|
// Clip values must be less than max signed 16bit integers
|
|
assert(clipValue < std::numeric_limits<int16_t>::max());
|
|
int16_t holder;
|
|
|
|
// if the clip is less than 10, then encode it as a positive number scaled from 0 to int16::max()
|
|
if (clipValue < SMALL_LIMIT) {
|
|
const float SMALL_RATIO_CONVERSION_RATIO = (std::numeric_limits<int16_t>::max() / SMALL_LIMIT);
|
|
holder = floorf(clipValue * SMALL_RATIO_CONVERSION_RATIO);
|
|
} else {
|
|
// otherwise we store it as a negative integer
|
|
holder = -1 * floorf(clipValue);
|
|
}
|
|
memcpy(buffer, &holder, sizeof(holder));
|
|
return sizeof(holder);
|
|
}
|
|
|
|
int unpackClipValueFromTwoByte(const unsigned char* buffer, float& clipValue) {
|
|
int16_t holder;
|
|
memcpy(&holder, buffer, sizeof(holder));
|
|
|
|
// If it's positive, than the original clipValue was less than SMALL_LIMIT
|
|
if (holder > 0) {
|
|
clipValue = (holder / (float) std::numeric_limits<int16_t>::max()) * SMALL_LIMIT;
|
|
} else {
|
|
// If it's negative, than the original holder can be found as the opposite sign of holder
|
|
clipValue = -1.0f * holder;
|
|
}
|
|
return sizeof(holder);
|
|
}
|
|
|
|
int packFloatToByte(unsigned char* buffer, float value, float scaleBy) {
|
|
quint8 holder;
|
|
const float CONVERSION_RATIO = (255 / scaleBy);
|
|
holder = floorf(value * CONVERSION_RATIO);
|
|
memcpy(buffer, &holder, sizeof(holder));
|
|
return sizeof(holder);
|
|
}
|
|
|
|
int unpackFloatFromByte(const unsigned char* buffer, float& value, float scaleBy) {
|
|
quint8 holder;
|
|
memcpy(&holder, buffer, sizeof(holder));
|
|
value = ((float)holder / (float) 255) * scaleBy;
|
|
return sizeof(holder);
|
|
}
|
|
|
|
unsigned char debug::DEADBEEF[] = { 0xDE, 0xAD, 0xBE, 0xEF };
|
|
int debug::DEADBEEF_SIZE = sizeof(DEADBEEF);
|
|
void debug::setDeadBeef(void* memoryVoid, int size) {
|
|
unsigned char* memoryAt = (unsigned char*)memoryVoid;
|
|
int deadBeefSet = 0;
|
|
int chunks = size / DEADBEEF_SIZE;
|
|
for (int i = 0; i < chunks; i++) {
|
|
memcpy(memoryAt + (i * DEADBEEF_SIZE), DEADBEEF, DEADBEEF_SIZE);
|
|
deadBeefSet += DEADBEEF_SIZE;
|
|
}
|
|
memcpy(memoryAt + deadBeefSet, DEADBEEF, size - deadBeefSet);
|
|
}
|
|
|
|
void debug::checkDeadBeef(void* memoryVoid, int size) {
|
|
assert(memcmp((unsigned char*)memoryVoid, DEADBEEF, std::min(size, DEADBEEF_SIZE)) != 0);
|
|
}
|
|
|
|
|
|
// glm::abs() works for signed or unsigned types
|
|
template <typename T>
|
|
QString formatUsecTime(T usecs) {
|
|
static const int PRECISION = 3;
|
|
static const int FRACTION_MASK = pow(10, PRECISION);
|
|
|
|
static const T USECS_PER_MSEC = 1000;
|
|
static const T USECS_PER_SECOND = 1000 * USECS_PER_MSEC;
|
|
static const T USECS_PER_MINUTE = USECS_PER_SECOND * 60;
|
|
static const T USECS_PER_HOUR = USECS_PER_MINUTE * 60;
|
|
|
|
QString result;
|
|
if (glm::abs(usecs) > USECS_PER_HOUR) {
|
|
if (std::is_integral<T>::value) {
|
|
result = QString::number(usecs / USECS_PER_HOUR);
|
|
result += "." + QString::number(((int)(usecs * FRACTION_MASK / USECS_PER_HOUR)) % FRACTION_MASK);
|
|
} else {
|
|
result = QString::number(usecs / USECS_PER_HOUR, 'f', PRECISION);
|
|
}
|
|
result += " hrs";
|
|
} else if (glm::abs(usecs) > USECS_PER_MINUTE) {
|
|
if (std::is_integral<T>::value) {
|
|
result = QString::number(usecs / USECS_PER_MINUTE);
|
|
result += "." + QString::number(((int)(usecs * FRACTION_MASK / USECS_PER_MINUTE)) % FRACTION_MASK);
|
|
} else {
|
|
result = QString::number(usecs / USECS_PER_MINUTE, 'f', PRECISION);
|
|
}
|
|
result += " mins";
|
|
} else if (glm::abs(usecs) > USECS_PER_SECOND) {
|
|
if (std::is_integral<T>::value) {
|
|
result = QString::number(usecs / USECS_PER_SECOND);
|
|
result += "." + QString::number(((int)(usecs * FRACTION_MASK / USECS_PER_SECOND)) % FRACTION_MASK);
|
|
} else {
|
|
result = QString::number(usecs / USECS_PER_SECOND, 'f', PRECISION);
|
|
}
|
|
result += " secs";
|
|
} else if (glm::abs(usecs) > USECS_PER_MSEC) {
|
|
if (std::is_integral<T>::value) {
|
|
result = QString::number(usecs / USECS_PER_MSEC);
|
|
result += "." + QString::number(((int)(usecs * FRACTION_MASK / USECS_PER_MSEC)) % FRACTION_MASK);
|
|
} else {
|
|
result = QString::number(usecs / USECS_PER_MSEC, 'f', PRECISION);
|
|
}
|
|
result += " msecs";
|
|
} else {
|
|
result = QString::number(usecs) + " usecs";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
QString formatUsecTime(quint64 usecs) {
|
|
return formatUsecTime<quint64>(usecs);
|
|
}
|
|
|
|
QString formatUsecTime(qint64 usecs) {
|
|
return formatUsecTime<qint64>(usecs);
|
|
}
|
|
|
|
QString formatUsecTime(float usecs) {
|
|
return formatUsecTime<float>(usecs);
|
|
}
|
|
|
|
QString formatUsecTime(double usecs) {
|
|
return formatUsecTime<double>(usecs);
|
|
}
|
|
|
|
|
|
QString formatSecondsElapsed(float seconds) {
|
|
QString result;
|
|
|
|
const float SECONDS_IN_DAY = 60.0f * 60.0f * 24.0f;
|
|
if (seconds > SECONDS_IN_DAY) {
|
|
float days = floor(seconds / SECONDS_IN_DAY);
|
|
float rest = seconds - (days * SECONDS_IN_DAY);
|
|
result = QString::number((int)days);
|
|
if (days > 1.0f) {
|
|
result += " days ";
|
|
} else {
|
|
result += " day ";
|
|
}
|
|
result += QDateTime::fromTime_t(rest).toUTC().toString("h 'hours' m 'minutes' s 'seconds'");
|
|
} else {
|
|
result = QDateTime::fromTime_t(seconds).toUTC().toString("h 'hours' m 'minutes' s 'seconds'");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool similarStrings(const QString& stringA, const QString& stringB) {
|
|
QStringList aWords = stringA.split(" ");
|
|
QStringList bWords = stringB.split(" ");
|
|
float aWordsInB = 0.0f;
|
|
foreach(QString aWord, aWords) {
|
|
if (bWords.contains(aWord)) {
|
|
aWordsInB += 1.0f;
|
|
}
|
|
}
|
|
float bWordsInA = 0.0f;
|
|
foreach(QString bWord, bWords) {
|
|
if (aWords.contains(bWord)) {
|
|
bWordsInA += 1.0f;
|
|
}
|
|
}
|
|
float similarity = 0.5f * (aWordsInB / (float)bWords.size()) + 0.5f * (bWordsInA / (float)aWords.size());
|
|
const float SIMILAR_ENOUGH = 0.5f; // half the words the same is similar enough for us
|
|
return similarity >= SIMILAR_ENOUGH;
|
|
}
|
|
|
|
void disableQtBearerPoll() {
|
|
// to disable the Qt constant wireless scanning, set the env for polling interval
|
|
qDebug() << "Disabling Qt wireless polling by using a negative value for QTimer::setInterval";
|
|
const QByteArray DISABLE_BEARER_POLL_TIMEOUT = QString::number(-1).toLocal8Bit();
|
|
qputenv("QT_BEARER_POLL_TIMEOUT", DISABLE_BEARER_POLL_TIMEOUT);
|
|
}
|
|
|
|
void printSystemInformation() {
|
|
// Write system information to log
|
|
qCDebug(shared) << "Build Information";
|
|
qCDebug(shared).noquote() << "\tBuild ABI: " << QSysInfo::buildAbi();
|
|
qCDebug(shared).noquote() << "\tBuild CPU Architecture: " << QSysInfo::buildCpuArchitecture();
|
|
|
|
qCDebug(shared).noquote() << "System Information";
|
|
qCDebug(shared).noquote() << "\tProduct Name: " << QSysInfo::prettyProductName();
|
|
qCDebug(shared).noquote() << "\tCPU Architecture: " << QSysInfo::currentCpuArchitecture();
|
|
qCDebug(shared).noquote() << "\tKernel Type: " << QSysInfo::kernelType();
|
|
qCDebug(shared).noquote() << "\tKernel Version: " << QSysInfo::kernelVersion();
|
|
|
|
auto macVersion = QSysInfo::macVersion();
|
|
if (macVersion != QSysInfo::MV_None) {
|
|
qCDebug(shared) << "\tMac Version: " << macVersion;
|
|
}
|
|
|
|
auto windowsVersion = QSysInfo::windowsVersion();
|
|
if (windowsVersion != QSysInfo::WV_None) {
|
|
qCDebug(shared) << "\tWindows Version: " << windowsVersion;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
SYSTEM_INFO si;
|
|
GetNativeSystemInfo(&si);
|
|
|
|
qCDebug(shared) << "SYSTEM_INFO";
|
|
qCDebug(shared).noquote() << "\tOEM ID: " << si.dwOemId;
|
|
qCDebug(shared).noquote() << "\tProcessor Architecture: " << si.wProcessorArchitecture;
|
|
qCDebug(shared).noquote() << "\tProcessor Type: " << si.dwProcessorType;
|
|
qCDebug(shared).noquote() << "\tProcessor Level: " << si.wProcessorLevel;
|
|
qCDebug(shared).noquote() << "\tProcessor Revision: "
|
|
<< QString("0x%1").arg(si.wProcessorRevision, 4, 16, QChar('0'));
|
|
qCDebug(shared).noquote() << "\tNumber of Processors: " << si.dwNumberOfProcessors;
|
|
qCDebug(shared).noquote() << "\tPage size: " << si.dwPageSize << " Bytes";
|
|
qCDebug(shared).noquote() << "\tMin Application Address: "
|
|
<< QString("0x%1").arg(qulonglong(si.lpMinimumApplicationAddress), 16, 16, QChar('0'));
|
|
qCDebug(shared).noquote() << "\tMax Application Address: "
|
|
<< QString("0x%1").arg(qulonglong(si.lpMaximumApplicationAddress), 16, 16, QChar('0'));
|
|
|
|
const double BYTES_TO_MEGABYTE = 1.0 / (1024 * 1024);
|
|
|
|
qCDebug(shared) << "MEMORYSTATUSEX";
|
|
MEMORYSTATUSEX ms;
|
|
ms.dwLength = sizeof(ms);
|
|
if (GlobalMemoryStatusEx(&ms)) {
|
|
qCDebug(shared).noquote()
|
|
<< QString("\tCurrent System Memory Usage: %1%").arg(ms.dwMemoryLoad);
|
|
qCDebug(shared).noquote()
|
|
<< QString("\tAvail Physical Memory: %1 MB").arg(ms.ullAvailPhys * BYTES_TO_MEGABYTE, 20, 'f', 2);
|
|
qCDebug(shared).noquote()
|
|
<< QString("\tTotal Physical Memory: %1 MB").arg(ms.ullTotalPhys * BYTES_TO_MEGABYTE, 20, 'f', 2);
|
|
qCDebug(shared).noquote()
|
|
<< QString("\tAvail in Page File: %1 MB").arg(ms.ullAvailPageFile * BYTES_TO_MEGABYTE, 20, 'f', 2);
|
|
qCDebug(shared).noquote()
|
|
<< QString("\tTotal in Page File: %1 MB").arg(ms.ullTotalPageFile * BYTES_TO_MEGABYTE, 20, 'f', 2);
|
|
qCDebug(shared).noquote()
|
|
<< QString("\tAvail Virtual Memory: %1 MB").arg(ms.ullAvailVirtual * BYTES_TO_MEGABYTE, 20, 'f', 2);
|
|
qCDebug(shared).noquote()
|
|
<< QString("\tTotal Virtual Memory: %1 MB").arg(ms.ullTotalVirtual * BYTES_TO_MEGABYTE, 20, 'f', 2);
|
|
} else {
|
|
qCDebug(shared) << "\tFailed to retrieve memory status: " << GetLastError();
|
|
}
|
|
|
|
qCDebug(shared) << "CPUID";
|
|
|
|
qCDebug(shared) << "\tCPU Vendor: " << CPUIdent::Vendor().c_str();
|
|
qCDebug(shared) << "\tCPU Brand: " << CPUIdent::Brand().c_str();
|
|
|
|
for (auto& feature : CPUIdent::getAllFeatures()) {
|
|
qCDebug(shared).nospace().noquote() << "\t[" << (feature.supported ? "x" : " ") << "] " << feature.name.c_str();
|
|
}
|
|
#endif
|
|
|
|
qCDebug(shared) << "Environment Variables";
|
|
// List of env variables to include in the log. For privacy reasons we don't send all env variables.
|
|
const QStringList envWhitelist = {
|
|
"QTWEBENGINE_REMOTE_DEBUGGING"
|
|
};
|
|
auto envVariables = QProcessEnvironment::systemEnvironment();
|
|
for (auto& env : envWhitelist)
|
|
{
|
|
qCDebug(shared).noquote().nospace() << "\t" <<
|
|
(envVariables.contains(env) ? " = " + envVariables.value(env) : " NOT FOUND");
|
|
}
|
|
}
|
|
|
|
bool getMemoryInfo(MemoryInfo& info) {
|
|
#ifdef Q_OS_WIN
|
|
MEMORYSTATUSEX ms;
|
|
ms.dwLength = sizeof(ms);
|
|
if (!GlobalMemoryStatusEx(&ms)) {
|
|
return false;
|
|
}
|
|
|
|
info.totalMemoryBytes = ms.ullTotalPhys;
|
|
info.availMemoryBytes = ms.ullAvailPhys;
|
|
info.usedMemoryBytes = ms.ullTotalPhys - ms.ullAvailPhys;
|
|
|
|
|
|
PROCESS_MEMORY_COUNTERS_EX pmc;
|
|
if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmc), sizeof(pmc))) {
|
|
return false;
|
|
}
|
|
info.processUsedMemoryBytes = pmc.PrivateUsage;
|
|
info.processPeakUsedMemoryBytes = pmc.PeakPagefileUsage;
|
|
|
|
return true;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
// Largely taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/ms683194(v=vs.85).aspx
|
|
|
|
#ifdef Q_OS_WIN
|
|
using LPFN_GLPI = BOOL(WINAPI*)(
|
|
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION,
|
|
PDWORD);
|
|
|
|
DWORD CountSetBits(ULONG_PTR bitMask)
|
|
{
|
|
DWORD LSHIFT = sizeof(ULONG_PTR) * 8 - 1;
|
|
DWORD bitSetCount = 0;
|
|
ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT;
|
|
DWORD i;
|
|
|
|
for (i = 0; i <= LSHIFT; ++i) {
|
|
bitSetCount += ((bitMask & bitTest) ? 1 : 0);
|
|
bitTest /= 2;
|
|
}
|
|
|
|
return bitSetCount;
|
|
}
|
|
#endif
|
|
|
|
bool getProcessorInfo(ProcessorInfo& info) {
|
|
|
|
#ifdef Q_OS_WIN
|
|
LPFN_GLPI glpi;
|
|
bool done = false;
|
|
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL;
|
|
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL;
|
|
DWORD returnLength = 0;
|
|
DWORD logicalProcessorCount = 0;
|
|
DWORD numaNodeCount = 0;
|
|
DWORD processorCoreCount = 0;
|
|
DWORD processorL1CacheCount = 0;
|
|
DWORD processorL2CacheCount = 0;
|
|
DWORD processorL3CacheCount = 0;
|
|
DWORD processorPackageCount = 0;
|
|
DWORD byteOffset = 0;
|
|
PCACHE_DESCRIPTOR Cache;
|
|
|
|
glpi = (LPFN_GLPI)GetProcAddress(
|
|
GetModuleHandle(TEXT("kernel32")),
|
|
"GetLogicalProcessorInformation");
|
|
if (nullptr == glpi) {
|
|
qCDebug(shared) << "GetLogicalProcessorInformation is not supported.";
|
|
return false;
|
|
}
|
|
|
|
while (!done) {
|
|
DWORD rc = glpi(buffer, &returnLength);
|
|
|
|
if (FALSE == rc) {
|
|
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
if (buffer) {
|
|
free(buffer);
|
|
}
|
|
|
|
buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(
|
|
returnLength);
|
|
|
|
if (NULL == buffer) {
|
|
qCDebug(shared) << "Error: Allocation failure";
|
|
return false;
|
|
}
|
|
} else {
|
|
qCDebug(shared) << "Error " << GetLastError();
|
|
return false;
|
|
}
|
|
} else {
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
ptr = buffer;
|
|
|
|
while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) {
|
|
switch (ptr->Relationship) {
|
|
case RelationNumaNode:
|
|
// Non-NUMA systems report a single record of this type.
|
|
numaNodeCount++;
|
|
break;
|
|
|
|
case RelationProcessorCore:
|
|
processorCoreCount++;
|
|
|
|
// A hyperthreaded core supplies more than one logical processor.
|
|
logicalProcessorCount += CountSetBits(ptr->ProcessorMask);
|
|
break;
|
|
|
|
case RelationCache:
|
|
// Cache data is in ptr->Cache, one CACHE_DESCRIPTOR structure for each cache.
|
|
Cache = &ptr->Cache;
|
|
if (Cache->Level == 1) {
|
|
processorL1CacheCount++;
|
|
} else if (Cache->Level == 2) {
|
|
processorL2CacheCount++;
|
|
} else if (Cache->Level == 3) {
|
|
processorL3CacheCount++;
|
|
}
|
|
break;
|
|
|
|
case RelationProcessorPackage:
|
|
// Logical processors share a physical package.
|
|
processorPackageCount++;
|
|
break;
|
|
|
|
default:
|
|
qCDebug(shared) << "\nError: Unsupported LOGICAL_PROCESSOR_RELATIONSHIP value.\n";
|
|
break;
|
|
}
|
|
byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
|
|
ptr++;
|
|
}
|
|
|
|
qCDebug(shared) << "GetLogicalProcessorInformation results:";
|
|
qCDebug(shared) << "Number of NUMA nodes:" << numaNodeCount;
|
|
qCDebug(shared) << "Number of physical processor packages:" << processorPackageCount;
|
|
qCDebug(shared) << "Number of processor cores:" << processorCoreCount;
|
|
qCDebug(shared) << "Number of logical processors:" << logicalProcessorCount;
|
|
qCDebug(shared) << "Number of processor L1/L2/L3 caches:"
|
|
<< processorL1CacheCount
|
|
<< "/" << processorL2CacheCount
|
|
<< "/" << processorL3CacheCount;
|
|
|
|
info.numPhysicalProcessorPackages = processorPackageCount;
|
|
info.numProcessorCores = processorCoreCount;
|
|
info.numLogicalProcessors = logicalProcessorCount;
|
|
info.numProcessorCachesL1 = processorL1CacheCount;
|
|
info.numProcessorCachesL2 = processorL2CacheCount;
|
|
info.numProcessorCachesL3 = processorL3CacheCount;
|
|
|
|
free(buffer);
|
|
|
|
return true;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
const QString& getInterfaceSharedMemoryName() {
|
|
static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
|
|
return applicationName;
|
|
}
|
|
|
|
const std::vector<uint8_t>& getAvailableCores() {
|
|
static std::vector<uint8_t> availableCores;
|
|
#ifdef Q_OS_WIN
|
|
static std::once_flag once;
|
|
std::call_once(once, [&] {
|
|
DWORD_PTR defaultProcessAffinity = 0, defaultSystemAffinity = 0;
|
|
HANDLE process = GetCurrentProcess();
|
|
GetProcessAffinityMask(process, &defaultProcessAffinity, &defaultSystemAffinity);
|
|
for (uint64_t i = 0; i < sizeof(DWORD_PTR) * BITS_IN_BYTE; ++i) {
|
|
DWORD_PTR coreMask = 1;
|
|
coreMask <<= i;
|
|
if (0 != (defaultSystemAffinity & coreMask)) {
|
|
availableCores.push_back(i);
|
|
}
|
|
}
|
|
});
|
|
#endif
|
|
return availableCores;
|
|
}
|
|
|
|
void setMaxCores(uint8_t maxCores) {
|
|
#ifdef Q_OS_WIN
|
|
HANDLE process = GetCurrentProcess();
|
|
auto availableCores = getAvailableCores();
|
|
if (availableCores.size() <= maxCores) {
|
|
DWORD_PTR currentProcessAffinity = 0, currentSystemAffinity = 0;
|
|
GetProcessAffinityMask(process, ¤tProcessAffinity, ¤tSystemAffinity);
|
|
SetProcessAffinityMask(GetCurrentProcess(), currentSystemAffinity);
|
|
return;
|
|
}
|
|
|
|
DWORD_PTR newProcessAffinity = 0;
|
|
while (maxCores) {
|
|
int index = randIntInRange(0, (int)availableCores.size() - 1);
|
|
DWORD_PTR coreMask = 1;
|
|
coreMask <<= availableCores[index];
|
|
newProcessAffinity |= coreMask;
|
|
availableCores.erase(availableCores.begin() + index);
|
|
maxCores--;
|
|
}
|
|
SetProcessAffinityMask(process, newProcessAffinity);
|
|
#endif
|
|
}
|
|
|
|
bool processIsRunning(int64_t pid) {
|
|
#ifdef Q_OS_WIN
|
|
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
|
if (process) {
|
|
DWORD exitCode;
|
|
if (GetExitCodeProcess(process, &exitCode) != 0) {
|
|
return exitCode == STILL_ACTIVE;
|
|
}
|
|
}
|
|
return false;
|
|
#else
|
|
if (kill(pid, 0) == -1) {
|
|
return errno != ESRCH;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void quitWithParentProcess() {
|
|
if (qApp) {
|
|
qDebug() << "Parent process died, quitting";
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
VOID CALLBACK parentDiedCallback(PVOID lpParameter, BOOLEAN timerOrWaitFired) {
|
|
if (!timerOrWaitFired) {
|
|
quitWithParentProcess();
|
|
}
|
|
}
|
|
|
|
void watchParentProcess(int parentPID) {
|
|
DWORD processID = parentPID;
|
|
HANDLE procHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
|
|
|
|
HANDLE newHandle;
|
|
RegisterWaitForSingleObject(&newHandle, procHandle, parentDiedCallback, NULL, INFINITE, WT_EXECUTEONLYONCE);
|
|
}
|
|
#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX)
|
|
void watchParentProcess(int parentPID) {
|
|
auto timer = new QTimer(qApp);
|
|
timer->setInterval(MSECS_PER_SECOND);
|
|
QObject::connect(timer, &QTimer::timeout, qApp, [parentPID]() {
|
|
auto ppid = getppid();
|
|
if (parentPID != ppid) {
|
|
// If the PPID changed, then that means our parent process died.
|
|
quitWithParentProcess();
|
|
}
|
|
});
|
|
timer->start();
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
QString getLastErrorAsString() {
|
|
DWORD errorMessageID = ::GetLastError();
|
|
if (errorMessageID == 0) {
|
|
return QString();
|
|
}
|
|
|
|
LPSTR messageBuffer = nullptr;
|
|
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
nullptr, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, nullptr);
|
|
|
|
auto message = QString::fromLocal8Bit(messageBuffer, (int)size);
|
|
|
|
//Free the buffer.
|
|
LocalFree(messageBuffer);
|
|
|
|
return message;
|
|
}
|
|
|
|
// All processes in the group will shut down with the process creating the group
|
|
void* createProcessGroup() {
|
|
HANDLE jobObject = CreateJobObject(nullptr, nullptr);
|
|
if (jobObject == nullptr) {
|
|
qWarning() << "Could NOT create job object:" << getLastErrorAsString();
|
|
return nullptr;
|
|
}
|
|
|
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION JELI;
|
|
if (!QueryInformationJobObject(jobObject, JobObjectExtendedLimitInformation, &JELI, sizeof(JELI), nullptr)) {
|
|
qWarning() << "Could NOT query job object information" << getLastErrorAsString();
|
|
return nullptr;
|
|
}
|
|
JELI.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
if (!SetInformationJobObject(jobObject, JobObjectExtendedLimitInformation, &JELI, sizeof(JELI))) {
|
|
qWarning() << "Could NOT set job object information" << getLastErrorAsString();
|
|
return nullptr;
|
|
}
|
|
|
|
return jobObject;
|
|
}
|
|
|
|
void addProcessToGroup(void* processGroup, qint64 processId) {
|
|
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
|
|
if (hProcess == nullptr) {
|
|
qCritical() << "Could NOT open process" << getLastErrorAsString();
|
|
}
|
|
if (!AssignProcessToJobObject(processGroup, hProcess)) {
|
|
qCritical() << "Could NOT assign process to job object" << getLastErrorAsString();
|
|
}
|
|
}
|
|
|
|
#endif
|