Merge pull request #9513 from sethalves/tablet-ui

Tablet ui
This commit is contained in:
Seth Alves 2017-01-25 15:58:29 -08:00 committed by GitHub
commit ad5c3f0b44
95 changed files with 1647 additions and 406 deletions

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Agent.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QEventLoop>
#include <QtCore/QStandardPaths>
@ -46,14 +48,12 @@
#include "RecordingScriptingInterface.h"
#include "AbstractAudioInterface.h"
#include "Agent.h"
#include "AvatarAudioTimer.h"
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
Agent::Agent(ReceivedMessage& message) :
ThreadedAssignment(message),
_entityEditSender(),
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES) {
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
@ -68,7 +68,7 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<recording::Recorder>();
DependencyManager::set<RecordingScriptingInterface>();
DependencyManager::set<ScriptCache>();
DependencyManager::set<ScriptEngines>();
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
@ -143,7 +143,7 @@ void Agent::handleAudioPacket(QSharedPointer<ReceivedMessage> message) {
_receivedAudioStream.clearBuffer();
}
const QString AGENT_LOGGING_NAME = "agent";
static const QString AGENT_LOGGING_NAME = "agent";
void Agent::run() {
@ -321,7 +321,7 @@ void Agent::scriptRequestFinished() {
}
void Agent::executeScript() {
_scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(_scriptContents, _payload));
_scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload));
_scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do
// setup an Avatar for the script to use
@ -376,6 +376,9 @@ void Agent::executeScript() {
_scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer);
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
_scriptEngine->registerGlobalObject("Recording", recordingInterface.data());
// we need to make sure that init has been called for our EntityScriptingInterface
// so that it actually has a jurisdiction listener when we ask it for it next
entityScriptingInterface->init();

View file

@ -12,12 +12,13 @@
#include <udt/PacketHeaders.h>
#include "Agent.h"
#include "assets/AssetServer.h"
#include "AssignmentFactory.h"
#include "audio/AudioMixer.h"
#include "avatars/AvatarMixer.h"
#include "entities/EntityServer.h"
#include "assets/AssetServer.h"
#include "messages/MessagesMixer.h"
#include "scripts/EntityScriptServer.h"
ThreadedAssignment* AssignmentFactory::unpackAssignment(ReceivedMessage& message) {
@ -39,7 +40,9 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(ReceivedMessage& message
return new AssetServer(message);
case Assignment::MessagesMixerType:
return new MessagesMixer(message);
case Assignment::EntityScriptServerType:
return new EntityScriptServer(message);
default:
return NULL;
return nullptr;
}
}

View file

@ -30,6 +30,7 @@
#include "NodeType.h"
#include "SendAssetTask.h"
#include "UploadAssetTask.h"
#include <ClientServerUtils.h>
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
@ -190,7 +191,7 @@ void AssetServer::completeSetup() {
cleanupUnmappedFiles();
}
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
} else {
qCritical() << "Asset Server assignment will not continue because mapping file could not be loaded.";
setFinished(true);

View file

@ -21,6 +21,7 @@
#include <udt/Packet.h>
#include "AssetUtils.h"
#include "ClientServerUtils.h"
SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) :
QRunnable(),

View file

@ -18,6 +18,8 @@
#include <NodeList.h>
#include <NLPacketList.h>
#include "ClientServerUtils.h"
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode,
const QDir& resourcesDir) :

View file

@ -398,7 +398,7 @@ void AudioMixer::start() {
auto nodeList = DependencyManager::get<NodeList>();
// prepare the NodeList
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
nodeList->linkedDataCreateCallback = [&](Node* node) { getOrCreateClientData(node); };
// parse out any AudioMixer settings

View file

@ -681,8 +681,8 @@ void AvatarMixer::run() {
void AvatarMixer::domainSettingsRequestComplete() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
// parse the settings to pull out the values we need
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());

View file

@ -11,6 +11,8 @@
#include "AssignmentParentFinder.h"
#include <AvatarHashMap.h>
SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const {
SpatiallyNestableWeakPointer parent;
@ -25,10 +27,21 @@ SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool&
} else {
parent = _tree->findEntityByEntityItemID(parentID);
}
if (parent.expired()) {
success = false;
} else {
if (!parent.expired()) {
success = true;
return parent;
}
// search avatars
if (DependencyManager::isSet<AvatarHashMap>()) {
auto avatarHashMap = DependencyManager::get<AvatarHashMap>();
parent = avatarHashMap->getAvatarBySessionID(parentID);
if (!parent.expired()) {
success = true;
return parent;
}
}
success = false;
return parent;
}

View file

@ -44,8 +44,7 @@ void MessagesMixer::handleMessages(QSharedPointer<ReceivedMessage> receivedMessa
nodeList->eachMatchingNode(
[&](const SharedNodePointer& node)->bool {
return node->getType() == NodeType::Agent && node->getActiveSocket() &&
_channelSubscribers[channel].contains(node->getUUID());
return node->getActiveSocket() && _channelSubscribers[channel].contains(node->getUUID());
},
[&](const SharedNodePointer& node) {
auto packetList = MessagesClient::encodeMessagesPacket(channel, message, senderID);
@ -83,5 +82,6 @@ void MessagesMixer::sendStatsPacket() {
void MessagesMixer::run() {
ThreadedAssignment::commonInit(MESSAGES_MIXER_LOGGING_NAME, NodeType::MessagesMixer);
DependencyManager::get<NodeList>()->addNodeTypeToInterestSet(NodeType::Agent);
}
auto nodeList = DependencyManager::get<NodeList>();
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
}

View file

@ -316,8 +316,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
int truePacketsSent = 0;
int trueBytesSent = 0;
int packetsSentThisInterval = 0;
bool isFullScene = ((!viewFrustumChanged) && nodeData->getViewFrustumJustStoppedChanging())
|| nodeData->hasLodChanged();
bool isFullScene = nodeData->haveJSONParametersChanged() ||
(nodeData->getUsesFrustum()
&& ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged()));
bool somethingToSend = true; // assume we have something
@ -432,7 +433,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
boundaryLevelAdjust, octreeSizeScale,
nodeData->getLastTimeBagEmpty(),
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
&nodeData->extraEncodeData);
&nodeData->extraEncodeData,
nodeData->getUsesFrustum(),
nodeData);
nodeData->copyCurrentViewFrustum(params.viewFrustum);
if (viewFrustumChanged) {
nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);

View file

@ -1136,8 +1136,8 @@ void OctreeServer::domainSettingsRequestComplete() {
auto nodeList = DependencyManager::get<NodeList>();
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");

View file

@ -0,0 +1,372 @@
//
// EntityScriptServer.cpp
// assignment-client/src/scripts
//
// Created by Clément Brisset on 1/5/17.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntityScriptServer.h"
#include <AudioConstants.h>
#include <AudioInjectorManager.h>
#include <EntityScriptingInterface.h>
#include <MessagesClient.h>
#include <plugins/CodecPlugin.h>
#include <plugins/PluginManager.h>
#include <ResourceManager.h>
#include <ScriptCache.h>
#include <ScriptEngines.h>
#include <SoundCache.h>
#include <UUID.h>
#include <WebSocketServerClass.h>
#include "ClientServerUtils.h"
#include "../entities/AssignmentParentFinder.h"
int EntityScriptServer::_entitiesScriptEngineCount = 0;
EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) {
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
ResourceManager::init();
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<SoundCache>();
DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<ScriptCache>();
DependencyManager::set<ScriptEngines>(ScriptEngine::ENTITY_SERVER_SCRIPT);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket");
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar");
packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
}
static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server";
void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
if (_entityViewer.getTree() && !_shuttingDown) {
qDebug() << "Reloading: " << entityID;
_entitiesScriptEngine->unloadEntityScript(entityID);
checkAndCallPreload(entityID, true);
}
}
}
void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
MessageID messageID;
message->readPrimitive(&messageID);
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
auto replyPacketList = NLPacketList::create(PacketType::EntityScriptGetStatusReply, QByteArray(), true, true);
replyPacketList->writePrimitive(messageID);
EntityScriptDetails details;
if (_entitiesScriptEngine->getEntityScriptDetails(entityID, details)) {
replyPacketList->writePrimitive(true);
replyPacketList->writePrimitive(details.status);
replyPacketList->writeString(details.errorInfo);
} else {
replyPacketList->writePrimitive(false);
}
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacketList(std::move(replyPacketList), *senderNode);
}
}
void EntityScriptServer::run() {
// make sure we request our script once the agent connects to the domain
auto nodeList = DependencyManager::get<NodeList>();
ThreadedAssignment::commonInit(ENTITY_SCRIPT_SERVER_LOGGING_NAME, NodeType::EntityScriptServer);
// Setup MessagesClient
auto messagesClient = DependencyManager::set<MessagesClient>();
QThread* messagesThread = new QThread;
messagesThread->setObjectName("Messages Client Thread");
messagesClient->moveToThread(messagesThread);
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
messagesThread->start();
// make sure we hear about connected nodes so we can grab an ATP script if a request is pending
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &EntityScriptServer::nodeActivated);
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptServer::nodeKilled);
nodeList->addSetOfNodeTypesToNodeInterestSet({
NodeType::Agent, NodeType::AudioMixer, NodeType::AvatarMixer,
NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer
});
// Setup Script Engine
resetEntitiesScriptEngine();
// we need to make sure that init has been called for our EntityScriptingInterface
// so that it actually has a jurisdiction listener when we ask it for it next
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
entityScriptingInterface->init();
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
_entityViewer.init();
// setup the JSON filter that asks for entities with a non-default serverScripts property
QJsonObject queryJSONParameters;
static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts";
queryJSONParameters[SERVER_SCRIPTS_PROPERTY] = EntityQueryFilterSymbol::NonDefault;
// setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter
_entityViewer.getOctreeQuery().setUsesFrustum(false);
_entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters);
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
auto tree = _entityViewer.getTree().get();
connect(tree, &EntityTree::deletingEntity, this, &EntityScriptServer::deletingEntity, Qt::QueuedConnection);
connect(tree, &EntityTree::addingEntity, this, &EntityScriptServer::addingEntity, Qt::QueuedConnection);
connect(tree, &EntityTree::entityServerScriptChanging, this, &EntityScriptServer::entityServerScriptChanging, Qt::QueuedConnection);
}
void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) {
if (activatedNode->getType() == NodeType::AudioMixer) {
negotiateAudioFormat();
}
}
void EntityScriptServer::negotiateAudioFormat() {
auto nodeList = DependencyManager::get<NodeList>();
auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat);
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
quint8 numberOfCodecs = (quint8)codecPlugins.size();
negotiateFormatPacket->writePrimitive(numberOfCodecs);
for (auto& plugin : codecPlugins) {
auto codecName = plugin->getName();
negotiateFormatPacket->writeString(codecName);
}
// grab our audio mixer from the NodeList, if it exists
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer) {
// send off this mute packet
nodeList->sendPacket(std::move(negotiateFormatPacket), *audioMixer);
}
}
void EntityScriptServer::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) {
QString selectedCodecName = message->readString();
selectAudioFormat(selectedCodecName);
}
void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) {
_selectedCodecName = selectedCodecName;
qDebug() << "Selected Codec:" << _selectedCodecName;
// release any old codec encoder/decoder first...
if (_codec && _encoder) {
_codec->releaseEncoder(_encoder);
_encoder = nullptr;
_codec = nullptr;
}
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
for (auto& plugin : codecPlugins) {
if (_selectedCodecName == plugin->getName()) {
_codec = plugin;
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
qDebug() << "Selected Codec Plugin:" << _codec.get();
break;
}
}
}
void EntityScriptServer::resetEntitiesScriptEngine() {
auto engineName = QString("Entities %1").arg(++_entitiesScriptEngineCount);
auto newEngine = QSharedPointer<ScriptEngine>(new ScriptEngine(ScriptEngine::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName));
auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor);
newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
newEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
connect(newEngine.data(), &ScriptEngine::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage);
connect(newEngine.data(), &ScriptEngine::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage);
connect(newEngine.data(), &ScriptEngine::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage);
connect(newEngine.data(), &ScriptEngine::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage);
connect(newEngine.data(), &ScriptEngine::update, this, [this] {
_entityViewer.queryOctree();
});
newEngine->runInThread();
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngine.data());
_entitiesScriptEngine.swap(newEngine);
}
void EntityScriptServer::clear() {
// unload and stop the engine
if (_entitiesScriptEngine) {
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
}
// reset the engine
if (!_shuttingDown) {
resetEntitiesScriptEngine();
}
_entityViewer.clear();
}
void EntityScriptServer::shutdownScriptEngine() {
if (_entitiesScriptEngine) {
_entitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
}
_shuttingDown = true;
clear(); // always clear() on shutdown
}
void EntityScriptServer::addingEntity(const EntityItemID& entityID) {
checkAndCallPreload(entityID);
}
void EntityScriptServer::deletingEntity(const EntityItemID& entityID) {
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
_entitiesScriptEngine->unloadEntityScript(entityID);
}
}
void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, const bool reload) {
if (_entityViewer.getTree() && !_shuttingDown) {
_entitiesScriptEngine->unloadEntityScript(entityID);
checkAndCallPreload(entityID, reload);
}
}
void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID);
EntityScriptDetails details;
bool notRunning = !_entitiesScriptEngine->getEntityScriptDetails(entityID, details);
if (entity && (reload || notRunning || details.scriptText != entity->getServerScripts())) {
QString scriptUrl = entity->getServerScripts();
if (!scriptUrl.isEmpty()) {
scriptUrl = ResourceManager::normalizeURL(scriptUrl);
qDebug() << "Loading entity server script" << scriptUrl << "for" << entityID;
ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload);
}
}
}
}
void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) {
if (!_shuttingDown && killedNode->getType() == NodeType::EntityServer) {
if (_entitiesScriptEngine) {
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
}
resetEntitiesScriptEngine();
_entityViewer.clear();
}
}
void EntityScriptServer::sendStatsPacket() {
}
void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
auto packetType = message->getType();
if (packetType == PacketType::OctreeStats) {
int statsMessageLength = OctreeHeadlessViewer::parseOctreeStats(message, senderNode);
if (message->getSize() > statsMessageLength) {
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength;
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader);
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr());
message = QSharedPointer<ReceivedMessage>::create(*newPacket);
} else {
return; // bail since no piggyback data
}
packetType = message->getType();
} // fall through to piggyback message
if (packetType == PacketType::EntityData) {
_entityViewer.processDatagram(*message, senderNode);
} else if (packetType == PacketType::EntityErase) {
_entityViewer.processEraseMessage(*message, senderNode);
}
}
void EntityScriptServer::handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
NodeType_t nodeType;
message->peekPrimitive(&nodeType);
// PacketType_JURISDICTION, first byte is the node type...
if (nodeType == NodeType::EntityServer) {
DependencyManager::get<EntityScriptingInterface>()->getJurisdictionListener()->
queueReceivedPacket(message, senderNode);
}
}
void EntityScriptServer::aboutToFinish() {
shutdownScriptEngine();
// our entity tree is going to go away so tell that to the EntityScriptingInterface
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
ResourceManager::cleanup();
// cleanup the AudioInjectorManager (and any still running injectors)
DependencyManager::destroy<AudioInjectorManager>();
DependencyManager::destroy<ScriptEngines>();
// cleanup codec & encoder
if (_codec && _encoder) {
_codec->releaseEncoder(_encoder);
_encoder = nullptr;
}
}

View file

@ -0,0 +1,70 @@
//
// EntityScriptServer.h
// assignment-client/src/scripts
//
// Created by Clément Brisset on 1/5/17.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_EntityScriptServer_h
#define hifi_EntityScriptServer_h
#include <QtCore/QObject>
#include <EntityEditPacketSender.h>
#include <EntityTreeHeadlessViewer.h>
#include <plugins/CodecPlugin.h>
#include <ScriptEngine.h>
#include <ThreadedAssignment.h>
class EntityScriptServer : public ThreadedAssignment {
Q_OBJECT
public:
EntityScriptServer(ReceivedMessage& message);
virtual void aboutToFinish() override;
public slots:
void run() override;
void nodeActivated(SharedNodePointer activatedNode);
void nodeKilled(SharedNodePointer killedNode);
void sendStatsPacket() override;
private slots:
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
void handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
private:
void negotiateAudioFormat();
void selectAudioFormat(const QString& selectedCodecName);
void resetEntitiesScriptEngine();
void clear();
void shutdownScriptEngine();
void addingEntity(const EntityItemID& entityID);
void deletingEntity(const EntityItemID& entityID);
void entityServerScriptChanging(const EntityItemID& entityID, const bool reload);
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
bool _shuttingDown { false };
static int _entitiesScriptEngineCount;
QSharedPointer<ScriptEngine> _entitiesScriptEngine;
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;
QString _selectedCodecName;
CodecPluginPointer _codec;
Encoder* _encoder { nullptr };
};
#endif // hifi_EntityScriptServer_h

View file

@ -6,8 +6,8 @@ if (WIN32)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi5.zip
URL_MD5 0530753e855ffc00232cc969bf1c84a8
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi6.zip
URL_MD5 fcac808c1ba0b0f5b44ea06e2612ebab
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -1285,7 +1285,7 @@
{
"name": "entityScriptSourceWhitelist",
"label": "Entity Scripts Allowed from:",
"help": "The domains that entity scripts are allowed from. A comma separated list of domains that entity scripts are allowed from, if someone attempts to create and entity or edit an entity to have a different domain, it will be rejected. If left blank, any domain is allowed.",
"help": "Comma separated list of URLs (with optional paths) that entity scripts are allowed from. If someone attempts to create and entity or edit an entity to have a different domain, it will be rejected. If left blank, any domain is allowed.",
"placeholder": "",
"default": "",
"advanced": true

View file

@ -46,10 +46,9 @@ QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID
}
}
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
<< NodeType::AvatarMixer << NodeType::EntityServer
<< NodeType::AssetServer
<< NodeType::MessagesMixer;
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer
<< NodeType::EntityScriptServer;
void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessage> message) {
if (message->getSize() == 0) {
@ -72,7 +71,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
}
static const NodeSet VALID_NODE_TYPES {
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent, NodeType::MessagesMixer, NodeType::EntityScriptServer
};
if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) {
@ -107,7 +106,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
if (node) {
// set the sending sock addr and node interest set on this node
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
nodeData->setSendingSockAddr(message->getSenderSockAddr());
// guard against patched agents asking to hear about other agents
@ -128,12 +127,12 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
emit connectedNode(node);
} else {
qDebug() << "Refusing connection from node at" << message->getSenderSockAddr()
<< "with hardware address" << nodeConnection.hardwareAddress
<< "with hardware address" << nodeConnection.hardwareAddress
<< "and machine fingerprint" << nodeConnection.machineFingerprint;
}
}
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress,
const QString& hardwareAddress, const QUuid& machineFingerprint) {
NodePermissions userPerms;
@ -283,7 +282,7 @@ void DomainGatekeeper::updateNodePermissions() {
QString hardwareAddress;
QUuid machineFingerprint;
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (nodeData) {
hardwareAddress = nodeData->getHardwareAddress();
machineFingerprint = nodeData->getMachineFingerprint();
@ -336,7 +335,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
// add the new node
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(newNode->getLinkedData());
// set assignment related data on the linked data for this node
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
@ -458,7 +457,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
newNode->setPermissions(userPerms);
// grab the linked data for our new node so we can set the username
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(newNode->getLinkedData());
// if we have a username from the connect request, set it on the DomainServerNodeData
nodeData->setUsername(username);

View file

@ -107,7 +107,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
// make sure we hear about newly connected nodes from our gatekeeper
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
@ -281,7 +281,7 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV);
qDebug() << "Reading certificate file at" << certPath << "for HTTPS.";
qDebug() << "Reading key file at" << keyPath << "for HTTPS.";
qDebug() << "Reading key file at" << keyPath << "for HTTPS.";
QFile certFile(certPath);
certFile.open(QIODevice::ReadOnly);
@ -528,12 +528,12 @@ void DomainServer::setupNodeListAndAssignments() {
packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket");
packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket");
// NodeList won't be available to the settings manager when it is created, so call registerListener here
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
packetReceiver.registerListener(PacketType::UsernameFromIDRequest, &_settingsManager, "processUsernameFromIDRequestPacket");
// register the gatekeeper for the packets it needs to receive
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
@ -542,7 +542,7 @@ void DomainServer::setupNodeListAndAssignments() {
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK");
// add whatever static assignments that have been parsed to the queue
addStaticAssignmentsToQueue();
@ -808,21 +808,19 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
for (Assignment::Type defaultedType = Assignment::AudioMixerType;
defaultedType != Assignment::AllTypes;
defaultedType = static_cast<Assignment::Type>(static_cast<int>(defaultedType) + 1)) {
if (!excludedTypes.contains(defaultedType)
&& defaultedType != Assignment::UNUSED_1
&& defaultedType != Assignment::AgentType) {
if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) {
if (defaultedType == Assignment::AssetServerType) {
// Make sure the asset-server is enabled before adding it here.
// Initially we do not assign it by default so we can test it in HF domains first
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) {
// skip to the next iteration if asset-server isn't enabled
continue;
}
}
// type has not been set from a command line or config file config, use the default
// by clearing whatever exists and writing a single default assignment with no payload
Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType);
@ -839,9 +837,9 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
// update this node's sockets in case they have changed
sendingNode->setPublicSocket(nodeRequestData.publicSockAddr);
sendingNode->setLocalSocket(nodeRequestData.localSockAddr);
// update the NodeInterestSet in case there have been any changes
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
// guard against patched agents asking to hear about other agents
auto safeInterestSet = nodeRequestData.interestList.toSet();
@ -857,6 +855,44 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
sendDomainListToNode(sendingNode, message->getSenderSockAddr());
}
bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) {
auto nodeAData = static_cast<DomainServerNodeData*>(nodeA->getLinkedData());
auto nodeBData = static_cast<DomainServerNodeData*>(nodeB->getLinkedData());
// if we have no linked data for node A then B can't possibly be in the interest set
if (!nodeAData) {
return false;
}
// first check if the general interest set A contains the type for B
if (nodeAData->getNodeInterestSet().contains(nodeB->getType())) {
// given that there is a match in the general interest set, do any special checks
// (1/19/17) Agents only need to connect to Entity Script Servers to perform administrative tasks
// related to entity server scripts. Only agents with rez permissions should be doing that, so
// if the agent does not have those permissions, we do not want them and the server to incur the
// overhead of connecting to one another. Additionally we exclude agents that do not care about the
// Entity Script Server and won't attempt to connect to it.
bool isAgentWithoutRights = nodeA->getType() == NodeType::Agent
&& nodeB->getType() == NodeType::EntityScriptServer
&& !nodeA->getCanRez() && !nodeA->getCanRezTmp();
if (isAgentWithoutRights) {
return false;
}
bool isScriptServerForIneffectiveAgent =
(nodeA->getType() == NodeType::EntityScriptServer && nodeB->getType() == NodeType::Agent)
&& ((nodeBData && !nodeBData->getNodeInterestSet().contains(NodeType::EntityScriptServer))
|| (!nodeB->getCanRez() && !nodeB->getCanRezTmp()));
return !isScriptServerForIneffectiveAgent;
} else {
return false;
}
}
unsigned int DomainServer::countConnectedUsers() {
unsigned int result = 0;
auto nodeList = DependencyManager::get<LimitedNodeList>();
@ -928,14 +964,14 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) {
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NUM_BYTES_RFC4122_UUID + 2;
// setup the extended header for the domain list packets
// this data is at the beginning of each of the domain list packets
QByteArray extendedHeader(NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES, 0);
QDataStream extendedHeaderStream(&extendedHeader, QIODevice::WriteOnly);
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
extendedHeaderStream << limitedNodeList->getSessionUUID();
extendedHeaderStream << node->getUUID();
extendedHeaderStream << node->getPermissions();
@ -945,7 +981,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
// always send the node their own UUID back
QDataStream domainListStream(domainListPackets.get());
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
// store the nodeInterestSet on this DomainServerNodeData, in case it has changed
auto& nodeInterestSet = nodeData->getNodeInterestSet();
@ -955,10 +991,8 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
// DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
if (nodeData->isAuthenticated()) {
// if this authenticated node has any interest types, send back those nodes as well
limitedNodeList->eachNode([&](const SharedNodePointer& otherNode){
if (otherNode->getUUID() != node->getUUID()
&& nodeInterestSet.contains(otherNode->getType())) {
limitedNodeList->eachNode([&](const SharedNodePointer& otherNode) {
if (otherNode->getUUID() != node->getUUID() && isInInterestSet(node, otherNode)) {
// since we're about to add a node to the packet we start a segment
domainListPackets->startSegment();
@ -974,7 +1008,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
});
}
}
// send an empty list to the node, in case there were no other nodes
domainListPackets->closeCurrentPacket(true);
@ -983,8 +1017,8 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
}
QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) {
DomainServerNodeData* nodeAData = dynamic_cast<DomainServerNodeData*>(nodeA->getLinkedData());
DomainServerNodeData* nodeBData = dynamic_cast<DomainServerNodeData*>(nodeB->getLinkedData());
DomainServerNodeData* nodeAData = static_cast<DomainServerNodeData*>(nodeA->getLinkedData());
DomainServerNodeData* nodeBData = static_cast<DomainServerNodeData*>(nodeB->getLinkedData());
if (nodeAData && nodeBData) {
QUuid& secretUUID = nodeAData->getSessionSecretHash()[nodeB->getUUID()];
@ -994,7 +1028,7 @@ QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, con
secretUUID = QUuid::createUuid();
// set it on the other Node's sessionSecretHash
reinterpret_cast<DomainServerNodeData*>(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID);
static_cast<DomainServerNodeData*>(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID);
}
return secretUUID;
@ -1020,8 +1054,7 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
[&](const SharedNodePointer& node)->bool {
if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) {
// is the added Node in this node's interest list?
DomainServerNodeData* nodeData = dynamic_cast<DomainServerNodeData*>(node->getLinkedData());
return nodeData->getNodeInterestSet().contains(addedNode->getType());
return isInInterestSet(node, addedNode);
} else {
return false;
}
@ -1124,7 +1157,7 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<ReceivedMessage
void DomainServer::setupPendingAssignmentCredits() {
// enumerate the NodeList to find the assigned nodes
DependencyManager::get<LimitedNodeList>()->eachNode([&](const SharedNodePointer& node){
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) {
// check if we have a non-finalized transaction for this node to add this amount to
@ -1510,7 +1543,7 @@ void DomainServer::sendHeartbeatToIceServer() {
}
void DomainServer::processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode) {
auto nodeData = dynamic_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
auto nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
if (nodeData) {
nodeData->updateJSONStats(packetList->getMessage());
}
@ -1556,7 +1589,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
nodeJson[JSON_KEY_UPTIME] = QString::number(double(QDateTime::currentMSecsSinceEpoch() - node->getWakeTimestamp()) / 1000.0);
// if the node has pool information, add it
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
// add the node username, if it exists
nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername();
@ -1624,23 +1657,23 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
if (connection->requestOperation() == QNetworkAccessManager::GetOperation
&& assignmentRegex.indexIn(url.path()) != -1) {
QUuid nodeUUID = QUuid(assignmentRegex.cap(1));
auto matchingNode = nodeList->nodeWithUUID(nodeUUID);
// don't handle if we don't have a matching node
if (!matchingNode) {
return false;
}
auto nodeData = dynamic_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
auto nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
// don't handle if we don't have node data for this node
if (!nodeData) {
return false;
}
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
// check if we have an assignment that matches this temp UUID, and it is a scripted assignment
if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) {
// we have a matching assignment and it is for the right type, have the HTTP manager handle it
@ -1655,7 +1688,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return true;
}
// request not handled
return false;
}
@ -1687,7 +1720,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
// enumerate the NodeList to find the assigned nodes
nodeList->eachNode([this, &assignedNodesJSON](const SharedNodePointer& node){
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (!nodeData->getAssignmentUUID().isNull()) {
// add the node using the UUID as the key
@ -1775,7 +1808,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
if (matchingNode) {
// create a QJsonDocument with the stats QJsonObject
QJsonObject statsObject =
reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->getStatsJSONObject();
static_cast<DomainServerNodeData*>(matchingNode->getLinkedData())->getStatsJSONObject();
// add the node type to the JSON data for output purposes
statsObject["node_type"] = NodeType::getNodeTypeName(matchingNode->getType()).toLower().replace(' ', '-');
@ -2247,7 +2280,7 @@ void DomainServer::addStaticAssignmentsToQueue() {
// if the domain-server has just restarted,
// check if there are static assignments that we need to throw into the assignment queue
auto sharedAssignments = _allAssignments.values();
// sort the assignments to put the server/mixer assignments first
qSort(sharedAssignments.begin(), sharedAssignments.end(), [](SharedAssignmentPointer a, SharedAssignmentPointer b){
if (a->getType() == b->getType()) {
@ -2258,9 +2291,9 @@ void DomainServer::addStaticAssignmentsToQueue() {
return a->getType() != Assignment::AgentType;
}
});
auto staticAssignment = sharedAssignments.begin();
while (staticAssignment != sharedAssignments.end()) {
// add any of the un-matched static assignments to the queue
@ -2371,7 +2404,6 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
}
void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
auto nodeType = nodeToKill->getType();
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
const QUuid& nodeUUID = nodeToKill->getUUID();
@ -2383,10 +2415,9 @@ void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
removedNodePacket->write(nodeUUID.toRfc4122());
// broadcast out the DomainServerRemovedNode message
limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool {
limitedNodeList->eachMatchingNode([this, &nodeToKill](const SharedNodePointer& otherNode) -> bool {
// only send the removed node packet to nodes that care about the type of node this was
auto nodeLinkedData = dynamic_cast<DomainServerNodeData*>(otherNode->getLinkedData());
return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType);
return isInInterestSet(otherNode, nodeToKill);
}, [&limitedNodeList](const SharedNodePointer& otherNode){
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
});

View file

@ -132,6 +132,8 @@ private:
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr);
bool isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB);
QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB);
void broadcastNewNode(const SharedNodePointer& node);

View file

@ -725,7 +725,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
}
// potentially remove connect permissions for the MAC address and machine fingerprint
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
if (nodeData) {
// mac address first
NodePermissionsKey macAddressKey(nodeData->getHardwareAddress(), 0);
@ -807,7 +807,7 @@ void DomainServerSettingsManager::processUsernameFromIDRequestPacket(QSharedPoin
usernameFromIDReplyPacket->writeString(verifiedUsername);
// now put in the machine fingerprint
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
machineFingerprint = nodeData ? nodeData->getMachineFingerprint() : QUuid();
usernameFromIDReplyPacket->write(machineFingerprint.toRfc4122());
} else {

View file

@ -61,7 +61,7 @@
#include <CursorManager.h>
#include <DebugDraw.h>
#include <DeferredLightingEffect.h>
#include <display-plugins/DisplayPlugin.h>
#include <EntityScriptClient.h>
#include <EntityScriptingInterface.h>
#include <ErrorDialog.h>
#include <FileScriptingInterface.h>
@ -174,6 +174,7 @@
#include "FrameTimingsScriptingInterface.h"
#include <GPUIdent.h>
#include <gl/GLHelpers.h>
#include <EntityScriptClient.h>
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.
@ -459,7 +460,7 @@ bool setupEssentials(int& argc, char** argv) {
// Set dependencies
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
DependencyManager::set<StatTracker>();
DependencyManager::set<ScriptEngines>();
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
DependencyManager::set<Preferences>();
DependencyManager::set<recording::Deck>();
DependencyManager::set<recording::Recorder>();
@ -516,6 +517,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp);
DependencyManager::set<CompositorHelper>();
DependencyManager::set<OffscreenQmlSurfaceCache>();
DependencyManager::set<EntityScriptClient>();
return previousSessionCrashed;
}
@ -854,7 +856,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// tell the NodeList instance who to tell the domain server we care about
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer);
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer << NodeType::EntityScriptServer);
// connect to the packet sent signal of the _entityEditSender
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
@ -5495,6 +5497,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("Controller", scriptingInterface.data());
UserInputMapper::registerControllerTypes(scriptEngine);
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
scriptEngine->registerGlobalObject("Recording", recordingInterface.data());
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
connect(scriptEngine, &ScriptEngine::printedMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onPrintedMessage);
connect(scriptEngine, &ScriptEngine::errorMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onErrorMessage);

View file

@ -180,9 +180,6 @@ bool AudioInjector::injectLocally() {
} else {
qCDebug(audio) << "AudioInjector::injectLocally called without any data in Sound QByteArray";
}
} else {
qCDebug(audio) << "AudioInjector::injectLocally cannot inject locally with no local audio interface present.";
}
return success;

View file

@ -45,9 +45,7 @@
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
AbstractScriptingServicesInterface* scriptingServices) :
OctreeRenderer(),
_wantScripts(wantScripts),
_entitiesScriptEngine(NULL),
_lastPointerEventValid(false),
_viewState(viewState),
_scriptingServices(scriptingServices),
@ -103,7 +101,7 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() {
// Keep a ref to oldEngine until newEngine is ready so EntityScriptingInterface has something to use
auto oldEngine = _entitiesScriptEngine;
auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount));
auto newEngine = new ScriptEngine(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount));
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(newEngine, entitiesScriptEngineDeleter);
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data());
@ -169,7 +167,7 @@ void EntityTreeRenderer::init() {
connect(entityTree.get(), &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity, Qt::QueuedConnection);
connect(entityTree.get(), &EntityTree::addingEntity, this, &EntityTreeRenderer::addingEntity, Qt::QueuedConnection);
connect(entityTree.get(), &EntityTree::entityScriptChanging,
this, &EntityTreeRenderer::entitySciptChanging, Qt::QueuedConnection);
this, &EntityTreeRenderer::entityScriptChanging, Qt::QueuedConnection);
}
void EntityTreeRenderer::shutdown() {
@ -939,7 +937,7 @@ void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) {
}
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const bool reload) {
void EntityTreeRenderer::entityScriptChanging(const EntityItemID& entityID, const bool reload) {
if (_tree && !_shuttingDown) {
_entitiesScriptEngine->unloadEntityScript(entityID);
checkAndCallPreload(entityID, reload);
@ -1063,7 +1061,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
}
}
if (isCollisionOwner(myNodeID, entityTree, idA, collision)) {
if (isCollisionOwner(myNodeID, entityTree, idB, collision)) {
emit collisionWithEntity(idB, idA, collision);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision);

View file

@ -122,7 +122,7 @@ signals:
public slots:
void addingEntity(const EntityItemID& entityID);
void deletingEntity(const EntityItemID& entityID);
void entitySciptChanging(const EntityItemID& entityID, const bool reload);
void entityScriptChanging(const EntityItemID& entityID, const bool reload);
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
void updateEntityRenderStatus(bool shouldRenderEntities);
void updateZone(const EntityItemID& id);

View file

@ -282,6 +282,8 @@ void RenderableWebEntityItem::setSourceUrl(const QString& value) {
WebEntityItem::setSourceUrl(value);
if (_sourceUrl != valueBeforeSuperclassSet && _webSurface) {
qCDebug(entities) << "Changing web entity source URL to " << _sourceUrl;
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
loadSourceURL();
if (_contentType == htmlContent) {

View file

@ -121,6 +121,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_LIFETIME;
requestedProperties += PROP_SCRIPT;
requestedProperties += PROP_SCRIPT_TIMESTAMP;
requestedProperties += PROP_SERVER_SCRIPTS;
requestedProperties += PROP_COLLISION_SOUND_URL;
requestedProperties += PROP_REGISTRATION_POINT;
requestedProperties += PROP_ANGULAR_DAMPING;
@ -265,6 +266,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, getLifetime());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, getScriptTimestamp());
APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, getServerScripts());
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping());
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible());
@ -778,6 +780,19 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
{
// We use this scope to work around an issue stopping server script changes
// from being received by an entity script server running a script that continously updates an entity.
// Basically, we'll allow recent changes to the server scripts even if there are local changes to other properties
// that have been made more recently.
bool overwriteLocalData = !ignoreServerPacket || (lastEditedFromBufferAdjusted > _serverScriptsChangedTimestamp);
READ_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, QString, setServerScripts);
}
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, updateRegistrationPoint);
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping);
@ -1186,6 +1201,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptTimestamp, getScriptTimestamp);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(serverScripts, getServerScripts);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionSoundURL, getCollisionSoundURL);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getLocalAngularVelocity);
@ -1298,6 +1314,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
// non-simulation properties below
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptTimestamp, setScriptTimestamp);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(serverScripts, setServerScripts);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
@ -2262,3 +2279,30 @@ void EntityItem::globalizeProperties(EntityItemProperties& properties, const QSt
QUuid empty;
properties.setParentID(empty);
}
bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
// The intention for the query JSON filter and this method is to be flexible to handle a variety of filters for
// 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)
// currently the only property filter we handle is '+' for serverScripts
// 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";
foreach(const auto& property, jsonFilters.keys()) {
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;
}
}
}
// the json filter syntax did not match what we expected, return a match
return true;
}

View file

@ -125,7 +125,7 @@ public:
void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); }
quint64 getLastChangedOnServer() const { return _changedOnServer; }
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
@ -254,12 +254,16 @@ public:
using SpatiallyNestable::getQueryAACube;
virtual AACube getQueryAACube(bool& success) const override;
const QString& getScript() const { return _script; }
QString getScript() const { return _script; }
void setScript(const QString& value) { _script = value; }
quint64 getScriptTimestamp() const { return _scriptTimestamp; }
void setScriptTimestamp(const quint64 value) { _scriptTimestamp = value; }
QString getServerScripts() const { return _serverScripts; }
void setServerScripts(const QString& serverScripts)
{ _serverScripts = serverScripts; _serverScriptsChangedTimestamp = usecTimestampNow(); }
const QString& getCollisionSoundURL() const { return _collisionSoundURL; }
void setCollisionSoundURL(const QString& value);
@ -464,6 +468,8 @@ public:
QUuid getLastEditedBy() const { return _lastEditedBy; }
void setLastEditedBy(QUuid value) { _lastEditedBy = value; }
bool matchesJSONFilters(const QJsonObject& jsonFilters) const;
protected:
@ -512,6 +518,10 @@ protected:
QString _loadedScript; /// the value of _script when the last preload signal was sent
quint64 _scriptTimestamp{ ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP }; /// the script loaded property used for forced reload
QString _serverScripts;
/// keep track of time when _serverScripts property was last changed
quint64 _serverScriptsChangedTimestamp { ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP };
/// the value of _scriptTimestamp when the last preload signal was sent
// NOTE: on construction we want this to be different from _scriptTimestamp so we intentionally bump it
quint64 _loadedScriptTimestamp{ ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP + 1 };

View file

@ -32,21 +32,21 @@ KeyLightPropertyGroup EntityItemProperties::_staticKeyLight;
EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1);
EntityItemProperties::EntityItemProperties(EntityPropertyFlags desiredProperties) :
_id(UNKNOWN_ENTITY_ID),
_idSet(false),
_lastEdited(0),
_type(EntityTypes::Unknown),
_id(UNKNOWN_ENTITY_ID),
_idSet(false),
_lastEdited(0),
_type(EntityTypes::Unknown),
_localRenderAlpha(1.0f),
_localRenderAlpha(1.0f),
_localRenderAlphaChanged(false),
_localRenderAlphaChanged(false),
_defaultSettings(true),
_naturalDimensions(1.0f, 1.0f, 1.0f),
_naturalPosition(0.0f, 0.0f, 0.0f),
_desiredProperties(desiredProperties)
_defaultSettings(true),
_naturalDimensions(1.0f, 1.0f, 1.0f),
_naturalPosition(0.0f, 0.0f, 0.0f),
_desiredProperties(desiredProperties)
{
}
void EntityItemProperties::setSittingPoints(const QVector<SittingPoint>& sittingPoints) {
@ -241,6 +241,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_LIFETIME, lifetime);
CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script);
CHECK_PROPERTY_CHANGE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp);
CHECK_PROPERTY_CHANGE(PROP_SERVER_SCRIPTS, serverScripts);
CHECK_PROPERTY_CHANGE(PROP_COLLISION_SOUND_URL, collisionSoundURL);
CHECK_PROPERTY_CHANGE(PROP_COLOR, color);
CHECK_PROPERTY_CHANGE(PROP_COLOR_SPREAD, colorSpread);
@ -388,6 +389,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LIFETIME, lifetime);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT, script);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SERVER_SCRIPTS, serverScripts);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_REGISTRATION_POINT, registrationPoint);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_VELOCITY, angularVelocity);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_DAMPING, angularDamping);
@ -628,6 +630,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(lifetime, float, setLifetime);
COPY_PROPERTY_FROM_QSCRIPTVALUE(script, QString, setScript);
COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptTimestamp, quint64, setScriptTimestamp);
COPY_PROPERTY_FROM_QSCRIPTVALUE(serverScripts, QString, setServerScripts);
COPY_PROPERTY_FROM_QSCRIPTVALUE(registrationPoint, glmVec3, setRegistrationPoint);
COPY_PROPERTY_FROM_QSCRIPTVALUE(angularVelocity, glmVec3, setAngularVelocity);
COPY_PROPERTY_FROM_QSCRIPTVALUE(angularDamping, float, setAngularDamping);
@ -917,6 +920,7 @@ QScriptValue EntityItemProperties::entityPropertyFlagsToScriptValue(QScriptEngin
static QHash<QString, EntityPropertyList> _propertyStringsToEnums;
void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) {
static std::once_flag initMap;
std::call_once(initMap, [](){
@ -934,6 +938,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_LIFETIME, Lifetime, lifetime, float);
ADD_PROPERTY_TO_MAP(PROP_SCRIPT, Script, script, QString);
ADD_PROPERTY_TO_MAP(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64);
ADD_PROPERTY_TO_MAP(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString);
ADD_PROPERTY_TO_MAP(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString);
ADD_PROPERTY_TO_MAP(PROP_COLOR, Color, color, xColor);
ADD_PROPERTY_TO_MAP(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor);
@ -1201,6 +1206,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, properties.getLifetime());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, properties.getScript());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, properties.getScriptTimestamp());
APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, properties.getServerScripts());
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, properties.getRegistrationPoint());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, properties.getAngularVelocity());
@ -1501,6 +1507,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT, QString, setScript);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SERVER_SCRIPTS, QString, setServerScripts);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity);
@ -1627,7 +1634,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
// NOTE: Spheres and Boxes are just special cases of Shape, and they need to include their PROP_SHAPE
// when encoding/decoding edits because otherwise they can't polymorph to other shape types
if (properties.getType() == EntityTypes::Shape ||
if (properties.getType() == EntityTypes::Shape ||
properties.getType() == EntityTypes::Box ||
properties.getType() == EntityTypes::Sphere) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape);
@ -1688,6 +1695,7 @@ void EntityItemProperties::markAllChanged() {
_userDataChanged = true;
_scriptChanged = true;
_scriptTimestampChanged = true;
_serverScriptsChanged = true;
_collisionSoundURLChanged = true;
_registrationPointChanged = true;
_angularVelocityChanged = true;
@ -1896,6 +1904,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (scriptTimestampChanged()) {
out += "scriptTimestamp";
}
if (serverScriptsChanged()) {
out += "serverScripts";
}
if (collisionSoundURLChanged()) {
out += "collisionSoundURL";
}

View file

@ -221,6 +221,8 @@ public:
DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY);
DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS);
static QString getBackgroundModeString(BackgroundMode mode);

View file

@ -35,6 +35,7 @@ const bool ENTITY_ITEM_DEFAULT_VISIBLE = true;
const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString("");
const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0;
const QString ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS = QString("");
const QString ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL = QString("");
const glm::vec3 ENTITY_ITEM_DEFAULT_REGISTRATION_POINT = ENTITY_ITEM_HALF_VEC3; // center

View file

@ -14,7 +14,7 @@
#include <udt/PacketHeaders.h>
#include "../octree/OctreeQueryNode.h"
#include <OctreeQueryNode.h>
class EntityNodeData : public OctreeQueryNode {
public:
@ -22,9 +22,15 @@ public:
quint64 getLastDeletedEntitiesSentAt() const { return _lastDeletedEntitiesSentAt; }
void setLastDeletedEntitiesSentAt(quint64 sentAt) { _lastDeletedEntitiesSentAt = sentAt; }
// these can only be called from the OctreeSendThread for the given Node
void insertEntitySentLastFrame(const QUuid& entityID) { _entitiesSentLastFrame.insert(entityID); }
void removeEntitySentLastFrame(const QUuid& entityID) { _entitiesSentLastFrame.remove(entityID); }
bool sentEntityLastFrame(const QUuid& entityID) { return _entitiesSentLastFrame.contains(entityID); }
private:
quint64 _lastDeletedEntitiesSentAt { usecTimestampNow() };
QSet<QUuid> _entitiesSentLastFrame;
};
#endif // hifi_EntityNodeData_h

View file

@ -183,6 +183,8 @@ enum EntityPropertyList {
PROP_LAST_EDITED_BY,
PROP_SERVER_SCRIPTS,
////////////////////////////////////////////////////////////////////////////////////////////////////
// ATTENTION: add new properties to end of list just ABOVE this line
PROP_AFTER_LAST_ITEM,

View file

@ -26,6 +26,8 @@
#include "SimulationOwner.h"
#include "ZoneEntityItem.h"
#include "WebEntityItem.h"
#include <EntityScriptClient.h>
EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) :
_entityTree(NULL),
@ -670,6 +672,38 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke
return result;
}
bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) {
auto client = DependencyManager::get<EntityScriptClient>();
return client->reloadServerScript(entityID);
}
bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValue callback) {
auto client = DependencyManager::get<EntityScriptClient>();
auto request = client->createScriptStatusRequest(entityID);
connect(request, &GetScriptStatusRequest::finished, callback.engine(), [callback](GetScriptStatusRequest* request) mutable {
QString statusString;
switch (request->getStatus()) {
case RUNNING:
statusString = "running";
break;
case ERROR_LOADING_SCRIPT:
statusString = "error_loading_script";
break;
case ERROR_RUNNING_SCRIPT:
statusString = "error_running_script";
break;
default:
statusString = "";
break;
}
QScriptValueList args { request->getResponseReceived(), request->getIsRunning(), statusString, request->getErrorInfo() };
callback.call(QScriptValue(), args);
request->deleteLater();
});
request->start();
return true;
}
void EntityScriptingInterface::setLightsArePickable(bool value) {
LightEntityItem::setLightsArePickable(value);
}

View file

@ -210,6 +210,9 @@ public slots:
/// order to return an accurate result
Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue());
Q_INVOKABLE bool reloadServerScripts(QUuid entityID);
Q_INVOKABLE bool getServerScriptStatus(QUuid entityID, QScriptValue callback);
Q_INVOKABLE void setLightsArePickable(bool value);
Q_INVOKABLE bool getLightsArePickable() const;

View file

@ -390,10 +390,14 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
return result;
}
void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID, const bool reload) {
void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID, bool reload) {
emit entityScriptChanging(entityItemID, reload);
}
void EntityTree::emitEntityServerScriptChanging(const EntityItemID& entityItemID, bool reload) {
emit entityServerScriptChanging(entityItemID, reload);
}
void EntityTree::notifyNewCollisionSoundURL(const QString& newURL, const EntityItemID& entityID) {
emit newCollisionSoundURL(QUrl(newURL), entityID);
}
@ -958,9 +962,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) {
bool passedWhiteList = false;
auto entityScript = properties.getScript();
// grab a URL representation of the entity script so we can check the host for this script
auto entityScriptURL = QUrl::fromUserInput(properties.getScript());
for (const auto& whiteListedPrefix : _entityScriptSourceWhitelist) {
if (entityScript.startsWith(whiteListedPrefix, Qt::CaseInsensitive)) {
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
if (entityScriptURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
entityScriptURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
passedWhiteList = true;
break;
}

View file

@ -31,6 +31,9 @@ using ModelWeakPointer = std::weak_ptr<Model>;
class EntitySimulation;
namespace EntityQueryFilterSymbol {
static const QString NonDefault = "+";
}
class NewlyCreatedEntityHook {
public:
@ -201,7 +204,8 @@ public:
void entityChanged(EntityItemPointer entity);
void emitEntityScriptChanging(const EntityItemID& entityItemID, const bool reload);
void emitEntityScriptChanging(const EntityItemID& entityItemID, bool reload);
void emitEntityServerScriptChanging(const EntityItemID& entityItemID, bool reload);
void setSimulation(EntitySimulationPointer simulation);
EntitySimulationPointer getSimulation() const { return _simulation; }
@ -270,6 +274,7 @@ signals:
void deletingEntity(const EntityItemID& entityID);
void addingEntity(const EntityItemID& entityID);
void entityScriptChanging(const EntityItemID& entityItemID, const bool reload);
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID);
void clearingEntities();

View file

@ -16,6 +16,7 @@
#include <OctreeUtils.h>
#include "EntitiesLogging.h"
#include "EntityNodeData.h"
#include "EntityItemProperties.h"
#include "EntityTree.h"
#include "EntityTreeElement.h"
@ -94,7 +95,7 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
@ -231,7 +232,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
}
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
EncodeBitstreamParams& params) const {
EncodeBitstreamParams& params) const {
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
@ -278,25 +279,57 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
int numberOfEntitiesOffset = 0;
withReadLock([&] {
QVector<uint16_t> indexesOfEntitiesToInclude;
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
// need to handle the case where our sibling elements need encoding but we don't.
if (!entityTreeElementExtraEncodeData->elementCompleted) {
QJsonObject jsonFilters;
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
if (entityNodeData) {
// we have an EntityNodeData instance
// so we should assume that means we might have JSON filters to check
jsonFilters = entityNodeData->getJSONParameters();
}
for (uint16_t i = 0; i < _entityItems.size(); i++) {
EntityItemPointer entity = _entityItems[i];
bool includeThisEntity = true;
if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastViewFrustumSent) {
if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastQuerySent) {
includeThisEntity = false;
}
if (hadElementExtraData) {
includeThisEntity = includeThisEntity &&
entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID());
// if this entity has been updated since our last full send and there are json filters, check them
if (includeThisEntity && !jsonFilters.isEmpty()) {
// if params include JSON filters, check if this entity matches
bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters);
if (entityMatchesFilters) {
// make sure this entity is in the set of entities sent last frame
entityNodeData->insertEntitySentLastFrame(entity->getID());
} else {
// we might include this entity if it matched in the previous frame
if (entityNodeData->sentEntityLastFrame(entity->getID())) {
entityNodeData->removeEntitySentLastFrame(entity->getID());
} else {
includeThisEntity = false;
}
}
}
if (includeThisEntity || params.recurseEverything) {
if (includeThisEntity && hadElementExtraData) {
includeThisEntity = entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID());
}
// we only check the bounds against our frustum and LOD if the query has asked us to check against the frustum
// which can sometimes not be the case when JSON filters are sent
if (params.usesFrustum && (includeThisEntity || params.recurseEverything)) {
// we want to use the maximum possible box for this, so that we don't have to worry about the nuance of
// simulation changing what's visible. consider the case where the entity contains an angular velocity
@ -925,6 +958,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
// 3) remember the old cube for the entity so we can mark it as dirty
if (entityItem) {
QString entityScriptBefore = entityItem->getScript();
QString entityServerScriptsBefore = entityItem->getServerScripts();
quint64 entityScriptTimestampBefore = entityItem->getScriptTimestamp();
bool bestFitBefore = bestFitEntityBounds(entityItem);
EntityTreeElementPointer currentContainingElement = _myTree->getContainingElement(entityItemID);
@ -948,6 +982,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
}
QString entityScriptAfter = entityItem->getScript();
QString entityServerScriptsAfter = entityItem->getServerScripts();
quint64 entityScriptTimestampAfter = entityItem->getScriptTimestamp();
bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter;
@ -956,6 +991,9 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
if (entityScriptBefore != entityScriptAfter || reload) {
_myTree->emitEntityScriptChanging(entityItemID, reload); // the entity script has changed
}
if (entityServerScriptsBefore != entityServerScriptsAfter || reload) {
_myTree->emitEntityServerScriptChanging(entityItemID, reload); // the entity server script has changed
}
} else {
entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args);

View file

@ -127,7 +127,6 @@ public:
bool alreadyFullyEncoded(EncodeBitstreamParams& params) const;
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual OctreeElement::AppendState appendElementData(OctreePacketData* packetData,
EncodeBitstreamParams& params) const override;

View file

@ -174,7 +174,7 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_IS_SPOTLIGHT;

View file

@ -126,7 +126,7 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;

View file

@ -26,7 +26,7 @@ class LineEntityItem : public EntityItem {
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -160,7 +160,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
return bytesRead;
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);

View file

@ -29,7 +29,7 @@ public:
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -469,7 +469,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);

View file

@ -170,7 +170,7 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;

View file

@ -26,7 +26,7 @@ class PolyLineEntityItem : public EntityItem {
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -179,7 +179,7 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_VOXEL_VOLUME_SIZE;

View file

@ -26,7 +26,7 @@ class PolyVoxEntityItem : public EntityItem {
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -137,7 +137,7 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_SHAPE;

View file

@ -99,7 +99,7 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_TEXT;

View file

@ -30,7 +30,7 @@ public:
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -84,7 +84,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_SOURCE_URL;
@ -129,7 +129,6 @@ void WebEntityItem::setSourceUrl(const QString& value) {
if (newURL.isValid()) {
_sourceUrl = newURL.toDisplayString();
qCDebug(entities) << "Changed web entity source URL to " << _sourceUrl;
} else {
qCDebug(entities) << "Clearing web entity source URL since" << value << "cannot be parsed to a valid URL.";
}

View file

@ -29,7 +29,7 @@ public:
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -133,7 +133,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
}
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);

View file

@ -30,7 +30,7 @@ public:
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual bool setProperties(const EntityItemProperties& properties) override;
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
// TODO: eventually only include properties changed since the params.lastQuerySent time
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,

View file

@ -21,11 +21,10 @@
#include <DependencyManager.h>
#include "AssetUtils.h"
#include "ClientServerUtils.h"
#include "LimitedNodeList.h"
#include "NLPacket.h"
#include "Node.h"
#include "ReceivedMessage.h"
#include "ResourceCache.h"
class GetMappingRequest;
class SetMappingRequest;
@ -60,8 +59,6 @@ public:
Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
static const MessageID INVALID_MESSAGE_ID = 0;
public slots:
void init();

View file

@ -77,7 +77,7 @@ void AssetRequest::start() {
_assetInfoRequestID = assetClient->getAssetInfo(_hash,
[this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
_assetInfoRequestID = AssetClient::INVALID_MESSAGE_ID;
_assetInfoRequestID = INVALID_MESSAGE_ID;
_info = info;
@ -119,7 +119,7 @@ void AssetRequest::start() {
// If the request is dead, return
return;
}
_assetRequestID = AssetClient::INVALID_MESSAGE_ID;
_assetRequestID = INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;

View file

@ -64,8 +64,8 @@ private:
QString _hash;
QByteArray _data;
int _numPendingRequests { 0 };
MessageID _assetRequestID { AssetClient::INVALID_MESSAGE_ID };
MessageID _assetInfoRequestID { AssetClient::INVALID_MESSAGE_ID };
MessageID _assetRequestID { INVALID_MESSAGE_ID };
MessageID _assetInfoRequestID { INVALID_MESSAGE_ID };
};
#endif

View file

@ -19,7 +19,6 @@
#include <QtCore/QByteArray>
#include <QtCore/QUrl>
using MessageID = uint32_t;
using DataOffset = int64_t;
using AssetPath = QString;
@ -64,4 +63,4 @@ bool isValidFilePath(const AssetPath& path);
bool isValidPath(const AssetPath& path);
bool isValidHash(const QString& hashString);
#endif
#endif // hifi_AssetUtils_h

View file

@ -35,6 +35,8 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) {
return Assignment::AssetServerType;
case NodeType::MessagesMixer:
return Assignment::MessagesMixerType;
case NodeType::EntityScriptServer:
return Assignment::EntityScriptServerType;
default:
return Assignment::AllTypes;
}
@ -139,6 +141,8 @@ const char* Assignment::getTypeName() const {
return "entity-server";
case Assignment::MessagesMixerType:
return "messages-mixer";
case Assignment::EntityScriptServerType:
return "entity-script-server";
default:
return "unknown";
}

View file

@ -33,7 +33,7 @@ public:
AgentType = 2,
AssetServerType = 3,
MessagesMixerType = 4,
UNUSED_1 = 5,
EntityScriptServerType = 5,
EntityServerType = 6,
AllTypes = 7
};

View file

@ -0,0 +1,21 @@
//
// ClientServerUtils.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2017/01/20
// Copyright 2017 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
//
#ifndef hifi_ClientServerUtils_h
#define hifi_ClientServerUtils_h
#include <cstdint>
using MessageID = uint32_t;
const MessageID INVALID_MESSAGE_ID = 0;
#endif // hifi_ClientServerUtils_h

View file

@ -0,0 +1,165 @@
#include "EntityScriptClient.h"
#include "NodeList.h"
#include "NetworkLogging.h"
#include "EntityScriptUtils.h"
#include <QThread>
MessageID EntityScriptClient::_currentID = 0;
GetScriptStatusRequest::GetScriptStatusRequest(QUuid entityID) : _entityID(entityID) {
}
GetScriptStatusRequest::~GetScriptStatusRequest() {
}
void GetScriptStatusRequest::start() {
auto client = DependencyManager::get<EntityScriptClient>();
client->getEntityServerScriptStatus(_entityID, [this](bool responseReceived, bool isRunning, EntityScriptStatus status, QString errorInfo) {
_responseReceived = responseReceived;
_isRunning = isRunning;
_status = status;
_errorInfo = errorInfo;
emit finished(this);
});
}
EntityScriptClient::EntityScriptClient() {
setCustomDeleter([](Dependency* dependency){
static_cast<EntityScriptClient*>(dependency)->deleteLater();
});
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityScriptGetStatusReply, this, "handleGetScriptStatusReply");
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptClient::handleNodeKilled);
connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset,
this, &EntityScriptClient::handleNodeClientConnectionReset);
}
GetScriptStatusRequest* EntityScriptClient::createScriptStatusRequest(QUuid entityID) {
auto request = new GetScriptStatusRequest(entityID);
request->moveToThread(thread());
return request;
}
bool EntityScriptClient::reloadServerScript(QUuid entityID) {
// Send packet to entity script server
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer entityScriptServer = nodeList->soloNodeOfType(NodeType::EntityScriptServer);
if (entityScriptServer) {
auto id = entityID.toRfc4122();
auto payloadSize = id.size();
auto packet = NLPacket::create(PacketType::ReloadEntityServerScript, payloadSize, true);
packet->write(id);
if (nodeList->sendPacket(std::move(packet), *entityScriptServer) != -1) {
return true;
}
}
return false;
}
MessageID EntityScriptClient::getEntityServerScriptStatus(QUuid entityID, GetScriptStatusCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer entityScriptServer = nodeList->soloNodeOfType(NodeType::EntityScriptServer);
if (entityScriptServer) {
auto packetList = NLPacketList::create(PacketType::EntityScriptGetStatus, QByteArray(), true, true);
auto messageID = ++_currentID;
packetList->writePrimitive(messageID);
packetList->write(entityID.toRfc4122());
if (nodeList->sendPacketList(std::move(packetList), *entityScriptServer) != -1) {
_pendingEntityScriptStatusRequests[entityScriptServer][messageID] = callback;
return messageID;
}
}
callback(false, false, ERROR_LOADING_SCRIPT, "");
return INVALID_MESSAGE_ID;
}
void EntityScriptClient::handleGetScriptStatusReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
Q_ASSERT(QThread::currentThread() == thread());
MessageID messageID;
bool isKnown { false };
EntityScriptStatus status = ERROR_LOADING_SCRIPT;
QString errorInfo { "" };
message->readPrimitive(&messageID);
message->readPrimitive(&isKnown);
if (isKnown) {
message->readPrimitive(&status);
errorInfo = message->readString();
}
// Check if we have any pending requests for this node
auto messageMapIt = _pendingEntityScriptStatusRequests.find(senderNode);
if (messageMapIt != _pendingEntityScriptStatusRequests.end()) {
// Found the node, get the MessageID -> Callback map
auto& messageCallbackMap = messageMapIt->second;
// Check if we have this pending request
auto requestIt = messageCallbackMap.find(messageID);
if (requestIt != messageCallbackMap.end()) {
auto callback = requestIt->second;
callback(true, isKnown, status, errorInfo);
messageCallbackMap.erase(requestIt);
}
// Although the messageCallbackMap may now be empty, we won't delete the node until we have disconnected from
// it to avoid constantly creating/deleting the map on subsequent requests.
}
}
void EntityScriptClient::handleNodeKilled(SharedNodePointer node) {
Q_ASSERT(QThread::currentThread() == thread());
if (node->getType() != NodeType::EntityScriptServer) {
return;
}
forceFailureOfPendingRequests(node);
}
void EntityScriptClient::handleNodeClientConnectionReset(SharedNodePointer node) {
// a client connection to a Node was reset
// if it was an EntityScriptServer we need to cause anything pending to fail so it is re-attempted
if (node->getType() != NodeType::EntityScriptServer) {
return;
}
//qCDebug(entity_script_client) << "EntityScriptClient detected client connection reset handshake with Asset Server - failing any pending requests";
forceFailureOfPendingRequests(node);
}
void EntityScriptClient::forceFailureOfPendingRequests(SharedNodePointer node) {
{
auto messageMapIt = _pendingEntityScriptStatusRequests.find(node);
if (messageMapIt != _pendingEntityScriptStatusRequests.end()) {
for (const auto& value : messageMapIt->second) {
value.second(false, false, ERROR_LOADING_SCRIPT, "");
}
messageMapIt->second.clear();
}
}
}

View file

@ -0,0 +1,75 @@
//
// EntityScriptClient.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2017/01/13
// Copyright 2017 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
//
#ifndef hifi_EntityScriptClient_h
#define hifi_EntityScriptClient_h
#include "ClientServerUtils.h"
#include "LimitedNodeList.h"
#include "ReceivedMessage.h"
#include "AssetUtils.h"
#include "EntityScriptUtils.h"
#include <DependencyManager.h>
#include <unordered_map>
using GetScriptStatusCallback = std::function<void(bool responseReceived, bool isRunning, EntityScriptStatus status, QString errorInfo)>;
class GetScriptStatusRequest : public QObject {
Q_OBJECT
public:
GetScriptStatusRequest(QUuid);
~GetScriptStatusRequest();
Q_INVOKABLE void start();
bool getResponseReceived() const { return _responseReceived; }
bool getIsRunning() const { return _isRunning; }
EntityScriptStatus getStatus() const { return _status; }
QString getErrorInfo() const { return _errorInfo; }
signals:
void finished(GetScriptStatusRequest* request);
private:
QUuid _entityID;
MessageID _messageID;
bool _responseReceived;
bool _isRunning;
EntityScriptStatus _status;
QString _errorInfo;
};
class EntityScriptClient : public QObject, public Dependency {
Q_OBJECT
public:
EntityScriptClient();
Q_INVOKABLE GetScriptStatusRequest* createScriptStatusRequest(QUuid entityID);
bool reloadServerScript(QUuid entityID);
MessageID getEntityServerScriptStatus(QUuid entityID, GetScriptStatusCallback callback);
private slots:
void handleNodeKilled(SharedNodePointer node);
void handleNodeClientConnectionReset(SharedNodePointer node);
void handleGetScriptStatusReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
private:
static MessageID _currentID;
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetScriptStatusCallback>> _pendingEntityScriptStatusRequests;
void forceFailureOfPendingRequests(SharedNodePointer node);
};
#endif

View file

@ -0,0 +1,21 @@
//
// EntityScriptUtils.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2017/01/13
// Copyright 2017 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
//
#ifndef hifi_EntityScriptUtils_h
#define hifi_EntityScriptUtils_h
enum EntityScriptStatus {
ERROR_LOADING_SCRIPT,
ERROR_RUNNING_SCRIPT,
RUNNING
};
#endif // hifi_EntityScriptUtils_h

View file

@ -68,7 +68,7 @@ void GetMappingRequest::doStart() {
_mappingRequestID = assetClient->getAssetMapping(_path,
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
_mappingRequestID = INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {
@ -100,7 +100,7 @@ void GetAllMappingsRequest::doStart() {
_mappingRequestID = assetClient->getAllAssetMappings(
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
_mappingRequestID = INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
@ -152,7 +152,7 @@ void SetMappingRequest::doStart() {
_mappingRequestID = assetClient->setAssetMapping(_path, _hash,
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
_mappingRequestID = INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {
@ -195,7 +195,7 @@ void DeleteMappingsRequest::doStart() {
_mappingRequestID = assetClient->deleteAssetMappings(_paths,
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
_mappingRequestID = INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {
@ -237,7 +237,7 @@ void RenameMappingRequest::doStart() {
_mappingRequestID = assetClient->renameAssetMapping(_oldPath, _newPath,
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
_mappingRequestID = INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {

View file

@ -40,7 +40,7 @@ public:
protected:
Error _error { NoError };
MessageID _mappingRequestID { AssetClient::INVALID_MESSAGE_ID };
MessageID _mappingRequestID { INVALID_MESSAGE_ID };
private:
virtual void doStart() = 0;

View file

@ -17,6 +17,7 @@
Q_DECLARE_LOGGING_CATEGORY(resourceLog)
Q_DECLARE_LOGGING_CATEGORY(networking)
Q_DECLARE_LOGGING_CATEGORY(asset_client)
Q_DECLARE_LOGGING_CATEGORY(entity_script_client)
Q_DECLARE_LOGGING_CATEGORY(messages_client)
#endif // hifi_NetworkLogging_h

View file

@ -41,6 +41,7 @@ void NodeType::init() {
TypeNameHash.insert(NodeType::AvatarMixer, "Avatar Mixer");
TypeNameHash.insert(NodeType::MessagesMixer, "Messages Mixer");
TypeNameHash.insert(NodeType::AssetServer, "Asset Server");
TypeNameHash.insert(NodeType::EntityScriptServer, "Entity Script Server");
TypeNameHash.insert(NodeType::Unassigned, "Unassigned");
}

View file

@ -24,6 +24,7 @@ namespace NodeType {
const NodeType_t AvatarMixer = 'W';
const NodeType_t AssetServer = 'A';
const NodeType_t MessagesMixer = 'm';
const NodeType_t EntityScriptServer = 'S';
const NodeType_t Unassigned = 1;
void init();

View file

@ -48,7 +48,9 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_ENTITIES_LAST_EDITED_BY;
return VERSION_ENTITIES_SERVER_SCRIPTS;
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::JsonFilter);
case PacketType::AvatarIdentity:
case PacketType::AvatarData:
case PacketType::BulkAvatarData:

View file

@ -107,7 +107,10 @@ public:
RequestsDomainListData,
ExitingSpaceBubble,
PerAvatarGainSet,
LAST_PACKET_TYPE = PerAvatarGainSet
EntityScriptGetStatus,
EntityScriptGetStatusReply,
ReloadEntityServerScript,
LAST_PACKET_TYPE = ReloadEntityServerScript
};
};
@ -197,6 +200,11 @@ const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62;
const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63;
const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64;
const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65;
const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66;
enum class EntityQueryPacketVersion: PacketVersion {
JsonFilter = 18
};
enum class AssetServerPacketVersion: PacketVersion {
VegasCongestionControl = 19

View file

@ -899,7 +899,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
}
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
if (!params.recurseEverything && !element->isInView(params.viewFrustum)) {
if (params.usesFrustum && !params.recurseEverything && !element->isInView(params.viewFrustum)) {
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
return bytesWritten;
}
@ -1015,7 +1015,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
}
ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
if (!params.recurseEverything) {
if (params.usesFrustum && !params.recurseEverything) {
float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust,
params.octreeElementSizeScale);
@ -1077,7 +1077,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// If we were previously in the view, then we normally will return out of here and stop recursing. But
// if we're in deltaView mode, and this element has changed since it was last sent, then we do
// need to send it.
if (wasInView && !(params.deltaView && element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) {
if (wasInView && !(params.deltaView && element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE))) {
if (params.stats) {
params.stats->skippedWasInView(element);
}
@ -1088,7 +1088,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed,
// then we can also bail early and save bits
if (!params.forceSendScene && !params.deltaView &&
!element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) {
!element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE)) {
if (params.stats) {
params.stats->skippedNoChange(element);
}
@ -1176,7 +1176,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
int originalIndex = indexOfChildren[i];
bool childIsInView = (childElement &&
(params.recurseEverything ||
(params.recurseEverything || !params.usesFrustum ||
(nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are
(nodeLocationThisView == ViewFrustum::INTERSECT &&
childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view
@ -1189,7 +1189,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
}
} else {
// Before we consider this further, let's see if it's in our LOD scope...
float boundaryDistance = params.recurseEverything ? 1 :
float boundaryDistance = params.recurseEverything || !params.usesFrustum ? 1 :
boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust,
params.octreeElementSizeScale);
@ -1211,7 +1211,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
bool childIsOccluded = false; // assume it's not occluded
bool shouldRender = params.recurseEverything ||
bool shouldRender = params.recurseEverything || !params.usesFrustum ||
childElement->calculateShouldRender(params.viewFrustum,
params.octreeElementSizeScale, params.boundaryLevelAdjust);
@ -1247,7 +1247,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// need to send it.
if (!childWasInView ||
(params.deltaView &&
childElement->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){
childElement->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE))){
childrenDataBits += (1 << (7 - originalIndex));
inViewWithColorCount++;
@ -1451,7 +1451,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep
// recursing, by returning TRUE in recurseChildrenWithData().
if (params.recurseEverything || recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) {
if (params.recurseEverything || !params.usesFrustum
|| recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) {
// Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this
// will be true. But if the tree has already been encoded, we will skip this.

View file

@ -17,6 +17,7 @@
#include <QHash>
#include <QObject>
#include <QtCore/QJsonObject>
#include <shared/ReadWriteLockable.h>
#include <SimpleMovingAverage.h>
@ -68,7 +69,7 @@ class EncodeBitstreamParams {
public:
ViewFrustum viewFrustum;
ViewFrustum lastViewFrustum;
quint64 lastViewFrustumSent;
quint64 lastQuerySent;
int maxEncodeLevel;
int maxLevelReached;
bool includeExistsBits;
@ -81,6 +82,8 @@ public:
OctreeSceneStats* stats;
JurisdictionMap* jurisdictionMap;
OctreeElementExtraEncodeData* extraEncodeData;
bool usesFrustum;
NodeData* nodeData;
// output hints from the encode process
typedef enum {
@ -104,12 +107,14 @@ public:
bool useDeltaView = false,
int boundaryLevelAdjust = NO_BOUNDARY_ADJUST,
float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
quint64 lastViewFrustumSent = IGNORE_LAST_SENT,
quint64 lastQuerySent = IGNORE_LAST_SENT,
bool forceSendScene = true,
OctreeSceneStats* stats = IGNORE_SCENE_STATS,
JurisdictionMap* jurisdictionMap = IGNORE_JURISDICTION_MAP,
OctreeElementExtraEncodeData* extraEncodeData = NULL) :
lastViewFrustumSent(lastViewFrustumSent),
OctreeElementExtraEncodeData* extraEncodeData = nullptr,
bool usesFrustum = true,
NodeData* nodeData = nullptr) :
lastQuerySent(lastQuerySent),
maxEncodeLevel(maxEncodeLevel),
maxLevelReached(0),
includeExistsBits(includeExistsBits),
@ -121,6 +126,8 @@ public:
stats(stats),
jurisdictionMap(jurisdictionMap),
extraEncodeData(extraEncodeData),
usesFrustum(usesFrustum),
nodeData(nodeData),
stopReason(UNKNOWN)
{
lastViewFrustum.invalidate();

View file

@ -12,7 +12,7 @@
#ifndef hifi_OctreeConstants_h
#define hifi_OctreeConstants_h
#include <QtGlobal> // for quint64
#include <QtCore/QString> // for quint64/QString
#include <glm/glm.hpp>
const quint64 CHANGE_FUDGE = 1000 * 200; // useconds of fudge in determining if we want to resend changed voxels

View file

@ -108,7 +108,7 @@ public:
virtual bool isRendered() const { return getShouldRender(); }
virtual bool deleteApproved() const { return true; }
virtual bool canRayIntersect() const { return isLeaf(); }
/// \param center center of sphere in meters
/// \param radius radius of sphere in meters

View file

@ -36,6 +36,8 @@ public:
virtual void render(RenderArgs* renderArgs) override { /* swallow these */ }
void setJurisdictionListener(JurisdictionListener* jurisdictionListener) { _jurisdictionListener = jurisdictionListener; }
OctreeQuery& getOctreeQuery() { return _octreeQuery; }
static int parseOctreeStats(QSharedPointer<ReceivedMessage> message, SharedNodePointer sourceNode);
static void trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket);

View file

@ -9,13 +9,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QJsonDocument>
#include <GLMHelpers.h>
#include <udt/PacketHeaders.h>
#include "OctreeConstants.h"
#include "OctreeQuery.h"
OctreeQuery::OctreeQuery() {
_maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
}
@ -23,35 +24,27 @@ OctreeQuery::OctreeQuery() {
int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
unsigned char* bufferStart = destinationBuffer;
// TODO: DRY this up to a shared method
// that can pack any type given the number of bytes
// and return the number of bytes to push the pointer
// back a boolean (cut to 1 byte) to designate if this query uses the sent view frustum
memcpy(destinationBuffer, &_usesFrustum, sizeof(_usesFrustum));
destinationBuffer += sizeof(_usesFrustum);
if (_usesFrustum) {
// TODO: DRY this up to a shared method
// that can pack any type given the number of bytes
// and return the number of bytes to push the pointer
// camera details
memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition));
destinationBuffer += sizeof(_cameraPosition);
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation);
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov);
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio);
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip);
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip);
memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition));
destinationBuffer += sizeof(_cameraEyeOffsetPosition);
}
// camera details
memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition));
destinationBuffer += sizeof(_cameraPosition);
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation);
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov);
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio);
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip);
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip);
memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition));
destinationBuffer += sizeof(_cameraEyeOffsetPosition);
// bitMask of less than byte wide items
unsigned char bitItems = 0;
// NOTE: we need to keep these here for new clients to talk to old servers. After we know that the clients and
// servers and clients have all been updated we could remove these bits. New servers will always force these
// features on old clients even if they don't ask for them. (which old clients will properly handle). New clients
// will always ask for these so that old servers will use these features.
setAtBit(bitItems, WANT_LOW_RES_MOVING_BIT);
setAtBit(bitItems, WANT_COLOR_AT_BIT);
setAtBit(bitItems, WANT_DELTA_AT_BIT);
setAtBit(bitItems, WANT_COMPRESSION);
*destinationBuffer++ = bitItems;
// desired Max Octree PPS
memcpy(destinationBuffer, &_maxQueryPPS, sizeof(_maxQueryPPS));
destinationBuffer += sizeof(_maxQueryPPS);
@ -67,6 +60,25 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius));
destinationBuffer += sizeof(_cameraCenterRadius);
// create a QByteArray that holds the binary representation of the JSON parameters
QByteArray binaryParametersDocument;
if (!_jsonParameters.isEmpty()) {
binaryParametersDocument = QJsonDocument(_jsonParameters).toBinaryData();
}
// write the size of the JSON parameters
uint16_t binaryParametersBytes = binaryParametersDocument.size();
memcpy(destinationBuffer, &binaryParametersBytes, sizeof(binaryParametersBytes));
destinationBuffer += sizeof(binaryParametersBytes);
// pack the binary JSON parameters
// NOTE: for now we assume that the filters that will be set are all small enough that we will not have a packet > MTU
if (binaryParametersDocument.size() > 0) {
memcpy(destinationBuffer, binaryParametersDocument.data(), binaryParametersBytes);
destinationBuffer += binaryParametersBytes;
}
return destinationBuffer - bufferStart;
}
@ -76,24 +88,22 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(message.getRawMessage());
const unsigned char* sourceBuffer = startPosition;
// camera details
memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition));
sourceBuffer += sizeof(_cameraPosition);
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov);
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio);
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip);
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip);
memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition));
sourceBuffer += sizeof(_cameraEyeOffsetPosition);
// optional feature flags
unsigned char bitItems = 0;
bitItems = (unsigned char)*sourceBuffer++;
// NOTE: we used to use these bits to set feature request items if we need to extend the protocol with optional features
// do it here with... wantFeature= oneAtBit(bitItems, WANT_FEATURE_BIT);
Q_UNUSED(bitItems);
// check if this query uses a view frustum
memcpy(&_usesFrustum, sourceBuffer, sizeof(_usesFrustum));
sourceBuffer += sizeof(_usesFrustum);
if (_usesFrustum) {
// unpack camera details
memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition));
sourceBuffer += sizeof(_cameraPosition);
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov);
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio);
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip);
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip);
memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition));
sourceBuffer += sizeof(_cameraEyeOffsetPosition);
}
// desired Max Octree PPS
memcpy(&_maxQueryPPS, sourceBuffer, sizeof(_maxQueryPPS));
@ -106,13 +116,28 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
// desired boundaryLevelAdjust
memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust));
sourceBuffer += sizeof(_boundaryLevelAdjust);
auto bytesRead = sourceBuffer - startPosition;
auto bytesLeft = message.getSize() - bytesRead;
if (bytesLeft >= (int)sizeof(_cameraCenterRadius)) {
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
sourceBuffer += sizeof(_cameraCenterRadius);
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
sourceBuffer += sizeof(_cameraCenterRadius);
// check if we have a packed JSON filter
uint16_t binaryParametersBytes;
memcpy(&binaryParametersBytes, sourceBuffer, sizeof(binaryParametersBytes));
sourceBuffer += sizeof(binaryParametersBytes);
if (binaryParametersBytes > 0) {
// unpack the binary JSON parameters
QByteArray binaryJSONParameters { binaryParametersBytes, 0 };
memcpy(binaryJSONParameters.data(), sourceBuffer, binaryParametersBytes);
sourceBuffer += binaryParametersBytes;
// grab the parameter object from the packed binary representation of JSON
auto newJsonDocument = QJsonDocument::fromBinaryData(binaryJSONParameters);
QWriteLocker jsonParameterLocker { &_jsonParametersLock };
_jsonParameters = newJsonDocument.object();
}
return sourceBuffer - startPosition;
}

View file

@ -31,14 +31,11 @@ typedef unsigned long long quint64;
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QtCore/QJsonObject>
#include <QtCore/QReadWriteLock>
#include <NodeData.h>
// First bitset
const int WANT_LOW_RES_MOVING_BIT = 0;
const int WANT_COLOR_AT_BIT = 1;
const int WANT_DELTA_AT_BIT = 2;
const int UNUSED_BIT_3 = 3; // unused... available for new feature
const int WANT_COMPRESSION = 4; // 5th bit
class OctreeQuery : public NodeData {
Q_OBJECT
@ -71,11 +68,19 @@ public:
void setCameraFarClip(float farClip) { _cameraFarClip = farClip; }
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; }
// getters/setters for JSON filter
QJsonObject getJSONParameters() { QReadLocker locker { &_jsonParametersLock }; return _jsonParameters; }
void setJSONParameters(const QJsonObject& jsonParameters)
{ QWriteLocker locker { &_jsonParametersLock }; _jsonParameters = jsonParameters; }
// related to Octree Sending strategies
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
float getOctreeSizeScale() const { return _octreeElementSizeScale; }
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
bool getUsesFrustum() { return _usesFrustum; }
void setUsesFrustum(bool usesFrustum) { _usesFrustum = usesFrustum; }
public slots:
void setMaxQueryPacketsPerSecond(int maxQueryPPS) { _maxQueryPPS = maxQueryPPS; }
@ -97,7 +102,12 @@ protected:
int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations
int _boundaryLevelAdjust = 0; /// used for LOD calculations
uint8_t _usesFrustum = true;
QJsonObject _jsonParameters;
QReadWriteLock _jsonParametersLock;
private:
// privatize the copy constructor and assignment operator so they cannot be called
OctreeQuery(const OctreeQuery&);

View file

@ -1,6 +1,6 @@
//
// OctreeQueryNode.cpp
// assignment-client/src/octree
// libraries/octree/src
//
// Created by Stephen Birarda on 3/21/13.
// Copyright 2013 High Fidelity, Inc.
@ -18,7 +18,6 @@
#include <SharedUtil.h>
#include <UUID.h>
#include "OctreeSendThread.h"
void OctreeQueryNode::nodeKilled() {
_isShuttingDown = true;
@ -156,65 +155,71 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
if (_isShuttingDown) {
return false;
}
bool currentViewFrustumChanged = false;
ViewFrustum newestViewFrustum;
// get position and orientation details from the camera
newestViewFrustum.setPosition(getCameraPosition());
newestViewFrustum.setOrientation(getCameraOrientation());
newestViewFrustum.setCenterRadius(getCameraCenterRadius());
// Also make sure it's got the correct lens details from the camera
float originalFOV = getCameraFov();
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
if (0.0f != getCameraAspectRatio() &&
0.0f != getCameraNearClip() &&
0.0f != getCameraFarClip() &&
getCameraNearClip() != getCameraFarClip()) {
newestViewFrustum.setProjection(glm::perspective(
glm::radians(wideFOV), // hack
getCameraAspectRatio(),
getCameraNearClip(),
getCameraFarClip()));
}
{ // if there has been a change, then recalculate
QMutexLocker viewLocker(&_viewMutex);
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
_currentViewFrustum = newestViewFrustum;
_currentViewFrustum.calculate();
currentViewFrustumChanged = true;
}
}
// Also check for LOD changes from the client
if (_lodInitialized) {
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
_lodChanged = true;
}
if (_lastClientOctreeSizeScale != getOctreeSizeScale()) {
_lastClientOctreeSizeScale = getOctreeSizeScale();
_lodChanged = true;
}
if (!_usesFrustum) {
// this client does not use a view frustum so the view frustum for this query has not changed
return false;
} else {
_lodInitialized = true;
_lastClientOctreeSizeScale = getOctreeSizeScale();
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
_lodChanged = false;
bool currentViewFrustumChanged = false;
ViewFrustum newestViewFrustum;
// get position and orientation details from the camera
newestViewFrustum.setPosition(getCameraPosition());
newestViewFrustum.setOrientation(getCameraOrientation());
newestViewFrustum.setCenterRadius(getCameraCenterRadius());
// Also make sure it's got the correct lens details from the camera
float originalFOV = getCameraFov();
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
if (0.0f != getCameraAspectRatio() &&
0.0f != getCameraNearClip() &&
0.0f != getCameraFarClip() &&
getCameraNearClip() != getCameraFarClip()) {
newestViewFrustum.setProjection(glm::perspective(
glm::radians(wideFOV), // hack
getCameraAspectRatio(),
getCameraNearClip(),
getCameraFarClip()));
}
{ // if there has been a change, then recalculate
QMutexLocker viewLocker(&_viewMutex);
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
_currentViewFrustum = newestViewFrustum;
_currentViewFrustum.calculate();
currentViewFrustumChanged = true;
}
}
// Also check for LOD changes from the client
if (_lodInitialized) {
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
_lodChanged = true;
}
if (_lastClientOctreeSizeScale != getOctreeSizeScale()) {
_lastClientOctreeSizeScale = getOctreeSizeScale();
_lodChanged = true;
}
} else {
_lodInitialized = true;
_lastClientOctreeSizeScale = getOctreeSizeScale();
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
_lodChanged = false;
}
// When we first detect that the view stopped changing, we record this.
// but we don't change it back to false until we've completely sent this
// scene.
if (_viewFrustumChanging && !currentViewFrustumChanged) {
_viewFrustumJustStoppedChanging = true;
}
_viewFrustumChanging = currentViewFrustumChanged;
return currentViewFrustumChanged;
}
// When we first detect that the view stopped changing, we record this.
// but we don't change it back to false until we've completely sent this
// scene.
if (_viewFrustumChanging && !currentViewFrustumChanged) {
_viewFrustumJustStoppedChanging = true;
}
_viewFrustumChanging = currentViewFrustumChanged;
return currentViewFrustumChanged;
}
void OctreeQueryNode::setViewSent(bool viewSent) {
@ -315,3 +320,15 @@ void OctreeQueryNode::parseNackPacket(ReceivedMessage& message) {
_nackedSequenceNumbers.enqueue(sequenceNumber);
}
}
bool OctreeQueryNode::haveJSONParametersChanged() {
bool parametersChanged = false;
auto currentParameters = getJSONParameters();
if (_lastCheckJSONParameters != currentParameters) {
parametersChanged = true;
_lastCheckJSONParameters = currentParameters;
}
return parametersChanged;
}

View file

@ -1,6 +1,6 @@
//
// OctreeQueryNode.h
// assignment-client/src/octree
// libraries/octree/src
//
// Created by Brad Hefta-Gaub on 12/4/13.
// Copyright 2013 High Fidelity, Inc.
@ -15,11 +15,11 @@
#include <iostream>
#include <NodeData.h>
#include <OctreeConstants.h>
#include <OctreeElementBag.h>
#include <OctreePacketData.h>
#include <OctreeQuery.h>
#include <OctreeSceneStats.h>
#include "OctreeConstants.h"
#include "OctreeElementBag.h"
#include "OctreePacketData.h"
#include "OctreeQuery.h"
#include "OctreeSceneStats.h"
#include "SentPacketHistory.h"
#include <qqueue.h>
@ -100,6 +100,9 @@ public:
bool hasNextNackedPacket() const;
const NLPacket* getNextNackedPacket();
// call only from OctreeSendThread for the given node
bool haveJSONParametersChanged();
private:
OctreeQueryNode(const OctreeQueryNode &);
OctreeQueryNode& operator= (const OctreeQueryNode&);
@ -143,6 +146,8 @@ private:
quint64 _sceneSendStartTime = 0;
std::array<char, udt::MAX_PACKET_SIZE> _lastOctreePayload;
QJsonObject _lastCheckJSONParameters;
};
#endif // hifi_OctreeQueryNode_h

View file

@ -27,7 +27,7 @@ class PhysicalEntitySimulation;
using PhysicalEntitySimulationPointer = std::shared_ptr<PhysicalEntitySimulation>;
using SetOfEntityMotionStates = QSet<EntityMotionState*>;
class PhysicalEntitySimulation :public EntitySimulation {
class PhysicalEntitySimulation : public EntitySimulation {
public:
PhysicalEntitySimulation();
~PhysicalEntitySimulation();

View file

@ -43,14 +43,14 @@ public:
class SteamScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool isRunning READ isRunning)
Q_PROPERTY(bool running READ isRunning)
public:
SteamScriptingInterface(QObject* parent, SteamClientPlugin* plugin) : QObject(parent) {}
SteamScriptingInterface(QObject* parent, SteamClientPlugin* plugin) : QObject(parent), _plugin(plugin) {}
public slots:
bool isRunning() const { return _plugin->isRunning(); }
void openInviteOverlay() const { _plugin->openInviteOverlay(); }
public slots:
bool isRunning() const { return _plugin && _plugin->isRunning(); }
void openInviteOverlay() const { if (_plugin) { _plugin->openInviteOverlay(); } }
private:
SteamClientPlugin* _plugin;

View file

@ -148,25 +148,29 @@ static bool hasCorrectSyntax(const QScriptProgram& program, ScriptEngine* report
return true;
}
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName, ScriptEngine* reportingEngine) {
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName, ScriptEngine* reportingEngine, QString* exceptionMessage = nullptr) {
if (engine.hasUncaughtException()) {
const auto backtrace = engine.uncaughtExceptionBacktrace();
const auto exception = engine.uncaughtException().toString();
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
engine.clearExceptions();
auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
QString message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
if (!backtrace.empty()) {
static const auto lineSeparator = "\n ";
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
}
reportingEngine->scriptErrorMessage(qPrintable(message));
if (exceptionMessage) {
*exceptionMessage = message;
}
return true;
}
return false;
}
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString) :
ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const QString& fileNameString) :
_context(context),
_scriptContents(scriptContents),
_timerFunctionMap(),
_fileNameString(fileNameString),
@ -181,6 +185,22 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
setProcessEventsInterval(MSECS_PER_SECOND);
}
QString ScriptEngine::getContext() const {
switch (_context) {
case CLIENT_SCRIPT:
return "client";
case ENTITY_CLIENT_SCRIPT:
return "entity_client";
case ENTITY_SERVER_SCRIPT:
return "entity_server";
case AGENT_SCRIPT:
return "agent";
default:
return "unknown";
}
return "unknown";
}
ScriptEngine::~ScriptEngine() {
scriptInfoMessage("Script Engine shutting down:" + getFilename());
@ -563,11 +583,7 @@ void ScriptEngine::init() {
// constants
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
registerGlobalObject("Recording", recordingInterface.data());
registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
registerGlobalObject("Assets", &_assetScriptingInterface);
registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
}
@ -1377,6 +1393,15 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
}
}
bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const {
auto it = _entityScripts.constFind(entityID);
if (it == _entityScripts.constEnd()) {
return false;
}
details = it.value();
return true;
}
// since all of these operations can be asynch we will always do the actual work in the response handler
// for the download
void ScriptEngine::loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
@ -1424,11 +1449,24 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
bool isFileUrl = isURL && scriptOrURL.startsWith("file://");
auto fileName = isURL ? scriptOrURL : "EmbeddedEntityScript";
EntityScriptDetails newDetails;
newDetails.scriptText = scriptOrURL;
if (!success) {
newDetails.status = ERROR_LOADING_SCRIPT;
newDetails.errorInfo = "Failed to load script";
_entityScripts[entityID] = newDetails;
return;
}
QScriptProgram program(contents, fileName);
if (!hasCorrectSyntax(program, this)) {
if (!isFileUrl) {
scriptCache->addScriptToBadScriptList(scriptOrURL);
}
newDetails.status = ERROR_RUNNING_SCRIPT;
newDetails.errorInfo = "Bad syntax";
_entityScripts[entityID] = newDetails;
return; // done processing script
}
@ -1453,7 +1491,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
});
testConstructor = sandbox.evaluate(program);
}
if (hadUncaughtExceptions(sandbox, program.fileName(), this)) {
QString exceptionMessage;
if (hadUncaughtExceptions(sandbox, program.fileName(), this, &exceptionMessage)) {
newDetails.status = ERROR_RUNNING_SCRIPT;
newDetails.errorInfo = exceptionMessage;
_entityScripts[entityID] = newDetails;
return;
}
@ -1476,6 +1520,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
scriptCache->addScriptToBadScriptList(scriptOrURL);
}
newDetails.status = ERROR_RUNNING_SCRIPT;
newDetails.errorInfo = "Could not find constructor";
_entityScripts[entityID] = newDetails;
return; // done processing script
}
@ -1492,8 +1540,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
};
doWithEnvironment(entityID, sandboxURL, initialization);
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified, sandboxURL };
newDetails.scriptObject = entityScriptObject;
newDetails.lastModified = lastModified;
newDetails.definingSandboxURL = sandboxURL;
_entityScripts[entityID] = newDetails;
if (isURL) {
setParentURL("");
}
@ -1519,7 +1570,9 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
#endif
if (_entityScripts.contains(entityID)) {
callEntityScriptMethod(entityID, "unload");
if (_entityScripts[entityID].status == RUNNING) {
callEntityScriptMethod(entityID, "unload");
}
_entityScripts.remove(entityID);
stopAllTimersForEntityScript(entityID);
}
@ -1538,7 +1591,9 @@ void ScriptEngine::unloadAllEntityScripts() {
qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]";
#endif
foreach(const EntityItemID& entityID, _entityScripts.keys()) {
callEntityScriptMethod(entityID, "unload");
if (_entityScripts[entityID].status == RUNNING) {
callEntityScriptMethod(entityID, "unload");
}
}
_entityScripts.clear();
@ -1577,7 +1632,7 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
QString scriptContents = QTextStream(&file).readAll();
this->unloadEntityScript(entityID);
this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true);
if (!_entityScripts.contains(entityID)) {
if (!_entityScripts.contains(entityID) || _entityScripts[entityID].status != RUNNING) {
scriptWarningMessage("Reload script " + details.scriptText + " failed");
} else {
details = _entityScripts[entityID];
@ -1636,7 +1691,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
#endif
refreshFileScript(entityID);
if (_entityScripts.contains(entityID)) {
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
EntityScriptDetails details = _entityScripts[entityID];
QScriptValue entityScript = details.scriptObject; // previously loaded
if (entityScript.property(methodName).isFunction()) {
@ -1668,7 +1723,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
#endif
refreshFileScript(entityID);
if (_entityScripts.contains(entityID)) {
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
EntityScriptDetails details = _entityScripts[entityID];
QScriptValue entityScript = details.scriptObject; // previously loaded
if (entityScript.property(methodName).isFunction()) {
@ -1701,7 +1756,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
#endif
refreshFileScript(entityID);
if (_entityScripts.contains(entityID)) {
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
EntityScriptDetails details = _entityScripts[entityID];
QScriptValue entityScript = details.scriptObject; // previously loaded
if (entityScript.property(methodName).isFunction()) {

View file

@ -29,6 +29,7 @@
#include <LimitedNodeList.h>
#include <EntityItemID.h>
#include <EntitiesScriptEngineProvider.h>
#include <EntityScriptUtils.h>
#include "PointerEvent.h"
#include "ArrayBufferClass.h"
@ -58,16 +59,30 @@ typedef QHash<QString, CallbackList> RegisteredEventHandlers;
class EntityScriptDetails {
public:
QString scriptText;
QScriptValue scriptObject;
int64_t lastModified;
QUrl definingSandboxURL;
EntityScriptStatus status { RUNNING };
// If status indicates an error, this contains a human-readable string giving more information about the error.
QString errorInfo { "" };
QString scriptText { "" };
QScriptValue scriptObject { QScriptValue() };
int64_t lastModified { 0 };
QUrl definingSandboxURL { QUrl() };
};
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider {
Q_OBJECT
Q_PROPERTY(QString context READ getContext)
public:
ScriptEngine(const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString(""));
enum Context {
CLIENT_SCRIPT,
ENTITY_CLIENT_SCRIPT,
ENTITY_SERVER_SCRIPT,
AGENT_SCRIPT
};
ScriptEngine(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString(""));
~ScriptEngine();
/// run the script in a dedicated thread. This will have the side effect of evalulating
@ -118,6 +133,12 @@ public:
/// to scripts. we may not need this to be invokable
void loadURL(const QUrl& scriptURL, bool reload);
Q_INVOKABLE QString getContext() const;
Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; }
Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; }
Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; }
Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE - these are intended to be public interfaces available to scripts
Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
@ -178,6 +199,8 @@ public:
void scriptWarningMessage(const QString& message);
void scriptInfoMessage(const QString& message);
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;
public slots:
void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler);
void updateMemoryCost(const qint64&);
@ -200,6 +223,30 @@ signals:
void doneRunning();
protected:
void init();
bool evaluatePending() const { return _evaluatesPending > 0; }
void timerFired();
void stopAllTimers();
void stopAllTimersForEntityScript(const EntityItemID& entityID);
void refreshFileScript(const EntityItemID& entityID);
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
void stopTimer(QTimer* timer);
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution.
QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation);
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
Context _context;
QString _scriptContents;
QString _parentURL;
std::atomic<bool> _isFinished { false };
@ -215,19 +262,6 @@ protected:
bool _debuggable { false };
qint64 _lastUpdate;
void init();
bool evaluatePending() const { return _evaluatesPending > 0; }
void timerFired();
void stopAllTimers();
void stopAllTimersForEntityScript(const EntityItemID& entityID);
void refreshFileScript(const EntityItemID& entityID);
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
void stopTimer(QTimer* timer);
QString _fileNameString;
Quat _quatLibrary;
Vec3 _vec3Library;
@ -240,15 +274,6 @@ protected:
AssetScriptingInterface _assetScriptingInterface{ this };
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution.
QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation);
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
std::function<bool()> _emitScriptUpdates{ [](){ return true; } };
std::recursive_mutex _lock;

View file

@ -62,8 +62,9 @@ void ScriptEngines::onErrorLoadingScript(const QString& url) {
emit errorLoadingScript(url, scriptName);
}
ScriptEngines::ScriptEngines()
: _scriptsLocationHandle("scriptsLocation", DESKTOP_LOCATION)
ScriptEngines::ScriptEngines(ScriptEngine::Context context)
: _context(context),
_scriptsLocationHandle("scriptsLocation", DESKTOP_LOCATION)
{
_scriptsModelFilter.setSourceModel(&_scriptsModel);
_scriptsModelFilter.sort(0, Qt::AscendingOrder);
@ -453,7 +454,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
return scriptEngine;
}
scriptEngine = new ScriptEngine(NO_SCRIPT, "");
scriptEngine = new ScriptEngine(_context, NO_SCRIPT, "");
scriptEngine->setUserLoaded(isUserLoaded);
connect(scriptEngine, &ScriptEngine::doneRunning, this, [scriptEngine] {
scriptEngine->deleteLater();

View file

@ -20,6 +20,7 @@
#include <SettingHandle.h>
#include <DependencyManager.h>
#include "ScriptEngine.h"
#include "ScriptsModel.h"
#include "ScriptsModelFilter.h"
@ -34,7 +35,7 @@ class ScriptEngines : public QObject, public Dependency {
public:
using ScriptInitializer = std::function<void(ScriptEngine*)>;
ScriptEngines();
ScriptEngines(ScriptEngine::Context context);
void registerScriptInitializer(ScriptInitializer initializer);
void loadScripts();
@ -90,7 +91,6 @@ public slots:
protected slots:
void onScriptFinished(const QString& fileNameString, ScriptEngine* engine);
protected:
friend class ScriptEngine;
@ -101,6 +101,7 @@ protected:
void onScriptEngineError(const QString& scriptFilename);
void launchScriptEngine(ScriptEngine* engine);
ScriptEngine::Context _context;
QReadWriteLock _scriptEnginesHashLock;
QHash<QUrl, ScriptEngine*> _scriptEnginesHash;
QSet<ScriptEngine*> _allKnownScriptEngines;

View file

@ -156,39 +156,42 @@ void ScriptsModel::reloadDefaultFiles() {
void ScriptsModel::requestDefaultFiles(QString marker) {
QUrl url(defaultScriptsLocation());
if (url.isLocalFile()) {
// if the url indicates a local directory, use QDirIterator
QString localDir = expandScriptUrl(url).toLocalFile();
int localDirPartCount = localDir.split("/").size();
if (localDir.endsWith("/")) {
localDirPartCount--;
}
#ifdef Q_OS_WIN
localDirPartCount++; // one for the drive letter
#endif
QDirIterator it(localDir, QStringList() << "*.js", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
QUrl jsFullPath = QUrl::fromLocalFile(it.next());
QString jsPartialPath = jsFullPath.path().split("/").mid(localDirPartCount).join("/");
jsFullPath = normalizeScriptURL(jsFullPath);
_treeNodes.append(new TreeNodeScript(jsPartialPath, jsFullPath.toString(), SCRIPT_ORIGIN_DEFAULT));
}
_loadingScripts = false;
} else {
// the url indicates http(s), use QNetworkRequest
QUrlQuery query;
query.addQueryItem(PREFIX_PARAMETER_NAME, ".");
if (!marker.isEmpty()) {
query.addQueryItem(MARKER_PARAMETER_NAME, marker);
}
url.setQuery(query);
// targets that don't have a scripts folder in the appropriate location will have an empty URL here
if (!url.isEmpty()) {
if (url.isLocalFile()) {
// if the url indicates a local directory, use QDirIterator
QString localDir = expandScriptUrl(url).toLocalFile();
int localDirPartCount = localDir.split("/").size();
if (localDir.endsWith("/")) {
localDirPartCount--;
}
#ifdef Q_OS_WIN
localDirPartCount++; // one for the drive letter
#endif
QDirIterator it(localDir, QStringList() << "*.js", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
QUrl jsFullPath = QUrl::fromLocalFile(it.next());
QString jsPartialPath = jsFullPath.path().split("/").mid(localDirPartCount).join("/");
jsFullPath = normalizeScriptURL(jsFullPath);
_treeNodes.append(new TreeNodeScript(jsPartialPath, jsFullPath.toString(), SCRIPT_ORIGIN_DEFAULT));
}
_loadingScripts = false;
} else {
// the url indicates http(s), use QNetworkRequest
QUrlQuery query;
query.addQueryItem(PREFIX_PARAMETER_NAME, ".");
if (!marker.isEmpty()) {
query.addQueryItem(MARKER_PARAMETER_NAME, marker);
}
url.setQuery(query);
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
}
}
}

View file

@ -186,7 +186,7 @@ void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext&
getInstance().printMessage((LogMsgType) type, context, message);
}
const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
void LogHandler::setupRepeatedMessageFlusher() {
static std::once_flag once;
std::call_once(once, [&] {
// setup our timer to flush the verbose logs every 5 seconds
@ -194,6 +194,11 @@ const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
connect(logFlushTimer, &QTimer::timeout, this, &LogHandler::flushRepeatedMessages);
logFlushTimer->start(VERBOSE_LOG_INTERVAL_SECONDS * 1000);
});
}
const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
// make sure we setup the repeated message flusher, but do it on the LogHandler thread
QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher");
QMutexLocker lock(&_mutex);
return *_repeatedMessageRegexes.insert(regexString);

View file

@ -53,6 +53,9 @@ public:
const QString& addRepeatedMessageRegex(const QString& regexString);
const QString& addOnlyOnceMessageRegex(const QString& regexString);
private slots:
void setupRepeatedMessageFlusher();
private:
LogHandler();
~LogHandler();

View file

@ -114,7 +114,7 @@ void Tooltip::handleAPIResponse(QNetworkReply& requestReply) {
if (_description.isEmpty()) {
const QString DESCRIPTION_KEY = "description";
// we have an empty description - did a non-empty desciption come back?
// we have an empty description - did a non-empty description come back?
if (placeObject.contains(DESCRIPTION_KEY)) {
QString placeDescription = placeObject[DESCRIPTION_KEY].toString();

View file

@ -1400,6 +1400,35 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) {
var ENTITY_PROPERTIES_URL = Script.resolvePath('html/entityProperties.html');
var ServerScriptStatusMonitor = function(entityID, statusCallback) {
var self = this;
self.entityID = entityID;
self.active = true;
self.sendRequestTimerID = null;
var onStatusReceived = function(success, isRunning, status, errorInfo) {
if (self.active) {
statusCallback({
statusRetrieved: success,
isRunning: isRunning,
status: status,
errorInfo: errorInfo
});
self.sendRequestTimerID = Script.setTimeout(function() {
if (self.active) {
Entities.getServerScriptStatus(entityID, onStatusReceived);
}
}, 1000);
};
};
self.stop = function() {
self.active = false;
}
Entities.getServerScriptStatus(entityID, onStatusReceived);
};
var PropertiesTool = function (opts) {
var that = {};
@ -1411,6 +1440,11 @@ var PropertiesTool = function (opts) {
var visible = false;
// This keeps track of the last entity ID that was selected. If multiple entities
// are selected or if no entity is selected this will be `null`.
var currentSelectedEntityID = null;
var statusMonitor = null;
webView.setVisible(visible);
that.setVisible = function (newVisible) {
@ -1418,10 +1452,44 @@ var PropertiesTool = function (opts) {
webView.setVisible(visible);
};
selectionManager.addEventListener(function () {
function updateScriptStatus(info) {
info.type = "server_script_status";
webView.emitScriptEvent(JSON.stringify(info));
};
function resetScriptStatus() {
updateScriptStatus({
statusRetrieved: false,
isRunning: false,
status: "",
errorInfo: ""
});
}
selectionManager.addEventListener(function (selectionUpdated) {
var data = {
type: 'update'
};
if (selectionUpdated) {
resetScriptStatus();
if (selectionManager.selections.length !== 1) {
if (statusMonitor !== null) {
statusMonitor.stop();
statusMonitor = null;
}
currentSelectedEntityID = null;
} else if (currentSelectedEntityID != selectionManager.selections[0]) {
if (statusMonitor !== null) {
statusMonitor.stop();
}
var entityID = selectionManager.selections[0];
currentSelectedEntityID = entityID;
statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus);
}
}
var selections = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
var entity = {};
@ -1581,7 +1649,7 @@ var PropertiesTool = function (opts) {
pushCommandForSelections();
selectionManager._update();
}
} else if (data.action === "reloadScript") {
} else if (data.action === "reloadClientScripts") {
if (selectionManager.hasSelection()) {
var timestamp = Date.now();
for (i = 0; i < selectionManager.selections.length; i++) {
@ -1590,6 +1658,12 @@ var PropertiesTool = function (opts) {
});
}
}
} else if (data.action === "reloadServerScripts") {
if (selectionManager.hasSelection()) {
for (i = 0; i < selectionManager.selections.length; i++) {
Entities.reloadServerScripts(selectionManager.selections[i]);
}
}
}
}
});

View file

@ -318,6 +318,18 @@
<input type="text" id="property-script-url">
<input type="button" id="reload-script-button" class="glyph" value="F">
</div>
<div class="behavior-group property url refresh">
<label for="property-server-scripts">Server Script URL</label>
<input type="text" id="property-server-scripts">
<input type="button" id="reload-server-scripts-button" class="glyph" value="F">
</div>
<div class="behavior-group property">
<label for="server-script-status">Server Script Status</label>
<span id="server-script-status"></span>
</div>
<div class="behavior-group property">
<textarea id="server-script-error"></textarea>
</div>
<div class="section-header model-group model-section zone-section">
<label>Model</label><span>M</span>
</div>

View file

@ -590,7 +590,11 @@ function loaded() {
var elLifetime = document.getElementById("property-lifetime");
var elScriptURL = document.getElementById("property-script-url");
var elScriptTimestamp = document.getElementById("property-script-timestamp");
var elReloadScriptButton = document.getElementById("reload-script-button");
var elReloadScriptsButton = document.getElementById("reload-script-button");
var elServerScripts = document.getElementById("property-server-scripts");
var elReloadServerScriptsButton = document.getElementById("reload-server-scripts-button");
var elServerScriptStatus = document.getElementById("server-script-status");
var elServerScriptError = document.getElementById("server-script-error");
var elUserData = document.getElementById("property-user-data");
var elClearUserData = document.getElementById("userdata-clear");
var elSaveUserData = document.getElementById("userdata-save");
@ -708,7 +712,27 @@ function loaded() {
var properties;
EventBridge.scriptEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type == "update") {
if (data.type == "server_script_status") {
if (!data.statusRetrieved) {
elServerScriptStatus.innerHTML = "Failed to retrieve status";
elServerScriptError.style.display = "none";
} else if (data.isRunning) {
if (data.status == "running") {
elServerScriptStatus.innerHTML = "Running";
elServerScriptError.style.display = "none";
} else if (data.status == "error_loading_script") {
elServerScriptStatus.innerHTML = "Error loading script";
elServerScriptError.style.display = "block";
} else if (data.status == "error_running_script") {
elServerScriptStatus.innerHTML = "Error running script";
elServerScriptError.style.display = "block";
}
elServerScriptError.innerHTML = data.errorInfo;;
} else {
elServerScriptStatus.innerHTML = "Not running";
elServerScriptError.style.display = "none";
}
} else if (data.type == "update") {
if (data.selections.length == 0) {
if (editor !== null && lastEntityID !== null) {
@ -847,6 +871,7 @@ function loaded() {
elLifetime.value = properties.lifetime;
elScriptURL.value = properties.script;
elScriptTimestamp.value = properties.scriptTimestamp;
elServerScripts.value = properties.serverScripts;
var json = null;
try {
@ -1143,6 +1168,7 @@ function loaded() {
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
elScriptTimestamp.addEventListener('change', createEmitNumberPropertyUpdateFunction('scriptTimestamp'));
elServerScripts.addEventListener('change', createEmitTextPropertyUpdateFunction('serverScripts'));
elClearUserData.addEventListener("click", function() {
deleteJSONEditor();
@ -1395,12 +1421,19 @@ function loaded() {
percentage: parseFloat(elRescaleDimensionsPct.value),
}));
});
elReloadScriptButton.addEventListener("click", function() {
elReloadScriptsButton.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "reloadScript"
action: "reloadClientScripts"
}));
});
elReloadServerScriptsButton.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "reloadServerScripts"
}));
});
window.onblur = function() {
// Fake a change event

View file

@ -113,7 +113,7 @@ SelectionManager = (function() {
that.selections.push(entityID);
}
that._update();
that._update(true);
};
that.addEntity = function(entityID, toggleSelection) {
@ -132,7 +132,7 @@ SelectionManager = (function() {
}
}
that._update();
that._update(true);
};
that.removeEntity = function(entityID) {
@ -140,15 +140,15 @@ SelectionManager = (function() {
if (idx >= 0) {
that.selections.splice(idx, 1);
}
that._update();
that._update(true);
};
that.clearSelections = function() {
that.selections = [];
that._update();
that._update(true);
};
that._update = function() {
that._update = function(selectionUpdated) {
if (that.selections.length == 0) {
that.localDimensions = null;
that.localPosition = null;
@ -205,7 +205,7 @@ SelectionManager = (function() {
for (var i = 0; i < listeners.length; i++) {
try {
listeners[i]();
listeners[i](selectionUpdated === true);
} catch (e) {
print("EntitySelectionTool got exception: " + JSON.stringify(e));
}

View file

@ -582,7 +582,7 @@ function checkNewContent() {
if (argv.noUpdater) {
return;
}
// Start downloading content set
var req = request.head({
url: HOME_CONTENT_URL
@ -859,7 +859,7 @@ function onContentLoaded() {
if (dsPath && acPath) {
domainServer = new Process('domain-server', dsPath, ["--get-temp-name"], logPath);
acMonitor = new ACMonitorProcess('ac-monitor', acPath, ['-n6',
acMonitor = new ACMonitorProcess('ac-monitor', acPath, ['-n7',
'--log-directory', logPath,
'--http-status-port', httpStatusPort], httpStatusPort, logPath);
homeServer = new ProcessGroup('home', [domainServer, acMonitor]);