mirror of
https://github.com/lubosz/overte.git
synced 2025-08-07 16:41:02 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into 21559-increaseComparisonThreshold
This commit is contained in:
commit
53d682d40e
66 changed files with 1608 additions and 711 deletions
|
@ -64,10 +64,6 @@ bool AudioMixerSlaveThread::try_pop(SharedNodePointer& node) {
|
||||||
return _pool._queue.try_pop(node);
|
return _pool._queue.try_pop(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef AUDIO_SINGLE_THREADED
|
|
||||||
static AudioMixerSlave slave;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) {
|
void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) {
|
||||||
_function = &AudioMixerSlave::processPackets;
|
_function = &AudioMixerSlave::processPackets;
|
||||||
_configure = [](AudioMixerSlave& slave) {};
|
_configure = [](AudioMixerSlave& slave) {};
|
||||||
|
@ -87,19 +83,9 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
||||||
_begin = begin;
|
_begin = begin;
|
||||||
_end = end;
|
_end = end;
|
||||||
|
|
||||||
#ifdef AUDIO_SINGLE_THREADED
|
|
||||||
_configure(slave);
|
|
||||||
std::for_each(begin, end, [&](const SharedNodePointer& node) {
|
|
||||||
_function(slave, node);
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
// fill the queue
|
// fill the queue
|
||||||
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
|
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
|
||||||
#if defined(__clang__) && defined(Q_OS_LINUX)
|
|
||||||
_queue.push(node);
|
_queue.push(node);
|
||||||
#else
|
|
||||||
_queue.emplace(node);
|
|
||||||
#endif
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -119,17 +105,12 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(_queue.empty());
|
assert(_queue.empty());
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixerSlavePool::each(std::function<void(AudioMixerSlave& slave)> functor) {
|
void AudioMixerSlavePool::each(std::function<void(AudioMixerSlave& slave)> functor) {
|
||||||
#ifdef AUDIO_SINGLE_THREADED
|
|
||||||
functor(slave);
|
|
||||||
#else
|
|
||||||
for (auto& slave : _slaves) {
|
for (auto& slave : _slaves) {
|
||||||
functor(*slave.get());
|
functor(*slave.get());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMixerSlavePool::setNumThreads(int numThreads) {
|
void AudioMixerSlavePool::setNumThreads(int numThreads) {
|
||||||
|
@ -155,9 +136,6 @@ void AudioMixerSlavePool::setNumThreads(int numThreads) {
|
||||||
void AudioMixerSlavePool::resize(int numThreads) {
|
void AudioMixerSlavePool::resize(int numThreads) {
|
||||||
assert(_numThreads == (int)_slaves.size());
|
assert(_numThreads == (int)_slaves.size());
|
||||||
|
|
||||||
#ifdef AUDIO_SINGLE_THREADED
|
|
||||||
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
|
|
||||||
#else
|
|
||||||
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
|
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
|
||||||
|
|
||||||
Lock lock(_mutex);
|
Lock lock(_mutex);
|
||||||
|
@ -205,5 +183,4 @@ void AudioMixerSlavePool::resize(int numThreads) {
|
||||||
|
|
||||||
_numThreads = _numStarted = _numFinished = numThreads;
|
_numThreads = _numStarted = _numFinished = numThreads;
|
||||||
assert(_numThreads == (int)_slaves.size());
|
assert(_numThreads == (int)_slaves.size());
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <QtCore/QRegularExpression>
|
#include <QtCore/QRegularExpression>
|
||||||
#include <QtCore/QTimer>
|
#include <QtCore/QTimer>
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
#include <AABox.h>
|
#include <AABox.h>
|
||||||
#include <AvatarLogging.h>
|
#include <AvatarLogging.h>
|
||||||
|
@ -32,6 +33,8 @@
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include <UUID.h>
|
#include <UUID.h>
|
||||||
#include <TryLocker.h>
|
#include <TryLocker.h>
|
||||||
|
#include "../AssignmentDynamicFactory.h"
|
||||||
|
#include "../entities/AssignmentParentFinder.h"
|
||||||
|
|
||||||
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
||||||
|
|
||||||
|
@ -55,6 +58,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
||||||
ThreadedAssignment(message),
|
ThreadedAssignment(message),
|
||||||
_slavePool(&_slaveSharedData)
|
_slavePool(&_slaveSharedData)
|
||||||
{
|
{
|
||||||
|
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||||
|
DependencyManager::set<AssignmentDynamicFactory>();
|
||||||
|
|
||||||
// make sure we hear about node kills so we can tell the other nodes
|
// make sure we hear about node kills so we can tell the other nodes
|
||||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
|
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
|
||||||
|
|
||||||
|
@ -69,6 +75,8 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
||||||
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
|
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
|
||||||
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
|
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
|
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
|
||||||
|
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||||
|
this, "handleOctreePacket");
|
||||||
|
|
||||||
packetReceiver.registerListenerForTypes({
|
packetReceiver.registerListenerForTypes({
|
||||||
PacketType::ReplicatedAvatarIdentity,
|
PacketType::ReplicatedAvatarIdentity,
|
||||||
|
@ -240,6 +248,10 @@ void AvatarMixer::start() {
|
||||||
|
|
||||||
int lockWait, nodeTransform, functor;
|
int lockWait, nodeTransform, functor;
|
||||||
|
|
||||||
|
{
|
||||||
|
_entityViewer.queryOctree();
|
||||||
|
}
|
||||||
|
|
||||||
// Allow nodes to process any pending/queued packets across our worker threads
|
// Allow nodes to process any pending/queued packets across our worker threads
|
||||||
{
|
{
|
||||||
auto start = usecTimestampNow();
|
auto start = usecTimestampNow();
|
||||||
|
@ -252,6 +264,10 @@ void AvatarMixer::start() {
|
||||||
}, &lockWait, &nodeTransform, &functor);
|
}, &lockWait, &nodeTransform, &functor);
|
||||||
auto end = usecTimestampNow();
|
auto end = usecTimestampNow();
|
||||||
_processQueuedAvatarDataPacketsElapsedTime += (end - start);
|
_processQueuedAvatarDataPacketsElapsedTime += (end - start);
|
||||||
|
|
||||||
|
_broadcastAvatarDataLockWait += lockWait;
|
||||||
|
_broadcastAvatarDataNodeTransform += nodeTransform;
|
||||||
|
_broadcastAvatarDataNodeFunctor += functor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// process pending display names... this doesn't currently run on multiple threads, because it
|
// process pending display names... this doesn't currently run on multiple threads, because it
|
||||||
|
@ -269,6 +285,10 @@ void AvatarMixer::start() {
|
||||||
}, &lockWait, &nodeTransform, &functor);
|
}, &lockWait, &nodeTransform, &functor);
|
||||||
auto end = usecTimestampNow();
|
auto end = usecTimestampNow();
|
||||||
_displayNameManagementElapsedTime += (end - start);
|
_displayNameManagementElapsedTime += (end - start);
|
||||||
|
|
||||||
|
_broadcastAvatarDataLockWait += lockWait;
|
||||||
|
_broadcastAvatarDataNodeTransform += nodeTransform;
|
||||||
|
_broadcastAvatarDataNodeFunctor += functor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is where we need to put the real work...
|
// this is where we need to put the real work...
|
||||||
|
@ -691,8 +711,11 @@ void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarMixer::sendStatsPacket() {
|
void AvatarMixer::sendStatsPacket() {
|
||||||
auto start = usecTimestampNow();
|
if (!_numTightLoopFrames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto start = usecTimestampNow();
|
||||||
|
|
||||||
QJsonObject statsObject;
|
QJsonObject statsObject;
|
||||||
|
|
||||||
|
@ -775,6 +798,7 @@ void AvatarMixer::sendStatsPacket() {
|
||||||
slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent);
|
slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent);
|
||||||
slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent);
|
slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent);
|
||||||
slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent);
|
slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent);
|
||||||
|
slavesAggregatObject["sent_7_averageHeroAvatars"] = TIGHT_LOOP_STAT(aggregateStats.numHeroesIncluded);
|
||||||
|
|
||||||
slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime);
|
slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime);
|
||||||
slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime);
|
slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime);
|
||||||
|
@ -882,13 +906,15 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node
|
||||||
void AvatarMixer::domainSettingsRequestComplete() {
|
void AvatarMixer::domainSettingsRequestComplete() {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
nodeList->addSetOfNodeTypesToNodeInterestSet({
|
nodeList->addSetOfNodeTypesToNodeInterestSet({
|
||||||
NodeType::Agent, NodeType::EntityScriptServer,
|
NodeType::Agent, NodeType::EntityScriptServer, NodeType::EntityServer,
|
||||||
NodeType::UpstreamAvatarMixer, NodeType::DownstreamAvatarMixer
|
NodeType::UpstreamAvatarMixer, NodeType::DownstreamAvatarMixer
|
||||||
});
|
});
|
||||||
|
|
||||||
// parse the settings to pull out the values we need
|
// parse the settings to pull out the values we need
|
||||||
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());
|
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());
|
||||||
|
|
||||||
|
setupEntityQuery();
|
||||||
|
|
||||||
// start our tight loop...
|
// start our tight loop...
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
@ -939,6 +965,14 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
||||||
qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads.";
|
qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const QString CONNECTION_RATE = "connection_rate";
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
auto defaultConnectionRate = nodeList->getMaxConnectionRate();
|
||||||
|
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt((int)defaultConnectionRate);
|
||||||
|
nodeList->setMaxConnectionRate(connectionRate);
|
||||||
|
}
|
||||||
|
|
||||||
const QString AVATARS_SETTINGS_KEY = "avatars";
|
const QString AVATARS_SETTINGS_KEY = "avatars";
|
||||||
|
|
||||||
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
|
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
|
||||||
|
@ -976,3 +1010,58 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
||||||
qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString());
|
qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarMixer::setupEntityQuery() {
|
||||||
|
_entityViewer.init();
|
||||||
|
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||||
|
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
|
||||||
|
_slaveSharedData.entityTree = _entityViewer.getTree();
|
||||||
|
|
||||||
|
// ES query: {"avatarPriority": true, "type": "Zone"}
|
||||||
|
QJsonObject priorityZoneQuery;
|
||||||
|
priorityZoneQuery["avatarPriority"] = true;
|
||||||
|
priorityZoneQuery["type"] = "Zone";
|
||||||
|
|
||||||
|
_entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||||
|
PacketType packetType = message->getType();
|
||||||
|
|
||||||
|
switch (packetType) {
|
||||||
|
case PacketType::OctreeStats:
|
||||||
|
{ // Ignore stats, but may have a different Entity packet appended.
|
||||||
|
OctreeHeadlessViewer::parseOctreeStats(message, senderNode);
|
||||||
|
const auto piggyBackedSizeWithHeader = message->getBytesLeftToRead();
|
||||||
|
if (piggyBackedSizeWithHeader > 0) {
|
||||||
|
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
|
||||||
|
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
|
||||||
|
memcpy(buffer.get(), message->getRawMessage() + message->getPosition(), piggyBackedSizeWithHeader);
|
||||||
|
|
||||||
|
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr());
|
||||||
|
auto newMessage = QSharedPointer<ReceivedMessage>::create(*newPacket);
|
||||||
|
handleOctreePacket(newMessage, senderNode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PacketType::EntityData:
|
||||||
|
_entityViewer.processDatagram(*message, senderNode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PacketType::EntityErase:
|
||||||
|
_entityViewer.processEraseMessage(*message, senderNode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
qCDebug(avatars) << "Unexpected packet type:" << packetType;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarMixer::aboutToFinish() {
|
||||||
|
DependencyManager::destroy<AssignmentDynamicFactory>();
|
||||||
|
DependencyManager::destroy<AssignmentParentFinder>();
|
||||||
|
|
||||||
|
ThreadedAssignment::aboutToFinish();
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <PortableHighResolutionClock.h>
|
#include <PortableHighResolutionClock.h>
|
||||||
|
|
||||||
#include <ThreadedAssignment.h>
|
#include <ThreadedAssignment.h>
|
||||||
|
#include "../entities/EntityTreeHeadlessViewer.h"
|
||||||
#include "AvatarMixerClientData.h"
|
#include "AvatarMixerClientData.h"
|
||||||
|
|
||||||
#include "AvatarMixerSlavePool.h"
|
#include "AvatarMixerSlavePool.h"
|
||||||
|
@ -29,6 +30,7 @@ class AvatarMixer : public ThreadedAssignment {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AvatarMixer(ReceivedMessage& message);
|
AvatarMixer(ReceivedMessage& message);
|
||||||
|
virtual void aboutToFinish() override;
|
||||||
|
|
||||||
static bool shouldReplicateTo(const Node& from, const Node& to) {
|
static bool shouldReplicateTo(const Node& from, const Node& to) {
|
||||||
return to.getType() == NodeType::DownstreamAvatarMixer &&
|
return to.getType() == NodeType::DownstreamAvatarMixer &&
|
||||||
|
@ -57,6 +59,7 @@ private slots:
|
||||||
void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message);
|
void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
void domainSettingsRequestComplete();
|
void domainSettingsRequestComplete();
|
||||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||||
|
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||||
void start();
|
void start();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -71,8 +74,13 @@ private:
|
||||||
|
|
||||||
void optionallyReplicatePacket(ReceivedMessage& message, const Node& node);
|
void optionallyReplicatePacket(ReceivedMessage& message, const Node& node);
|
||||||
|
|
||||||
|
void setupEntityQuery();
|
||||||
|
|
||||||
p_high_resolution_clock::time_point _lastFrameTimestamp;
|
p_high_resolution_clock::time_point _lastFrameTimestamp;
|
||||||
|
|
||||||
|
// Attach to entity tree for avatar-priority zone info.
|
||||||
|
EntityTreeHeadlessViewer _entityViewer;
|
||||||
|
|
||||||
// FIXME - new throttling - use these values somehow
|
// FIXME - new throttling - use these values somehow
|
||||||
float _trailingMixRatio { 0.0f };
|
float _trailingMixRatio { 0.0f };
|
||||||
float _throttlingRatio { 0.0f };
|
float _throttlingRatio { 0.0f };
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
|
#include <EntityTree.h>
|
||||||
|
#include <ZoneEntityItem.h>
|
||||||
|
|
||||||
|
#include "AvatarLogging.h"
|
||||||
|
|
||||||
#include "AvatarMixerSlave.h"
|
#include "AvatarMixerSlave.h"
|
||||||
|
|
||||||
|
@ -62,7 +66,7 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
|
||||||
|
|
||||||
switch (packet->getType()) {
|
switch (packet->getType()) {
|
||||||
case PacketType::AvatarData:
|
case PacketType::AvatarData:
|
||||||
parseData(*packet);
|
parseData(*packet, slaveSharedData);
|
||||||
break;
|
break;
|
||||||
case PacketType::SetAvatarTraits:
|
case PacketType::SetAvatarTraits:
|
||||||
processSetTraitsMessage(*packet, slaveSharedData, *node);
|
processSetTraitsMessage(*packet, slaveSharedData, *node);
|
||||||
|
@ -80,7 +84,42 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
|
||||||
return packetsProcessed;
|
return packetsProcessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AvatarMixerClientData::parseData(ReceivedMessage& message) {
|
namespace {
|
||||||
|
using std::static_pointer_cast;
|
||||||
|
|
||||||
|
// Operator to find if a point is within an avatar-priority (hero) Zone Entity.
|
||||||
|
struct FindPriorityZone {
|
||||||
|
glm::vec3 position;
|
||||||
|
bool isInPriorityZone { false };
|
||||||
|
float zoneVolume { std::numeric_limits<float>::max() };
|
||||||
|
|
||||||
|
static bool operation(const OctreeElementPointer& element, void* extraData) {
|
||||||
|
auto findPriorityZone = static_cast<FindPriorityZone*>(extraData);
|
||||||
|
if (element->getAACube().contains(findPriorityZone->position)) {
|
||||||
|
const EntityTreeElementPointer entityTreeElement = static_pointer_cast<EntityTreeElement>(element);
|
||||||
|
entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) {
|
||||||
|
if (item->getType() == EntityTypes::Zone
|
||||||
|
&& item->contains(findPriorityZone->position)) {
|
||||||
|
auto zoneItem = static_pointer_cast<ZoneEntityItem>(item);
|
||||||
|
if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) {
|
||||||
|
float volume = zoneItem->getVolumeEstimate();
|
||||||
|
if (volume < findPriorityZone->zoneVolume) { // Smaller volume wins
|
||||||
|
findPriorityZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED;
|
||||||
|
findPriorityZone->zoneVolume = volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true; // Keep recursing
|
||||||
|
} else { // Position isn't within this subspace, so end recursion.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Close anonymous namespace.
|
||||||
|
|
||||||
|
int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) {
|
||||||
// pull the sequence number from the data first
|
// pull the sequence number from the data first
|
||||||
uint16_t sequenceNumber;
|
uint16_t sequenceNumber;
|
||||||
|
|
||||||
|
@ -90,9 +129,33 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
|
||||||
incrementNumOutOfOrderSends();
|
incrementNumOutOfOrderSends();
|
||||||
}
|
}
|
||||||
_lastReceivedSequenceNumber = sequenceNumber;
|
_lastReceivedSequenceNumber = sequenceNumber;
|
||||||
|
glm::vec3 oldPosition = getPosition();
|
||||||
|
|
||||||
// compute the offset to the data payload
|
// compute the offset to the data payload
|
||||||
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
|
if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto newPosition = getPosition();
|
||||||
|
if (newPosition != oldPosition) {
|
||||||
|
//#define AVATAR_HERO_TEST_HACK
|
||||||
|
#ifdef AVATAR_HERO_TEST_HACK
|
||||||
|
{
|
||||||
|
const static QString heroKey { "HERO" };
|
||||||
|
_avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
EntityTree& entityTree = *slaveSharedData.entityTree;
|
||||||
|
FindPriorityZone findPriorityZone { newPosition, false } ;
|
||||||
|
entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone);
|
||||||
|
_avatar->setHasPriority(findPriorityZone.isInPriorityZone);
|
||||||
|
//if (findPriorityZone.isInPriorityZone) {
|
||||||
|
// qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone";
|
||||||
|
//}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
#include <QtCore/QJsonObject>
|
#include <QtCore/QJsonObject>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
|
|
||||||
#include <AvatarData.h>
|
#include "MixerAvatar.h"
|
||||||
#include <AssociatedTraitValues.h>
|
#include <AssociatedTraitValues.h>
|
||||||
#include <NodeData.h>
|
#include <NodeData.h>
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
|
@ -45,11 +45,12 @@ public:
|
||||||
using HRCTime = p_high_resolution_clock::time_point;
|
using HRCTime = p_high_resolution_clock::time_point;
|
||||||
using PerNodeTraitVersions = std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions>;
|
using PerNodeTraitVersions = std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions>;
|
||||||
|
|
||||||
int parseData(ReceivedMessage& message) override;
|
using NodeData::parseData; // Avoid clang warning about hiding.
|
||||||
AvatarData& getAvatar() { return *_avatar; }
|
int parseData(ReceivedMessage& message, const SlaveSharedData& SlaveSharedData);
|
||||||
const AvatarData& getAvatar() const { return *_avatar; }
|
MixerAvatar& getAvatar() { return *_avatar; }
|
||||||
const AvatarData* getConstAvatarData() const { return _avatar.get(); }
|
const MixerAvatar& getAvatar() const { return *_avatar; }
|
||||||
AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
|
const MixerAvatar* getConstAvatarData() const { return _avatar.get(); }
|
||||||
|
MixerAvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
|
||||||
|
|
||||||
uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const;
|
uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const;
|
||||||
void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber)
|
void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber)
|
||||||
|
@ -163,7 +164,7 @@ private:
|
||||||
};
|
};
|
||||||
PacketQueue _packetQueue;
|
PacketQueue _packetQueue;
|
||||||
|
|
||||||
AvatarSharedPointer _avatar { new AvatarData() };
|
MixerAvatarSharedPointer _avatar { new MixerAvatar() };
|
||||||
|
|
||||||
uint16_t _lastReceivedSequenceNumber { 0 };
|
uint16_t _lastReceivedSequenceNumber { 0 };
|
||||||
std::unordered_map<NLPacket::LocalID, uint16_t> _lastBroadcastSequenceNumbers;
|
std::unordered_map<NLPacket::LocalID, uint16_t> _lastBroadcastSequenceNumbers;
|
||||||
|
|
|
@ -281,7 +281,34 @@ AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) {
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class SortableAvatar : public PrioritySortUtil::Sortable {
|
||||||
|
public:
|
||||||
|
SortableAvatar() = delete;
|
||||||
|
SortableAvatar(const MixerAvatar* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
|
||||||
|
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {
|
||||||
|
}
|
||||||
|
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
|
||||||
|
float getRadius() const override {
|
||||||
|
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
|
||||||
|
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
|
||||||
|
}
|
||||||
|
uint64_t getTimestamp() const override {
|
||||||
|
return _lastEncodeTime;
|
||||||
|
}
|
||||||
|
const Node* getNode() const { return _node; }
|
||||||
|
const MixerAvatar* getAvatar() const { return _avatar; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const MixerAvatar* _avatar;
|
||||||
|
const Node* _node;
|
||||||
|
uint64_t _lastEncodeTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Close anonymous namespace.
|
||||||
|
|
||||||
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
|
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
|
||||||
|
const float AVATAR_HERO_FRACTION { 0.4f };
|
||||||
const Node* destinationNode = node.data();
|
const Node* destinationNode = node.data();
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
@ -293,29 +320,30 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
|
|
||||||
_stats.nodesBroadcastedTo++;
|
_stats.nodesBroadcastedTo++;
|
||||||
|
|
||||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData());
|
AvatarMixerClientData* destinationNodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData());
|
||||||
|
|
||||||
nodeData->resetInViewStats();
|
destinationNodeData->resetInViewStats();
|
||||||
|
|
||||||
const AvatarData& avatar = nodeData->getAvatar();
|
const AvatarData& avatar = destinationNodeData->getAvatar();
|
||||||
glm::vec3 myPosition = avatar.getClientGlobalPosition();
|
glm::vec3 destinationPosition = avatar.getClientGlobalPosition();
|
||||||
|
|
||||||
// reset the internal state for correct random number distribution
|
// reset the internal state for correct random number distribution
|
||||||
distribution.reset();
|
distribution.reset();
|
||||||
|
|
||||||
// Estimate number to sort on number sent last frame (with min. of 20).
|
// Estimate number to sort on number sent last frame (with min. of 20).
|
||||||
const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20);
|
const int numToSendEst = std::max(int(destinationNodeData->getNumAvatarsSentLastFrame() * 2.5f), 20);
|
||||||
|
|
||||||
// reset the number of sent avatars
|
// reset the number of sent avatars
|
||||||
nodeData->resetNumAvatarsSentLastFrame();
|
destinationNodeData->resetNumAvatarsSentLastFrame();
|
||||||
|
|
||||||
// keep track of outbound data rate specifically for avatar data
|
// keep track of outbound data rate specifically for avatar data
|
||||||
int numAvatarDataBytes = 0;
|
int numAvatarDataBytes = 0;
|
||||||
int identityBytesSent = 0;
|
int identityBytesSent = 0;
|
||||||
int traitBytesSent = 0;
|
int traitBytesSent = 0;
|
||||||
|
|
||||||
// max number of avatarBytes per frame
|
// max number of avatarBytes per frame (13 900, typical)
|
||||||
int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
|
const int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
|
||||||
|
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * AVATAR_HERO_FRACTION); // 5555, typical
|
||||||
|
|
||||||
// keep track of the number of other avatars held back in this frame
|
// keep track of the number of other avatars held back in this frame
|
||||||
int numAvatarsHeldBack = 0;
|
int numAvatarsHeldBack = 0;
|
||||||
|
@ -325,8 +353,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
|
|
||||||
// When this is true, the AvatarMixer will send Avatar data to a client
|
// When this is true, the AvatarMixer will send Avatar data to a client
|
||||||
// about avatars they've ignored or that are out of view
|
// about avatars they've ignored or that are out of view
|
||||||
bool PALIsOpen = nodeData->getRequestsDomainListData();
|
bool PALIsOpen = destinationNodeData->getRequestsDomainListData();
|
||||||
bool PALWasOpen = nodeData->getPrevRequestsDomainListData();
|
bool PALWasOpen = destinationNodeData->getPrevRequestsDomainListData();
|
||||||
|
|
||||||
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
|
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
|
||||||
bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick();
|
bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick();
|
||||||
|
@ -337,36 +365,23 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
|
|
||||||
// compute node bounding box
|
// compute node bounding box
|
||||||
const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically
|
const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically
|
||||||
AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
|
AABox destinationNodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
|
||||||
|
|
||||||
class SortableAvatar: public PrioritySortUtil::Sortable {
|
|
||||||
public:
|
|
||||||
SortableAvatar() = delete;
|
|
||||||
SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
|
|
||||||
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {}
|
|
||||||
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
|
|
||||||
float getRadius() const override {
|
|
||||||
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
|
|
||||||
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
|
|
||||||
}
|
|
||||||
uint64_t getTimestamp() const override {
|
|
||||||
return _lastEncodeTime;
|
|
||||||
}
|
|
||||||
const Node* getNode() const { return _node; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
const AvatarData* _avatar;
|
|
||||||
const Node* _node;
|
|
||||||
uint64_t _lastEncodeTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
// prepare to sort
|
// prepare to sort
|
||||||
const auto& cameraViews = nodeData->getViewFrustums();
|
const auto& cameraViews = destinationNodeData->getViewFrustums();
|
||||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraViews,
|
|
||||||
AvatarData::_avatarSortCoefficientSize,
|
using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue<SortableAvatar>;
|
||||||
AvatarData::_avatarSortCoefficientCenter,
|
// Keep two independent queues, one for heroes and one for the riff-raff.
|
||||||
AvatarData::_avatarSortCoefficientAge);
|
enum PriorityVariants { kHero, kNonhero };
|
||||||
sortedAvatars.reserve(_end - _begin);
|
AvatarPriorityQueue avatarPriorityQueues[2] =
|
||||||
|
{
|
||||||
|
{cameraViews, AvatarData::_avatarSortCoefficientSize,
|
||||||
|
AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge},
|
||||||
|
{cameraViews, AvatarData::_avatarSortCoefficientSize,
|
||||||
|
AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge}
|
||||||
|
};
|
||||||
|
|
||||||
|
avatarPriorityQueues[kNonhero].reserve(_end - _begin);
|
||||||
|
|
||||||
for (auto listedNode = _begin; listedNode != _end; ++listedNode) {
|
for (auto listedNode = _begin; listedNode != _end; ++listedNode) {
|
||||||
Node* otherNodeRaw = (*listedNode).data();
|
Node* otherNodeRaw = (*listedNode).data();
|
||||||
|
@ -376,47 +391,47 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto avatarNode = otherNodeRaw;
|
auto sourceAvatarNode = otherNodeRaw;
|
||||||
|
|
||||||
bool shouldIgnore = false;
|
bool sendAvatar = true; // We will consider this source avatar for sending.
|
||||||
// We ignore other nodes for a couple of reasons:
|
// We ignore other nodes for a couple of reasons:
|
||||||
// 1) ignore bubbles and ignore specific node
|
// 1) ignore bubbles and ignore specific node
|
||||||
// 2) the node hasn't really updated it's frame data recently, this can
|
// 2) the node hasn't really updated it's frame data recently, this can
|
||||||
// happen if for example the avatar is connected on a desktop and sending
|
// happen if for example the avatar is connected on a desktop and sending
|
||||||
// updates at ~30hz. So every 3 frames we skip a frame.
|
// updates at ~30hz. So every 3 frames we skip a frame.
|
||||||
|
|
||||||
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
assert(sourceAvatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||||
|
|
||||||
const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
const AvatarMixerClientData* sourceAvatarNodeData = reinterpret_cast<const AvatarMixerClientData*>(sourceAvatarNode->getLinkedData());
|
||||||
assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data
|
assert(sourceAvatarNodeData); // we can't have gotten here without sourceAvatarNode having valid data
|
||||||
quint64 startIgnoreCalculation = usecTimestampNow();
|
quint64 startIgnoreCalculation = usecTimestampNow();
|
||||||
|
|
||||||
// make sure we have data for this avatar, that it isn't the same node,
|
// make sure we have data for this avatar, that it isn't the same node,
|
||||||
// and isn't an avatar that the viewing node has ignored
|
// and isn't an avatar that the viewing node has ignored
|
||||||
// or that has ignored the viewing node
|
// or that has ignored the viewing node
|
||||||
if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen)
|
if ((destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) && !PALIsOpen)
|
||||||
|| (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) {
|
|| (sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) {
|
||||||
shouldIgnore = true;
|
sendAvatar = false;
|
||||||
} else {
|
} else {
|
||||||
// Check to see if the space bubble is enabled
|
// Check to see if the space bubble is enabled
|
||||||
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
|
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
|
||||||
if (nodeData->isIgnoreRadiusEnabled() || (avatarClientNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
if (destinationNodeData->isIgnoreRadiusEnabled() || (sourceAvatarNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||||
// Perform the collision check between the two bounding boxes
|
// Perform the collision check between the two bounding boxes
|
||||||
AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox();
|
AABox sourceNodeBox = sourceAvatarNodeData->getAvatar().getDefaultBubbleBox();
|
||||||
if (nodeBox.touches(otherNodeBox)) {
|
if (destinationNodeBox.touches(sourceNodeBox)) {
|
||||||
nodeData->ignoreOther(destinationNode, avatarNode);
|
destinationNodeData->ignoreOther(destinationNode, sourceAvatarNode);
|
||||||
shouldIgnore = !getsAnyIgnored;
|
sendAvatar = getsAnyIgnored;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not close enough to ignore
|
// Not close enough to ignore
|
||||||
if (!shouldIgnore) {
|
if (sendAvatar) {
|
||||||
nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID());
|
destinationNodeData->removeFromRadiusIgnoringSet(sourceAvatarNode->getUUID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldIgnore) {
|
if (sendAvatar) {
|
||||||
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID());
|
AvatarDataSequenceNumber lastSeqToReceiver = destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID());
|
||||||
AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber();
|
AvatarDataSequenceNumber lastSeqFromSender = sourceAvatarNodeData->getLastReceivedSequenceNumber();
|
||||||
|
|
||||||
// FIXME - This code does appear to be working. But it seems brittle.
|
// FIXME - This code does appear to be working. But it seems brittle.
|
||||||
// It supports determining if the frame of data for this "other"
|
// It supports determining if the frame of data for this "other"
|
||||||
|
@ -430,26 +445,28 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
// or that somehow we haven't sent
|
// or that somehow we haven't sent
|
||||||
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
||||||
++numAvatarsHeldBack;
|
++numAvatarsHeldBack;
|
||||||
shouldIgnore = true;
|
sendAvatar = false;
|
||||||
} else if (lastSeqFromSender == 0) {
|
} else if (lastSeqFromSender == 0) {
|
||||||
// We have have not yet recieved any data about this avatar. Ignore it for now
|
// We have have not yet received any data about this avatar. Ignore it for now
|
||||||
// This is important for Agent scripts that are not avatar
|
// This is important for Agent scripts that are not avatar
|
||||||
// so that they don't appear to be an avatar at the origin
|
// so that they don't appear to be an avatar at the origin
|
||||||
shouldIgnore = true;
|
sendAvatar = false;
|
||||||
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
|
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
|
||||||
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
|
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
|
||||||
++numAvatarsWithSkippedFrames;
|
++numAvatarsWithSkippedFrames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 endIgnoreCalculation = usecTimestampNow();
|
quint64 endIgnoreCalculation = usecTimestampNow();
|
||||||
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
|
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
|
||||||
|
|
||||||
if (!shouldIgnore) {
|
if (sendAvatar) {
|
||||||
// sort this one for later
|
// sort this one for later
|
||||||
const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData();
|
const MixerAvatar* avatarNodeData = sourceAvatarNodeData->getConstAvatarData();
|
||||||
auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID());
|
auto lastEncodeTime = destinationNodeData->getLastOtherAvatarEncodeTime(sourceAvatarNode->getLocalID());
|
||||||
|
|
||||||
sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime));
|
avatarPriorityQueues[avatarNodeData->getHasPriority() ? kHero : kNonhero].push(
|
||||||
|
SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Avatar A's PAL WAS open but is no longer open, AND
|
// If Avatar A's PAL WAS open but is no longer open, AND
|
||||||
|
@ -459,135 +476,153 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
// will be sent when it doesn't need to be (but where it _should_ be OK to send).
|
// will be sent when it doesn't need to be (but where it _should_ be OK to send).
|
||||||
// However, it's less heavy-handed than using `shouldIgnore`.
|
// However, it's less heavy-handed than using `shouldIgnore`.
|
||||||
if (PALWasOpen && !PALIsOpen &&
|
if (PALWasOpen && !PALIsOpen &&
|
||||||
(destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) ||
|
(destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) ||
|
||||||
avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) {
|
sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) {
|
||||||
// ...send a Kill Packet to Node A, instructing Node A to kill Avatar B,
|
// ...send a Kill Packet to Node A, instructing Node A to kill Avatar B,
|
||||||
// then have Node A cleanup the killed Node B.
|
// then have Node A cleanup the killed Node B.
|
||||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
|
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
|
||||||
packet->write(avatarNode->getUUID().toRfc4122());
|
packet->write(sourceAvatarNode->getUUID().toRfc4122());
|
||||||
packet->writePrimitive(KillAvatarReason::AvatarIgnored);
|
packet->writePrimitive(KillAvatarReason::AvatarIgnored);
|
||||||
nodeList->sendPacket(std::move(packet), *destinationNode);
|
nodeList->sendPacket(std::move(packet), *destinationNode);
|
||||||
nodeData->cleanupKilledNode(avatarNode->getUUID(), avatarNode->getLocalID());
|
destinationNodeData->cleanupKilledNode(sourceAvatarNode->getUUID(), sourceAvatarNode->getLocalID());
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeData->setPrevRequestsDomainListData(PALIsOpen);
|
destinationNodeData->setPrevRequestsDomainListData(PALIsOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
||||||
|
|
||||||
int remainingAvatars = (int)sortedAvatars.size();
|
int remainingAvatars = (int)avatarPriorityQueues[kHero].size() + (int)avatarPriorityQueues[kNonhero].size();
|
||||||
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
|
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
|
||||||
|
|
||||||
auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
||||||
const int avatarPacketCapacity = avatarPacket->getPayloadCapacity();
|
const int avatarPacketCapacity = avatarPacket->getPayloadCapacity();
|
||||||
int avatarSpaceAvailable = avatarPacketCapacity;
|
int avatarSpaceAvailable = avatarPacketCapacity;
|
||||||
int numPacketsSent = 0;
|
int numPacketsSent = 0;
|
||||||
|
int numAvatarsSent = 0;
|
||||||
auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
||||||
|
|
||||||
const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst);
|
// Loop over two priorities - hero avatars then everyone else:
|
||||||
for (const auto& sortedAvatar : sortedAvatarVector) {
|
for (PriorityVariants currentVariant = kHero; currentVariant <= kNonhero; ++((int&)currentVariant)) {
|
||||||
const Node* otherNode = sortedAvatar.getNode();
|
const auto& sortedAvatarVector = avatarPriorityQueues[currentVariant].getSortedVector(numToSendEst);
|
||||||
auto lastEncodeForOther = sortedAvatar.getTimestamp();
|
for (const auto& sortedAvatar : sortedAvatarVector) {
|
||||||
|
const Node* sourceNode = sortedAvatar.getNode();
|
||||||
|
auto lastEncodeForOther = sortedAvatar.getTimestamp();
|
||||||
|
|
||||||
assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map
|
assert(sourceNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||||
|
|
||||||
AvatarData::AvatarDataDetail detail = AvatarData::NoData;
|
AvatarData::AvatarDataDetail detail = AvatarData::NoData;
|
||||||
|
|
||||||
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
|
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
|
||||||
// or send minimal avatar data in uncommon case of PALIsOpen.
|
// or send minimal avatar data in uncommon case of PALIsOpen.
|
||||||
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
||||||
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
|
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
|
||||||
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
|
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
|
||||||
if (overBudget) {
|
if (overBudget) {
|
||||||
if (PALIsOpen) {
|
if (PALIsOpen) {
|
||||||
_stats.overBudgetAvatars++;
|
_stats.overBudgetAvatars++;
|
||||||
detail = AvatarData::PALMinimum;
|
detail = AvatarData::PALMinimum;
|
||||||
} else {
|
} else {
|
||||||
_stats.overBudgetAvatars += remainingAvatars;
|
_stats.overBudgetAvatars += remainingAvatars;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool overHeroBudget = currentVariant == kHero && numAvatarDataBytes > maxHeroBytesPerFrame;
|
||||||
|
if (overHeroBudget) {
|
||||||
|
break; // No more heroes (this frame).
|
||||||
|
}
|
||||||
|
|
||||||
|
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
const AvatarMixerClientData* sourceNodeData = reinterpret_cast<const AvatarMixerClientData*>(sourceNode->getLinkedData());
|
||||||
|
const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData();
|
||||||
|
|
||||||
|
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
|
||||||
|
bool isLowerPriority = currentVariant != kHero && sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling?
|
||||||
|
|
||||||
|
if (isLowerPriority) {
|
||||||
|
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
|
||||||
|
destinationNodeData->incrementAvatarOutOfView();
|
||||||
|
} else if (!overBudget) {
|
||||||
|
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||||
|
destinationNodeData->incrementAvatarInView();
|
||||||
|
|
||||||
|
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO
|
||||||
|
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
|
||||||
|
if (sourceAvatar->hasProcessedFirstIdentity()
|
||||||
|
&& destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) {
|
||||||
|
identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode);
|
||||||
|
|
||||||
|
// remember the last time we sent identity details about this other node to the receiver
|
||||||
|
destinationNodeData->setLastBroadcastTime(sourceNode->getLocalID(), usecTimestampNow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<JointData>& lastSentJointsForOther = destinationNodeData->getLastOtherAvatarSentJoints(sourceNode->getLocalID());
|
||||||
|
|
||||||
|
const bool distanceAdjust = true;
|
||||||
|
const bool dropFaceTracking = false;
|
||||||
|
AvatarDataPacket::SendStatus sendStatus;
|
||||||
|
sendStatus.sendUUID = true;
|
||||||
|
|
||||||
|
do {
|
||||||
|
auto startSerialize = chrono::high_resolution_clock::now();
|
||||||
|
QByteArray bytes = sourceAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||||
|
sendStatus, dropFaceTracking, distanceAdjust, destinationPosition,
|
||||||
|
&lastSentJointsForOther, avatarSpaceAvailable);
|
||||||
|
auto endSerialize = chrono::high_resolution_clock::now();
|
||||||
|
_stats.toByteArrayElapsedTime +=
|
||||||
|
(quint64)chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
|
||||||
|
|
||||||
|
avatarPacket->write(bytes);
|
||||||
|
avatarSpaceAvailable -= bytes.size();
|
||||||
|
numAvatarDataBytes += bytes.size();
|
||||||
|
if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) {
|
||||||
|
// Weren't able to fit everything.
|
||||||
|
nodeList->sendPacket(std::move(avatarPacket), *destinationNode);
|
||||||
|
++numPacketsSent;
|
||||||
|
avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
||||||
|
avatarSpaceAvailable = avatarPacketCapacity;
|
||||||
|
}
|
||||||
|
} while (!sendStatus);
|
||||||
|
|
||||||
|
if (detail != AvatarData::NoData) {
|
||||||
|
_stats.numOthersIncluded++;
|
||||||
|
if (sourceAvatar->getHasPriority()) {
|
||||||
|
_stats.numHeroesIncluded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment the number of avatars sent to this receiver
|
||||||
|
destinationNodeData->incrementNumAvatarsSentLastFrame();
|
||||||
|
|
||||||
|
// set the last sent sequence number for this sender on the receiver
|
||||||
|
destinationNodeData->setLastBroadcastSequenceNumber(sourceNode->getLocalID(),
|
||||||
|
sourceNodeData->getLastReceivedSequenceNumber());
|
||||||
|
destinationNodeData->setLastOtherAvatarEncodeTime(sourceNode->getLocalID(), usecTimestampNow());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto endAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||||
|
_stats.avatarDataPackingElapsedTime +=
|
||||||
|
(quint64)chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
||||||
|
|
||||||
|
if (!overBudget) {
|
||||||
|
// use helper to add any changed traits to our packet list
|
||||||
|
traitBytesSent += addChangedTraitsToBulkPacket(destinationNodeData, sourceNodeData, *traitsPacketList);
|
||||||
|
}
|
||||||
|
numAvatarsSent++;
|
||||||
|
remainingAvatars--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentVariant == kHero) { // Dump any remaining heroes into the commoners.
|
||||||
|
for (auto avIter = sortedAvatarVector.begin() + numAvatarsSent; avIter < sortedAvatarVector.end(); ++avIter) {
|
||||||
|
avatarPriorityQueues[kNonhero].push(*avIter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
|
||||||
const AvatarData* otherAvatar = otherNodeData->getConstAvatarData();
|
|
||||||
|
|
||||||
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
|
|
||||||
bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD;
|
|
||||||
|
|
||||||
if (isLowerPriority) {
|
|
||||||
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
|
|
||||||
nodeData->incrementAvatarOutOfView();
|
|
||||||
} else if (!overBudget) {
|
|
||||||
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
|
|
||||||
nodeData->incrementAvatarInView();
|
|
||||||
|
|
||||||
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO
|
|
||||||
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
|
|
||||||
if (otherAvatar->hasProcessedFirstIdentity()
|
|
||||||
&& nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) {
|
|
||||||
identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode);
|
|
||||||
|
|
||||||
// remember the last time we sent identity details about this other node to the receiver
|
|
||||||
nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID());
|
|
||||||
|
|
||||||
const bool distanceAdjust = true;
|
|
||||||
const bool dropFaceTracking = false;
|
|
||||||
AvatarDataPacket::SendStatus sendStatus;
|
|
||||||
sendStatus.sendUUID = true;
|
|
||||||
|
|
||||||
do {
|
|
||||||
auto startSerialize = chrono::high_resolution_clock::now();
|
|
||||||
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
|
||||||
sendStatus, dropFaceTracking, distanceAdjust, myPosition,
|
|
||||||
&lastSentJointsForOther, avatarSpaceAvailable);
|
|
||||||
auto endSerialize = chrono::high_resolution_clock::now();
|
|
||||||
_stats.toByteArrayElapsedTime +=
|
|
||||||
(quint64)chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
|
|
||||||
|
|
||||||
avatarPacket->write(bytes);
|
|
||||||
avatarSpaceAvailable -= bytes.size();
|
|
||||||
numAvatarDataBytes += bytes.size();
|
|
||||||
if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) {
|
|
||||||
// Weren't able to fit everything.
|
|
||||||
nodeList->sendPacket(std::move(avatarPacket), *destinationNode);
|
|
||||||
++numPacketsSent;
|
|
||||||
avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
|
||||||
avatarSpaceAvailable = avatarPacketCapacity;
|
|
||||||
}
|
|
||||||
} while (!sendStatus);
|
|
||||||
|
|
||||||
if (detail != AvatarData::NoData) {
|
|
||||||
_stats.numOthersIncluded++;
|
|
||||||
|
|
||||||
// increment the number of avatars sent to this receiver
|
|
||||||
nodeData->incrementNumAvatarsSentLastFrame();
|
|
||||||
|
|
||||||
// set the last sent sequence number for this sender on the receiver
|
|
||||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(),
|
|
||||||
otherNodeData->getLastReceivedSequenceNumber());
|
|
||||||
nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto endAvatarDataPacking = chrono::high_resolution_clock::now();
|
|
||||||
_stats.avatarDataPackingElapsedTime +=
|
|
||||||
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
|
||||||
|
|
||||||
if (!overBudget) {
|
|
||||||
// use helper to add any changed traits to our packet list
|
|
||||||
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingAvatars--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) {
|
if (destinationNodeData->getNumAvatarsSentLastFrame() > numToSendEst) {
|
||||||
qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame()
|
qCWarning(avatars) << "More avatars sent than upper estimate" << destinationNodeData->getNumAvatarsSentLastFrame()
|
||||||
<< " / " << numToSendEst;
|
<< " / " << numToSendEst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,12 +653,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
||||||
nodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent);
|
destinationNodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent);
|
||||||
|
|
||||||
|
|
||||||
// record the number of avatars held back this frame
|
// record the number of avatars held back this frame
|
||||||
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
destinationNodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
||||||
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
destinationNodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
||||||
|
|
||||||
quint64 endPacketSending = usecTimestampNow();
|
quint64 endPacketSending = usecTimestampNow();
|
||||||
_stats.packetSendingElapsedTime += (endPacketSending - startPacketSending);
|
_stats.packetSendingElapsedTime += (endPacketSending - startPacketSending);
|
||||||
|
|
|
@ -32,6 +32,7 @@ public:
|
||||||
int numIdentityPacketsSent { 0 };
|
int numIdentityPacketsSent { 0 };
|
||||||
int numOthersIncluded { 0 };
|
int numOthersIncluded { 0 };
|
||||||
int overBudgetAvatars { 0 };
|
int overBudgetAvatars { 0 };
|
||||||
|
int numHeroesIncluded { 0 };
|
||||||
|
|
||||||
quint64 ignoreCalculationElapsedTime { 0 };
|
quint64 ignoreCalculationElapsedTime { 0 };
|
||||||
quint64 avatarDataPackingElapsedTime { 0 };
|
quint64 avatarDataPackingElapsedTime { 0 };
|
||||||
|
@ -57,6 +58,7 @@ public:
|
||||||
numIdentityPacketsSent = 0;
|
numIdentityPacketsSent = 0;
|
||||||
numOthersIncluded = 0;
|
numOthersIncluded = 0;
|
||||||
overBudgetAvatars = 0;
|
overBudgetAvatars = 0;
|
||||||
|
numHeroesIncluded = 0;
|
||||||
|
|
||||||
ignoreCalculationElapsedTime = 0;
|
ignoreCalculationElapsedTime = 0;
|
||||||
avatarDataPackingElapsedTime = 0;
|
avatarDataPackingElapsedTime = 0;
|
||||||
|
@ -80,6 +82,7 @@ public:
|
||||||
numIdentityPacketsSent += rhs.numIdentityPacketsSent;
|
numIdentityPacketsSent += rhs.numIdentityPacketsSent;
|
||||||
numOthersIncluded += rhs.numOthersIncluded;
|
numOthersIncluded += rhs.numOthersIncluded;
|
||||||
overBudgetAvatars += rhs.overBudgetAvatars;
|
overBudgetAvatars += rhs.overBudgetAvatars;
|
||||||
|
numHeroesIncluded += rhs.numHeroesIncluded;
|
||||||
|
|
||||||
ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime;
|
ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime;
|
||||||
avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime;
|
avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime;
|
||||||
|
@ -90,9 +93,13 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class EntityTree;
|
||||||
|
using EntityTreePointer = std::shared_ptr<EntityTree>;
|
||||||
|
|
||||||
struct SlaveSharedData {
|
struct SlaveSharedData {
|
||||||
QStringList skeletonURLWhitelist;
|
QStringList skeletonURLWhitelist;
|
||||||
QUrl skeletonReplacementURL;
|
QUrl skeletonReplacementURL;
|
||||||
|
EntityTreePointer entityTree;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AvatarMixerSlave {
|
class AvatarMixerSlave {
|
||||||
|
|
|
@ -63,10 +63,6 @@ bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node) {
|
||||||
return _pool._queue.try_pop(node);
|
return _pool._queue.try_pop(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef AVATAR_SINGLE_THREADED
|
|
||||||
static AvatarMixerSlave slave;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) {
|
void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) {
|
||||||
_function = &AvatarMixerSlave::processIncomingPackets;
|
_function = &AvatarMixerSlave::processIncomingPackets;
|
||||||
_configure = [=](AvatarMixerSlave& slave) {
|
_configure = [=](AvatarMixerSlave& slave) {
|
||||||
|
@ -89,19 +85,9 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
||||||
_begin = begin;
|
_begin = begin;
|
||||||
_end = end;
|
_end = end;
|
||||||
|
|
||||||
#ifdef AUDIO_SINGLE_THREADED
|
|
||||||
_configure(slave);
|
|
||||||
std::for_each(begin, end, [&](const SharedNodePointer& node) {
|
|
||||||
_function(slave, node);
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
// fill the queue
|
// fill the queue
|
||||||
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
|
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
|
||||||
#if defined(__clang__) && defined(Q_OS_LINUX)
|
|
||||||
_queue.push(node);
|
_queue.push(node);
|
||||||
#else
|
|
||||||
_queue.emplace(node);
|
|
||||||
#endif
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -121,18 +107,13 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(_queue.empty());
|
assert(_queue.empty());
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AvatarMixerSlavePool::each(std::function<void(AvatarMixerSlave& slave)> functor) {
|
void AvatarMixerSlavePool::each(std::function<void(AvatarMixerSlave& slave)> functor) {
|
||||||
#ifdef AVATAR_SINGLE_THREADED
|
|
||||||
functor(slave);
|
|
||||||
#else
|
|
||||||
for (auto& slave : _slaves) {
|
for (auto& slave : _slaves) {
|
||||||
functor(*slave.get());
|
functor(*slave.get());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarMixerSlavePool::setNumThreads(int numThreads) {
|
void AvatarMixerSlavePool::setNumThreads(int numThreads) {
|
||||||
|
@ -158,9 +139,6 @@ void AvatarMixerSlavePool::setNumThreads(int numThreads) {
|
||||||
void AvatarMixerSlavePool::resize(int numThreads) {
|
void AvatarMixerSlavePool::resize(int numThreads) {
|
||||||
assert(_numThreads == (int)_slaves.size());
|
assert(_numThreads == (int)_slaves.size());
|
||||||
|
|
||||||
#ifdef AVATAR_SINGLE_THREADED
|
|
||||||
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
|
|
||||||
#else
|
|
||||||
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
|
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
|
||||||
|
|
||||||
Lock lock(_mutex);
|
Lock lock(_mutex);
|
||||||
|
@ -208,5 +186,4 @@ void AvatarMixerSlavePool::resize(int numThreads) {
|
||||||
|
|
||||||
_numThreads = _numStarted = _numFinished = numThreads;
|
_numThreads = _numStarted = _numFinished = numThreads;
|
||||||
assert(_numThreads == (int)_slaves.size());
|
assert(_numThreads == (int)_slaves.size());
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
31
assignment-client/src/avatars/MixerAvatar.h
Normal file
31
assignment-client/src/avatars/MixerAvatar.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// MixerAvatar.h
|
||||||
|
// assignment-client/src/avatars
|
||||||
|
//
|
||||||
|
// Created by Simon Walton Feb 2019.
|
||||||
|
// Copyright 2019 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
|
||||||
|
//
|
||||||
|
|
||||||
|
// Avatar class for use within the avatar mixer - encapsulates data required only for
|
||||||
|
// sorting priorities within the mixer.
|
||||||
|
|
||||||
|
#ifndef hifi_MixerAvatar_h
|
||||||
|
#define hifi_MixerAvatar_h
|
||||||
|
|
||||||
|
#include <AvatarData.h>
|
||||||
|
|
||||||
|
class MixerAvatar : public AvatarData {
|
||||||
|
public:
|
||||||
|
bool getHasPriority() const { return _hasPriority; }
|
||||||
|
void setHasPriority(bool hasPriority) { _hasPriority = hasPriority; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _hasPriority { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
||||||
|
|
||||||
|
#endif // hifi_MixerAvatar_h
|
|
@ -1203,7 +1203,8 @@ void OctreeServer::beginRunning() {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
// we need to ask the DS about agents so we can ping/reply with them
|
// we need to ask the DS about agents so we can ping/reply with them
|
||||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer,
|
||||||
|
NodeType::AvatarMixer });
|
||||||
|
|
||||||
beforeRun(); // after payload has been processed
|
beforeRun(); // after payload has been processed
|
||||||
|
|
||||||
|
|
|
@ -1302,6 +1302,14 @@
|
||||||
"placeholder": "1",
|
"placeholder": "1",
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"advanced": true
|
"advanced": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "connection_rate",
|
||||||
|
"label": "Connection Rate",
|
||||||
|
"help": "Number of new agents that can connect to the mixer every second",
|
||||||
|
"placeholder": "50",
|
||||||
|
"default": "50",
|
||||||
|
"advanced": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1243,12 +1243,11 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
|
||||||
|
|
||||||
limitedNodeList->eachMatchingNode(
|
limitedNodeList->eachMatchingNode(
|
||||||
[this, addedNode](const SharedNodePointer& node)->bool {
|
[this, addedNode](const SharedNodePointer& node)->bool {
|
||||||
if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) {
|
// is the added Node in this node's interest list?
|
||||||
// is the added Node in this node's interest list?
|
return node->getLinkedData()
|
||||||
return isInInterestSet(node, addedNode);
|
&& node->getActiveSocket()
|
||||||
} else {
|
&& node != addedNode
|
||||||
return false;
|
&& isInInterestSet(node, addedNode);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) {
|
[this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) {
|
||||||
// send off this packet to the node
|
// send off this packet to the node
|
||||||
|
|
|
@ -213,6 +213,63 @@ Item {
|
||||||
popup.open();
|
popup.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: errorsGlyph
|
||||||
|
visible: !AvatarPackagerCore.currentAvatarProject || AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||||
|
text: hifi.glyphs.alert
|
||||||
|
size: 315
|
||||||
|
color: "#EA4C5F"
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
topMargin: -30
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: successGlyph
|
||||||
|
visible: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
topMargin: 52
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
width: 149.6
|
||||||
|
height: 149
|
||||||
|
source: "../../../icons/checkmark-stroke.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
RalewayRegular {
|
||||||
|
id: doctorStatusMessage
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
when: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||||
|
name: "noErrors"
|
||||||
|
PropertyChanges {
|
||||||
|
target: doctorStatusMessage
|
||||||
|
text: "Your avatar looks fine."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
when: !AvatarPackagerCore.currentAvatarProject || AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||||
|
name: "errors"
|
||||||
|
PropertyChanges {
|
||||||
|
target: doctorStatusMessage
|
||||||
|
text: "Your avatar has a few issues."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
color: 'white'
|
||||||
|
size: 20
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: errorsGlyph.bottom
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
|
||||||
RalewayRegular {
|
RalewayRegular {
|
||||||
id: infoMessage
|
id: infoMessage
|
||||||
|
|
||||||
|
@ -240,7 +297,7 @@ Item {
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: doctorStatusMessage.bottom
|
||||||
|
|
||||||
anchors.bottomMargin: 24
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
|
@ -249,6 +306,53 @@ Item {
|
||||||
text: "You can upload your files to our servers to always access them, and to make your avatar visible to other users."
|
text: "You can upload your files to our servers to always access them, and to make your avatar visible to other users."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RalewayRegular {
|
||||||
|
id: notForSaleMessage
|
||||||
|
|
||||||
|
visible: root.hasSuccessfullyUploaded
|
||||||
|
|
||||||
|
color: 'white'
|
||||||
|
linkColor: '#00B4EF'
|
||||||
|
size: 20
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: infoMessage.bottom
|
||||||
|
anchors.topMargin: 10
|
||||||
|
|
||||||
|
anchors.bottomMargin: 24
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
text: "This item is not for sale yet, <a href='#'>learn more</a>."
|
||||||
|
|
||||||
|
onLinkActivated: {
|
||||||
|
Qt.openUrlExternally("https://docs.highfidelity.com/sell/add-item/upload-avatar.html");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RalewayRegular {
|
||||||
|
id: showErrorsLink
|
||||||
|
|
||||||
|
color: 'white'
|
||||||
|
linkColor: '#00B4EF'
|
||||||
|
|
||||||
|
visible: AvatarPackagerCore.currentAvatarProject && AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: notForSaleMessage.bottom
|
||||||
|
topMargin: 16
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
size: 28
|
||||||
|
|
||||||
|
text: "<a href='toggle'>View all errors</a>"
|
||||||
|
|
||||||
|
onLinkActivated: {
|
||||||
|
avatarPackager.state = AvatarPackagerState.avatarDoctorErrorReport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HifiControls.Button {
|
HifiControls.Button {
|
||||||
id: openFolderButton
|
id: openFolderButton
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,10 @@ public:
|
||||||
return QDir::cleanPath(QDir(_projectPath).absoluteFilePath(_fst->getModelPath()));
|
return QDir::cleanPath(QDir(_projectPath).absoluteFilePath(_fst->getModelPath()));
|
||||||
}
|
}
|
||||||
Q_INVOKABLE bool getHasErrors() const { return _hasErrors; }
|
Q_INVOKABLE bool getHasErrors() const { return _hasErrors; }
|
||||||
Q_INVOKABLE void setHasErrors(bool hasErrors) { _hasErrors = hasErrors; }
|
Q_INVOKABLE void setHasErrors(bool hasErrors) {
|
||||||
|
_hasErrors = hasErrors;
|
||||||
|
emit hasErrorsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the AvatarProject or a nullptr on failure.
|
* returns the AvatarProject or a nullptr on failure.
|
||||||
|
|
|
@ -503,6 +503,7 @@ void OtherAvatar::handleChangedAvatarEntityData() {
|
||||||
// then set the the original ID for the changes to take effect
|
// then set the the original ID for the changes to take effect
|
||||||
// TODO: This is a horrible hack and once properties.constructFromBuffer no longer causes
|
// TODO: This is a horrible hack and once properties.constructFromBuffer no longer causes
|
||||||
// side effects...remove the following three lines
|
// side effects...remove the following three lines
|
||||||
|
|
||||||
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
|
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
|
||||||
entity->setParentID(NULL_ID);
|
entity->setParentID(NULL_ID);
|
||||||
entity->setParentID(oldParentID);
|
entity->setParentID(oldParentID);
|
||||||
|
|
|
@ -1066,13 +1066,6 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
||||||
|
|
||||||
if (_enableInverseKinematics) {
|
if (_enableInverseKinematics) {
|
||||||
_animVars.set("ikOverlayAlpha", 1.0f);
|
_animVars.set("ikOverlayAlpha", 1.0f);
|
||||||
_animVars.set("splineIKEnabled", true);
|
|
||||||
_animVars.set("leftHandIKEnabled", true);
|
|
||||||
_animVars.set("rightHandIKEnabled", true);
|
|
||||||
_animVars.set("leftFootIKEnabled", true);
|
|
||||||
_animVars.set("rightFootIKEnabled", true);
|
|
||||||
_animVars.set("leftFootPoleVectorEnabled", true);
|
|
||||||
_animVars.set("rightFootPoleVectorEnabled", true);
|
|
||||||
} else {
|
} else {
|
||||||
_animVars.set("ikOverlayAlpha", 0.0f);
|
_animVars.set("ikOverlayAlpha", 0.0f);
|
||||||
_animVars.set("splineIKEnabled", false);
|
_animVars.set("splineIKEnabled", false);
|
||||||
|
@ -1086,6 +1079,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
||||||
_animVars.set("rightFootPoleVectorEnabled", false);
|
_animVars.set("rightFootPoleVectorEnabled", false);
|
||||||
}
|
}
|
||||||
_lastEnableInverseKinematics = _enableInverseKinematics;
|
_lastEnableInverseKinematics = _enableInverseKinematics;
|
||||||
|
|
||||||
}
|
}
|
||||||
_lastForward = forward;
|
_lastForward = forward;
|
||||||
_lastPosition = worldPosition;
|
_lastPosition = worldPosition;
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#include "EntitySimulation.h"
|
#include "EntitySimulation.h"
|
||||||
#include "EntityDynamicFactoryInterface.h"
|
#include "EntityDynamicFactoryInterface.h"
|
||||||
|
|
||||||
|
//#define WANT_DEBUG
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(EntityItemPointer);
|
Q_DECLARE_METATYPE(EntityItemPointer);
|
||||||
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
|
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
|
||||||
|
@ -95,6 +96,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
||||||
requestedProperties += PROP_LAST_EDITED_BY;
|
requestedProperties += PROP_LAST_EDITED_BY;
|
||||||
requestedProperties += PROP_ENTITY_HOST_TYPE;
|
requestedProperties += PROP_ENTITY_HOST_TYPE;
|
||||||
requestedProperties += PROP_OWNING_AVATAR_ID;
|
requestedProperties += PROP_OWNING_AVATAR_ID;
|
||||||
|
requestedProperties += PROP_PARENT_ID;
|
||||||
|
requestedProperties += PROP_PARENT_JOINT_INDEX;
|
||||||
requestedProperties += PROP_QUERY_AA_CUBE;
|
requestedProperties += PROP_QUERY_AA_CUBE;
|
||||||
requestedProperties += PROP_CAN_CAST_SHADOW;
|
requestedProperties += PROP_CAN_CAST_SHADOW;
|
||||||
requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA;
|
requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA;
|
||||||
|
@ -502,6 +505,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
|
{
|
||||||
quint64 lastEdited = getLastEdited();
|
quint64 lastEdited = getLastEdited();
|
||||||
float editedAgo = getEditedAgo();
|
float editedAgo = getEditedAgo();
|
||||||
QString agoAsString = formatSecondsElapsed(editedAgo);
|
QString agoAsString = formatSecondsElapsed(editedAgo);
|
||||||
|
@ -515,6 +519,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString;
|
qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString;
|
||||||
qCDebug(entities) << " lastEdited =" << lastEdited;
|
qCDebug(entities) << " lastEdited =" << lastEdited;
|
||||||
qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString;
|
qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
quint64 lastEditedFromBuffer = 0;
|
quint64 lastEditedFromBuffer = 0;
|
||||||
|
@ -1099,7 +1104,7 @@ void EntityItem::simulate(const quint64& now) {
|
||||||
qCDebug(entities) << " hasGravity=" << hasGravity();
|
qCDebug(entities) << " hasGravity=" << hasGravity();
|
||||||
qCDebug(entities) << " hasAcceleration=" << hasAcceleration();
|
qCDebug(entities) << " hasAcceleration=" << hasAcceleration();
|
||||||
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
|
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
|
||||||
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity();
|
qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity();
|
||||||
qCDebug(entities) << " isMortal=" << isMortal();
|
qCDebug(entities) << " isMortal=" << isMortal();
|
||||||
qCDebug(entities) << " getAge()=" << getAge();
|
qCDebug(entities) << " getAge()=" << getAge();
|
||||||
qCDebug(entities) << " getLifetime()=" << getLifetime();
|
qCDebug(entities) << " getLifetime()=" << getLifetime();
|
||||||
|
@ -1111,12 +1116,12 @@ void EntityItem::simulate(const quint64& now) {
|
||||||
qCDebug(entities) << " hasGravity=" << hasGravity();
|
qCDebug(entities) << " hasGravity=" << hasGravity();
|
||||||
qCDebug(entities) << " hasAcceleration=" << hasAcceleration();
|
qCDebug(entities) << " hasAcceleration=" << hasAcceleration();
|
||||||
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
|
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
|
||||||
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity();
|
qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity();
|
||||||
}
|
}
|
||||||
if (hasAngularVelocity()) {
|
if (hasAngularVelocity()) {
|
||||||
qCDebug(entities) << " CHANGING...=";
|
qCDebug(entities) << " CHANGING...=";
|
||||||
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
|
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
|
||||||
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity();
|
qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity();
|
||||||
}
|
}
|
||||||
if (isMortal()) {
|
if (isMortal()) {
|
||||||
qCDebug(entities) << " MORTAL...=";
|
qCDebug(entities) << " MORTAL...=";
|
||||||
|
@ -1738,7 +1743,7 @@ bool EntityItem::contains(const glm::vec3& point) const {
|
||||||
// the above cases not yet supported --> fall through to BOX case
|
// the above cases not yet supported --> fall through to BOX case
|
||||||
case SHAPE_TYPE_BOX: {
|
case SHAPE_TYPE_BOX: {
|
||||||
localPoint = glm::abs(localPoint);
|
localPoint = glm::abs(localPoint);
|
||||||
return glm::any(glm::lessThanEqual(localPoint, glm::vec3(NORMALIZED_HALF_SIDE)));
|
return glm::all(glm::lessThanEqual(localPoint, glm::vec3(NORMALIZED_HALF_SIDE)));
|
||||||
}
|
}
|
||||||
case SHAPE_TYPE_ELLIPSOID: {
|
case SHAPE_TYPE_ELLIPSOID: {
|
||||||
// since we've transformed into the normalized space this is just a sphere-point intersection test
|
// since we've transformed into the normalized space this is just a sphere-point intersection test
|
||||||
|
@ -2652,13 +2657,23 @@ bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
|
||||||
// ALL entity properties. Some work will need to be done to the property system so that it can be more flexible
|
// ALL entity properties. Some work will need to be done to the property system so that it can be more flexible
|
||||||
// (to grab the value and default value of a property given the string representation of that property, for example)
|
// (to grab the value and default value of a property given the string representation of that property, for example)
|
||||||
|
|
||||||
// currently the only property filter we handle is '+' for serverScripts
|
// currently the only property filter we handle in EntityItem is '+' for serverScripts
|
||||||
// which means that we only handle a filtered query asking for entities where the serverScripts property is non-default
|
// which means that we only handle a filtered query asking for entities where the serverScripts property is non-default
|
||||||
|
|
||||||
static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts";
|
static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts";
|
||||||
|
static const QString ENTITY_TYPE_PROPERTY = "type";
|
||||||
|
|
||||||
if (jsonFilters[SERVER_SCRIPTS_PROPERTY] == EntityQueryFilterSymbol::NonDefault) {
|
foreach(const auto& property, jsonFilters.keys()) {
|
||||||
return _serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS;
|
if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) {
|
||||||
|
// check if this entity has a non-default value for serverScripts
|
||||||
|
if (_serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (property == ENTITY_TYPE_PROPERTY) {
|
||||||
|
return (jsonFilters[property] == EntityTypes::getEntityTypeName(getType()) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the json filter syntax did not match what we expected, return a match
|
// the json filter syntax did not match what we expected, return a match
|
||||||
|
|
|
@ -514,7 +514,7 @@ public:
|
||||||
QUuid getLastEditedBy() const { return _lastEditedBy; }
|
QUuid getLastEditedBy() const { return _lastEditedBy; }
|
||||||
void setLastEditedBy(QUuid value) { _lastEditedBy = value; }
|
void setLastEditedBy(QUuid value) { _lastEditedBy = value; }
|
||||||
|
|
||||||
bool matchesJSONFilters(const QJsonObject& jsonFilters) const;
|
virtual bool matchesJSONFilters(const QJsonObject& jsonFilters) const;
|
||||||
|
|
||||||
virtual bool getMeshes(MeshProxyList& result) { return true; }
|
virtual bool getMeshes(MeshProxyList& result) { return true; }
|
||||||
|
|
||||||
|
|
|
@ -225,6 +225,15 @@ QString EntityItemProperties::getBloomModeAsString() const {
|
||||||
return getComponentModeAsString(_bloomMode);
|
return getComponentModeAsString(_bloomMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const QStringList AVATAR_PRIORITIES_AS_STRING
|
||||||
|
{ "inherit", "crowd", "hero" };
|
||||||
|
}
|
||||||
|
|
||||||
|
QString EntityItemProperties::getAvatarPriorityAsString() const {
|
||||||
|
return AVATAR_PRIORITIES_AS_STRING.value(_avatarPriority);
|
||||||
|
}
|
||||||
|
|
||||||
std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT>::const_iterator EntityItemProperties::findComponent(const QString& mode) {
|
std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT>::const_iterator EntityItemProperties::findComponent(const QString& mode) {
|
||||||
return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) {
|
return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) {
|
||||||
return (pair.second == mode);
|
return (pair.second == mode);
|
||||||
|
@ -249,6 +258,15 @@ void EntityItemProperties::setBloomModeFromString(const QString& bloomMode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityItemProperties::setAvatarPriorityFromString(QString const& avatarPriority) {
|
||||||
|
auto result = AVATAR_PRIORITIES_AS_STRING.indexOf(avatarPriority);
|
||||||
|
|
||||||
|
if (result != -1) {
|
||||||
|
_avatarPriority = result;
|
||||||
|
_avatarPriorityChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString EntityItemProperties::getKeyLightModeAsString() const {
|
QString EntityItemProperties::getKeyLightModeAsString() const {
|
||||||
return getComponentModeAsString(_keyLightMode);
|
return getComponentModeAsString(_keyLightMode);
|
||||||
}
|
}
|
||||||
|
@ -622,6 +640,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||||
CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode);
|
CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode);
|
CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode);
|
CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode);
|
||||||
|
CHECK_PROPERTY_CHANGE(PROP_AVATAR_PRIORITY, avatarPriority);
|
||||||
|
|
||||||
// Polyvox
|
// Polyvox
|
||||||
CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize);
|
CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize);
|
||||||
|
@ -1426,7 +1445,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||||
* @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the
|
* @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the
|
||||||
* zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to
|
* zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to
|
||||||
* certain properties.<br />
|
* certain properties.<br />
|
||||||
|
*
|
||||||
|
* @property {string} avatarPriority="inherit" - Configures the update priority of contained avatars to other clients.<br />
|
||||||
|
* <code>"inherit"</code>: Priority from enclosing zones is unchanged.<br />
|
||||||
|
* <code>"crowd"</code>: Priority in this zone is the normal priority.<br />
|
||||||
|
* <code>"hero"</code>: Avatars in this zone will have an increased update priority
|
||||||
* <pre>
|
* <pre>
|
||||||
|
*
|
||||||
* function filter(properties) {
|
* function filter(properties) {
|
||||||
* // Test and edit properties object values,
|
* // Test and edit properties object values,
|
||||||
* // e.g., properties.modelURL, as required.
|
* // e.g., properties.modelURL, as required.
|
||||||
|
@ -1761,6 +1786,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString());
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString());
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString());
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString());
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString());
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString());
|
||||||
|
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AVATAR_PRIORITY, avatarPriority, getAvatarPriorityAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Web only
|
// Web only
|
||||||
|
@ -2123,6 +2149,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
||||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode);
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode);
|
||||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode);
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode);
|
||||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode);
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode);
|
||||||
|
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(avatarPriority, AvatarPriority);
|
||||||
|
|
||||||
// Polyvox
|
// Polyvox
|
||||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize);
|
COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize);
|
||||||
|
@ -2403,6 +2430,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
|
||||||
COPY_PROPERTY_IF_CHANGED(skyboxMode);
|
COPY_PROPERTY_IF_CHANGED(skyboxMode);
|
||||||
COPY_PROPERTY_IF_CHANGED(hazeMode);
|
COPY_PROPERTY_IF_CHANGED(hazeMode);
|
||||||
COPY_PROPERTY_IF_CHANGED(bloomMode);
|
COPY_PROPERTY_IF_CHANGED(bloomMode);
|
||||||
|
COPY_PROPERTY_IF_CHANGED(avatarPriority);
|
||||||
|
|
||||||
// Polyvox
|
// Polyvox
|
||||||
COPY_PROPERTY_IF_CHANGED(voxelVolumeSize);
|
COPY_PROPERTY_IF_CHANGED(voxelVolumeSize);
|
||||||
|
@ -2789,6 +2817,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
|
||||||
ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t);
|
ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t);
|
||||||
ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t);
|
ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t);
|
||||||
ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t);
|
ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t);
|
||||||
|
ADD_PROPERTY_TO_MAP(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t);
|
||||||
|
|
||||||
// Polyvox
|
// Polyvox
|
||||||
ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3);
|
ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3);
|
||||||
|
@ -3191,6 +3220,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode());
|
APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode());
|
APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode());
|
APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode());
|
||||||
|
APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, (uint32_t)properties.getAvatarPriority());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.getType() == EntityTypes::PolyVox) {
|
if (properties.getType() == EntityTypes::PolyVox) {
|
||||||
|
@ -3656,6 +3686,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode);
|
||||||
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.getType() == EntityTypes::PolyVox) {
|
if (properties.getType() == EntityTypes::PolyVox) {
|
||||||
|
@ -4039,6 +4070,7 @@ void EntityItemProperties::markAllChanged() {
|
||||||
_skyboxModeChanged = true;
|
_skyboxModeChanged = true;
|
||||||
_hazeModeChanged = true;
|
_hazeModeChanged = true;
|
||||||
_bloomModeChanged = true;
|
_bloomModeChanged = true;
|
||||||
|
_avatarPriorityChanged = true;
|
||||||
|
|
||||||
// Polyvox
|
// Polyvox
|
||||||
_voxelVolumeSizeChanged = true;
|
_voxelVolumeSizeChanged = true;
|
||||||
|
@ -4637,6 +4669,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
||||||
if (bloomModeChanged()) {
|
if (bloomModeChanged()) {
|
||||||
out += "bloomMode";
|
out += "bloomMode";
|
||||||
}
|
}
|
||||||
|
if (avatarPriorityChanged()) {
|
||||||
|
out += "avatarPriority";
|
||||||
|
}
|
||||||
|
|
||||||
// Polyvox
|
// Polyvox
|
||||||
if (voxelVolumeSizeChanged()) {
|
if (voxelVolumeSizeChanged()) {
|
||||||
|
|
|
@ -321,6 +321,7 @@ public:
|
||||||
DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
|
DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
|
||||||
DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
|
DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
|
||||||
DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
|
DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
|
||||||
|
DEFINE_PROPERTY_REF_ENUM(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
|
||||||
|
|
||||||
// Polyvox
|
// Polyvox
|
||||||
DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE);
|
DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE);
|
||||||
|
@ -681,6 +682,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
||||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, "");
|
DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, "");
|
||||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, "");
|
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, "");
|
||||||
|
|
||||||
|
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AvatarPriority, avatarPriority, "");
|
||||||
|
|
||||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, "");
|
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, "");
|
||||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, "");
|
DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, "");
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,7 @@ enum EntityPropertyList {
|
||||||
PROP_DERIVED_28,
|
PROP_DERIVED_28,
|
||||||
PROP_DERIVED_29,
|
PROP_DERIVED_29,
|
||||||
PROP_DERIVED_30,
|
PROP_DERIVED_30,
|
||||||
|
PROP_DERIVED_31,
|
||||||
|
|
||||||
PROP_AFTER_LAST_ITEM,
|
PROP_AFTER_LAST_ITEM,
|
||||||
|
|
||||||
|
@ -276,6 +277,8 @@ enum EntityPropertyList {
|
||||||
PROP_SKYBOX_MODE = PROP_DERIVED_28,
|
PROP_SKYBOX_MODE = PROP_DERIVED_28,
|
||||||
PROP_HAZE_MODE = PROP_DERIVED_29,
|
PROP_HAZE_MODE = PROP_DERIVED_29,
|
||||||
PROP_BLOOM_MODE = PROP_DERIVED_30,
|
PROP_BLOOM_MODE = PROP_DERIVED_30,
|
||||||
|
// Avatar priority
|
||||||
|
PROP_AVATAR_PRIORITY = PROP_DERIVED_31,
|
||||||
|
|
||||||
// Polyvox
|
// Polyvox
|
||||||
PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0,
|
PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0,
|
||||||
|
|
|
@ -71,6 +71,7 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode);
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode);
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode);
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode);
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode);
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode);
|
||||||
|
COPY_ENTITY_PROPERTY_TO_PROPERTIES(avatarPriority, getAvatarPriority);
|
||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
@ -117,6 +118,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode);
|
||||||
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority);
|
||||||
|
|
||||||
somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged ||
|
somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged ||
|
||||||
_skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged;
|
_skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged;
|
||||||
|
@ -192,6 +194,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
||||||
READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode);
|
READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode);
|
||||||
READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode);
|
READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode);
|
||||||
READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode);
|
READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode);
|
||||||
|
READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority);
|
||||||
|
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
@ -211,6 +214,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p
|
||||||
requestedProperties += PROP_FLYING_ALLOWED;
|
requestedProperties += PROP_FLYING_ALLOWED;
|
||||||
requestedProperties += PROP_GHOSTING_ALLOWED;
|
requestedProperties += PROP_GHOSTING_ALLOWED;
|
||||||
requestedProperties += PROP_FILTER_URL;
|
requestedProperties += PROP_FILTER_URL;
|
||||||
|
requestedProperties += PROP_AVATAR_PRIORITY;
|
||||||
|
|
||||||
requestedProperties += PROP_KEY_LIGHT_MODE;
|
requestedProperties += PROP_KEY_LIGHT_MODE;
|
||||||
requestedProperties += PROP_AMBIENT_LIGHT_MODE;
|
requestedProperties += PROP_AMBIENT_LIGHT_MODE;
|
||||||
|
@ -256,6 +260,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode());
|
APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode());
|
APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode());
|
APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode());
|
||||||
|
APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZoneEntityItem::debugDump() const {
|
void ZoneEntityItem::debugDump() const {
|
||||||
|
@ -269,6 +274,7 @@ void ZoneEntityItem::debugDump() const {
|
||||||
qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeAsString(_ambientLightMode);
|
qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeAsString(_ambientLightMode);
|
||||||
qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeAsString(_skyboxMode);
|
qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeAsString(_skyboxMode);
|
||||||
qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeAsString(_bloomMode);
|
qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeAsString(_bloomMode);
|
||||||
|
qCDebug(entities) << " _avatarPriority:" << getAvatarPriority();
|
||||||
|
|
||||||
_keyLightProperties.debugDump();
|
_keyLightProperties.debugDump();
|
||||||
_ambientLightProperties.debugDump();
|
_ambientLightProperties.debugDump();
|
||||||
|
@ -463,3 +469,18 @@ void ZoneEntityItem::fetchCollisionGeometryResource() {
|
||||||
_shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
|
_shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ZoneEntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
|
||||||
|
// currently the only property filter we handle in ZoneEntityItem is value of avatarPriority
|
||||||
|
|
||||||
|
static const QString AVATAR_PRIORITY_PROPERTY = "avatarPriority";
|
||||||
|
|
||||||
|
// If set ignore only priority-inherit zones:
|
||||||
|
if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY) && jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool()
|
||||||
|
&& _avatarPriority != COMPONENT_MODE_INHERIT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain to base:
|
||||||
|
return EntityItem::matchesJSONFilters(jsonFilters);
|
||||||
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ public:
|
||||||
QString getCompoundShapeURL() const;
|
QString getCompoundShapeURL() const;
|
||||||
virtual void setCompoundShapeURL(const QString& url);
|
virtual void setCompoundShapeURL(const QString& url);
|
||||||
|
|
||||||
|
virtual bool matchesJSONFilters(const QJsonObject& jsonFilters) const override;
|
||||||
|
|
||||||
KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock<KeyLightPropertyGroup>([&] { return _keyLightProperties; }); }
|
KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock<KeyLightPropertyGroup>([&] { return _keyLightProperties; }); }
|
||||||
AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock<AmbientLightPropertyGroup>([&] { return _ambientLightProperties; }); }
|
AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock<AmbientLightPropertyGroup>([&] { return _ambientLightProperties; }); }
|
||||||
|
|
||||||
|
@ -96,6 +98,9 @@ public:
|
||||||
QString getFilterURL() const;
|
QString getFilterURL() const;
|
||||||
void setFilterURL(const QString url);
|
void setFilterURL(const QString url);
|
||||||
|
|
||||||
|
uint32_t getAvatarPriority() const { return _avatarPriority; }
|
||||||
|
void setAvatarPriority(uint32_t value) { _avatarPriority = value; }
|
||||||
|
|
||||||
bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; }
|
bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; }
|
||||||
bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; }
|
bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; }
|
||||||
bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; }
|
bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; }
|
||||||
|
@ -147,6 +152,9 @@ protected:
|
||||||
bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED };
|
bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED };
|
||||||
QString _filterURL { DEFAULT_FILTER_URL };
|
QString _filterURL { DEFAULT_FILTER_URL };
|
||||||
|
|
||||||
|
// Avatar-updates priority
|
||||||
|
uint32_t _avatarPriority { COMPONENT_MODE_INHERIT };
|
||||||
|
|
||||||
// Dirty flags turn true when either keylight properties is changing values.
|
// Dirty flags turn true when either keylight properties is changing values.
|
||||||
bool _keyLightPropertiesChanged { false };
|
bool _keyLightPropertiesChanged { false };
|
||||||
bool _ambientLightPropertiesChanged { false };
|
bool _ambientLightPropertiesChanged { false };
|
||||||
|
|
|
@ -167,7 +167,6 @@ glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return globalTransform;
|
return globalTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,6 +435,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
||||||
hfmModel.originalURL = url;
|
hfmModel.originalURL = url;
|
||||||
|
|
||||||
float unitScaleFactor = 1.0f;
|
float unitScaleFactor = 1.0f;
|
||||||
|
glm::quat upAxisZRotation;
|
||||||
|
bool applyUpAxisZRotation = false;
|
||||||
glm::vec3 ambientColor;
|
glm::vec3 ambientColor;
|
||||||
QString hifiGlobalNodeID;
|
QString hifiGlobalNodeID;
|
||||||
unsigned int meshIndex = 0;
|
unsigned int meshIndex = 0;
|
||||||
|
@ -473,11 +474,22 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
||||||
if (subobject.name == propertyName) {
|
if (subobject.name == propertyName) {
|
||||||
static const QVariant UNIT_SCALE_FACTOR = QByteArray("UnitScaleFactor");
|
static const QVariant UNIT_SCALE_FACTOR = QByteArray("UnitScaleFactor");
|
||||||
static const QVariant AMBIENT_COLOR = QByteArray("AmbientColor");
|
static const QVariant AMBIENT_COLOR = QByteArray("AmbientColor");
|
||||||
|
static const QVariant UP_AXIS = QByteArray("UpAxis");
|
||||||
const auto& subpropName = subobject.properties.at(0);
|
const auto& subpropName = subobject.properties.at(0);
|
||||||
if (subpropName == UNIT_SCALE_FACTOR) {
|
if (subpropName == UNIT_SCALE_FACTOR) {
|
||||||
unitScaleFactor = subobject.properties.at(index).toFloat();
|
unitScaleFactor = subobject.properties.at(index).toFloat();
|
||||||
} else if (subpropName == AMBIENT_COLOR) {
|
} else if (subpropName == AMBIENT_COLOR) {
|
||||||
ambientColor = getVec3(subobject.properties, index);
|
ambientColor = getVec3(subobject.properties, index);
|
||||||
|
} else if (subpropName == UP_AXIS) {
|
||||||
|
constexpr int UP_AXIS_Y = 1;
|
||||||
|
constexpr int UP_AXIS_Z = 2;
|
||||||
|
int upAxis = subobject.properties.at(index).toInt();
|
||||||
|
if (upAxis == UP_AXIS_Y) {
|
||||||
|
// No update necessary, y up is the default
|
||||||
|
} else if (upAxis == UP_AXIS_Z) {
|
||||||
|
upAxisZRotation = glm::angleAxis(glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
applyUpAxisZRotation = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1269,9 +1281,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
||||||
joint.geometricScaling = fbxModel.geometricScaling;
|
joint.geometricScaling = fbxModel.geometricScaling;
|
||||||
joint.isSkeletonJoint = fbxModel.isLimbNode;
|
joint.isSkeletonJoint = fbxModel.isLimbNode;
|
||||||
hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint);
|
hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint);
|
||||||
|
if (applyUpAxisZRotation && joint.parentIndex == -1) {
|
||||||
|
joint.rotation *= upAxisZRotation;
|
||||||
|
joint.translation = upAxisZRotation * joint.translation;
|
||||||
|
}
|
||||||
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
||||||
|
|
||||||
if (joint.parentIndex == -1) {
|
if (joint.parentIndex == -1) {
|
||||||
joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform *
|
joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform *
|
||||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||||
|
@ -1664,6 +1678,14 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (applyUpAxisZRotation) {
|
||||||
|
hfmModelPtr->meshExtents.transform(glm::mat4_cast(upAxisZRotation));
|
||||||
|
hfmModelPtr->bindExtents.transform(glm::mat4_cast(upAxisZRotation));
|
||||||
|
for (auto &mesh : hfmModelPtr->meshes) {
|
||||||
|
mesh.modelTransform *= glm::mat4_cast(upAxisZRotation);
|
||||||
|
mesh.meshExtents.transform(glm::mat4_cast(upAxisZRotation));
|
||||||
|
}
|
||||||
|
}
|
||||||
return hfmModelPtr;
|
return hfmModelPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,7 +117,7 @@ namespace baker {
|
||||||
|
|
||||||
class BakerEngineBuilder {
|
class BakerEngineBuilder {
|
||||||
public:
|
public:
|
||||||
using Input = VaryingSet2<hfm::Model::Pointer, QVariantHash>;
|
using Input = VaryingSet2<hfm::Model::Pointer, GeometryMappingPair>;
|
||||||
using Output = VaryingSet2<hfm::Model::Pointer, MaterialMapping>;
|
using Output = VaryingSet2<hfm::Model::Pointer, MaterialMapping>;
|
||||||
using JobModel = Task::ModelIO<BakerEngineBuilder, Input, Output>;
|
using JobModel = Task::ModelIO<BakerEngineBuilder, Input, Output>;
|
||||||
void build(JobModel& model, const Varying& input, Varying& output) {
|
void build(JobModel& model, const Varying& input, Varying& output) {
|
||||||
|
@ -169,7 +169,7 @@ namespace baker {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Baker::Baker(const hfm::Model::Pointer& hfmModel, const QVariantHash& mapping) :
|
Baker::Baker(const hfm::Model::Pointer& hfmModel, const GeometryMappingPair& mapping) :
|
||||||
_engine(std::make_shared<Engine>(BakerEngineBuilder::JobModel::create("Baker"), std::make_shared<BakeContext>())) {
|
_engine(std::make_shared<Engine>(BakerEngineBuilder::JobModel::create("Baker"), std::make_shared<BakeContext>())) {
|
||||||
_engine->feedInput<BakerEngineBuilder::Input>(0, hfmModel);
|
_engine->feedInput<BakerEngineBuilder::Input>(0, hfmModel);
|
||||||
_engine->feedInput<BakerEngineBuilder::Input>(1, mapping);
|
_engine->feedInput<BakerEngineBuilder::Input>(1, mapping);
|
||||||
|
|
|
@ -17,13 +17,14 @@
|
||||||
#include <hfm/HFM.h>
|
#include <hfm/HFM.h>
|
||||||
|
|
||||||
#include "Engine.h"
|
#include "Engine.h"
|
||||||
|
#include "BakerTypes.h"
|
||||||
|
|
||||||
#include "ParseMaterialMappingTask.h"
|
#include "ParseMaterialMappingTask.h"
|
||||||
|
|
||||||
namespace baker {
|
namespace baker {
|
||||||
class Baker {
|
class Baker {
|
||||||
public:
|
public:
|
||||||
Baker(const hfm::Model::Pointer& hfmModel, const QVariantHash& mapping);
|
Baker(const hfm::Model::Pointer& hfmModel, const GeometryMappingPair& mapping);
|
||||||
|
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#ifndef hifi_BakerTypes_h
|
#ifndef hifi_BakerTypes_h
|
||||||
#define hifi_BakerTypes_h
|
#define hifi_BakerTypes_h
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
#include <hfm/HFM.h>
|
#include <hfm/HFM.h>
|
||||||
|
|
||||||
namespace baker {
|
namespace baker {
|
||||||
|
@ -35,6 +36,7 @@ namespace baker {
|
||||||
using TangentsPerBlendshape = std::vector<std::vector<glm::vec3>>;
|
using TangentsPerBlendshape = std::vector<std::vector<glm::vec3>>;
|
||||||
|
|
||||||
using MeshIndicesToModelNames = QHash<int, QString>;
|
using MeshIndicesToModelNames = QHash<int, QString>;
|
||||||
|
using GeometryMappingPair = std::pair<QUrl, QVariantHash>;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_BakerTypes_h
|
#endif // hifi_BakerTypes_h
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
|
|
||||||
#include "ModelBakerLogging.h"
|
#include "ModelBakerLogging.h"
|
||||||
|
|
||||||
void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& mapping, Output& output) {
|
void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) {
|
||||||
|
const auto& url = input.first;
|
||||||
|
const auto& mapping = input.second;
|
||||||
MaterialMapping materialMapping;
|
MaterialMapping materialMapping;
|
||||||
|
|
||||||
auto mappingIter = mapping.find("materialMap");
|
auto mappingIter = mapping.find("materialMap");
|
||||||
|
@ -59,14 +61,13 @@ void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, con
|
||||||
{
|
{
|
||||||
NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); });
|
NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); });
|
||||||
materialResource->moveToThread(qApp->thread());
|
materialResource->moveToThread(qApp->thread());
|
||||||
// TODO: add baseURL to allow FSTs to reference relative files next to them
|
materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), url);
|
||||||
materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), QUrl());
|
|
||||||
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>(mapping.toStdString(), materialResource));
|
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>(mapping.toStdString(), materialResource));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (mappingJSON.isString()) {
|
} else if (mappingJSON.isString()) {
|
||||||
auto mappingValue = mappingJSON.toString();
|
auto mappingValue = mappingJSON.toString();
|
||||||
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>(mapping.toStdString(), MaterialCache::instance().getMaterial(mappingValue)));
|
materialMapping.push_back(std::pair<std::string, NetworkMaterialResourcePointer>(mapping.toStdString(), MaterialCache::instance().getMaterial(url.resolved(mappingValue))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,13 @@
|
||||||
#include <hfm/HFM.h>
|
#include <hfm/HFM.h>
|
||||||
|
|
||||||
#include "Engine.h"
|
#include "Engine.h"
|
||||||
|
#include "BakerTypes.h"
|
||||||
|
|
||||||
#include <material-networking/MaterialCache.h>
|
#include <material-networking/MaterialCache.h>
|
||||||
|
|
||||||
class ParseMaterialMappingTask {
|
class ParseMaterialMappingTask {
|
||||||
public:
|
public:
|
||||||
using Input = QVariantHash;
|
using Input = baker::GeometryMappingPair;
|
||||||
using Output = MaterialMapping;
|
using Output = MaterialMapping;
|
||||||
using JobModel = baker::Job::ModelIO<ParseMaterialMappingTask, Input, Output>;
|
using JobModel = baker::Job::ModelIO<ParseMaterialMappingTask, Input, Output>;
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu
|
||||||
auto& jointIndices = output.edit2();
|
auto& jointIndices = output.edit2();
|
||||||
|
|
||||||
// Get joint renames
|
// Get joint renames
|
||||||
auto jointNameMapping = getJointNameMapping(mapping);
|
auto jointNameMapping = getJointNameMapping(mapping.second);
|
||||||
// Apply joint metadata from FST file mappings
|
// Apply joint metadata from FST file mappings
|
||||||
for (const auto& jointIn : jointsIn) {
|
for (const auto& jointIn : jointsIn) {
|
||||||
jointsOut.push_back(jointIn);
|
jointsOut.push_back(jointIn);
|
||||||
|
@ -73,7 +73,7 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get joint rotation offsets from FST file mappings
|
// Get joint rotation offsets from FST file mappings
|
||||||
auto offsets = getJointRotationOffsets(mapping);
|
auto offsets = getJointRotationOffsets(mapping.second);
|
||||||
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
||||||
QString jointName = itr.key();
|
QString jointName = itr.key();
|
||||||
int jointIndex = jointIndices.value(jointName) - 1;
|
int jointIndex = jointIndices.value(jointName) - 1;
|
||||||
|
|
|
@ -17,10 +17,11 @@
|
||||||
#include <hfm/HFM.h>
|
#include <hfm/HFM.h>
|
||||||
|
|
||||||
#include "Engine.h"
|
#include "Engine.h"
|
||||||
|
#include "BakerTypes.h"
|
||||||
|
|
||||||
class PrepareJointsTask {
|
class PrepareJointsTask {
|
||||||
public:
|
public:
|
||||||
using Input = baker::VaryingSet2<std::vector<hfm::Joint>, QVariantHash /*mapping*/>;
|
using Input = baker::VaryingSet2<std::vector<hfm::Joint>, baker::GeometryMappingPair /*mapping*/>;
|
||||||
using Output = baker::VaryingSet3<std::vector<hfm::Joint>, QMap<int, glm::quat> /*jointRotationOffsets*/, QHash<QString, int> /*jointIndices*/>;
|
using Output = baker::VaryingSet3<std::vector<hfm::Joint>, QMap<int, glm::quat> /*jointRotationOffsets*/, QHash<QString, int> /*jointIndices*/>;
|
||||||
using JobModel = baker::Job::ModelIO<PrepareJointsTask, Input, Output>;
|
using JobModel = baker::Job::ModelIO<PrepareJointsTask, Input, Output>;
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,13 @@ class GeometryReader;
|
||||||
|
|
||||||
class GeometryExtra {
|
class GeometryExtra {
|
||||||
public:
|
public:
|
||||||
const QVariantHash& mapping;
|
const GeometryMappingPair& mapping;
|
||||||
const QUrl& textureBaseUrl;
|
const QUrl& textureBaseUrl;
|
||||||
bool combineParts;
|
bool combineParts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int geometryMappingPairTypeId = qRegisterMetaType<GeometryMappingPair>("GeometryMappingPair");
|
||||||
|
|
||||||
// From: https://stackoverflow.com/questions/41145012/how-to-hash-qvariant
|
// From: https://stackoverflow.com/questions/41145012/how-to-hash-qvariant
|
||||||
class QVariantHasher {
|
class QVariantHasher {
|
||||||
public:
|
public:
|
||||||
|
@ -78,7 +80,8 @@ namespace std {
|
||||||
struct hash<GeometryExtra> {
|
struct hash<GeometryExtra> {
|
||||||
size_t operator()(const GeometryExtra& geometryExtra) const {
|
size_t operator()(const GeometryExtra& geometryExtra) const {
|
||||||
size_t result = 0;
|
size_t result = 0;
|
||||||
hash_combine(result, geometryExtra.mapping, geometryExtra.textureBaseUrl, geometryExtra.combineParts);
|
hash_combine(result, geometryExtra.mapping.first, geometryExtra.mapping.second, geometryExtra.textureBaseUrl,
|
||||||
|
geometryExtra.combineParts);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -151,7 +154,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto modelCache = DependencyManager::get<ModelCache>();
|
auto modelCache = DependencyManager::get<ModelCache>();
|
||||||
GeometryExtra extra { _mapping, _textureBaseUrl, false };
|
GeometryExtra extra { GeometryMappingPair(_url, _mapping), _textureBaseUrl, false };
|
||||||
|
|
||||||
// Get the raw GeometryResource
|
// Get the raw GeometryResource
|
||||||
_geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash<GeometryExtra>()(extra)).staticCast<GeometryResource>();
|
_geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash<GeometryExtra>()(extra)).staticCast<GeometryResource>();
|
||||||
|
@ -191,7 +194,7 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
|
||||||
|
|
||||||
class GeometryReader : public QRunnable {
|
class GeometryReader : public QRunnable {
|
||||||
public:
|
public:
|
||||||
GeometryReader(const ModelLoader& modelLoader, QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
|
GeometryReader(const ModelLoader& modelLoader, QWeakPointer<Resource>& resource, const QUrl& url, const GeometryMappingPair& mapping,
|
||||||
const QByteArray& data, bool combineParts, const QString& webMediaType) :
|
const QByteArray& data, bool combineParts, const QString& webMediaType) :
|
||||||
_modelLoader(modelLoader), _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts), _webMediaType(webMediaType) {
|
_modelLoader(modelLoader), _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts), _webMediaType(webMediaType) {
|
||||||
|
|
||||||
|
@ -204,7 +207,7 @@ private:
|
||||||
ModelLoader _modelLoader;
|
ModelLoader _modelLoader;
|
||||||
QWeakPointer<Resource> _resource;
|
QWeakPointer<Resource> _resource;
|
||||||
QUrl _url;
|
QUrl _url;
|
||||||
QVariantHash _mapping;
|
GeometryMappingPair _mapping;
|
||||||
QByteArray _data;
|
QByteArray _data;
|
||||||
bool _combineParts;
|
bool _combineParts;
|
||||||
QString _webMediaType;
|
QString _webMediaType;
|
||||||
|
@ -244,7 +247,7 @@ void GeometryReader::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
HFMModel::Pointer hfmModel;
|
HFMModel::Pointer hfmModel;
|
||||||
QVariantHash serializerMapping = _mapping;
|
QVariantHash serializerMapping = _mapping.second;
|
||||||
serializerMapping["combineParts"] = _combineParts;
|
serializerMapping["combineParts"] = _combineParts;
|
||||||
|
|
||||||
if (_url.path().toLower().endsWith(".gz")) {
|
if (_url.path().toLower().endsWith(".gz")) {
|
||||||
|
@ -270,15 +273,14 @@ void GeometryReader::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add scripts to hfmModel
|
// Add scripts to hfmModel
|
||||||
if (!_mapping.value(SCRIPT_FIELD).isNull()) {
|
if (!serializerMapping.value(SCRIPT_FIELD).isNull()) {
|
||||||
QVariantList scripts = _mapping.values(SCRIPT_FIELD);
|
QVariantList scripts = serializerMapping.values(SCRIPT_FIELD);
|
||||||
for (auto &script : scripts) {
|
for (auto &script : scripts) {
|
||||||
hfmModel->scripts.push_back(script.toString());
|
hfmModel->scripts.push_back(script.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
|
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
|
||||||
Q_ARG(HFMModel::Pointer, hfmModel), Q_ARG(QVariantHash, _mapping));
|
Q_ARG(HFMModel::Pointer, hfmModel), Q_ARG(GeometryMappingPair, _mapping));
|
||||||
} catch (const std::exception&) {
|
} catch (const std::exception&) {
|
||||||
auto resource = _resource.toStrongRef();
|
auto resource = _resource.toStrongRef();
|
||||||
if (resource) {
|
if (resource) {
|
||||||
|
@ -312,17 +314,17 @@ public:
|
||||||
void setExtra(void* extra) override;
|
void setExtra(void* extra) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, QVariantHash mapping);
|
Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, const GeometryMappingPair& mapping);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModelLoader _modelLoader;
|
ModelLoader _modelLoader;
|
||||||
QVariantHash _mapping;
|
GeometryMappingPair _mapping;
|
||||||
bool _combineParts;
|
bool _combineParts;
|
||||||
};
|
};
|
||||||
|
|
||||||
void GeometryDefinitionResource::setExtra(void* extra) {
|
void GeometryDefinitionResource::setExtra(void* extra) {
|
||||||
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
|
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
|
||||||
_mapping = geometryExtra ? geometryExtra->mapping : QVariantHash();
|
_mapping = geometryExtra ? geometryExtra->mapping : GeometryMappingPair(QUrl(), QVariantHash());
|
||||||
_textureBaseUrl = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl();
|
_textureBaseUrl = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl();
|
||||||
_combineParts = geometryExtra ? geometryExtra->combineParts : true;
|
_combineParts = geometryExtra ? geometryExtra->combineParts : true;
|
||||||
}
|
}
|
||||||
|
@ -335,7 +337,7 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
|
||||||
QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType()));
|
QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, QVariantHash mapping) {
|
void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const GeometryMappingPair& mapping) {
|
||||||
// Do processing on the model
|
// Do processing on the model
|
||||||
baker::Baker modelBaker(hfmModel, mapping);
|
baker::Baker modelBaker(hfmModel, mapping);
|
||||||
modelBaker.run();
|
modelBaker.run();
|
||||||
|
@ -394,11 +396,15 @@ QSharedPointer<Resource> ModelCache::createResource(const QUrl& url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<Resource> ModelCache::createResourceCopy(const QSharedPointer<Resource>& resource) {
|
QSharedPointer<Resource> ModelCache::createResourceCopy(const QSharedPointer<Resource>& resource) {
|
||||||
return QSharedPointer<Resource>(new GeometryDefinitionResource(*resource.staticCast<GeometryDefinitionResource>()), &Resource::deleter);
|
if (resource->getURL().path().toLower().endsWith(".fst")) {
|
||||||
|
return QSharedPointer<Resource>(new GeometryMappingResource(*resource.staticCast<GeometryMappingResource>()), &Resource::deleter);
|
||||||
|
} else {
|
||||||
|
return QSharedPointer<Resource>(new GeometryDefinitionResource(*resource.staticCast<GeometryDefinitionResource>()), &Resource::deleter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url,
|
GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url,
|
||||||
const QVariantHash& mapping, const QUrl& textureBaseUrl) {
|
const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) {
|
||||||
bool combineParts = true;
|
bool combineParts = true;
|
||||||
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
|
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
|
||||||
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash<GeometryExtra>()(geometryExtra)).staticCast<GeometryResource>();
|
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash<GeometryExtra>()(geometryExtra)).staticCast<GeometryResource>();
|
||||||
|
@ -411,7 +417,8 @@ GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url,
|
||||||
}
|
}
|
||||||
|
|
||||||
GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& url,
|
GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& url,
|
||||||
const QVariantHash& mapping, const QUrl& textureBaseUrl) {
|
const GeometryMappingPair& mapping,
|
||||||
|
const QUrl& textureBaseUrl) {
|
||||||
bool combineParts = false;
|
bool combineParts = false;
|
||||||
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
|
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
|
||||||
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash<GeometryExtra>()(geometryExtra)).staticCast<GeometryResource>();
|
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash<GeometryExtra>()(geometryExtra)).staticCast<GeometryResource>();
|
||||||
|
|
|
@ -26,6 +26,9 @@ class MeshPart;
|
||||||
|
|
||||||
class GeometryMappingResource;
|
class GeometryMappingResource;
|
||||||
|
|
||||||
|
using GeometryMappingPair = std::pair<QUrl, QVariantHash>;
|
||||||
|
Q_DECLARE_METATYPE(GeometryMappingPair)
|
||||||
|
|
||||||
class Geometry {
|
class Geometry {
|
||||||
public:
|
public:
|
||||||
using Pointer = std::shared_ptr<Geometry>;
|
using Pointer = std::shared_ptr<Geometry>;
|
||||||
|
@ -145,11 +148,13 @@ class ModelCache : public ResourceCache, public Dependency {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
GeometryResource::Pointer getGeometryResource(const QUrl& url,
|
GeometryResource::Pointer getGeometryResource(const QUrl& url,
|
||||||
const QVariantHash& mapping = QVariantHash(),
|
const GeometryMappingPair& mapping =
|
||||||
|
GeometryMappingPair(QUrl(), QVariantHash()),
|
||||||
const QUrl& textureBaseUrl = QUrl());
|
const QUrl& textureBaseUrl = QUrl());
|
||||||
|
|
||||||
GeometryResource::Pointer getCollisionGeometryResource(const QUrl& url,
|
GeometryResource::Pointer getCollisionGeometryResource(const QUrl& url,
|
||||||
const QVariantHash& mapping = QVariantHash(),
|
const GeometryMappingPair& mapping =
|
||||||
|
GeometryMappingPair(QUrl(), QVariantHash()),
|
||||||
const QUrl& textureBaseUrl = QUrl());
|
const QUrl& textureBaseUrl = QUrl());
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -40,6 +40,9 @@
|
||||||
|
|
||||||
static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0);
|
static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0);
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL_MS = 1s;
|
||||||
|
|
||||||
const std::set<NodeType_t> SOLO_NODE_TYPES = {
|
const std::set<NodeType_t> SOLO_NODE_TYPES = {
|
||||||
NodeType::AvatarMixer,
|
NodeType::AvatarMixer,
|
||||||
NodeType::AudioMixer,
|
NodeType::AudioMixer,
|
||||||
|
@ -88,6 +91,11 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) :
|
||||||
connect(statsSampleTimer, &QTimer::timeout, this, &LimitedNodeList::sampleConnectionStats);
|
connect(statsSampleTimer, &QTimer::timeout, this, &LimitedNodeList::sampleConnectionStats);
|
||||||
statsSampleTimer->start(CONNECTION_STATS_SAMPLE_INTERVAL_MSECS);
|
statsSampleTimer->start(CONNECTION_STATS_SAMPLE_INTERVAL_MSECS);
|
||||||
|
|
||||||
|
// Flush delayed adds every second
|
||||||
|
QTimer* delayedAddsFlushTimer = new QTimer(this);
|
||||||
|
connect(delayedAddsFlushTimer, &QTimer::timeout, this, &NodeList::processDelayedAdds);
|
||||||
|
delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL_MS.count());
|
||||||
|
|
||||||
// check the local socket right now
|
// check the local socket right now
|
||||||
updateLocalSocket();
|
updateLocalSocket();
|
||||||
|
|
||||||
|
@ -367,7 +375,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else {
|
} else if (!isDelayedNode(sourceID)){
|
||||||
HIFI_FCDEBUG(networking(),
|
HIFI_FCDEBUG(networking(),
|
||||||
"Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID);
|
"Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID);
|
||||||
}
|
}
|
||||||
|
@ -558,25 +566,23 @@ SharedNodePointer LimitedNodeList::nodeWithLocalID(Node::LocalID localID) const
|
||||||
}
|
}
|
||||||
|
|
||||||
void LimitedNodeList::eraseAllNodes() {
|
void LimitedNodeList::eraseAllNodes() {
|
||||||
QSet<SharedNodePointer> killedNodes;
|
std::vector<SharedNodePointer> killedNodes;
|
||||||
|
|
||||||
{
|
{
|
||||||
// iterate the current nodes - grab them so we can emit that they are dying
|
// iterate the current nodes - grab them so we can emit that they are dying
|
||||||
// and then remove them from the hash
|
// and then remove them from the hash
|
||||||
QWriteLocker writeLocker(&_nodeMutex);
|
QWriteLocker writeLocker(&_nodeMutex);
|
||||||
|
|
||||||
_localIDMap.clear();
|
|
||||||
|
|
||||||
if (_nodeHash.size() > 0) {
|
if (_nodeHash.size() > 0) {
|
||||||
qCDebug(networking) << "LimitedNodeList::eraseAllNodes() removing all nodes from NodeList.";
|
qCDebug(networking) << "LimitedNodeList::eraseAllNodes() removing all nodes from NodeList.";
|
||||||
|
|
||||||
auto it = _nodeHash.begin();
|
killedNodes.reserve(_nodeHash.size());
|
||||||
|
for (auto& pair : _nodeHash) {
|
||||||
while (it != _nodeHash.end()) {
|
killedNodes.push_back(pair.second);
|
||||||
killedNodes.insert(it->second);
|
|
||||||
it = _nodeHash.unsafe_erase(it);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_localIDMap.clear();
|
||||||
|
_nodeHash.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(const SharedNodePointer& killedNode, killedNodes) {
|
foreach(const SharedNodePointer& killedNode, killedNodes) {
|
||||||
|
@ -593,18 +599,13 @@ void LimitedNodeList::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID) {
|
bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID) {
|
||||||
QReadLocker readLocker(&_nodeMutex);
|
auto matchingNode = nodeWithUUID(nodeUUID);
|
||||||
|
|
||||||
NodeHash::iterator it = _nodeHash.find(nodeUUID);
|
|
||||||
if (it != _nodeHash.end()) {
|
|
||||||
SharedNodePointer matchingNode = it->second;
|
|
||||||
|
|
||||||
readLocker.unlock();
|
|
||||||
|
|
||||||
|
if (matchingNode) {
|
||||||
{
|
{
|
||||||
QWriteLocker writeLocker(&_nodeMutex);
|
QWriteLocker writeLocker(&_nodeMutex);
|
||||||
_localIDMap.unsafe_erase(matchingNode->getLocalID());
|
_localIDMap.unsafe_erase(matchingNode->getLocalID());
|
||||||
_nodeHash.unsafe_erase(it);
|
_nodeHash.unsafe_erase(matchingNode->getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNodeKill(matchingNode, newConnectionID);
|
handleNodeKill(matchingNode, newConnectionID);
|
||||||
|
@ -645,30 +646,26 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
||||||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
|
||||||
Node::LocalID localID, bool isReplicated, bool isUpstream,
|
Node::LocalID localID, bool isReplicated, bool isUpstream,
|
||||||
const QUuid& connectionSecret, const NodePermissions& permissions) {
|
const QUuid& connectionSecret, const NodePermissions& permissions) {
|
||||||
{
|
auto matchingNode = nodeWithUUID(uuid);
|
||||||
QReadLocker readLocker(&_nodeMutex);
|
if (matchingNode) {
|
||||||
NodeHash::const_iterator it = _nodeHash.find(uuid);
|
matchingNode->setPublicSocket(publicSocket);
|
||||||
|
matchingNode->setLocalSocket(localSocket);
|
||||||
|
matchingNode->setPermissions(permissions);
|
||||||
|
matchingNode->setConnectionSecret(connectionSecret);
|
||||||
|
matchingNode->setIsReplicated(isReplicated);
|
||||||
|
matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType));
|
||||||
|
matchingNode->setLocalID(localID);
|
||||||
|
|
||||||
if (it != _nodeHash.end()) {
|
return matchingNode;
|
||||||
SharedNodePointer& matchingNode = it->second;
|
|
||||||
|
|
||||||
matchingNode->setPublicSocket(publicSocket);
|
|
||||||
matchingNode->setLocalSocket(localSocket);
|
|
||||||
matchingNode->setPermissions(permissions);
|
|
||||||
matchingNode->setConnectionSecret(connectionSecret);
|
|
||||||
matchingNode->setIsReplicated(isReplicated);
|
|
||||||
matchingNode->setIsUpstream(isUpstream || NodeType::isUpstream(nodeType));
|
|
||||||
matchingNode->setLocalID(localID);
|
|
||||||
|
|
||||||
return matchingNode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto removeOldNode = [&](auto node) {
|
auto removeOldNode = [&](auto node) {
|
||||||
if (node) {
|
if (node) {
|
||||||
QWriteLocker writeLocker(&_nodeMutex);
|
{
|
||||||
_localIDMap.unsafe_erase(node->getLocalID());
|
QWriteLocker writeLocker(&_nodeMutex);
|
||||||
_nodeHash.unsafe_erase(node->getUUID());
|
_localIDMap.unsafe_erase(node->getLocalID());
|
||||||
|
_nodeHash.unsafe_erase(node->getUUID());
|
||||||
|
}
|
||||||
handleNodeKill(node);
|
handleNodeKill(node);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -736,6 +733,53 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
||||||
return newNodePointer;
|
return newNodePointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LimitedNodeList::addNewNode(NewNodeInfo info) {
|
||||||
|
// Throttle connection of new agents.
|
||||||
|
if (info.type == NodeType::Agent && _nodesAddedInCurrentTimeSlice >= _maxConnectionRate) {
|
||||||
|
delayNodeAdd(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedNodePointer node = addOrUpdateNode(info.uuid, info.type, info.publicSocket, info.localSocket,
|
||||||
|
info.sessionLocalID, info.isReplicated, false,
|
||||||
|
info.connectionSecretUUID, info.permissions);
|
||||||
|
|
||||||
|
++_nodesAddedInCurrentTimeSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LimitedNodeList::delayNodeAdd(NewNodeInfo info) {
|
||||||
|
_delayedNodeAdds.push_back(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LimitedNodeList::removeDelayedAdd(QUuid nodeUUID) {
|
||||||
|
auto it = std::find_if(_delayedNodeAdds.begin(), _delayedNodeAdds.end(), [&](auto info) {
|
||||||
|
return info.uuid == nodeUUID;
|
||||||
|
});
|
||||||
|
if (it != _delayedNodeAdds.end()) {
|
||||||
|
_delayedNodeAdds.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LimitedNodeList::isDelayedNode(QUuid nodeUUID) {
|
||||||
|
auto it = std::find_if(_delayedNodeAdds.begin(), _delayedNodeAdds.end(), [&](auto info) {
|
||||||
|
return info.uuid == nodeUUID;
|
||||||
|
});
|
||||||
|
return it != _delayedNodeAdds.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LimitedNodeList::processDelayedAdds() {
|
||||||
|
_nodesAddedInCurrentTimeSlice = 0;
|
||||||
|
|
||||||
|
auto nodesToAdd = glm::min(_delayedNodeAdds.size(), _maxConnectionRate);
|
||||||
|
auto firstNodeToAdd = _delayedNodeAdds.begin();
|
||||||
|
auto lastNodeToAdd = firstNodeToAdd + nodesToAdd;
|
||||||
|
|
||||||
|
for (auto it = firstNodeToAdd; it != lastNodeToAdd; ++it) {
|
||||||
|
addNewNode(*it);
|
||||||
|
}
|
||||||
|
_delayedNodeAdds.erase(firstNodeToAdd, lastNodeToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) {
|
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) {
|
||||||
int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(int64_t);
|
int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(int64_t);
|
||||||
|
|
||||||
|
@ -793,13 +837,13 @@ unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr<NLPacket> packet,
|
||||||
|
|
||||||
eachNode([&](const SharedNodePointer& node){
|
eachNode([&](const SharedNodePointer& node){
|
||||||
if (node && destinationNodeTypes.contains(node->getType())) {
|
if (node && destinationNodeTypes.contains(node->getType())) {
|
||||||
if (packet->isReliable()) {
|
if (packet->isReliable()) {
|
||||||
auto packetCopy = NLPacket::createCopy(*packet);
|
auto packetCopy = NLPacket::createCopy(*packet);
|
||||||
sendPacket(std::move(packetCopy), *node);
|
sendPacket(std::move(packetCopy), *node);
|
||||||
} else {
|
} else {
|
||||||
sendUnreliablePacket(*packet, *node);
|
sendUnreliablePacket(*packet, *node);
|
||||||
}
|
}
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,8 @@ const int INVALID_PORT = -1;
|
||||||
|
|
||||||
const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
||||||
|
|
||||||
|
static const size_t DEFAULT_MAX_CONNECTION_RATE { std::numeric_limits<size_t>::max() };
|
||||||
|
|
||||||
extern const std::set<NodeType_t> SOLO_NODE_TYPES;
|
extern const std::set<NodeType_t> SOLO_NODE_TYPES;
|
||||||
|
|
||||||
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
|
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
|
||||||
|
@ -205,7 +207,10 @@ public:
|
||||||
int* lockWaitOut = nullptr,
|
int* lockWaitOut = nullptr,
|
||||||
int* nodeTransformOut = nullptr,
|
int* nodeTransformOut = nullptr,
|
||||||
int* functorOut = nullptr) {
|
int* functorOut = nullptr) {
|
||||||
auto start = usecTimestampNow();
|
quint64 start, endTransform, endFunctor;
|
||||||
|
|
||||||
|
start = usecTimestampNow();
|
||||||
|
std::vector<SharedNodePointer> nodes;
|
||||||
{
|
{
|
||||||
QReadLocker readLock(&_nodeMutex);
|
QReadLocker readLock(&_nodeMutex);
|
||||||
auto endLock = usecTimestampNow();
|
auto endLock = usecTimestampNow();
|
||||||
|
@ -216,21 +221,21 @@ public:
|
||||||
// Size of _nodeHash could change at any time,
|
// Size of _nodeHash could change at any time,
|
||||||
// so reserve enough memory for the current size
|
// so reserve enough memory for the current size
|
||||||
// and then back insert all the nodes found
|
// and then back insert all the nodes found
|
||||||
std::vector<SharedNodePointer> nodes;
|
|
||||||
nodes.reserve(_nodeHash.size());
|
nodes.reserve(_nodeHash.size());
|
||||||
std::transform(_nodeHash.cbegin(), _nodeHash.cend(), std::back_inserter(nodes), [&](const NodeHash::value_type& it) {
|
std::transform(_nodeHash.cbegin(), _nodeHash.cend(), std::back_inserter(nodes), [&](const NodeHash::value_type& it) {
|
||||||
return it.second;
|
return it.second;
|
||||||
});
|
});
|
||||||
auto endTransform = usecTimestampNow();
|
|
||||||
|
endTransform = usecTimestampNow();
|
||||||
if (nodeTransformOut) {
|
if (nodeTransformOut) {
|
||||||
*nodeTransformOut = (endTransform - endLock);
|
*nodeTransformOut = (endTransform - endLock);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
functor(nodes.cbegin(), nodes.cend());
|
functor(nodes.cbegin(), nodes.cend());
|
||||||
auto endFunctor = usecTimestampNow();
|
endFunctor = usecTimestampNow();
|
||||||
if (functorOut) {
|
if (functorOut) {
|
||||||
*functorOut = (endFunctor - endTransform);
|
*functorOut = (endFunctor - endTransform);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,6 +321,9 @@ public:
|
||||||
void sendFakedHandshakeRequestToNode(SharedNodePointer node);
|
void sendFakedHandshakeRequestToNode(SharedNodePointer node);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
size_t getMaxConnectionRate() const { return _maxConnectionRate; }
|
||||||
|
void setMaxConnectionRate(size_t rate) { _maxConnectionRate = rate; }
|
||||||
|
|
||||||
int getInboundPPS() const { return _inboundPPS; }
|
int getInboundPPS() const { return _inboundPPS; }
|
||||||
int getOutboundPPS() const { return _outboundPPS; }
|
int getOutboundPPS() const { return _outboundPPS; }
|
||||||
float getInboundKbps() const { return _inboundKbps; }
|
float getInboundKbps() const { return _inboundKbps; }
|
||||||
|
@ -367,7 +375,20 @@ protected slots:
|
||||||
|
|
||||||
void clientConnectionToSockAddrReset(const HifiSockAddr& sockAddr);
|
void clientConnectionToSockAddrReset(const HifiSockAddr& sockAddr);
|
||||||
|
|
||||||
|
void processDelayedAdds();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
struct NewNodeInfo {
|
||||||
|
qint8 type;
|
||||||
|
QUuid uuid;
|
||||||
|
HifiSockAddr publicSocket;
|
||||||
|
HifiSockAddr localSocket;
|
||||||
|
NodePermissions permissions;
|
||||||
|
bool isReplicated;
|
||||||
|
Node::LocalID sessionLocalID;
|
||||||
|
QUuid connectionSecretUUID;
|
||||||
|
};
|
||||||
|
|
||||||
LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
|
LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
|
||||||
LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
|
LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
|
||||||
void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
|
void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
|
||||||
|
@ -390,6 +411,11 @@ protected:
|
||||||
|
|
||||||
bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr);
|
bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr);
|
||||||
|
|
||||||
|
void addNewNode(NewNodeInfo info);
|
||||||
|
void delayNodeAdd(NewNodeInfo info);
|
||||||
|
void removeDelayedAdd(QUuid nodeUUID);
|
||||||
|
bool isDelayedNode(QUuid nodeUUID);
|
||||||
|
|
||||||
NodeHash _nodeHash;
|
NodeHash _nodeHash;
|
||||||
mutable QReadWriteLock _nodeMutex { QReadWriteLock::Recursive };
|
mutable QReadWriteLock _nodeMutex { QReadWriteLock::Recursive };
|
||||||
udt::Socket _nodeSocket;
|
udt::Socket _nodeSocket;
|
||||||
|
@ -440,6 +466,10 @@ private:
|
||||||
Node::LocalID _sessionLocalID { 0 };
|
Node::LocalID _sessionLocalID { 0 };
|
||||||
bool _flagTimeForConnectionStep { false }; // only keep track in interface
|
bool _flagTimeForConnectionStep { false }; // only keep track in interface
|
||||||
|
|
||||||
|
size_t _maxConnectionRate { DEFAULT_MAX_CONNECTION_RATE };
|
||||||
|
size_t _nodesAddedInCurrentTimeSlice { 0 };
|
||||||
|
std::vector<NewNodeInfo> _delayedNodeAdds;
|
||||||
|
|
||||||
int _inboundPPS { 0 };
|
int _inboundPPS { 0 };
|
||||||
int _outboundPPS { 0 };
|
int _outboundPPS { 0 };
|
||||||
float _inboundKbps { 0.0f };
|
float _inboundKbps { 0.0f };
|
||||||
|
|
|
@ -200,7 +200,6 @@ void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer&
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||||
|
|
||||||
// send back a reply
|
// send back a reply
|
||||||
auto replyPacket = constructPingReplyPacket(*message);
|
auto replyPacket = constructPingReplyPacket(*message);
|
||||||
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
|
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
|
||||||
|
@ -291,41 +290,47 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes)
|
||||||
|
|
||||||
void NodeList::sendDomainServerCheckIn() {
|
void NodeList::sendDomainServerCheckIn() {
|
||||||
|
|
||||||
|
// This function is called by the server check-in timer thread
|
||||||
|
// not the NodeList thread. Calling it on the NodeList thread
|
||||||
|
// resulted in starvation of the server check-in function.
|
||||||
|
// be VERY CAREFUL modifying this code as members of NodeList
|
||||||
|
// may be called by multiple threads.
|
||||||
|
|
||||||
if (!_sendDomainServerCheckInEnabled) {
|
if (!_sendDomainServerCheckInEnabled) {
|
||||||
qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled.";
|
qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread() != QThread::currentThread()) {
|
|
||||||
QMetaObject::invokeMethod(this, "sendDomainServerCheckIn", Qt::QueuedConnection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_isShuttingDown) {
|
if (_isShuttingDown) {
|
||||||
qCDebug(networking) << "Refusing to send a domain-server check in while shutting down.";
|
qCDebug(networking) << "Refusing to send a domain-server check in while shutting down.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_publicSockAddr.isNull()) {
|
auto publicSockAddr = _publicSockAddr;
|
||||||
|
auto domainHandlerIp = _domainHandler.getIP();
|
||||||
|
|
||||||
|
if (publicSockAddr.isNull()) {
|
||||||
// we don't know our public socket and we need to send it to the domain server
|
// we don't know our public socket and we need to send it to the domain server
|
||||||
qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in.";
|
qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in.";
|
||||||
} else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) {
|
} else if (domainHandlerIp.isNull() && _domainHandler.requiresICE()) {
|
||||||
qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
|
qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
|
||||||
handleICEConnectionToDomainServer();
|
handleICEConnectionToDomainServer();
|
||||||
// let the domain handler know we are due to send a checkin packet
|
// let the domain handler know we are due to send a checkin packet
|
||||||
} else if (!_domainHandler.getIP().isNull() && !_domainHandler.checkInPacketTimeout()) {
|
} else if (!domainHandlerIp.isNull() && !_domainHandler.checkInPacketTimeout()) {
|
||||||
|
bool domainIsConnected = _domainHandler.isConnected();
|
||||||
PacketType domainPacketType = !_domainHandler.isConnected()
|
HifiSockAddr domainSockAddr = _domainHandler.getSockAddr();
|
||||||
|
PacketType domainPacketType = !domainIsConnected
|
||||||
? PacketType::DomainConnectRequest : PacketType::DomainListRequest;
|
? PacketType::DomainConnectRequest : PacketType::DomainListRequest;
|
||||||
|
|
||||||
if (!_domainHandler.isConnected()) {
|
if (!domainIsConnected) {
|
||||||
qCDebug(networking) << "Sending connect request to domain-server at" << _domainHandler.getHostname();
|
auto hostname = _domainHandler.getHostname();
|
||||||
|
qCDebug(networking) << "Sending connect request to domain-server at" << hostname;
|
||||||
|
|
||||||
// is this our localhost domain-server?
|
// is this our localhost domain-server?
|
||||||
// if so we need to make sure we have an up-to-date local port in case it restarted
|
// if so we need to make sure we have an up-to-date local port in case it restarted
|
||||||
|
|
||||||
if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|
if (domainSockAddr.getAddress() == QHostAddress::LocalHost
|
||||||
|| _domainHandler.getHostname() == "localhost") {
|
|| hostname == "localhost") {
|
||||||
|
|
||||||
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
|
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
|
||||||
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort);
|
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort);
|
||||||
|
@ -338,7 +343,7 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
const QUuid& connectionToken = _domainHandler.getConnectionToken();
|
const QUuid& connectionToken = _domainHandler.getConnectionToken();
|
||||||
|
|
||||||
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull();
|
bool requiresUsernameSignature = !domainIsConnected && !connectionToken.isNull();
|
||||||
|
|
||||||
if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {
|
if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {
|
||||||
qWarning() << "A keypair is required to present a username signature to the domain-server"
|
qWarning() << "A keypair is required to present a username signature to the domain-server"
|
||||||
|
@ -353,6 +358,7 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
|
|
||||||
QDataStream packetStream(domainPacket.get());
|
QDataStream packetStream(domainPacket.get());
|
||||||
|
|
||||||
|
HifiSockAddr localSockAddr = _localSockAddr;
|
||||||
if (domainPacketType == PacketType::DomainConnectRequest) {
|
if (domainPacketType == PacketType::DomainConnectRequest) {
|
||||||
|
|
||||||
#if (PR_BUILD || DEV_BUILD)
|
#if (PR_BUILD || DEV_BUILD)
|
||||||
|
@ -361,13 +367,9 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QUuid connectUUID;
|
QUuid connectUUID = _domainHandler.getAssignmentUUID();
|
||||||
|
|
||||||
if (!_domainHandler.getAssignmentUUID().isNull()) {
|
if (connectUUID.isNull() && _domainHandler.requiresICE()) {
|
||||||
// this is a connect request and we're an assigned node
|
|
||||||
// so set our packetUUID as the assignment UUID
|
|
||||||
connectUUID = _domainHandler.getAssignmentUUID();
|
|
||||||
} else if (_domainHandler.requiresICE()) {
|
|
||||||
// this is a connect request and we're an interface client
|
// this is a connect request and we're an interface client
|
||||||
// that used ice to discover the DS
|
// that used ice to discover the DS
|
||||||
// so send our ICE client UUID with the connect request
|
// so send our ICE client UUID with the connect request
|
||||||
|
@ -383,10 +385,9 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
|
|
||||||
// if possible, include the MAC address for the current interface in our connect request
|
// if possible, include the MAC address for the current interface in our connect request
|
||||||
QString hardwareAddress;
|
QString hardwareAddress;
|
||||||
|
|
||||||
for (auto networkInterface : QNetworkInterface::allInterfaces()) {
|
for (auto networkInterface : QNetworkInterface::allInterfaces()) {
|
||||||
for (auto interfaceAddress : networkInterface.addressEntries()) {
|
for (auto interfaceAddress : networkInterface.addressEntries()) {
|
||||||
if (interfaceAddress.ip() == _localSockAddr.getAddress()) {
|
if (interfaceAddress.ip() == localSockAddr.getAddress()) {
|
||||||
// this is the interface whose local IP matches what we've detected the current IP to be
|
// this is the interface whose local IP matches what we've detected the current IP to be
|
||||||
hardwareAddress = networkInterface.hardwareAddress();
|
hardwareAddress = networkInterface.hardwareAddress();
|
||||||
|
|
||||||
|
@ -410,10 +411,10 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
|
|
||||||
// pack our data to send to the domain-server including
|
// pack our data to send to the domain-server including
|
||||||
// the hostname information (so the domain-server can see which place name we came in on)
|
// the hostname information (so the domain-server can see which place name we came in on)
|
||||||
packetStream << _ownerType.load() << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList();
|
||||||
packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
|
packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
|
||||||
|
|
||||||
if (!_domainHandler.isConnected()) {
|
if (!domainIsConnected) {
|
||||||
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
|
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
|
||||||
packetStream << accountInfo.getUsername();
|
packetStream << accountInfo.getUsername();
|
||||||
|
|
||||||
|
@ -433,9 +434,9 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
checkinCount = std::min(checkinCount, MAX_CHECKINS_TOGETHER);
|
checkinCount = std::min(checkinCount, MAX_CHECKINS_TOGETHER);
|
||||||
for (int i = 1; i < checkinCount; ++i) {
|
for (int i = 1; i < checkinCount; ++i) {
|
||||||
auto packetCopy = domainPacket->createCopy(*domainPacket);
|
auto packetCopy = domainPacket->createCopy(*domainPacket);
|
||||||
sendPacket(std::move(packetCopy), _domainHandler.getSockAddr());
|
sendPacket(std::move(packetCopy), domainSockAddr);
|
||||||
}
|
}
|
||||||
sendPacket(std::move(domainPacket), _domainHandler.getSockAddr());
|
sendPacket(std::move(domainPacket), domainSockAddr);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -708,37 +709,28 @@ void NodeList::processDomainServerRemovedNode(QSharedPointer<ReceivedMessage> me
|
||||||
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
qCDebug(networking) << "Received packet from domain-server to remove node with UUID" << uuidStringWithoutCurlyBraces(nodeUUID);
|
qCDebug(networking) << "Received packet from domain-server to remove node with UUID" << uuidStringWithoutCurlyBraces(nodeUUID);
|
||||||
killNodeWithUUID(nodeUUID);
|
killNodeWithUUID(nodeUUID);
|
||||||
|
removeDelayedAdd(nodeUUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
|
void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
|
||||||
// setup variables to read into from QDataStream
|
NewNodeInfo info;
|
||||||
qint8 nodeType;
|
|
||||||
QUuid nodeUUID, connectionSecretUUID;
|
|
||||||
HifiSockAddr nodePublicSocket, nodeLocalSocket;
|
|
||||||
NodePermissions permissions;
|
|
||||||
bool isReplicated;
|
|
||||||
Node::LocalID sessionLocalID;
|
|
||||||
|
|
||||||
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> permissions
|
packetStream >> info.type
|
||||||
>> isReplicated >> sessionLocalID;
|
>> info.uuid
|
||||||
|
>> info.publicSocket
|
||||||
|
>> info.localSocket
|
||||||
|
>> info.permissions
|
||||||
|
>> info.isReplicated
|
||||||
|
>> info.sessionLocalID
|
||||||
|
>> info.connectionSecretUUID;
|
||||||
|
|
||||||
// if the public socket address is 0 then it's reachable at the same IP
|
// if the public socket address is 0 then it's reachable at the same IP
|
||||||
// as the domain server
|
// as the domain server
|
||||||
if (nodePublicSocket.getAddress().isNull()) {
|
if (info.publicSocket.getAddress().isNull()) {
|
||||||
nodePublicSocket.setAddress(_domainHandler.getIP());
|
info.publicSocket.setAddress(_domainHandler.getIP());
|
||||||
}
|
}
|
||||||
|
|
||||||
packetStream >> connectionSecretUUID;
|
addNewNode(info);
|
||||||
|
|
||||||
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket,
|
|
||||||
sessionLocalID, isReplicated, false, connectionSecretUUID, permissions);
|
|
||||||
|
|
||||||
// nodes that are downstream or upstream of our own type are kept alive when we hear about them from the domain server
|
|
||||||
// and always have their public socket as their active socket
|
|
||||||
if (node->getType() == NodeType::downstreamType(_ownerType) || node->getType() == NodeType::upstreamType(_ownerType)) {
|
|
||||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
|
||||||
node->activatePublicSocket();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::sendAssignment(Assignment& assignment) {
|
void NodeList::sendAssignment(Assignment& assignment) {
|
||||||
|
@ -785,7 +777,6 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::startNodeHolePunch(const SharedNodePointer& node) {
|
void NodeList::startNodeHolePunch(const SharedNodePointer& node) {
|
||||||
|
|
||||||
// we don't hole punch to downstream servers, since it is assumed that we have a direct line to them
|
// we don't hole punch to downstream servers, since it is assumed that we have a direct line to them
|
||||||
// we also don't hole punch to relayed upstream nodes, since we do not communicate directly with them
|
// we also don't hole punch to relayed upstream nodes, since we do not communicate directly with them
|
||||||
|
|
||||||
|
@ -799,6 +790,14 @@ void NodeList::startNodeHolePunch(const SharedNodePointer& node) {
|
||||||
// ping this node immediately
|
// ping this node immediately
|
||||||
pingPunchForInactiveNode(node);
|
pingPunchForInactiveNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nodes that are downstream or upstream of our own type are kept alive when we hear about them from the domain server
|
||||||
|
// and always have their public socket as their active socket
|
||||||
|
if (node->getType() == NodeType::downstreamType(_ownerType) || node->getType() == NodeType::upstreamType(_ownerType)) {
|
||||||
|
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||||
|
node->activatePublicSocket();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::handleNodePingTimeout() {
|
void NodeList::handleNodePingTimeout() {
|
||||||
|
|
|
@ -102,6 +102,11 @@ void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObjec
|
||||||
|
|
||||||
statsObject["io_stats"] = ioStats;
|
statsObject["io_stats"] = ioStats;
|
||||||
|
|
||||||
|
QJsonObject assignmentStats;
|
||||||
|
assignmentStats["numQueuedCheckIns"] = _numQueuedCheckIns;
|
||||||
|
|
||||||
|
statsObject["assignmentStats"] = assignmentStats;
|
||||||
|
|
||||||
nodeList->sendStatsToDomainServer(statsObject);
|
nodeList->sendStatsToDomainServer(statsObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,10 +124,16 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() {
|
||||||
stop();
|
stop();
|
||||||
} else {
|
} else {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn");
|
// Call sendDomainServerCheckIn directly instead of putting it on
|
||||||
|
// the event queue. Under high load, the event queue can back up
|
||||||
|
// longer than the total timeout period and cause a restart
|
||||||
|
nodeList->sendDomainServerCheckIn();
|
||||||
|
|
||||||
// increase the number of queued check ins
|
// increase the number of queued check ins
|
||||||
_numQueuedCheckIns++;
|
_numQueuedCheckIns++;
|
||||||
|
if (_numQueuedCheckIns > 1) {
|
||||||
|
qCDebug(networking) << "Number of queued checkins = " << _numQueuedCheckIns;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -260,6 +260,7 @@ enum class EntityVersion : PacketVersion {
|
||||||
MissingWebEntityProperties,
|
MissingWebEntityProperties,
|
||||||
PulseProperties,
|
PulseProperties,
|
||||||
RingGizmoEntities,
|
RingGizmoEntities,
|
||||||
|
AvatarPriorityZone,
|
||||||
ShowKeyboardFocusHighlight,
|
ShowKeyboardFocusHighlight,
|
||||||
WebBillboardMode,
|
WebBillboardMode,
|
||||||
ModelScale,
|
ModelScale,
|
||||||
|
|
|
@ -134,6 +134,9 @@
|
||||||
"bloom.bloomSize": {
|
"bloom.bloomSize": {
|
||||||
"tooltip": "The radius of bloom. The higher the value, the larger the bloom."
|
"tooltip": "The radius of bloom. The higher the value, the larger the bloom."
|
||||||
},
|
},
|
||||||
|
"avatarPriority": {
|
||||||
|
"tooltip": "Alter Avatars' update priorities."
|
||||||
|
},
|
||||||
"modelURL": {
|
"modelURL": {
|
||||||
"tooltip": "A mesh model from an FBX or OBJ file."
|
"tooltip": "A mesh model from an FBX or OBJ file."
|
||||||
},
|
},
|
||||||
|
|
|
@ -382,7 +382,8 @@ const DEFAULT_ENTITY_PROPERTIES = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
shapeType: "box",
|
shapeType: "box",
|
||||||
bloomMode: "inherit"
|
bloomMode: "inherit",
|
||||||
|
avatarPriority: "inherit"
|
||||||
},
|
},
|
||||||
Model: {
|
Model: {
|
||||||
collisionShape: "none",
|
collisionShape: "none",
|
||||||
|
|
|
@ -428,6 +428,13 @@ const GROUPS = [
|
||||||
propertyID: "bloom.bloomSize",
|
propertyID: "bloom.bloomSize",
|
||||||
showPropertyRule: { "bloomMode": "enabled" },
|
showPropertyRule: { "bloomMode": "enabled" },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Avatar Priority",
|
||||||
|
type: "dropdown",
|
||||||
|
options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" },
|
||||||
|
propertyID: "avatarPriority",
|
||||||
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -125,9 +125,6 @@ module.exports = (function() {
|
||||||
|
|
||||||
Script.scriptEnding.connect(this, function() {
|
Script.scriptEnding.connect(this, function() {
|
||||||
this.window.close();
|
this.window.close();
|
||||||
// FIXME: temp solution for reload crash (MS18269),
|
|
||||||
// we should decide on proper object ownership strategy for InteractiveWindow API
|
|
||||||
this.window = null;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setVisible: function(visible) {
|
setVisible: function(visible) {
|
||||||
|
|
|
@ -47,24 +47,30 @@ These steps assume the hifi repository has been cloned to `~/hifi`.
|
||||||
### Windows
|
### Windows
|
||||||
1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/)
|
1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/)
|
||||||
1. Click the "add python to path" checkbox on the python installer
|
1. Click the "add python to path" checkbox on the python installer
|
||||||
1. After installation - add the path to python.exe to the Windows PATH environment variable.
|
1. After installation:
|
||||||
|
1. Open a new terminal
|
||||||
|
1. Enter `python` and hit enter
|
||||||
|
1. Verify that python is available (the prompt will change to `>>>`)
|
||||||
|
1. Type `exit()` and hit enter to close python
|
||||||
|
1. Install requests (a python library to download files from URLs)
|
||||||
|
`pip3 install requests`
|
||||||
1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/
|
1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/
|
||||||
1. Open a new command prompt and run
|
1. Open a new command prompt and run
|
||||||
`aws configure`
|
`aws configure`
|
||||||
1. Enter the AWS account number
|
1. Enter the AWS account number
|
||||||
1. Enter the secret key
|
1. Enter the secret key
|
||||||
1. Leave region name and ouput format as default [None]
|
1. Leave region name and ouput format as default [None]
|
||||||
1. Install the latest release of Boto3 via pip:
|
1. Install the latest release of Boto3 via pip (from a terminal):
|
||||||
`pip install boto3`
|
`pip install boto3`
|
||||||
|
|
||||||
1. (First time) Download adb (Android Debug Bridge) from *https://dl.google.com/android/repository/platform-tools-latest-windows.zip*
|
1. (First time) Download adb (Android Debug Bridge) from *https://dl.google.com/android/repository/platform-tools-latest-windows.zip*
|
||||||
1. Copy the downloaded file to (for example) **C:\adb** and extract in place.
|
1. Copy the downloaded file to (for example) **C:\adb** and extract in place.
|
||||||
Verify you see *adb.exe* in **C:\adb\platform-tools\\**.
|
Verify you see *adb.exe* in **C:\adb\platform-tools\\**.
|
||||||
1. After installation - add the path to adb.exe to the Windows PATH environment variable (note that it is in *adb\platform-tools*).
|
1. After installation - add the path to adb.exe to the Windows PATH environment variable (note that it is in *adb\platform-tools*).
|
||||||
1. `nitpick` is included in the High Fidelity installer but can also be downloaded from:
|
1. `nitpick` is included in the High Fidelity installer but can also be downloaded from (change X.X.X to correct version):
|
||||||
[here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vX.X.X.dmg>).*
|
[here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vX.X.X.dmg>).*
|
||||||
### Mac
|
### Mac
|
||||||
1. (first time) Install brew
|
1. (First time) Install brew
|
||||||
In a terminal:
|
In a terminal:
|
||||||
`/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
|
`/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
|
||||||
Note that you will need to press RETURN again, and will then be asked for your password.
|
Note that you will need to press RETURN again, and will then be asked for your password.
|
||||||
|
@ -76,11 +82,13 @@ These steps assume the hifi repository has been cloned to `~/hifi`.
|
||||||
`open "/Applications/Python 3.7/Install Certificates.command"`.
|
`open "/Applications/Python 3.7/Install Certificates.command"`.
|
||||||
This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates.
|
This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates.
|
||||||
1. Verify that `/usr/local/bin/python3` exists.
|
1. Verify that `/usr/local/bin/python3` exists.
|
||||||
1. (First time - AWS interface) Install pip with the script provided by the Python Packaging Authority:
|
|
||||||
In a terminal:
|
In a terminal:
|
||||||
`curl -O https://bootstrap.pypa.io/get-pip.py`
|
`curl -O https://bootstrap.pypa.io/get-pip.py`
|
||||||
In a terminal:
|
In a terminal:
|
||||||
`python3 get-pip.py --user`
|
`python3 get-pip.py --user`
|
||||||
|
1. Install requests (a python library to download files from URLs)
|
||||||
|
`pip3 install requests`
|
||||||
|
1. (First time - AWS interface) Install pip with the script provided by the Python Packaging Authority:
|
||||||
1. Use pip to install the AWS CLI.
|
1. Use pip to install the AWS CLI.
|
||||||
`pip3 install awscli --upgrade --user`
|
`pip3 install awscli --upgrade --user`
|
||||||
This will install aws in your user. For user XXX, aws will be located in ~/Library/Python/3.7/bin
|
This will install aws in your user. For user XXX, aws will be located in ~/Library/Python/3.7/bin
|
||||||
|
@ -92,6 +100,16 @@ This is needed because the Mac Python supplied no longer links with the deprecat
|
||||||
1. Install the latest release of Boto3 via pip: pip3 install boto3
|
1. Install the latest release of Boto3 via pip: pip3 install boto3
|
||||||
1. (First time)Install adb (the Android Debug Bridge) - in a terminal:
|
1. (First time)Install adb (the Android Debug Bridge) - in a terminal:
|
||||||
`brew cask install android-platform-tools`
|
`brew cask install android-platform-tools`
|
||||||
|
1. (First time) Set terminal privileges
|
||||||
|
1. Click on Apple icon (top left)
|
||||||
|
1. Select System Preferences...
|
||||||
|
1. Select Security & Privacy
|
||||||
|
1. Select Accessibility
|
||||||
|
1. Click on "Click the lock to make changes" and enter passsword if requested
|
||||||
|
1. Set Checkbox near *Terminal* to checked.
|
||||||
|
1. Click on "Click the lock to prevent furthur changes"
|
||||||
|
1. Close window
|
||||||
|
|
||||||
1. `nitpick` is included in the High Fidelity installer but can also be downloaded from:
|
1. `nitpick` is included in the High Fidelity installer but can also be downloaded from:
|
||||||
[here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-vX.X.X.dmg>).*
|
[here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-vX.X.X.dmg>).*
|
||||||
# Usage
|
# Usage
|
||||||
|
|
|
@ -27,7 +27,10 @@ AWSInterface::AWSInterface(QObject* parent) : QObject(parent) {
|
||||||
void AWSInterface::createWebPageFromResults(const QString& testResults,
|
void AWSInterface::createWebPageFromResults(const QString& testResults,
|
||||||
const QString& workingDirectory,
|
const QString& workingDirectory,
|
||||||
QCheckBox* updateAWSCheckBox,
|
QCheckBox* updateAWSCheckBox,
|
||||||
QLineEdit* urlLineEdit) {
|
QRadioButton* diffImageRadioButton,
|
||||||
|
QRadioButton* ssimImageRadionButton,
|
||||||
|
QLineEdit* urlLineEdit
|
||||||
|
) {
|
||||||
_workingDirectory = workingDirectory;
|
_workingDirectory = workingDirectory;
|
||||||
|
|
||||||
// Verify filename is in correct format
|
// Verify filename is in correct format
|
||||||
|
@ -52,6 +55,13 @@ void AWSInterface::createWebPageFromResults(const QString& testResults,
|
||||||
|
|
||||||
QString zipFilenameWithoutExtension = zipFilename.split('.')[0];
|
QString zipFilenameWithoutExtension = zipFilename.split('.')[0];
|
||||||
extractTestFailuresFromZippedFolder(_workingDirectory + "/" + zipFilenameWithoutExtension);
|
extractTestFailuresFromZippedFolder(_workingDirectory + "/" + zipFilenameWithoutExtension);
|
||||||
|
|
||||||
|
if (diffImageRadioButton->isChecked()) {
|
||||||
|
_comparisonImageFilename = "Difference Image.png";
|
||||||
|
} else {
|
||||||
|
_comparisonImageFilename = "SSIM Image.png";
|
||||||
|
}
|
||||||
|
|
||||||
createHTMLFile();
|
createHTMLFile();
|
||||||
|
|
||||||
if (updateAWSCheckBox->isChecked()) {
|
if (updateAWSCheckBox->isChecked()) {
|
||||||
|
@ -353,7 +363,7 @@ void AWSInterface::openTable(QTextStream& stream, const QString& testResult, con
|
||||||
stream << "\t\t\t\t<th><h1>Test</h1></th>\n";
|
stream << "\t\t\t\t<th><h1>Test</h1></th>\n";
|
||||||
stream << "\t\t\t\t<th><h1>Actual Image</h1></th>\n";
|
stream << "\t\t\t\t<th><h1>Actual Image</h1></th>\n";
|
||||||
stream << "\t\t\t\t<th><h1>Expected Image</h1></th>\n";
|
stream << "\t\t\t\t<th><h1>Expected Image</h1></th>\n";
|
||||||
stream << "\t\t\t\t<th><h1>Difference Image</h1></th>\n";
|
stream << "\t\t\t\t<th><h1>Comparison Image</h1></th>\n";
|
||||||
stream << "\t\t\t</tr>\n";
|
stream << "\t\t\t</tr>\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,12 +388,13 @@ void AWSInterface::createEntry(const int index, const QString& testResult, QText
|
||||||
|
|
||||||
QString folder;
|
QString folder;
|
||||||
bool differenceFileFound;
|
bool differenceFileFound;
|
||||||
|
|
||||||
if (isFailure) {
|
if (isFailure) {
|
||||||
folder = FAILURES_FOLDER;
|
folder = FAILURES_FOLDER;
|
||||||
differenceFileFound = QFile::exists(_htmlFailuresFolder + "/" + resultName + "/Difference Image.png");
|
differenceFileFound = QFile::exists(_htmlFailuresFolder + "/" + resultName + "/" + _comparisonImageFilename);
|
||||||
} else {
|
} else {
|
||||||
folder = SUCCESSES_FOLDER;
|
folder = SUCCESSES_FOLDER;
|
||||||
differenceFileFound = QFile::exists(_htmlSuccessesFolder + "/" + resultName + "/Difference Image.png");
|
differenceFileFound = QFile::exists(_htmlSuccessesFolder + "/" + resultName + "/" + _comparisonImageFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textResultsFileFound) {
|
if (textResultsFileFound) {
|
||||||
|
@ -450,7 +461,7 @@ void AWSInterface::createEntry(const int index, const QString& testResult, QText
|
||||||
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Expected Image.png\" width = \"576\" height = \"324\" ></td>\n";
|
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Expected Image.png\" width = \"576\" height = \"324\" ></td>\n";
|
||||||
|
|
||||||
if (differenceFileFound) {
|
if (differenceFileFound) {
|
||||||
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Difference Image.png\" width = \"576\" height = \"324\" ></td>\n";
|
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/" << _comparisonImageFilename << "\" width = \"576\" height = \"324\" ></td>\n";
|
||||||
} else {
|
} else {
|
||||||
stream << "\t\t\t\t<td><h2>No Image Found</h2>\n";
|
stream << "\t\t\t\t<td><h2>No Image Found</h2>\n";
|
||||||
}
|
}
|
||||||
|
@ -469,7 +480,7 @@ void AWSInterface::updateAWS() {
|
||||||
|
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||||
"Could not create 'addTestCases.py'");
|
"Could not create 'updateAWS.py'");
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,12 +523,12 @@ void AWSInterface::updateAWS() {
|
||||||
|
|
||||||
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n";
|
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n";
|
||||||
|
|
||||||
if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) {
|
if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/" + _comparisonImageFilename)) {
|
||||||
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
|
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
|
||||||
<< "Difference Image.png"
|
<< _comparisonImageFilename
|
||||||
<< "', 'rb')\n";
|
<< "', 'rb')\n";
|
||||||
|
|
||||||
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n";
|
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << _comparisonImageFilename << "', Body=data)\n\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -555,12 +566,12 @@ void AWSInterface::updateAWS() {
|
||||||
|
|
||||||
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n";
|
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n";
|
||||||
|
|
||||||
if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) {
|
if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/" + _comparisonImageFilename)) {
|
||||||
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
|
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
|
||||||
<< "Difference Image.png"
|
<< _comparisonImageFilename
|
||||||
<< "', 'rb')\n";
|
<< "', 'rb')\n";
|
||||||
|
|
||||||
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n";
|
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << _comparisonImageFilename << "', Body=data)\n\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -578,6 +589,7 @@ void AWSInterface::updateAWS() {
|
||||||
|
|
||||||
QProcess* process = new QProcess();
|
QProcess* process = new QProcess();
|
||||||
|
|
||||||
|
_busyWindow.setWindowTitle("Updating AWS");
|
||||||
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
||||||
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
||||||
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QRadioButton>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
#include "BusyWindow.h"
|
#include "BusyWindow.h"
|
||||||
|
@ -28,6 +29,8 @@ public:
|
||||||
void createWebPageFromResults(const QString& testResults,
|
void createWebPageFromResults(const QString& testResults,
|
||||||
const QString& workingDirectory,
|
const QString& workingDirectory,
|
||||||
QCheckBox* updateAWSCheckBox,
|
QCheckBox* updateAWSCheckBox,
|
||||||
|
QRadioButton* diffImageRadioButton,
|
||||||
|
QRadioButton* ssimImageRadionButton,
|
||||||
QLineEdit* urlLineEdit);
|
QLineEdit* urlLineEdit);
|
||||||
|
|
||||||
void extractTestFailuresFromZippedFolder(const QString& folderName);
|
void extractTestFailuresFromZippedFolder(const QString& folderName);
|
||||||
|
@ -67,6 +70,9 @@ private:
|
||||||
QString AWS_BUCKET{ "hifi-qa" };
|
QString AWS_BUCKET{ "hifi-qa" };
|
||||||
|
|
||||||
QLineEdit* _urlLineEdit;
|
QLineEdit* _urlLineEdit;
|
||||||
|
|
||||||
|
|
||||||
|
QString _comparisonImageFilename;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AWSInterface_h
|
#endif // hifi_AWSInterface_h
|
|
@ -16,12 +16,13 @@
|
||||||
QString AdbInterface::getAdbCommand() {
|
QString AdbInterface::getAdbCommand() {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
if (_adbCommand.isNull()) {
|
if (_adbCommand.isNull()) {
|
||||||
QString adbPath = PathUtils::getPathToExecutable("adb.exe");
|
QString adbExe{ "adb.exe" };
|
||||||
|
QString adbPath = PathUtils::getPathToExecutable(adbExe);
|
||||||
if (!adbPath.isNull()) {
|
if (!adbPath.isNull()) {
|
||||||
_adbCommand = adbPath + _adbExe;
|
_adbCommand = adbExe;
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::critical(0, "python.exe not found",
|
QMessageBox::critical(0, "adb.exe not found",
|
||||||
"Please verify that pyton.exe is in the PATH");
|
"Please verify that adb.exe is in the PATH");
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,6 @@ public:
|
||||||
QString getAdbCommand();
|
QString getAdbCommand();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
const QString _adbExe{ "adb.exe" };
|
|
||||||
#else
|
|
||||||
// Both Mac and Linux use "python"
|
|
||||||
const QString _adbExe{ "adb" };
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QString _adbCommand;
|
QString _adbCommand;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,32 +8,66 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
#include "Downloader.h"
|
#include "Downloader.h"
|
||||||
|
#include "PythonInterface.h"
|
||||||
|
|
||||||
#include <QtWidgets/QMessageBox>
|
#include <QFile>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
Downloader::Downloader(QUrl fileURL, QObject *parent) : QObject(parent) {
|
Downloader::Downloader() {
|
||||||
_networkAccessManager.get(QNetworkRequest(fileURL));
|
PythonInterface* pythonInterface = new PythonInterface();
|
||||||
|
_pythonCommand = pythonInterface->getPythonCommand();
|
||||||
connect(
|
|
||||||
&_networkAccessManager, SIGNAL (finished(QNetworkReply*)),
|
|
||||||
this, SLOT (fileDownloaded(QNetworkReply*))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downloader::fileDownloaded(QNetworkReply* reply) {
|
void Downloader::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller) {
|
||||||
QNetworkReply::NetworkError error = reply->error();
|
if (URLs.size() <= 0) {
|
||||||
if (error != QNetworkReply::NetworkError::NoError) {
|
|
||||||
QMessageBox::information(0, "Test Aborted", "Failed to download file: " + reply->errorString());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadedData = reply->readAll();
|
QString filename = directoryName + "/downloadFiles.py";
|
||||||
|
if (QFile::exists(filename)) {
|
||||||
|
QFile::remove(filename);
|
||||||
|
}
|
||||||
|
QFile file(filename);
|
||||||
|
|
||||||
//emit a signal
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
reply->deleteLater();
|
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||||
emit downloaded();
|
"Could not create 'downloadFiles.py'");
|
||||||
}
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray Downloader::downloadedData() const {
|
QTextStream stream(&file);
|
||||||
return _downloadedData;
|
|
||||||
|
stream << "import requests\n";
|
||||||
|
|
||||||
|
for (int i = 0; i < URLs.size(); ++i) {
|
||||||
|
stream << "\nurl = '" + URLs[i] + "'\n";
|
||||||
|
stream << "r = requests.get(url)\n";
|
||||||
|
stream << "open('" + directoryName + '/' + filenames [i] + "', 'wb').write(r.content)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QProcess* process = new QProcess();
|
||||||
|
_busyWindow.setWindowTitle("Downloading Files");
|
||||||
|
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
||||||
|
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
||||||
|
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
||||||
|
[=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); });
|
||||||
|
|
||||||
|
QStringList parameters = QStringList() << filename;
|
||||||
|
process->start(_pythonCommand, parameters);
|
||||||
|
#elif defined Q_OS_MAC
|
||||||
|
QProcess* process = new QProcess();
|
||||||
|
QStringList parameters = QStringList() << "-c" << _pythonCommand + " " + filename;
|
||||||
|
process->start("sh", parameters);
|
||||||
|
|
||||||
|
// Wait for the last file to download
|
||||||
|
while (!QFile::exists(directoryName + '/' + filenames[filenames.length() - 1])) {
|
||||||
|
QThread::msleep(200);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,38 +11,19 @@
|
||||||
#ifndef hifi_downloader_h
|
#ifndef hifi_downloader_h
|
||||||
#define hifi_downloader_h
|
#define hifi_downloader_h
|
||||||
|
|
||||||
#include <QObject>
|
#include "BusyWindow.h"
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QByteArray>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
class Downloader : public QObject {
|
class Downloader : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit Downloader(QUrl fileURL, QObject *parent = 0);
|
Downloader();
|
||||||
|
|
||||||
QByteArray downloadedData() const;
|
void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller);
|
||||||
|
|
||||||
signals:
|
|
||||||
void downloaded();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void fileDownloaded(QNetworkReply* pReply);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkAccessManager _networkAccessManager;
|
QString _pythonCommand;
|
||||||
QByteArray _downloadedData;
|
BusyWindow _busyWindow;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_downloader_h
|
#endif // hifi_downloader_h
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
// Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity
|
// Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity
|
||||||
// The value is computed for the luminance component and the average value is returned
|
// The value is computed for the luminance component and the average value is returned
|
||||||
double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const {
|
void ImageComparer::compareImages(const QImage& resultImage, const QImage& expectedImage) {
|
||||||
const int L = 255; // (2^number of bits per pixel) - 1
|
const int L = 255; // (2^number of bits per pixel) - 1
|
||||||
|
|
||||||
const double K1 { 0.01 };
|
const double K1 { 0.01 };
|
||||||
|
@ -39,8 +39,13 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co
|
||||||
double p[WIN_SIZE * WIN_SIZE];
|
double p[WIN_SIZE * WIN_SIZE];
|
||||||
double q[WIN_SIZE * WIN_SIZE];
|
double q[WIN_SIZE * WIN_SIZE];
|
||||||
|
|
||||||
|
_ssimResults.results.clear();
|
||||||
|
|
||||||
int windowCounter{ 0 };
|
int windowCounter{ 0 };
|
||||||
double ssim{ 0.0 };
|
double ssim{ 0.0 };
|
||||||
|
double min { 1.0 };
|
||||||
|
double max { -1.0 };
|
||||||
|
|
||||||
while (x < expectedImage.width()) {
|
while (x < expectedImage.width()) {
|
||||||
int lastX = x + WIN_SIZE - 1;
|
int lastX = x + WIN_SIZE - 1;
|
||||||
if (lastX > expectedImage.width() - 1) {
|
if (lastX > expectedImage.width() - 1) {
|
||||||
|
@ -96,7 +101,13 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co
|
||||||
double numerator = (2.0 * mP * mQ + c1) * (2.0 * sigPQ + c2);
|
double numerator = (2.0 * mP * mQ + c1) * (2.0 * sigPQ + c2);
|
||||||
double denominator = (mP * mP + mQ * mQ + c1) * (sigsqP + sigsqQ + c2);
|
double denominator = (mP * mP + mQ * mQ + c1) * (sigsqP + sigsqQ + c2);
|
||||||
|
|
||||||
ssim += numerator / denominator;
|
double value { numerator / denominator };
|
||||||
|
_ssimResults.results.push_back(value);
|
||||||
|
ssim += value;
|
||||||
|
|
||||||
|
if (value < min) min = value;
|
||||||
|
if (value > max) max = value;
|
||||||
|
|
||||||
++windowCounter;
|
++windowCounter;
|
||||||
|
|
||||||
y += WIN_SIZE;
|
y += WIN_SIZE;
|
||||||
|
@ -106,5 +117,17 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co
|
||||||
y = 0;
|
y = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ssim / windowCounter;
|
_ssimResults.width = (int)(expectedImage.width() / WIN_SIZE);
|
||||||
};
|
_ssimResults.height = (int)(expectedImage.height() / WIN_SIZE);
|
||||||
|
_ssimResults.min = min;
|
||||||
|
_ssimResults.max = max;
|
||||||
|
_ssimResults.ssim = ssim / windowCounter;
|
||||||
|
};
|
||||||
|
|
||||||
|
double ImageComparer::getSSIMValue() {
|
||||||
|
return _ssimResults.ssim;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSIMResults ImageComparer::getSSIMResults() {
|
||||||
|
return _ssimResults;
|
||||||
|
}
|
||||||
|
|
|
@ -10,12 +10,20 @@
|
||||||
#ifndef hifi_ImageComparer_h
|
#ifndef hifi_ImageComparer_h
|
||||||
#define hifi_ImageComparer_h
|
#define hifi_ImageComparer_h
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <QtCore/QString>
|
#include <QtCore/QString>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
class ImageComparer {
|
class ImageComparer {
|
||||||
public:
|
public:
|
||||||
double compareImages(QImage resultImage, QImage expectedImage) const;
|
void compareImages(const QImage& resultImage, const QImage& expectedImage);
|
||||||
|
double getSSIMValue();
|
||||||
|
|
||||||
|
SSIMResults getSSIMResults();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SSIMResults _ssimResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ImageComparer_h
|
#endif // hifi_ImageComparer_h
|
||||||
|
|
|
@ -21,7 +21,7 @@ MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) {
|
||||||
diffImage->setScaledContents(true);
|
diffImage->setScaledContents(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) {
|
QPixmap MismatchWindow::computeDiffPixmap(const QImage& expectedImage, const QImage& resultImage) {
|
||||||
// Create an empty difference image if the images differ in size
|
// Create an empty difference image if the images differ in size
|
||||||
if (expectedImage.height() != resultImage.height() || expectedImage.width() != resultImage.width()) {
|
if (expectedImage.height() != resultImage.height() || expectedImage.width() != resultImage.width()) {
|
||||||
return QPixmap();
|
return QPixmap();
|
||||||
|
@ -60,7 +60,7 @@ QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultIma
|
||||||
return resultPixmap;
|
return resultPixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MismatchWindow::setTestResult(TestResult testResult) {
|
void MismatchWindow::setTestResult(const TestResult& testResult) {
|
||||||
errorLabel->setText("Similarity: " + QString::number(testResult._error));
|
errorLabel->setText("Similarity: " + QString::number(testResult._error));
|
||||||
|
|
||||||
imagePath->setText("Path to test: " + testResult._pathname);
|
imagePath->setText("Path to test: " + testResult._pathname);
|
||||||
|
@ -99,3 +99,36 @@ void MismatchWindow::on_abortTestsButton_clicked() {
|
||||||
QPixmap MismatchWindow::getComparisonImage() {
|
QPixmap MismatchWindow::getComparisonImage() {
|
||||||
return _diffPixmap;
|
return _diffPixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPixmap MismatchWindow::getSSIMResultsImage(const SSIMResults& ssimResults) {
|
||||||
|
// This is an optimization, as QImage.setPixel() is embarrassingly slow
|
||||||
|
const int ELEMENT_SIZE { 8 };
|
||||||
|
const int WIDTH{ ssimResults.width * ELEMENT_SIZE };
|
||||||
|
const int HEIGHT{ ssimResults.height * ELEMENT_SIZE };
|
||||||
|
|
||||||
|
unsigned char* buffer = new unsigned char[WIDTH * HEIGHT * 3];
|
||||||
|
|
||||||
|
|
||||||
|
// loop over each SSIM result
|
||||||
|
for (int y = 0; y < ssimResults.height; ++y) {
|
||||||
|
for (int x = 0; x < ssimResults.width; ++x) {
|
||||||
|
double scaledResult = (ssimResults.results[x * ssimResults.height + y] + 1.0) / (2.0);
|
||||||
|
//double scaledResult = (ssimResults.results[x * ssimResults.height + y] - ssimResults.min) / (ssimResults.max - ssimResults.min);
|
||||||
|
// Create a square
|
||||||
|
for (int yy = 0; yy < ELEMENT_SIZE; ++yy) {
|
||||||
|
for (int xx = 0; xx < ELEMENT_SIZE; ++xx) {
|
||||||
|
buffer[(xx + yy * WIDTH + x * ELEMENT_SIZE + y * WIDTH * ELEMENT_SIZE) * 3 + 0] = 255 * (1.0 - scaledResult); // R
|
||||||
|
buffer[(xx + yy * WIDTH + x * ELEMENT_SIZE + y * WIDTH * ELEMENT_SIZE) * 3 + 1] = 255 * scaledResult; // G
|
||||||
|
buffer[(xx + yy * WIDTH + x * ELEMENT_SIZE + y * WIDTH * ELEMENT_SIZE) * 3 + 2] = 0; // B
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage image(buffer, WIDTH, HEIGHT, QImage::Format_RGB888);
|
||||||
|
QPixmap pixmap = QPixmap::fromImage(image);
|
||||||
|
|
||||||
|
delete[] buffer;
|
||||||
|
|
||||||
|
return pixmap;
|
||||||
|
}
|
||||||
|
|
|
@ -20,12 +20,14 @@ class MismatchWindow : public QDialog, public Ui::MismatchWindow {
|
||||||
public:
|
public:
|
||||||
MismatchWindow(QWidget *parent = Q_NULLPTR);
|
MismatchWindow(QWidget *parent = Q_NULLPTR);
|
||||||
|
|
||||||
void setTestResult(TestResult testResult);
|
void setTestResult(const TestResult& testResult);
|
||||||
|
|
||||||
UserResponse getUserResponse() { return _userResponse; }
|
UserResponse getUserResponse() { return _userResponse; }
|
||||||
|
|
||||||
QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage);
|
QPixmap computeDiffPixmap(const QImage& expectedImage, const QImage& resultImage);
|
||||||
|
|
||||||
QPixmap getComparisonImage();
|
QPixmap getComparisonImage();
|
||||||
|
QPixmap getSSIMResultsImage(const SSIMResults& ssimResults);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_passTestButton_clicked();
|
void on_passTestButton_clicked();
|
||||||
|
|
|
@ -24,8 +24,6 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
|
||||||
_ui.progressBar->setVisible(false);
|
_ui.progressBar->setVisible(false);
|
||||||
_ui.tabWidget->setCurrentIndex(0);
|
_ui.tabWidget->setCurrentIndex(0);
|
||||||
|
|
||||||
_signalMapper = new QSignalMapper();
|
|
||||||
|
|
||||||
connect(_ui.actionClose, &QAction::triggered, this, &Nitpick::on_closePushbutton_clicked);
|
connect(_ui.actionClose, &QAction::triggered, this, &Nitpick::on_closePushbutton_clicked);
|
||||||
connect(_ui.actionAbout, &QAction::triggered, this, &Nitpick::about);
|
connect(_ui.actionAbout, &QAction::triggered, this, &Nitpick::about);
|
||||||
connect(_ui.actionContent, &QAction::triggered, this, &Nitpick::content);
|
connect(_ui.actionContent, &QAction::triggered, this, &Nitpick::content);
|
||||||
|
@ -48,10 +46,8 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Nitpick::~Nitpick() {
|
Nitpick::~Nitpick() {
|
||||||
delete _signalMapper;
|
if (_testCreator) {
|
||||||
|
delete _testCreator;
|
||||||
if (_test) {
|
|
||||||
delete _test;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_testRunnerDesktop) {
|
if (_testRunnerDesktop) {
|
||||||
|
@ -64,10 +60,10 @@ Nitpick::~Nitpick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::setup() {
|
void Nitpick::setup() {
|
||||||
if (_test) {
|
if (_testCreator) {
|
||||||
delete _test;
|
delete _testCreator;
|
||||||
}
|
}
|
||||||
_test = new Test(_ui.progressBar, _ui.checkBoxInteractiveMode);
|
_testCreator = new TestCreator(_ui.progressBar, _ui.checkBoxInteractiveMode);
|
||||||
|
|
||||||
std::vector<QCheckBox*> dayCheckboxes;
|
std::vector<QCheckBox*> dayCheckboxes;
|
||||||
dayCheckboxes.emplace_back(_ui.mondayCheckBox);
|
dayCheckboxes.emplace_back(_ui.mondayCheckBox);
|
||||||
|
@ -99,9 +95,12 @@ void Nitpick::setup() {
|
||||||
timeEditCheckboxes,
|
timeEditCheckboxes,
|
||||||
timeEdits,
|
timeEdits,
|
||||||
_ui.workingFolderRunOnDesktopLabel,
|
_ui.workingFolderRunOnDesktopLabel,
|
||||||
_ui.checkBoxServerless,
|
_ui.checkBoxServerless,
|
||||||
|
_ui.usePreviousInstallationOnDesktopCheckBox,
|
||||||
_ui.runLatestOnDesktopCheckBox,
|
_ui.runLatestOnDesktopCheckBox,
|
||||||
_ui.urlOnDesktopLineEdit,
|
_ui.urlOnDesktopLineEdit,
|
||||||
|
_ui.runFullSuiteOnDesktopCheckBox,
|
||||||
|
_ui.scriptURLOnDesktopLineEdit,
|
||||||
_ui.runNowPushbutton,
|
_ui.runNowPushbutton,
|
||||||
_ui.statusLabelOnDesktop
|
_ui.statusLabelOnDesktop
|
||||||
);
|
);
|
||||||
|
@ -118,8 +117,11 @@ void Nitpick::setup() {
|
||||||
_ui.downloadAPKPushbutton,
|
_ui.downloadAPKPushbutton,
|
||||||
_ui.installAPKPushbutton,
|
_ui.installAPKPushbutton,
|
||||||
_ui.runInterfacePushbutton,
|
_ui.runInterfacePushbutton,
|
||||||
|
_ui.usePreviousInstallationOnMobileCheckBox,
|
||||||
_ui.runLatestOnMobileCheckBox,
|
_ui.runLatestOnMobileCheckBox,
|
||||||
_ui.urlOnMobileLineEdit,
|
_ui.urlOnMobileLineEdit,
|
||||||
|
_ui.runFullSuiteOnMobileCheckBox,
|
||||||
|
_ui.scriptURLOnMobileLineEdit,
|
||||||
_ui.statusLabelOnMobile
|
_ui.statusLabelOnMobile
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -130,7 +132,7 @@ void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||||
const QString& branch,
|
const QString& branch,
|
||||||
const QString& user
|
const QString& user
|
||||||
) {
|
) {
|
||||||
_test->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user);
|
_testCreator->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_tabWidget_currentChanged(int index) {
|
void Nitpick::on_tabWidget_currentChanged(int index) {
|
||||||
|
@ -148,48 +150,44 @@ void Nitpick::on_tabWidget_currentChanged(int index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_evaluateTestsPushbutton_clicked() {
|
|
||||||
_test->startTestsEvaluation(false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Nitpick::on_createRecursiveScriptPushbutton_clicked() {
|
void Nitpick::on_createRecursiveScriptPushbutton_clicked() {
|
||||||
_test->createRecursiveScript();
|
_testCreator->createRecursiveScript();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createAllRecursiveScriptsPushbutton_clicked() {
|
void Nitpick::on_createAllRecursiveScriptsPushbutton_clicked() {
|
||||||
_test->createAllRecursiveScripts();
|
_testCreator->createAllRecursiveScripts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createTestsPushbutton_clicked() {
|
void Nitpick::on_createTestsPushbutton_clicked() {
|
||||||
_test->createTests(_ui.clientProfileComboBox->currentText());
|
_testCreator->createTests(_ui.clientProfileComboBox->currentText());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createMDFilePushbutton_clicked() {
|
void Nitpick::on_createMDFilePushbutton_clicked() {
|
||||||
_test->createMDFile();
|
_testCreator->createMDFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createAllMDFilesPushbutton_clicked() {
|
void Nitpick::on_createAllMDFilesPushbutton_clicked() {
|
||||||
_test->createAllMDFiles();
|
_testCreator->createAllMDFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createTestAutoScriptPushbutton_clicked() {
|
void Nitpick::on_createTestAutoScriptPushbutton_clicked() {
|
||||||
_test->createTestAutoScript();
|
_testCreator->createTestAutoScript();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createAllTestAutoScriptsPushbutton_clicked() {
|
void Nitpick::on_createAllTestAutoScriptsPushbutton_clicked() {
|
||||||
_test->createAllTestAutoScripts();
|
_testCreator->createAllTestAutoScripts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createTestsOutlinePushbutton_clicked() {
|
void Nitpick::on_createTestsOutlinePushbutton_clicked() {
|
||||||
_test->createTestsOutline();
|
_testCreator->createTestsOutline();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createTestRailTestCasesPushbutton_clicked() {
|
void Nitpick::on_createTestRailTestCasesPushbutton_clicked() {
|
||||||
_test->createTestRailTestCases();
|
_testCreator->createTestRailTestCases();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createTestRailRunButton_clicked() {
|
void Nitpick::on_createTestRailRunButton_clicked() {
|
||||||
_test->createTestRailRun();
|
_testCreator->createTestRailRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_setWorkingFolderRunOnDesktopPushbutton_clicked() {
|
void Nitpick::on_setWorkingFolderRunOnDesktopPushbutton_clicked() {
|
||||||
|
@ -206,16 +204,25 @@ void Nitpick::on_runNowPushbutton_clicked() {
|
||||||
_testRunnerDesktop->run();
|
_testRunnerDesktop->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Nitpick::on_usePreviousInstallationOnDesktopCheckBox_clicked() {
|
||||||
|
_ui.runLatestOnDesktopCheckBox->setEnabled(!_ui.usePreviousInstallationOnDesktopCheckBox->isChecked());
|
||||||
|
_ui.urlOnDesktopLineEdit->setEnabled(!_ui.usePreviousInstallationOnDesktopCheckBox->isChecked() && !_ui.runLatestOnDesktopCheckBox->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
void Nitpick::on_runLatestOnDesktopCheckBox_clicked() {
|
void Nitpick::on_runLatestOnDesktopCheckBox_clicked() {
|
||||||
_ui.urlOnDesktopLineEdit->setEnabled(!_ui.runLatestOnDesktopCheckBox->isChecked());
|
_ui.urlOnDesktopLineEdit->setEnabled(!_ui.runLatestOnDesktopCheckBox->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Nitpick::on_runFullSuiteOnDesktopCheckBox_clicked() {
|
||||||
|
_ui.scriptURLOnDesktopLineEdit->setEnabled(!_ui.runFullSuiteOnDesktopCheckBox->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
void Nitpick::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) {
|
void Nitpick::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) {
|
||||||
_testRunnerDesktop->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures);
|
_testRunnerDesktop->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_updateTestRailRunResultsPushbutton_clicked() {
|
void Nitpick::on_updateTestRailRunResultsPushbutton_clicked() {
|
||||||
_test->updateTestRailRunResult();
|
_testCreator->updateTestRailRunResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
// To toggle between show and hide
|
// To toggle between show and hide
|
||||||
|
@ -242,85 +249,24 @@ void Nitpick::on_showTaskbarPushbutton_clicked() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Nitpick::on_evaluateTestsPushbutton_clicked() {
|
||||||
|
_testCreator->startTestsEvaluation(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
void Nitpick::on_closePushbutton_clicked() {
|
void Nitpick::on_closePushbutton_clicked() {
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createPythonScriptRadioButton_clicked() {
|
void Nitpick::on_createPythonScriptRadioButton_clicked() {
|
||||||
_test->setTestRailCreateMode(PYTHON);
|
_testCreator->setTestRailCreateMode(PYTHON);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createXMLScriptRadioButton_clicked() {
|
void Nitpick::on_createXMLScriptRadioButton_clicked() {
|
||||||
_test->setTestRailCreateMode(XML);
|
_testCreator->setTestRailCreateMode(XML);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::on_createWebPagePushbutton_clicked() {
|
void Nitpick::on_createWebPagePushbutton_clicked() {
|
||||||
_test->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit);
|
_testCreator->createWebPage(_ui.updateAWSCheckBox, _ui.diffImageRadioButton, _ui.ssimImageRadioButton, _ui.awsURLLineEdit);
|
||||||
}
|
|
||||||
|
|
||||||
void Nitpick::downloadFile(const QUrl& url) {
|
|
||||||
_downloaders.emplace_back(new Downloader(url, this));
|
|
||||||
connect(_downloaders[_index], SIGNAL(downloaded()), _signalMapper, SLOT(map()));
|
|
||||||
|
|
||||||
_signalMapper->setMapping(_downloaders[_index], _index);
|
|
||||||
|
|
||||||
++_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Nitpick::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller) {
|
|
||||||
connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int)));
|
|
||||||
|
|
||||||
_directoryName = directoryName;
|
|
||||||
_filenames = filenames;
|
|
||||||
_caller = caller;
|
|
||||||
|
|
||||||
_numberOfFilesToDownload = URLs.size();
|
|
||||||
_numberOfFilesDownloaded = 0;
|
|
||||||
_index = 0;
|
|
||||||
|
|
||||||
_ui.progressBar->setMinimum(0);
|
|
||||||
_ui.progressBar->setMaximum(_numberOfFilesToDownload - 1);
|
|
||||||
_ui.progressBar->setValue(0);
|
|
||||||
_ui.progressBar->setVisible(true);
|
|
||||||
|
|
||||||
foreach (auto downloader, _downloaders) {
|
|
||||||
delete downloader;
|
|
||||||
}
|
|
||||||
|
|
||||||
_downloaders.clear();
|
|
||||||
for (int i = 0; i < _numberOfFilesToDownload; ++i) {
|
|
||||||
downloadFile(URLs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Nitpick::saveFile(int index) {
|
|
||||||
try {
|
|
||||||
QFile file(_directoryName + "/" + _filenames[index]);
|
|
||||||
file.open(QIODevice::WriteOnly);
|
|
||||||
file.write(_downloaders[index]->downloadedData());
|
|
||||||
file.close();
|
|
||||||
} catch (...) {
|
|
||||||
QMessageBox::information(0, "Test Aborted", "Failed to save file: " + _filenames[index]);
|
|
||||||
_ui.progressBar->setVisible(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
++_numberOfFilesDownloaded;
|
|
||||||
|
|
||||||
if (_numberOfFilesDownloaded == _numberOfFilesToDownload) {
|
|
||||||
disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int)));
|
|
||||||
if (_caller == _test) {
|
|
||||||
_test->finishTestsEvaluation();
|
|
||||||
} else if (_caller == _testRunnerDesktop) {
|
|
||||||
_testRunnerDesktop->downloadComplete();
|
|
||||||
} else if (_caller == _testRunnerMobile) {
|
|
||||||
_testRunnerMobile->downloadComplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
_ui.progressBar->setVisible(false);
|
|
||||||
} else {
|
|
||||||
_ui.progressBar->setValue(_numberOfFilesDownloaded);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nitpick::about() {
|
void Nitpick::about() {
|
||||||
|
@ -360,10 +306,19 @@ void Nitpick::on_connectDevicePushbutton_clicked() {
|
||||||
_testRunnerMobile->connectDevice();
|
_testRunnerMobile->connectDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Nitpick::on_usePreviousInstallationOnMobileCheckBox_clicked() {
|
||||||
|
_ui.runLatestOnMobileCheckBox->setEnabled(!_ui.usePreviousInstallationOnMobileCheckBox->isChecked());
|
||||||
|
_ui.urlOnMobileLineEdit->setEnabled(!_ui.usePreviousInstallationOnMobileCheckBox->isChecked() && !_ui.runLatestOnMobileCheckBox->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
void Nitpick::on_runLatestOnMobileCheckBox_clicked() {
|
void Nitpick::on_runLatestOnMobileCheckBox_clicked() {
|
||||||
_ui.urlOnMobileLineEdit->setEnabled(!_ui.runLatestOnMobileCheckBox->isChecked());
|
_ui.urlOnMobileLineEdit->setEnabled(!_ui.runLatestOnMobileCheckBox->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Nitpick::on_runFullSuiteOnMobileCheckBox_clicked() {
|
||||||
|
_ui.scriptURLOnMobileLineEdit->setEnabled(!_ui.runFullSuiteOnMobileCheckBox->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
void Nitpick::on_downloadAPKPushbutton_clicked() {
|
void Nitpick::on_downloadAPKPushbutton_clicked() {
|
||||||
_testRunnerMobile->downloadAPK();
|
_testRunnerMobile->downloadAPK();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,10 @@
|
||||||
#define hifi_Nitpick_h
|
#define hifi_Nitpick_h
|
||||||
|
|
||||||
#include <QtWidgets/QMainWindow>
|
#include <QtWidgets/QMainWindow>
|
||||||
#include <QSignalMapper>
|
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include "ui_Nitpick.h"
|
#include "ui_Nitpick.h"
|
||||||
|
|
||||||
#include "Downloader.h"
|
#include "TestCreator.h"
|
||||||
#include "Test.h"
|
|
||||||
|
|
||||||
#include "TestRunnerDesktop.h"
|
#include "TestRunnerDesktop.h"
|
||||||
#include "TestRunnerMobile.h"
|
#include "TestRunnerMobile.h"
|
||||||
|
@ -38,9 +36,6 @@ public:
|
||||||
|
|
||||||
void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures);
|
void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures);
|
||||||
|
|
||||||
void downloadFile(const QUrl& url);
|
|
||||||
void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void* caller);
|
|
||||||
|
|
||||||
void setUserText(const QString& user);
|
void setUserText(const QString& user);
|
||||||
QString getSelectedUser();
|
QString getSelectedUser();
|
||||||
|
|
||||||
|
@ -56,7 +51,6 @@ private slots:
|
||||||
|
|
||||||
void on_tabWidget_currentChanged(int index);
|
void on_tabWidget_currentChanged(int index);
|
||||||
|
|
||||||
void on_evaluateTestsPushbutton_clicked();
|
|
||||||
void on_createRecursiveScriptPushbutton_clicked();
|
void on_createRecursiveScriptPushbutton_clicked();
|
||||||
void on_createAllRecursiveScriptsPushbutton_clicked();
|
void on_createAllRecursiveScriptsPushbutton_clicked();
|
||||||
void on_createTestsPushbutton_clicked();
|
void on_createTestsPushbutton_clicked();
|
||||||
|
@ -75,27 +69,32 @@ private slots:
|
||||||
void on_setWorkingFolderRunOnDesktopPushbutton_clicked();
|
void on_setWorkingFolderRunOnDesktopPushbutton_clicked();
|
||||||
void on_runNowPushbutton_clicked();
|
void on_runNowPushbutton_clicked();
|
||||||
|
|
||||||
|
void on_usePreviousInstallationOnDesktopCheckBox_clicked();
|
||||||
void on_runLatestOnDesktopCheckBox_clicked();
|
void on_runLatestOnDesktopCheckBox_clicked();
|
||||||
|
void on_runFullSuiteOnDesktopCheckBox_clicked();
|
||||||
|
|
||||||
void on_updateTestRailRunResultsPushbutton_clicked();
|
void on_updateTestRailRunResultsPushbutton_clicked();
|
||||||
|
|
||||||
void on_hideTaskbarPushbutton_clicked();
|
void on_hideTaskbarPushbutton_clicked();
|
||||||
void on_showTaskbarPushbutton_clicked();
|
void on_showTaskbarPushbutton_clicked();
|
||||||
|
|
||||||
|
void on_evaluateTestsPushbutton_clicked();
|
||||||
|
|
||||||
void on_createPythonScriptRadioButton_clicked();
|
void on_createPythonScriptRadioButton_clicked();
|
||||||
void on_createXMLScriptRadioButton_clicked();
|
void on_createXMLScriptRadioButton_clicked();
|
||||||
|
|
||||||
void on_createWebPagePushbutton_clicked();
|
void on_createWebPagePushbutton_clicked();
|
||||||
|
|
||||||
void saveFile(int index);
|
|
||||||
|
|
||||||
void about();
|
void about();
|
||||||
void content();
|
void content();
|
||||||
|
|
||||||
// Run on Mobile controls
|
// Run on Mobile controls
|
||||||
void on_setWorkingFolderRunOnMobilePushbutton_clicked();
|
void on_setWorkingFolderRunOnMobilePushbutton_clicked();
|
||||||
void on_connectDevicePushbutton_clicked();
|
void on_connectDevicePushbutton_clicked();
|
||||||
|
|
||||||
|
void on_usePreviousInstallationOnMobileCheckBox_clicked();
|
||||||
void on_runLatestOnMobileCheckBox_clicked();
|
void on_runLatestOnMobileCheckBox_clicked();
|
||||||
|
void on_runFullSuiteOnMobileCheckBox_clicked();
|
||||||
|
|
||||||
void on_downloadAPKPushbutton_clicked();
|
void on_downloadAPKPushbutton_clicked();
|
||||||
void on_installAPKPushbutton_clicked();
|
void on_installAPKPushbutton_clicked();
|
||||||
|
@ -105,28 +104,13 @@ private slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::NitpickClass _ui;
|
Ui::NitpickClass _ui;
|
||||||
Test* _test{ nullptr };
|
TestCreator* _testCreator{ nullptr };
|
||||||
|
|
||||||
TestRunnerDesktop* _testRunnerDesktop{ nullptr };
|
TestRunnerDesktop* _testRunnerDesktop{ nullptr };
|
||||||
TestRunnerMobile* _testRunnerMobile{ nullptr };
|
TestRunnerMobile* _testRunnerMobile{ nullptr };
|
||||||
|
|
||||||
std::vector<Downloader*> _downloaders;
|
|
||||||
|
|
||||||
// local storage for parameters - folder to store downloaded files in, and a list of their names
|
|
||||||
QString _directoryName;
|
|
||||||
QStringList _filenames;
|
|
||||||
|
|
||||||
// Used to enable passing a parameter to slots
|
|
||||||
QSignalMapper* _signalMapper;
|
|
||||||
|
|
||||||
int _numberOfFilesToDownload{ 0 };
|
|
||||||
int _numberOfFilesDownloaded{ 0 };
|
|
||||||
int _index{ 0 };
|
|
||||||
|
|
||||||
bool _isRunningFromCommandline{ false };
|
bool _isRunningFromCommandline{ false };
|
||||||
|
|
||||||
void* _caller;
|
|
||||||
|
|
||||||
QStringList clientProfiles;
|
QStringList clientProfiles;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// Test.cpp
|
// TestCreator.cpp
|
||||||
//
|
//
|
||||||
// Created by Nissim Hadar on 2 Nov 2017.
|
// Created by Nissim Hadar on 2 Nov 2017.
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
// Copyright 2013 High Fidelity, Inc.
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
#include "Test.h"
|
#include "TestCreator.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <QtCore/QTextStream>
|
#include <QtCore/QTextStream>
|
||||||
|
@ -24,7 +24,9 @@ extern Nitpick* nitpick;
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
Test::Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) : _awsInterface(NULL) {
|
TestCreator::TestCreator(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) : _awsInterface(NULL) {
|
||||||
|
_downloader = new Downloader();
|
||||||
|
|
||||||
_progressBar = progressBar;
|
_progressBar = progressBar;
|
||||||
_checkBoxInteractiveMode = checkBoxInteractiveMode;
|
_checkBoxInteractiveMode = checkBoxInteractiveMode;
|
||||||
|
|
||||||
|
@ -36,7 +38,7 @@ Test::Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) : _aws
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Test::createTestResultsFolderPath(const QString& directory) {
|
bool TestCreator::createTestResultsFolderPath(const QString& directory) {
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
QDateTime now = QDateTime::currentDateTime();
|
||||||
_testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT) + "(local)[" + QHostInfo::localHostName() + "]";
|
_testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT) + "(local)[" + QHostInfo::localHostName() + "]";
|
||||||
QDir testResultsFolder(_testResultsFolderPath);
|
QDir testResultsFolder(_testResultsFolderPath);
|
||||||
|
@ -45,7 +47,7 @@ bool Test::createTestResultsFolderPath(const QString& directory) {
|
||||||
return QDir().mkdir(_testResultsFolderPath);
|
return QDir().mkdir(_testResultsFolderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Test::zipAndDeleteTestResultsFolder() {
|
QString TestCreator::zipAndDeleteTestResultsFolder() {
|
||||||
QString zippedResultsFileName { _testResultsFolderPath + ".zip" };
|
QString zippedResultsFileName { _testResultsFolderPath + ".zip" };
|
||||||
QFileInfo fileInfo(zippedResultsFileName);
|
QFileInfo fileInfo(zippedResultsFileName);
|
||||||
if (fileInfo.exists()) {
|
if (fileInfo.exists()) {
|
||||||
|
@ -65,7 +67,7 @@ QString Test::zipAndDeleteTestResultsFolder() {
|
||||||
return zippedResultsFileName;
|
return zippedResultsFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Test::compareImageLists() {
|
int TestCreator::compareImageLists() {
|
||||||
_progressBar->setMinimum(0);
|
_progressBar->setMinimum(0);
|
||||||
_progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1);
|
_progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1);
|
||||||
_progressBar->setValue(0);
|
_progressBar->setValue(0);
|
||||||
|
@ -89,23 +91,25 @@ int Test::compareImageLists() {
|
||||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size");
|
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size");
|
||||||
similarityIndex = -100.0;
|
similarityIndex = -100.0;
|
||||||
} else {
|
} else {
|
||||||
similarityIndex = _imageComparer.compareImages(resultImage, expectedImage);
|
_imageComparer.compareImages(resultImage, expectedImage);
|
||||||
|
similarityIndex = _imageComparer.getSSIMValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResult testResult = TestResult{
|
TestResult testResult = TestResult{
|
||||||
(float)similarityIndex,
|
(float)similarityIndex,
|
||||||
_expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
|
_expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
|
||||||
QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
|
QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
|
||||||
QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image
|
QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of result image
|
||||||
|
_imageComparer.getSSIMResults() // results of SSIM algoritm
|
||||||
};
|
};
|
||||||
|
|
||||||
_mismatchWindow.setTestResult(testResult);
|
_mismatchWindow.setTestResult(testResult);
|
||||||
|
|
||||||
if (similarityIndex < THRESHOLD) {
|
if (similarityIndex < THRESHOLD) {
|
||||||
++numberOfFailures;
|
++numberOfFailures;
|
||||||
|
|
||||||
if (!isInteractiveMode) {
|
if (!isInteractiveMode) {
|
||||||
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true);
|
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true);
|
||||||
} else {
|
} else {
|
||||||
_mismatchWindow.exec();
|
_mismatchWindow.exec();
|
||||||
|
|
||||||
|
@ -113,7 +117,7 @@ int Test::compareImageLists() {
|
||||||
case USER_RESPONSE_PASS:
|
case USER_RESPONSE_PASS:
|
||||||
break;
|
break;
|
||||||
case USE_RESPONSE_FAIL:
|
case USE_RESPONSE_FAIL:
|
||||||
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true);
|
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true);
|
||||||
break;
|
break;
|
||||||
case USER_RESPONSE_ABORT:
|
case USER_RESPONSE_ABORT:
|
||||||
keepOn = false;
|
keepOn = false;
|
||||||
|
@ -124,7 +128,7 @@ int Test::compareImageLists() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), false);
|
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_progressBar->setValue(i);
|
_progressBar->setValue(i);
|
||||||
|
@ -134,7 +138,7 @@ int Test::compareImageLists() {
|
||||||
return numberOfFailures;
|
return numberOfFailures;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Test::checkTextResults() {
|
int TestCreator::checkTextResults() {
|
||||||
// Create lists of failed and passed tests
|
// Create lists of failed and passed tests
|
||||||
QStringList nameFilterFailed;
|
QStringList nameFilterFailed;
|
||||||
nameFilterFailed << "*.failed.txt";
|
nameFilterFailed << "*.failed.txt";
|
||||||
|
@ -144,7 +148,7 @@ int Test::checkTextResults() {
|
||||||
nameFilterPassed << "*.passed.txt";
|
nameFilterPassed << "*.passed.txt";
|
||||||
QStringList testsPassed = QDir(_snapshotDirectory).entryList(nameFilterPassed, QDir::Files, QDir::Name);
|
QStringList testsPassed = QDir(_snapshotDirectory).entryList(nameFilterPassed, QDir::Files, QDir::Name);
|
||||||
|
|
||||||
// Add results to Test Results folder
|
// Add results to TestCreator Results folder
|
||||||
foreach(QString currentFilename, testsFailed) {
|
foreach(QString currentFilename, testsFailed) {
|
||||||
appendTestResultsToFile(currentFilename, true);
|
appendTestResultsToFile(currentFilename, true);
|
||||||
}
|
}
|
||||||
|
@ -156,7 +160,7 @@ int Test::checkTextResults() {
|
||||||
return testsFailed.length();
|
return testsFailed.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed) {
|
void TestCreator::appendTestResultsToFile(const TestResult& testResult, const QPixmap& comparisonImage, const QPixmap& ssimResultsImage, bool hasFailed) {
|
||||||
// Critical error if Test Results folder does not exist
|
// Critical error if Test Results folder does not exist
|
||||||
if (!QDir().exists(_testResultsFolderPath)) {
|
if (!QDir().exists(_testResultsFolderPath)) {
|
||||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found");
|
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found");
|
||||||
|
@ -191,7 +195,7 @@ void Test::appendTestResultsToFile(TestResult testResult, QPixmap comparisonImag
|
||||||
|
|
||||||
// Create text file describing the failure
|
// Create text file describing the failure
|
||||||
QTextStream stream(&descriptionFile);
|
QTextStream stream(&descriptionFile);
|
||||||
stream << "Test in folder " << testResult._pathname.left(testResult._pathname.length() - 1) << endl; // remove trailing '/'
|
stream << "TestCreator in folder " << testResult._pathname.left(testResult._pathname.length() - 1) << endl; // remove trailing '/'
|
||||||
stream << "Expected image was " << testResult._expectedImageFilename << endl;
|
stream << "Expected image was " << testResult._expectedImageFilename << endl;
|
||||||
stream << "Actual image was " << testResult._actualImageFilename << endl;
|
stream << "Actual image was " << testResult._actualImageFilename << endl;
|
||||||
stream << "Similarity index was " << testResult._error << endl;
|
stream << "Similarity index was " << testResult._error << endl;
|
||||||
|
@ -217,9 +221,12 @@ void Test::appendTestResultsToFile(TestResult testResult, QPixmap comparisonImag
|
||||||
}
|
}
|
||||||
|
|
||||||
comparisonImage.save(resultFolderPath + "/" + "Difference Image.png");
|
comparisonImage.save(resultFolderPath + "/" + "Difference Image.png");
|
||||||
|
|
||||||
|
// Save the SSIM results image
|
||||||
|
ssimResultsImage.save(resultFolderPath + "/" + "SSIM Image.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
void::Test::appendTestResultsToFile(QString testResultFilename, bool hasFailed) {
|
void::TestCreator::appendTestResultsToFile(QString testResultFilename, bool hasFailed) {
|
||||||
// The test name includes everything until the penultimate period
|
// The test name includes everything until the penultimate period
|
||||||
QString testNameTemp = testResultFilename.left(testResultFilename.lastIndexOf('.'));
|
QString testNameTemp = testResultFilename.left(testResultFilename.lastIndexOf('.'));
|
||||||
QString testName = testResultFilename.left(testNameTemp.lastIndexOf('.'));
|
QString testName = testResultFilename.left(testNameTemp.lastIndexOf('.'));
|
||||||
|
@ -246,7 +253,7 @@ void::Test::appendTestResultsToFile(QString testResultFilename, bool hasFailed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::startTestsEvaluation(const bool isRunningFromCommandLine,
|
void TestCreator::startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||||
const bool isRunningInAutomaticTestRun,
|
const bool isRunningInAutomaticTestRun,
|
||||||
const QString& snapshotDirectory,
|
const QString& snapshotDirectory,
|
||||||
const QString& branchFromCommandLine,
|
const QString& branchFromCommandLine,
|
||||||
|
@ -319,10 +326,11 @@ void Test::startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nitpick->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this);
|
_downloader->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this);
|
||||||
|
finishTestsEvaluation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::finishTestsEvaluation() {
|
void TestCreator::finishTestsEvaluation() {
|
||||||
// First - compare the pairs of images
|
// First - compare the pairs of images
|
||||||
int numberOfFailures = compareImageLists();
|
int numberOfFailures = compareImageLists();
|
||||||
|
|
||||||
|
@ -348,7 +356,7 @@ void Test::finishTestsEvaluation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Test::isAValidDirectory(const QString& pathname) {
|
bool TestCreator::isAValidDirectory(const QString& pathname) {
|
||||||
// Only process directories
|
// Only process directories
|
||||||
QDir dir(pathname);
|
QDir dir(pathname);
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
|
@ -363,7 +371,7 @@ bool Test::isAValidDirectory(const QString& pathname) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Test::extractPathFromTestsDown(const QString& fullPath) {
|
QString TestCreator::extractPathFromTestsDown(const QString& fullPath) {
|
||||||
// `fullPath` includes the full path to the test. We need the portion below (and including) `tests`
|
// `fullPath` includes the full path to the test. We need the portion below (and including) `tests`
|
||||||
QStringList pathParts = fullPath.split('/');
|
QStringList pathParts = fullPath.split('/');
|
||||||
int i{ 0 };
|
int i{ 0 };
|
||||||
|
@ -384,14 +392,14 @@ QString Test::extractPathFromTestsDown(const QString& fullPath) {
|
||||||
return partialPath;
|
return partialPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::includeTest(QTextStream& textStream, const QString& testPathname) {
|
void TestCreator::includeTest(QTextStream& textStream, const QString& testPathname) {
|
||||||
QString partialPath = extractPathFromTestsDown(testPathname);
|
QString partialPath = extractPathFromTestsDown(testPathname);
|
||||||
QString partialPathWithoutTests = partialPath.right(partialPath.length() - 7);
|
QString partialPathWithoutTests = partialPath.right(partialPath.length() - 7);
|
||||||
|
|
||||||
textStream << "Script.include(testsRootPath + \"" << partialPathWithoutTests + "\");" << endl;
|
textStream << "Script.include(testsRootPath + \"" << partialPathWithoutTests + "\");" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createTests(const QString& clientProfile) {
|
void TestCreator::createTests(const QString& clientProfile) {
|
||||||
// Rename files sequentially, as ExpectedResult_00000.png, ExpectedResult_00001.png and so on
|
// Rename files sequentially, as ExpectedResult_00000.png, ExpectedResult_00001.png and so on
|
||||||
// Any existing expected result images will be deleted
|
// Any existing expected result images will be deleted
|
||||||
QString previousSelection = _snapshotDirectory;
|
QString previousSelection = _snapshotDirectory;
|
||||||
|
@ -469,7 +477,7 @@ void Test::createTests(const QString& clientProfile) {
|
||||||
QMessageBox::information(0, "Success", "Test images have been created");
|
QMessageBox::information(0, "Success", "Test images have been created");
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtractedText Test::getTestScriptLines(QString testFileName) {
|
ExtractedText TestCreator::getTestScriptLines(QString testFileName) {
|
||||||
ExtractedText relevantTextFromTest;
|
ExtractedText relevantTextFromTest;
|
||||||
|
|
||||||
QFile inputFile(testFileName);
|
QFile inputFile(testFileName);
|
||||||
|
@ -534,7 +542,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
|
||||||
return relevantTextFromTest;
|
return relevantTextFromTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Test::createFileSetup() {
|
bool TestCreator::createFileSetup() {
|
||||||
// Folder selection
|
// Folder selection
|
||||||
QString previousSelection = _testDirectory;
|
QString previousSelection = _testDirectory;
|
||||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||||
|
@ -554,7 +562,7 @@ bool Test::createFileSetup() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Test::createAllFilesSetup() {
|
bool TestCreator::createAllFilesSetup() {
|
||||||
// Select folder to start recursing from
|
// Select folder to start recursing from
|
||||||
QString previousSelection = _testsRootDirectory;
|
QString previousSelection = _testsRootDirectory;
|
||||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||||
|
@ -576,7 +584,7 @@ bool Test::createAllFilesSetup() {
|
||||||
|
|
||||||
// Create an MD file for a user-selected test.
|
// Create an MD file for a user-selected test.
|
||||||
// The folder selected must contain a script named "test.js", the file produced is named "test.md"
|
// The folder selected must contain a script named "test.js", the file produced is named "test.md"
|
||||||
void Test::createMDFile() {
|
void TestCreator::createMDFile() {
|
||||||
if (!createFileSetup()) {
|
if (!createFileSetup()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -586,7 +594,7 @@ void Test::createMDFile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createAllMDFiles() {
|
void TestCreator::createAllMDFiles() {
|
||||||
if (!createAllFilesSetup()) {
|
if (!createAllFilesSetup()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -618,7 +626,7 @@ void Test::createAllMDFiles() {
|
||||||
QMessageBox::information(0, "Success", "MD files have been created");
|
QMessageBox::information(0, "Success", "MD files have been created");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Test::createMDFile(const QString& directory) {
|
bool TestCreator::createMDFile(const QString& directory) {
|
||||||
// Verify folder contains test.js file
|
// Verify folder contains test.js file
|
||||||
QString testFileName(directory + "/" + TEST_FILENAME);
|
QString testFileName(directory + "/" + TEST_FILENAME);
|
||||||
QFileInfo testFileInfo(testFileName);
|
QFileInfo testFileInfo(testFileName);
|
||||||
|
@ -638,7 +646,7 @@ bool Test::createMDFile(const QString& directory) {
|
||||||
|
|
||||||
QTextStream stream(&mdFile);
|
QTextStream stream(&mdFile);
|
||||||
|
|
||||||
//Test title
|
//TestCreator title
|
||||||
QString testName = testScriptLines.title;
|
QString testName = testScriptLines.title;
|
||||||
stream << "# " << testName << "\n";
|
stream << "# " << testName << "\n";
|
||||||
|
|
||||||
|
@ -670,7 +678,7 @@ bool Test::createMDFile(const QString& directory) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createTestAutoScript() {
|
void TestCreator::createTestAutoScript() {
|
||||||
if (!createFileSetup()) {
|
if (!createFileSetup()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -680,7 +688,7 @@ void Test::createTestAutoScript() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createAllTestAutoScripts() {
|
void TestCreator::createAllTestAutoScripts() {
|
||||||
if (!createAllFilesSetup()) {
|
if (!createAllFilesSetup()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -712,7 +720,7 @@ void Test::createAllTestAutoScripts() {
|
||||||
QMessageBox::information(0, "Success", "All 'testAuto.js' scripts have been created");
|
QMessageBox::information(0, "Success", "All 'testAuto.js' scripts have been created");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Test::createTestAutoScript(const QString& directory) {
|
bool TestCreator::createTestAutoScript(const QString& directory) {
|
||||||
// Verify folder contains test.js file
|
// Verify folder contains test.js file
|
||||||
QString testFileName(directory + "/" + TEST_FILENAME);
|
QString testFileName(directory + "/" + TEST_FILENAME);
|
||||||
QFileInfo testFileInfo(testFileName);
|
QFileInfo testFileInfo(testFileName);
|
||||||
|
@ -743,7 +751,7 @@ bool Test::createTestAutoScript(const QString& directory) {
|
||||||
|
|
||||||
// Creates a single script in a user-selected folder.
|
// Creates a single script in a user-selected folder.
|
||||||
// This script will run all text.js scripts in every applicable sub-folder
|
// This script will run all text.js scripts in every applicable sub-folder
|
||||||
void Test::createRecursiveScript() {
|
void TestCreator::createRecursiveScript() {
|
||||||
if (!createFileSetup()) {
|
if (!createFileSetup()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -753,7 +761,7 @@ void Test::createRecursiveScript() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method creates a `testRecursive.js` script in every sub-folder.
|
// This method creates a `testRecursive.js` script in every sub-folder.
|
||||||
void Test::createAllRecursiveScripts() {
|
void TestCreator::createAllRecursiveScripts() {
|
||||||
if (!createAllFilesSetup()) {
|
if (!createAllFilesSetup()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -763,7 +771,7 @@ void Test::createAllRecursiveScripts() {
|
||||||
QMessageBox::information(0, "Success", "Scripts have been created");
|
QMessageBox::information(0, "Success", "Scripts have been created");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createAllRecursiveScripts(const QString& directory) {
|
void TestCreator::createAllRecursiveScripts(const QString& directory) {
|
||||||
QDirIterator it(directory, QDirIterator::Subdirectories);
|
QDirIterator it(directory, QDirIterator::Subdirectories);
|
||||||
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
@ -775,7 +783,7 @@ void Test::createAllRecursiveScripts(const QString& directory) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createRecursiveScript(const QString& directory, bool interactiveMode) {
|
void TestCreator::createRecursiveScript(const QString& directory, bool interactiveMode) {
|
||||||
// If folder contains a test, then we are at a leaf
|
// If folder contains a test, then we are at a leaf
|
||||||
const QString testPathname{ directory + "/" + TEST_FILENAME };
|
const QString testPathname{ directory + "/" + TEST_FILENAME };
|
||||||
if (QFileInfo(testPathname).exists()) {
|
if (QFileInfo(testPathname).exists()) {
|
||||||
|
@ -841,7 +849,10 @@ void Test::createRecursiveScript(const QString& directory, bool interactiveMode)
|
||||||
textStream << " nitpick = createNitpick(Script.resolvePath(\".\"));" << endl;
|
textStream << " nitpick = createNitpick(Script.resolvePath(\".\"));" << endl;
|
||||||
textStream << " testsRootPath = nitpick.getTestsRootPath();" << endl << endl;
|
textStream << " testsRootPath = nitpick.getTestsRootPath();" << endl << endl;
|
||||||
textStream << " nitpick.enableRecursive();" << endl;
|
textStream << " nitpick.enableRecursive();" << endl;
|
||||||
textStream << " nitpick.enableAuto();" << endl;
|
textStream << " nitpick.enableAuto();" << endl << endl;
|
||||||
|
textStream << " if (typeof Test !== 'undefined') {" << endl;
|
||||||
|
textStream << " Test.wait(10000);" << endl;
|
||||||
|
textStream << " }" << endl;
|
||||||
textStream << "} else {" << endl;
|
textStream << "} else {" << endl;
|
||||||
textStream << " depth++" << endl;
|
textStream << " depth++" << endl;
|
||||||
textStream << "}" << endl << endl;
|
textStream << "}" << endl << endl;
|
||||||
|
@ -861,7 +872,7 @@ void Test::createRecursiveScript(const QString& directory, bool interactiveMode)
|
||||||
recursiveTestsFile.close();
|
recursiveTestsFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createTestsOutline() {
|
void TestCreator::createTestsOutline() {
|
||||||
QString previousSelection = _testDirectory;
|
QString previousSelection = _testDirectory;
|
||||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||||
if (!parent.isNull() && parent.right(1) != "/") {
|
if (!parent.isNull() && parent.right(1) != "/") {
|
||||||
|
@ -887,7 +898,7 @@ void Test::createTestsOutline() {
|
||||||
|
|
||||||
QTextStream stream(&mdFile);
|
QTextStream stream(&mdFile);
|
||||||
|
|
||||||
//Test title
|
//TestCreator title
|
||||||
stream << "# Outline of all tests\n";
|
stream << "# Outline of all tests\n";
|
||||||
stream << "Directories with an appended (*) have an automatic test\n\n";
|
stream << "Directories with an appended (*) have an automatic test\n\n";
|
||||||
|
|
||||||
|
@ -945,10 +956,10 @@ void Test::createTestsOutline() {
|
||||||
|
|
||||||
mdFile.close();
|
mdFile.close();
|
||||||
|
|
||||||
QMessageBox::information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created");
|
QMessageBox::information(0, "Success", "TestCreator outline file " + testsOutlineFilename + " has been created");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createTestRailTestCases() {
|
void TestCreator::createTestRailTestCases() {
|
||||||
QString previousSelection = _testDirectory;
|
QString previousSelection = _testDirectory;
|
||||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||||
if (!parent.isNull() && parent.right(1) != "/") {
|
if (!parent.isNull() && parent.right(1) != "/") {
|
||||||
|
@ -985,7 +996,7 @@ void Test::createTestRailTestCases() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createTestRailRun() {
|
void TestCreator::createTestRailRun() {
|
||||||
QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in",
|
QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in",
|
||||||
nullptr, QFileDialog::ShowDirsOnly);
|
nullptr, QFileDialog::ShowDirsOnly);
|
||||||
|
|
||||||
|
@ -1001,9 +1012,9 @@ void Test::createTestRailRun() {
|
||||||
_testRailInterface->createTestRailRun(outputDirectory);
|
_testRailInterface->createTestRailRun(outputDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::updateTestRailRunResult() {
|
void TestCreator::updateTestRailRunResult() {
|
||||||
QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr,
|
QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr,
|
||||||
"Zipped Test Results (*.zip)");
|
"Zipped TestCreator Results (*.zip)");
|
||||||
if (testResults.isNull()) {
|
if (testResults.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1022,7 +1033,7 @@ void Test::updateTestRailRunResult() {
|
||||||
_testRailInterface->updateTestRailRunResults(testResults, tempDirectory);
|
_testRailInterface->updateTestRailRunResults(testResults, tempDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) {
|
QStringList TestCreator::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) {
|
||||||
_imageDirectory = QDir(pathToImageDirectory);
|
_imageDirectory = QDir(pathToImageDirectory);
|
||||||
QStringList nameFilters;
|
QStringList nameFilters;
|
||||||
nameFilters << "*." + imageFormat;
|
nameFilters << "*." + imageFormat;
|
||||||
|
@ -1034,7 +1045,7 @@ QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat,
|
||||||
// Filename (i.e. without extension) contains tests (this is based on all test scripts being within the tests folder)
|
// Filename (i.e. without extension) contains tests (this is based on all test scripts being within the tests folder)
|
||||||
// Last 5 characters in filename are digits (after removing the extension)
|
// Last 5 characters in filename are digits (after removing the extension)
|
||||||
// Extension is 'imageFormat'
|
// Extension is 'imageFormat'
|
||||||
bool Test::isInSnapshotFilenameFormat(const QString& imageFormat, const QString& filename) {
|
bool TestCreator::isInSnapshotFilenameFormat(const QString& imageFormat, const QString& filename) {
|
||||||
bool contains_tests = filename.contains("tests" + PATH_SEPARATOR);
|
bool contains_tests = filename.contains("tests" + PATH_SEPARATOR);
|
||||||
|
|
||||||
QString filenameWithoutExtension = filename.left(filename.lastIndexOf('.'));
|
QString filenameWithoutExtension = filename.left(filename.lastIndexOf('.'));
|
||||||
|
@ -1049,7 +1060,7 @@ bool Test::isInSnapshotFilenameFormat(const QString& imageFormat, const QString&
|
||||||
// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the test directory is
|
// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the test directory is
|
||||||
// D:/GitHub/hifi-tests/tests/content/entity/zone/create
|
// D:/GitHub/hifi-tests/tests/content/entity/zone/create
|
||||||
// This method assumes the filename is in the correct format
|
// This method assumes the filename is in the correct format
|
||||||
QString Test::getExpectedImageDestinationDirectory(const QString& filename) {
|
QString TestCreator::getExpectedImageDestinationDirectory(const QString& filename) {
|
||||||
QString filenameWithoutExtension = filename.left(filename.length() - 4);
|
QString filenameWithoutExtension = filename.left(filename.length() - 4);
|
||||||
QStringList filenameParts = filenameWithoutExtension.split(PATH_SEPARATOR);
|
QStringList filenameParts = filenameWithoutExtension.split(PATH_SEPARATOR);
|
||||||
|
|
||||||
|
@ -1066,7 +1077,7 @@ QString Test::getExpectedImageDestinationDirectory(const QString& filename) {
|
||||||
// is ...tests/content/entity/zone/create
|
// is ...tests/content/entity/zone/create
|
||||||
// This is used to create the full URL
|
// This is used to create the full URL
|
||||||
// This method assumes the filename is in the correct format
|
// This method assumes the filename is in the correct format
|
||||||
QString Test::getExpectedImagePartialSourceDirectory(const QString& filename) {
|
QString TestCreator::getExpectedImagePartialSourceDirectory(const QString& filename) {
|
||||||
QString filenameWithoutExtension = filename.left(filename.length() - 4);
|
QString filenameWithoutExtension = filename.left(filename.length() - 4);
|
||||||
QStringList filenameParts = filenameWithoutExtension.split(PATH_SEPARATOR);
|
QStringList filenameParts = filenameWithoutExtension.split(PATH_SEPARATOR);
|
||||||
|
|
||||||
|
@ -1091,13 +1102,18 @@ QString Test::getExpectedImagePartialSourceDirectory(const QString& filename) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) {
|
void TestCreator::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) {
|
||||||
_testRailCreateMode = testRailCreateMode;
|
_testRailCreateMode = testRailCreateMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) {
|
void TestCreator::createWebPage(
|
||||||
|
QCheckBox* updateAWSCheckBox,
|
||||||
|
QRadioButton* diffImageRadioButton,
|
||||||
|
QRadioButton* ssimImageRadionButton,
|
||||||
|
QLineEdit* urlLineEdit
|
||||||
|
) {
|
||||||
QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr,
|
QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr,
|
||||||
"Zipped Test Results (TestResults--*.zip)");
|
"Zipped TestCreator Results (TestResults--*.zip)");
|
||||||
if (testResults.isNull()) {
|
if (testResults.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1112,5 +1128,12 @@ void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) {
|
||||||
_awsInterface = new AWSInterface;
|
_awsInterface = new AWSInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
_awsInterface->createWebPageFromResults(testResults, workingDirectory, updateAWSCheckBox, urlLineEdit);
|
_awsInterface->createWebPageFromResults(
|
||||||
|
testResults,
|
||||||
|
workingDirectory,
|
||||||
|
updateAWSCheckBox,
|
||||||
|
diffImageRadioButton,
|
||||||
|
ssimImageRadionButton,
|
||||||
|
urlLineEdit
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// Test.h
|
// TestCreator.h
|
||||||
//
|
//
|
||||||
// Created by Nissim Hadar on 2 Nov 2017.
|
// Created by Nissim Hadar on 2 Nov 2017.
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
// Copyright 2013 High Fidelity, Inc.
|
||||||
|
@ -8,8 +8,8 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef hifi_test_h
|
#ifndef hifi_testCreator_h
|
||||||
#define hifi_test_h
|
#define hifi_testCreator_h
|
||||||
|
|
||||||
#include <QtWidgets/QFileDialog>
|
#include <QtWidgets/QFileDialog>
|
||||||
#include <QtWidgets/QMessageBox>
|
#include <QtWidgets/QMessageBox>
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
#include "AWSInterface.h"
|
#include "AWSInterface.h"
|
||||||
#include "ImageComparer.h"
|
#include "ImageComparer.h"
|
||||||
|
#include "Downloader.h"
|
||||||
#include "MismatchWindow.h"
|
#include "MismatchWindow.h"
|
||||||
#include "TestRailInterface.h"
|
#include "TestRailInterface.h"
|
||||||
|
|
||||||
|
@ -40,9 +41,9 @@ enum TestRailCreateMode {
|
||||||
XML
|
XML
|
||||||
};
|
};
|
||||||
|
|
||||||
class Test {
|
class TestCreator {
|
||||||
public:
|
public:
|
||||||
Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode);
|
TestCreator(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode);
|
||||||
|
|
||||||
void startTestsEvaluation(const bool isRunningFromCommandLine,
|
void startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||||
const bool isRunningInAutomaticTestRun,
|
const bool isRunningInAutomaticTestRun,
|
||||||
|
@ -87,7 +88,7 @@ public:
|
||||||
|
|
||||||
void includeTest(QTextStream& textStream, const QString& testPathname);
|
void includeTest(QTextStream& textStream, const QString& testPathname);
|
||||||
|
|
||||||
void appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed);
|
void appendTestResultsToFile(const TestResult& testResult, const QPixmap& comparisonImage, const QPixmap& ssimResultsImage, bool hasFailed);
|
||||||
void appendTestResultsToFile(QString testResultFilename, bool hasFailed);
|
void appendTestResultsToFile(QString testResultFilename, bool hasFailed);
|
||||||
|
|
||||||
bool createTestResultsFolderPath(const QString& directory);
|
bool createTestResultsFolderPath(const QString& directory);
|
||||||
|
@ -102,7 +103,11 @@ public:
|
||||||
|
|
||||||
void setTestRailCreateMode(TestRailCreateMode testRailCreateMode);
|
void setTestRailCreateMode(TestRailCreateMode testRailCreateMode);
|
||||||
|
|
||||||
void createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit);
|
void createWebPage(
|
||||||
|
QCheckBox* updateAWSCheckBox,
|
||||||
|
QRadioButton* diffImageRadioButton,
|
||||||
|
QRadioButton* ssimImageRadionButton,
|
||||||
|
QLineEdit* urlLineEdit);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QProgressBar* _progressBar;
|
QProgressBar* _progressBar;
|
||||||
|
@ -116,7 +121,7 @@ private:
|
||||||
const QString TEST_RESULTS_FOLDER { "TestResults" };
|
const QString TEST_RESULTS_FOLDER { "TestResults" };
|
||||||
const QString TEST_RESULTS_FILENAME { "TestResults.txt" };
|
const QString TEST_RESULTS_FILENAME { "TestResults.txt" };
|
||||||
|
|
||||||
const double THRESHOLD{ 0.990 };
|
const double THRESHOLD{ 0.995 };
|
||||||
|
|
||||||
QDir _imageDirectory;
|
QDir _imageDirectory;
|
||||||
|
|
||||||
|
@ -163,6 +168,7 @@ private:
|
||||||
TestRailCreateMode _testRailCreateMode { PYTHON };
|
TestRailCreateMode _testRailCreateMode { PYTHON };
|
||||||
|
|
||||||
AWSInterface* _awsInterface;
|
AWSInterface* _awsInterface;
|
||||||
|
Downloader* _downloader;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_test_h
|
#endif
|
|
@ -9,7 +9,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "TestRailInterface.h"
|
#include "TestRailInterface.h"
|
||||||
#include "Test.h"
|
#include "TestCreator.h"
|
||||||
|
|
||||||
#include <quazip5/quazip.h>
|
#include <quazip5/quazip.h>
|
||||||
#include <quazip5/JlCompress.h>
|
#include <quazip5/JlCompress.h>
|
||||||
|
@ -258,7 +258,7 @@ bool TestRailInterface::requestTestRailResultsDataFromUser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestRailInterface::isAValidTestDirectory(const QString& directory) {
|
bool TestRailInterface::isAValidTestDirectory(const QString& directory) {
|
||||||
if (Test::isAValidDirectory(directory)) {
|
if (TestCreator::isAValidDirectory(directory)) {
|
||||||
// Ignore the utils and preformance directories
|
// Ignore the utils and preformance directories
|
||||||
if (directory.right(QString("utils").length()) == "utils" ||
|
if (directory.right(QString("utils").length()) == "utils" ||
|
||||||
directory.right(QString("performance").length()) == "performance") {
|
directory.right(QString("performance").length()) == "performance") {
|
||||||
|
@ -352,6 +352,7 @@ void TestRailInterface::createAddTestCasesPythonScript(const QString& testDirect
|
||||||
) {
|
) {
|
||||||
QProcess* process = new QProcess();
|
QProcess* process = new QProcess();
|
||||||
|
|
||||||
|
_busyWindow.setWindowTitle("Updating TestRail");
|
||||||
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
||||||
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
||||||
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
||||||
|
@ -482,6 +483,7 @@ void TestRailInterface::addRun() {
|
||||||
QMessageBox::Yes | QMessageBox::No).exec()
|
QMessageBox::Yes | QMessageBox::No).exec()
|
||||||
) {
|
) {
|
||||||
QProcess* process = new QProcess();
|
QProcess* process = new QProcess();
|
||||||
|
_busyWindow.setWindowTitle("Updating TestRail");
|
||||||
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
||||||
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
||||||
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
||||||
|
@ -591,6 +593,7 @@ void TestRailInterface::updateRunWithResults() {
|
||||||
QMessageBox::Yes | QMessageBox::No).exec()
|
QMessageBox::Yes | QMessageBox::No).exec()
|
||||||
) {
|
) {
|
||||||
QProcess* process = new QProcess();
|
QProcess* process = new QProcess();
|
||||||
|
_busyWindow.setWindowTitle("Updating TestRail");
|
||||||
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
|
||||||
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
|
||||||
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
||||||
|
|
|
@ -14,6 +14,26 @@
|
||||||
#include "Nitpick.h"
|
#include "Nitpick.h"
|
||||||
extern Nitpick* nitpick;
|
extern Nitpick* nitpick;
|
||||||
|
|
||||||
|
TestRunner::TestRunner(
|
||||||
|
QLabel* workingFolderLabel,
|
||||||
|
QLabel* statusLabel,
|
||||||
|
QCheckBox* usePreviousInstallationCheckBox,
|
||||||
|
QCheckBox* runLatest,
|
||||||
|
QLineEdit* url,
|
||||||
|
QCheckBox* runFullSuite,
|
||||||
|
QLineEdit* scriptURL
|
||||||
|
) {
|
||||||
|
_workingFolderLabel = workingFolderLabel;
|
||||||
|
_statusLabel = statusLabel;
|
||||||
|
_usePreviousInstallationCheckBox = usePreviousInstallationCheckBox;
|
||||||
|
_runLatest = runLatest;
|
||||||
|
_url = url;
|
||||||
|
_runFullSuite = runFullSuite;
|
||||||
|
_scriptURL = scriptURL;
|
||||||
|
|
||||||
|
_downloader = new Downloader();
|
||||||
|
}
|
||||||
|
|
||||||
void TestRunner::setWorkingFolder(QLabel* workingFolderLabel) {
|
void TestRunner::setWorkingFolder(QLabel* workingFolderLabel) {
|
||||||
// Everything will be written to this folder
|
// Everything will be written to this folder
|
||||||
QString previousSelection = _workingFolder;
|
QString previousSelection = _workingFolder;
|
||||||
|
@ -49,7 +69,7 @@ void TestRunner::downloadBuildXml(void* caller) {
|
||||||
urls << DEV_BUILD_XML_URL;
|
urls << DEV_BUILD_XML_URL;
|
||||||
filenames << DEV_BUILD_XML_FILENAME;
|
filenames << DEV_BUILD_XML_FILENAME;
|
||||||
|
|
||||||
nitpick->downloadFiles(urls, _workingFolder, filenames, caller);
|
_downloader->downloadFiles(urls, _workingFolder, filenames, caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestRunner::parseBuildInformation() {
|
void TestRunner::parseBuildInformation() {
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#ifndef hifi_testRunner_h
|
#ifndef hifi_testRunner_h
|
||||||
#define hifi_testRunner_h
|
#define hifi_testRunner_h
|
||||||
|
|
||||||
|
#include "Downloader.h"
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
@ -28,7 +30,18 @@ public:
|
||||||
|
|
||||||
class TestRunner {
|
class TestRunner {
|
||||||
public:
|
public:
|
||||||
|
TestRunner(
|
||||||
|
QLabel* workingFolderLabel,
|
||||||
|
QLabel* statusLabel,
|
||||||
|
QCheckBox* usePreviousInstallationOnMobileCheckBox,
|
||||||
|
QCheckBox* runLatest,
|
||||||
|
QLineEdit* url,
|
||||||
|
QCheckBox* runFullSuite,
|
||||||
|
QLineEdit* scriptURL
|
||||||
|
);
|
||||||
|
|
||||||
void setWorkingFolder(QLabel* workingFolderLabel);
|
void setWorkingFolder(QLabel* workingFolderLabel);
|
||||||
|
|
||||||
void downloadBuildXml(void* caller);
|
void downloadBuildXml(void* caller);
|
||||||
void parseBuildInformation();
|
void parseBuildInformation();
|
||||||
QString getInstallerNameFromURL(const QString& url);
|
QString getInstallerNameFromURL(const QString& url);
|
||||||
|
@ -36,10 +49,15 @@ public:
|
||||||
void appendLog(const QString& message);
|
void appendLog(const QString& message);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
Downloader* _downloader;
|
||||||
|
|
||||||
QLabel* _workingFolderLabel;
|
QLabel* _workingFolderLabel;
|
||||||
QLabel* _statusLabel;
|
QLabel* _statusLabel;
|
||||||
QLineEdit* _url;
|
QCheckBox* _usePreviousInstallationCheckBox;
|
||||||
QCheckBox* _runLatest;
|
QCheckBox* _runLatest;
|
||||||
|
QLineEdit* _url;
|
||||||
|
QCheckBox* _runFullSuite;
|
||||||
|
QLineEdit* _scriptURL;
|
||||||
|
|
||||||
QString _workingFolder;
|
QString _workingFolder;
|
||||||
|
|
||||||
|
|
|
@ -27,23 +27,22 @@ TestRunnerDesktop::TestRunnerDesktop(
|
||||||
std::vector<QTimeEdit*> timeEdits,
|
std::vector<QTimeEdit*> timeEdits,
|
||||||
QLabel* workingFolderLabel,
|
QLabel* workingFolderLabel,
|
||||||
QCheckBox* runServerless,
|
QCheckBox* runServerless,
|
||||||
|
QCheckBox* usePreviousInstallationOnMobileCheckBox,
|
||||||
QCheckBox* runLatest,
|
QCheckBox* runLatest,
|
||||||
QLineEdit* url,
|
QLineEdit* url,
|
||||||
|
QCheckBox* runFullSuite,
|
||||||
|
QLineEdit* scriptURL,
|
||||||
QPushButton* runNow,
|
QPushButton* runNow,
|
||||||
QLabel* statusLabel,
|
QLabel* statusLabel,
|
||||||
|
|
||||||
QObject* parent
|
QObject* parent
|
||||||
) : QObject(parent)
|
) : QObject(parent), TestRunner(workingFolderLabel, statusLabel, usePreviousInstallationOnMobileCheckBox, runLatest, url, runFullSuite, scriptURL)
|
||||||
{
|
{
|
||||||
_dayCheckboxes = dayCheckboxes;
|
_dayCheckboxes = dayCheckboxes;
|
||||||
_timeEditCheckboxes = timeEditCheckboxes;
|
_timeEditCheckboxes = timeEditCheckboxes;
|
||||||
_timeEdits = timeEdits;
|
_timeEdits = timeEdits;
|
||||||
_workingFolderLabel = workingFolderLabel;
|
|
||||||
_runServerless = runServerless;
|
_runServerless = runServerless;
|
||||||
_runLatest = runLatest;
|
|
||||||
_url = url;
|
|
||||||
_runNow = runNow;
|
_runNow = runNow;
|
||||||
_statusLabel = statusLabel;
|
|
||||||
|
|
||||||
_installerThread = new QThread();
|
_installerThread = new QThread();
|
||||||
_installerWorker = new InstallerWorker();
|
_installerWorker = new InstallerWorker();
|
||||||
|
@ -179,10 +178,14 @@ void TestRunnerDesktop::run() {
|
||||||
// This will be restored at the end of the tests
|
// This will be restored at the end of the tests
|
||||||
saveExistingHighFidelityAppDataFolder();
|
saveExistingHighFidelityAppDataFolder();
|
||||||
|
|
||||||
_statusLabel->setText("Downloading Build XML");
|
if (_usePreviousInstallationCheckBox->isChecked()) {
|
||||||
downloadBuildXml((void*)this);
|
installationComplete();
|
||||||
|
} else {
|
||||||
|
_statusLabel->setText("Downloading Build XML");
|
||||||
|
downloadBuildXml((void*)this);
|
||||||
|
|
||||||
// `downloadComplete` will run after download has completed
|
downloadComplete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestRunnerDesktop::downloadComplete() {
|
void TestRunnerDesktop::downloadComplete() {
|
||||||
|
@ -209,9 +212,9 @@ void TestRunnerDesktop::downloadComplete() {
|
||||||
|
|
||||||
_statusLabel->setText("Downloading installer");
|
_statusLabel->setText("Downloading installer");
|
||||||
|
|
||||||
nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this);
|
_downloader->downloadFiles(urls, _workingFolder, filenames, (void*)this);
|
||||||
|
|
||||||
// `downloadComplete` will run again after download has completed
|
downloadComplete();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Download of Installer has completed
|
// Download of Installer has completed
|
||||||
|
@ -292,15 +295,19 @@ void TestRunnerDesktop::installationComplete() {
|
||||||
|
|
||||||
void TestRunnerDesktop::verifyInstallationSucceeded() {
|
void TestRunnerDesktop::verifyInstallationSucceeded() {
|
||||||
// Exit if the executables are missing.
|
// Exit if the executables are missing.
|
||||||
// On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
QFileInfo interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe");
|
QFileInfo interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe");
|
||||||
QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe");
|
QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe");
|
||||||
QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe");
|
QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe");
|
||||||
|
|
||||||
if (!interfaceExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) {
|
if (!interfaceExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) {
|
||||||
QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled");
|
if (_runLatest->isChecked()) {
|
||||||
exit(-1);
|
// On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error
|
||||||
|
QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled");
|
||||||
|
exit(-1);
|
||||||
|
} else {
|
||||||
|
QMessageBox::critical(0, "Installation of High Fidelity not found", "Please verify that working folder contains a proper installation");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -457,8 +464,9 @@ void TestRunnerDesktop::runInterfaceWithTestScript() {
|
||||||
QString deleteScript =
|
QString deleteScript =
|
||||||
QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/utils/deleteNearbyEntities.js";
|
QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/utils/deleteNearbyEntities.js";
|
||||||
|
|
||||||
QString testScript =
|
QString testScript = (_runFullSuite->isChecked())
|
||||||
QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js";
|
? QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"
|
||||||
|
: _scriptURL->text();
|
||||||
|
|
||||||
QString commandLine;
|
QString commandLine;
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
@ -537,15 +545,16 @@ void TestRunnerDesktop::runInterfaceWithTestScript() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestRunnerDesktop::interfaceExecutionComplete() {
|
void TestRunnerDesktop::interfaceExecutionComplete() {
|
||||||
|
QThread::msleep(500);
|
||||||
QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt");
|
QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt");
|
||||||
if (!testCompleted.exists()) {
|
if (!testCompleted.exists()) {
|
||||||
QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts\nExisting images will be evaluated");
|
QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts\nExisting images will be evaluated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
killProcesses();
|
||||||
|
|
||||||
evaluateResults();
|
evaluateResults();
|
||||||
|
|
||||||
killProcesses();
|
|
||||||
|
|
||||||
// The High Fidelity AppData folder will be restored after evaluation has completed
|
// The High Fidelity AppData folder will be restored after evaluation has completed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,7 +600,6 @@ void TestRunnerDesktop::addBuildNumberToResults(const QString& zippedFolderName)
|
||||||
if (!QFile::rename(zippedFolderName, augmentedFilename)) {
|
if (!QFile::rename(zippedFolderName, augmentedFilename)) {
|
||||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not rename '" + zippedFolderName + "' to '" + augmentedFilename);
|
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not rename '" + zippedFolderName + "' to '" + augmentedFilename);
|
||||||
exit(-1);
|
exit(-1);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,6 +675,13 @@ void TestRunnerDesktop::checkTime() {
|
||||||
QString TestRunnerDesktop::getPRNumberFromURL(const QString& url) {
|
QString TestRunnerDesktop::getPRNumberFromURL(const QString& url) {
|
||||||
try {
|
try {
|
||||||
QStringList urlParts = url.split("/");
|
QStringList urlParts = url.split("/");
|
||||||
|
if (urlParts.size() <= 2) {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`";
|
||||||
|
#elif defined Q_OS_MAC
|
||||||
|
throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.dmg`";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
QStringList filenameParts = urlParts[urlParts.size() - 1].split("-");
|
QStringList filenameParts = urlParts[urlParts.size() - 1].split("-");
|
||||||
if (filenameParts.size() <= 3) {
|
if (filenameParts.size() <= 3) {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
#define hifi_testRunnerDesktop_h
|
#define hifi_testRunnerDesktop_h
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QLabel>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
@ -32,8 +31,11 @@ public:
|
||||||
std::vector<QTimeEdit*> timeEdits,
|
std::vector<QTimeEdit*> timeEdits,
|
||||||
QLabel* workingFolderLabel,
|
QLabel* workingFolderLabel,
|
||||||
QCheckBox* runServerless,
|
QCheckBox* runServerless,
|
||||||
|
QCheckBox* usePreviousInstallationOnMobileCheckBox,
|
||||||
QCheckBox* runLatest,
|
QCheckBox* runLatest,
|
||||||
QLineEdit* url,
|
QLineEdit* url,
|
||||||
|
QCheckBox* runFullSuite,
|
||||||
|
QLineEdit* scriptURL,
|
||||||
QPushButton* runNow,
|
QPushButton* runNow,
|
||||||
QLabel* statusLabel,
|
QLabel* statusLabel,
|
||||||
|
|
||||||
|
@ -99,7 +101,6 @@ private:
|
||||||
std::vector<QCheckBox*> _dayCheckboxes;
|
std::vector<QCheckBox*> _dayCheckboxes;
|
||||||
std::vector<QCheckBox*> _timeEditCheckboxes;
|
std::vector<QCheckBox*> _timeEditCheckboxes;
|
||||||
std::vector<QTimeEdit*> _timeEdits;
|
std::vector<QTimeEdit*> _timeEdits;
|
||||||
QLabel* _workingFolderLabel;
|
|
||||||
QCheckBox* _runServerless;
|
QCheckBox* _runServerless;
|
||||||
QPushButton* _runNow;
|
QPushButton* _runNow;
|
||||||
QTimer* _timer;
|
QTimer* _timer;
|
||||||
|
|
|
@ -25,14 +25,16 @@ TestRunnerMobile::TestRunnerMobile(
|
||||||
QPushButton* downloadAPKPushbutton,
|
QPushButton* downloadAPKPushbutton,
|
||||||
QPushButton* installAPKPushbutton,
|
QPushButton* installAPKPushbutton,
|
||||||
QPushButton* runInterfacePushbutton,
|
QPushButton* runInterfacePushbutton,
|
||||||
|
QCheckBox* usePreviousInstallationOnMobileCheckBox,
|
||||||
QCheckBox* runLatest,
|
QCheckBox* runLatest,
|
||||||
QLineEdit* url,
|
QLineEdit* url,
|
||||||
|
QCheckBox* runFullSuite,
|
||||||
|
QLineEdit* scriptURL,
|
||||||
QLabel* statusLabel,
|
QLabel* statusLabel,
|
||||||
|
|
||||||
QObject* parent
|
QObject* parent
|
||||||
) : QObject(parent), _adbInterface(NULL)
|
) : QObject(parent), TestRunner(workingFolderLabel, statusLabel, usePreviousInstallationOnMobileCheckBox, runLatest, url, runFullSuite, scriptURL)
|
||||||
{
|
{
|
||||||
_workingFolderLabel = workingFolderLabel;
|
|
||||||
_connectDeviceButton = connectDeviceButton;
|
_connectDeviceButton = connectDeviceButton;
|
||||||
_pullFolderButton = pullFolderButton;
|
_pullFolderButton = pullFolderButton;
|
||||||
_detectedDeviceLabel = detectedDeviceLabel;
|
_detectedDeviceLabel = detectedDeviceLabel;
|
||||||
|
@ -40,13 +42,15 @@ TestRunnerMobile::TestRunnerMobile(
|
||||||
_downloadAPKPushbutton = downloadAPKPushbutton;
|
_downloadAPKPushbutton = downloadAPKPushbutton;
|
||||||
_installAPKPushbutton = installAPKPushbutton;
|
_installAPKPushbutton = installAPKPushbutton;
|
||||||
_runInterfacePushbutton = runInterfacePushbutton;
|
_runInterfacePushbutton = runInterfacePushbutton;
|
||||||
_runLatest = runLatest;
|
|
||||||
_url = url;
|
|
||||||
_statusLabel = statusLabel;
|
|
||||||
|
|
||||||
folderLineEdit->setText("/sdcard/DCIM/TEST");
|
folderLineEdit->setText("/sdcard/DCIM/TEST");
|
||||||
|
|
||||||
modelNames["SM_G955U1"] = "Samsung S8+ unlocked";
|
modelNames["SM_G955U1"] = "Samsung S8+ unlocked";
|
||||||
|
modelNames["SM_N960U1"] = "Samsung Note 9 unlocked";
|
||||||
|
modelNames["SM_T380"] = "Samsung Tab A";
|
||||||
|
modelNames["Quest"] = "Quest";
|
||||||
|
|
||||||
|
_adbInterface = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestRunnerMobile::~TestRunnerMobile() {
|
TestRunnerMobile::~TestRunnerMobile() {
|
||||||
|
@ -66,6 +70,7 @@ void TestRunnerMobile::connectDevice() {
|
||||||
|
|
||||||
QString devicesFullFilename{ _workingFolder + "/devices.txt" };
|
QString devicesFullFilename{ _workingFolder + "/devices.txt" };
|
||||||
QString command = _adbInterface->getAdbCommand() + " devices -l > " + devicesFullFilename;
|
QString command = _adbInterface->getAdbCommand() + " devices -l > " + devicesFullFilename;
|
||||||
|
appendLog(command);
|
||||||
system(command.toStdString().c_str());
|
system(command.toStdString().c_str());
|
||||||
|
|
||||||
if (!QFile::exists(devicesFullFilename)) {
|
if (!QFile::exists(devicesFullFilename)) {
|
||||||
|
@ -93,7 +98,7 @@ void TestRunnerMobile::connectDevice() {
|
||||||
QString deviceID = tokens[0];
|
QString deviceID = tokens[0];
|
||||||
|
|
||||||
QString modelID = tokens[3].split(':')[1];
|
QString modelID = tokens[3].split(':')[1];
|
||||||
QString modelName = "UKNOWN";
|
QString modelName = "UNKNOWN";
|
||||||
if (modelNames.count(modelID) == 1) {
|
if (modelNames.count(modelID) == 1) {
|
||||||
modelName = modelNames[modelID];
|
modelName = modelNames[modelID];
|
||||||
}
|
}
|
||||||
|
@ -102,6 +107,8 @@ void TestRunnerMobile::connectDevice() {
|
||||||
_pullFolderButton->setEnabled(true);
|
_pullFolderButton->setEnabled(true);
|
||||||
_folderLineEdit->setEnabled(true);
|
_folderLineEdit->setEnabled(true);
|
||||||
_downloadAPKPushbutton->setEnabled(true);
|
_downloadAPKPushbutton->setEnabled(true);
|
||||||
|
_installAPKPushbutton->setEnabled(true);
|
||||||
|
_runInterfacePushbutton->setEnabled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -109,6 +116,8 @@ void TestRunnerMobile::connectDevice() {
|
||||||
|
|
||||||
void TestRunnerMobile::downloadAPK() {
|
void TestRunnerMobile::downloadAPK() {
|
||||||
downloadBuildXml((void*)this);
|
downloadBuildXml((void*)this);
|
||||||
|
|
||||||
|
downloadComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -141,11 +150,12 @@ void TestRunnerMobile::downloadComplete() {
|
||||||
|
|
||||||
_statusLabel->setText("Downloading installer");
|
_statusLabel->setText("Downloading installer");
|
||||||
|
|
||||||
nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this);
|
_downloader->downloadFiles(urls, _workingFolder, filenames, (void*)this);
|
||||||
} else {
|
} else {
|
||||||
_statusLabel->setText("Installer download complete");
|
_statusLabel->setText("Installer download complete");
|
||||||
_installAPKPushbutton->setEnabled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_installAPKPushbutton->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestRunnerMobile::installAPK() {
|
void TestRunnerMobile::installAPK() {
|
||||||
|
@ -154,11 +164,25 @@ void TestRunnerMobile::installAPK() {
|
||||||
_adbInterface = new AdbInterface();
|
_adbInterface = new AdbInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_installerFilename.isNull()) {
|
||||||
|
QString installerPathname = QFileDialog::getOpenFileName(nullptr, "Please select the APK", _workingFolder,
|
||||||
|
"Available APKs (*.apk)"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (installerPathname.isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the path
|
||||||
|
QStringList parts = installerPathname.split('/');
|
||||||
|
_installerFilename = parts[parts.length() - 1];
|
||||||
|
}
|
||||||
|
|
||||||
_statusLabel->setText("Installing");
|
_statusLabel->setText("Installing");
|
||||||
QString command = _adbInterface->getAdbCommand() + " install -r -d " + _workingFolder + "/" + _installerFilename + " >" + _workingFolder + "/installOutput.txt";
|
QString command = _adbInterface->getAdbCommand() + " install -r -d " + _workingFolder + "/" + _installerFilename + " >" + _workingFolder + "/installOutput.txt";
|
||||||
|
appendLog(command);
|
||||||
system(command.toStdString().c_str());
|
system(command.toStdString().c_str());
|
||||||
_statusLabel->setText("Installation complete");
|
_statusLabel->setText("Installation complete");
|
||||||
_runInterfacePushbutton->setEnabled(true);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +193,22 @@ void TestRunnerMobile::runInterface() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_statusLabel->setText("Starting Interface");
|
_statusLabel->setText("Starting Interface");
|
||||||
QString command = _adbInterface->getAdbCommand() + " shell monkey -p io.highfidelity.hifiinterface -v 1";
|
|
||||||
|
QString testScript = (_runFullSuite->isChecked())
|
||||||
|
? QString("https://raw.githubusercontent.com/") + nitpick->getSelectedUser() + "/hifi_tests/" + nitpick->getSelectedBranch() + "/tests/testRecursive.js"
|
||||||
|
: _scriptURL->text();
|
||||||
|
|
||||||
|
QString command = _adbInterface->getAdbCommand() +
|
||||||
|
" shell am start -n io.highfidelity.hifiinterface/.PermissionChecker" +
|
||||||
|
" --es args \\\"" +
|
||||||
|
" --url file:///~/serverless/tutorial.json" +
|
||||||
|
" --no-updater" +
|
||||||
|
" --no-login-suggestion" +
|
||||||
|
" --testScript " + testScript + " quitWhenFinished" +
|
||||||
|
" --testResultsLocation /sdcard/snapshots" +
|
||||||
|
"\\\"";
|
||||||
|
|
||||||
|
appendLog(command);
|
||||||
system(command.toStdString().c_str());
|
system(command.toStdString().c_str());
|
||||||
_statusLabel->setText("Interface started");
|
_statusLabel->setText("Interface started");
|
||||||
#endif
|
#endif
|
||||||
|
@ -182,7 +221,8 @@ void TestRunnerMobile::pullFolder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_statusLabel->setText("Pulling folder");
|
_statusLabel->setText("Pulling folder");
|
||||||
QString command = _adbInterface->getAdbCommand() + " pull " + _folderLineEdit->text() + " " + _workingFolder + _installerFilename;
|
QString command = _adbInterface->getAdbCommand() + " pull " + _folderLineEdit->text() + " " + _workingFolder;
|
||||||
|
appendLog(command);
|
||||||
system(command.toStdString().c_str());
|
system(command.toStdString().c_str());
|
||||||
_statusLabel->setText("Pull complete");
|
_statusLabel->setText("Pull complete");
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
#define hifi_testRunnerMobile_h
|
#define hifi_testRunnerMobile_h
|
||||||
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QLabel>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
|
@ -31,8 +30,11 @@ public:
|
||||||
QPushButton* downloadAPKPushbutton,
|
QPushButton* downloadAPKPushbutton,
|
||||||
QPushButton* installAPKPushbutton,
|
QPushButton* installAPKPushbutton,
|
||||||
QPushButton* runInterfacePushbutton,
|
QPushButton* runInterfacePushbutton,
|
||||||
|
QCheckBox* usePreviousInstallationOnMobileCheckBox,
|
||||||
QCheckBox* runLatest,
|
QCheckBox* runLatest,
|
||||||
QLineEdit* url,
|
QLineEdit* url,
|
||||||
|
QCheckBox* runFullSuite,
|
||||||
|
QLineEdit* scriptURL,
|
||||||
QLabel* statusLabel,
|
QLabel* statusLabel,
|
||||||
|
|
||||||
QObject* parent = 0
|
QObject* parent = 0
|
||||||
|
|
|
@ -10,21 +10,38 @@
|
||||||
#ifndef hifi_common_h
|
#ifndef hifi_common_h
|
||||||
#define hifi_common_h
|
#define hifi_common_h
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
#include <QtCore/QString>
|
#include <QtCore/QString>
|
||||||
|
|
||||||
|
class SSIMResults {
|
||||||
|
public:
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
std::vector<double> results;
|
||||||
|
double ssim;
|
||||||
|
|
||||||
|
// Used for scaling
|
||||||
|
double min;
|
||||||
|
double max;
|
||||||
|
};
|
||||||
|
|
||||||
class TestResult {
|
class TestResult {
|
||||||
public:
|
public:
|
||||||
TestResult(float error, QString pathname, QString expectedImageFilename, QString actualImageFilename) :
|
TestResult(float error, const QString& pathname, const QString& expectedImageFilename, const QString& actualImageFilename, const SSIMResults& ssimResults) :
|
||||||
_error(error),
|
_error(error),
|
||||||
_pathname(pathname),
|
_pathname(pathname),
|
||||||
_expectedImageFilename(expectedImageFilename),
|
_expectedImageFilename(expectedImageFilename),
|
||||||
_actualImageFilename(actualImageFilename)
|
_actualImageFilename(actualImageFilename),
|
||||||
|
_ssimResults(ssimResults)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
double _error;
|
double _error;
|
||||||
|
|
||||||
QString _pathname;
|
QString _pathname;
|
||||||
QString _expectedImageFilename;
|
QString _expectedImageFilename;
|
||||||
QString _actualImageFilename;
|
QString _actualImageFilename;
|
||||||
|
|
||||||
|
SSIMResults _ssimResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum UserResponse {
|
enum UserResponse {
|
||||||
|
|
|
@ -34,6 +34,9 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>45</x>
|
<x>45</x>
|
||||||
|
@ -43,7 +46,7 @@
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="tab_1">
|
<widget class="QWidget" name="tab_1">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
|
@ -495,7 +498,7 @@
|
||||||
<widget class="QCheckBox" name="checkBoxServerless">
|
<widget class="QCheckBox" name="checkBoxServerless">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>20</x>
|
<x>240</x>
|
||||||
<y>70</y>
|
<y>70</y>
|
||||||
<width>120</width>
|
<width>120</width>
|
||||||
<height>20</height>
|
<height>20</height>
|
||||||
|
@ -549,13 +552,80 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>170</x>
|
<x>175</x>
|
||||||
<y>100</y>
|
<y>100</y>
|
||||||
<width>451</width>
|
<width>445</width>
|
||||||
<height>21</height>
|
<height>21</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QLabel" name="workingFolderLabel_5">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>128</x>
|
||||||
|
<y>125</y>
|
||||||
|
<width>40</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Script</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QLineEdit" name="scriptURLOnDesktopLineEdit">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>175</x>
|
||||||
|
<y>130</y>
|
||||||
|
<width>445</width>
|
||||||
|
<height>21</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QCheckBox" name="runFullSuiteOnDesktopCheckBox">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>130</y>
|
||||||
|
<width>120</width>
|
||||||
|
<height>20</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Run Full Suite</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QCheckBox" name="usePreviousInstallationOnDesktopCheckBox">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>70</y>
|
||||||
|
<width>171</width>
|
||||||
|
<height>20</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>usePreviousInstallation</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab_5">
|
<widget class="QWidget" name="tab_5">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
|
@ -568,7 +638,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>90</y>
|
<y>150</y>
|
||||||
<width>160</width>
|
<width>160</width>
|
||||||
<height>30</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -581,7 +651,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>190</x>
|
<x>190</x>
|
||||||
<y>96</y>
|
<y>156</y>
|
||||||
<width>320</width>
|
<width>320</width>
|
||||||
<height>30</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -623,7 +693,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>460</x>
|
<x>460</x>
|
||||||
<y>410</y>
|
<y>440</y>
|
||||||
<width>160</width>
|
<width>160</width>
|
||||||
<height>30</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -639,7 +709,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>410</y>
|
<y>440</y>
|
||||||
<width>440</width>
|
<width>440</width>
|
||||||
<height>30</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -651,9 +721,9 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>170</x>
|
<x>175</x>
|
||||||
<y>170</y>
|
<y>245</y>
|
||||||
<width>451</width>
|
<width>445</width>
|
||||||
<height>21</height>
|
<height>21</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -662,7 +732,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>20</x>
|
<x>20</x>
|
||||||
<y>170</y>
|
<y>245</y>
|
||||||
<width>120</width>
|
<width>120</width>
|
||||||
<height>20</height>
|
<height>20</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -684,7 +754,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>210</y>
|
<y>100</y>
|
||||||
<width>160</width>
|
<width>160</width>
|
||||||
<height>30</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -696,7 +766,7 @@
|
||||||
<widget class="QLabel" name="workingFolderLabel_4">
|
<widget class="QLabel" name="workingFolderLabel_4">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>300</x>
|
<x>20</x>
|
||||||
<y>60</y>
|
<y>60</y>
|
||||||
<width>41</width>
|
<width>41</width>
|
||||||
<height>31</height>
|
<height>31</height>
|
||||||
|
@ -709,7 +779,7 @@
|
||||||
<widget class="QLabel" name="statusLabelOnMobile">
|
<widget class="QLabel" name="statusLabelOnMobile">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>350</x>
|
<x>70</x>
|
||||||
<y>60</y>
|
<y>60</y>
|
||||||
<width>271</width>
|
<width>271</width>
|
||||||
<height>31</height>
|
<height>31</height>
|
||||||
|
@ -726,7 +796,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>250</y>
|
<y>325</y>
|
||||||
<width>160</width>
|
<width>160</width>
|
||||||
<height>30</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -742,7 +812,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>300</y>
|
<y>375</y>
|
||||||
<width>160</width>
|
<width>160</width>
|
||||||
<height>30</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -751,6 +821,86 @@
|
||||||
<string>Run Interface</string>
|
<string>Run Interface</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QLabel" name="workingFolderLabel_6">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>140</x>
|
||||||
|
<y>240</y>
|
||||||
|
<width>31</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>URL</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QLineEdit" name="scriptURLOnMobileLineEdit">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>175</x>
|
||||||
|
<y>275</y>
|
||||||
|
<width>445</width>
|
||||||
|
<height>21</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QLabel" name="workingFolderLabel_7">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>140</x>
|
||||||
|
<y>270</y>
|
||||||
|
<width>40</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Script</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QCheckBox" name="runFullSuiteOnMobileCheckBox">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>275</y>
|
||||||
|
<width>120</width>
|
||||||
|
<height>20</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Run Full Suite</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QCheckBox" name="usePreviousInstallationOnMobileCheckBox">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>210</y>
|
||||||
|
<width>171</width>
|
||||||
|
<height>20</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>usePreviousInstallation</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab_2">
|
<widget class="QWidget" name="tab_2">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
|
@ -760,7 +910,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>190</x>
|
<x>190</x>
|
||||||
<y>180</y>
|
<y>200</y>
|
||||||
<width>131</width>
|
<width>131</width>
|
||||||
<height>20</height>
|
<height>20</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -776,7 +926,7 @@
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>330</x>
|
<x>330</x>
|
||||||
<y>170</y>
|
<y>190</y>
|
||||||
<width>181</width>
|
<width>181</width>
|
||||||
<height>51</height>
|
<height>51</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -889,8 +1039,8 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>270</x>
|
<x>370</x>
|
||||||
<y>30</y>
|
<y>20</y>
|
||||||
<width>160</width>
|
<width>160</width>
|
||||||
<height>51</height>
|
<height>51</height>
|
||||||
</rect>
|
</rect>
|
||||||
|
@ -921,6 +1071,41 @@
|
||||||
<height>21</height>
|
<height>21</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QRadioButton" name="diffImageRadioButton">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>260</x>
|
||||||
|
<y>50</y>
|
||||||
|
<width>95</width>
|
||||||
|
<height>20</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Diff Image</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QRadioButton" name="ssimImageRadioButton">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>260</x>
|
||||||
|
<y>30</y>
|
||||||
|
<width>95</width>
|
||||||
|
<height>20</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>SSIM Image</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<zorder>groupBox</zorder>
|
<zorder>groupBox</zorder>
|
||||||
|
|
Loading…
Reference in a new issue