From 5b8047ded42eda4f342b41358ee30d6004825b41 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 18 Nov 2015 14:04:45 -0800 Subject: [PATCH 01/15] Make recording and playback work in interface, playback in agent --- assignment-client/src/Agent.cpp | 28 +++++- interface/src/Application.cpp | 17 ++-- interface/src/avatar/MyAvatar.cpp | 63 +++++++++++- libraries/audio-client/src/AudioClient.cpp | 95 +++---------------- libraries/audio-client/src/AudioClient.h | 6 +- .../audio/src/AbstractAudioInterface.cpp | 61 ++++++++++++ libraries/audio/src/AbstractAudioInterface.h | 5 + libraries/audio/src/AudioConstants.h | 4 +- libraries/recording/src/recording/Frame.cpp | 5 + libraries/recording/src/recording/Frame.h | 3 +- .../src/RecordingScriptingInterface.cpp | 90 ------------------ .../src/RecordingScriptingInterface.h | 28 +++--- 12 files changed, 202 insertions(+), 203 deletions(-) create mode 100644 libraries/audio/src/AbstractAudioInterface.cpp diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 9136833d22..1b019034e9 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -27,12 +27,14 @@ #include #include +#include #include #include // TODO: consider moving to scriptengine.h #include "avatars/ScriptableAvatar.h" #include "RecordingScriptingInterface.h" +#include "AbstractAudioInterface.h" #include "Agent.h" @@ -183,8 +185,22 @@ void Agent::run() { scriptedAvatar.setSkeletonModelURL(QUrl()); // give this AvatarData object to the script engine + auto scriptedAvatarPtr = &scriptedAvatar; setAvatarData(&scriptedAvatar, "Avatar"); + using namespace recording; + static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::AUDIO_FRAME_NAME); + Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) { + const QByteArray& audio = frame->data; + static quint16 audioSequenceNumber{ 0 }; + Transform audioTransform; + audioTransform.setTranslation(scriptedAvatar.getPosition()); + audioTransform.setRotation(scriptedAvatar.getOrientation()); + AbstractAudioInterface::emitAudioPacket(audio.data(), audio.size(), audioSequenceNumber, audioTransform, PacketType::MicrophoneAudioNoEcho); + }); + + + auto avatarHashMap = DependencyManager::set(); _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); @@ -223,6 +239,9 @@ void Agent::run() { QObject::connect(_scriptEngine.get(), &ScriptEngine::update, this, &Agent::processAgentAvatarAndAudio); _scriptEngine->run(); + + Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [](Frame::ConstPointer frame) {}); + setFinished(true); } @@ -244,7 +263,6 @@ void Agent::setIsAvatar(bool isAvatar) { } if (!_isAvatar) { - DependencyManager::get()->setControlledAvatar(nullptr); if (_avatarIdentityTimer) { _avatarIdentityTimer->stop(); @@ -263,7 +281,13 @@ void Agent::setIsAvatar(bool isAvatar) { void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) { _avatarData = avatarData; _scriptEngine->registerGlobalObject(objectName, avatarData); - DependencyManager::get()->setControlledAvatar(avatarData); + + using namespace recording; + static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); + // FIXME how to deal with driving multiple avatars locally? + Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this](Frame::ConstPointer frame) { + AvatarData::fromFrame(frame->data, *_avatarData); + }); } void Agent::sendAvatarIdentityPacket() { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 308ab85672..1a585a02bd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -454,6 +454,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : audioIO->setOrientationGetter([this]{ return getMyAvatar()->getOrientationForAudio(); }); audioIO->moveToThread(audioThread); + recording::Frame::registerFrameHandler(AudioConstants::AUDIO_FRAME_NAME, [=](recording::Frame::ConstPointer frame) { + audioIO->handleRecordedAudioInput(frame->data); + }); + + connect(audioIO.data(), &AudioClient::inputReceived, [](const QByteArray& audio){ + static auto recorder = DependencyManager::get(); + if (recorder->isRecording()) { + static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AudioConstants::AUDIO_FRAME_NAME); + recorder->recordFrame(AUDIO_FRAME_TYPE, audio); + } + }); auto& audioScriptingInterface = AudioScriptingInterface::getInstance(); @@ -743,10 +754,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); - // Assign MyAvatar to th eRecording Singleton - DependencyManager::get()->setControlledAvatar(getMyAvatar()); - - // Now that menu is initalized we can sync myAvatar with it's state. getMyAvatar()->updateMotionBehaviorFromMenu(); @@ -841,8 +848,6 @@ void Application::cleanupBeforeQuit() { #ifdef HAVE_IVIEWHMD DependencyManager::get()->setEnabled(false, true); #endif - DependencyManager::get()->setControlledAvatar(nullptr); - AnimDebugDraw::getInstance().shutdown(); // FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete: diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2eb005fc1c..1c151bcd3f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -39,10 +39,10 @@ #include #include #include -#include "devices/Faceshift.h" - +#include #include "Application.h" +#include "devices/Faceshift.h" #include "AvatarManager.h" #include "Environment.h" #include "Menu.h" @@ -127,6 +127,65 @@ MyAvatar::MyAvatar(RigPointer rig) : _characterController.setEnabled(true); _bodySensorMatrix = deriveBodyFromHMDSensor(); + + using namespace recording; + + auto player = DependencyManager::get(); + auto recorder = DependencyManager::get(); + connect(player.data(), &Deck::playbackStateChanged, [=] { + if (player->isPlaying()) { + auto recordingInterface = DependencyManager::get(); + if (recordingInterface->getPlayFromCurrentLocation()) { + setRecordingBasis(); + } + } else { + clearRecordingBasis(); + } + }); + + connect(recorder.data(), &Recorder::recordingStateChanged, [=] { + if (recorder->isRecording()) { + setRecordingBasis(); + } else { + clearRecordingBasis(); + } + }); + + static const recording::FrameType AVATAR_FRAME_TYPE = recording::Frame::registerFrameType(AvatarData::FRAME_NAME); + Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [=](Frame::ConstPointer frame) { + static AvatarData dummyAvatar; + AvatarData::fromFrame(frame->data, dummyAvatar); + if (getRecordingBasis()) { + dummyAvatar.setRecordingBasis(getRecordingBasis()); + } else { + dummyAvatar.clearRecordingBasis(); + } + + auto recordingInterface = DependencyManager::get(); + if (recordingInterface->getPlayerUseHeadModel() && dummyAvatar.getFaceModelURL().isValid() && + (dummyAvatar.getFaceModelURL() != getFaceModelURL())) { + // FIXME + //myAvatar->setFaceModelURL(_dummyAvatar.getFaceModelURL()); + } + + if (recordingInterface->getPlayerUseSkeletonModel() && dummyAvatar.getSkeletonModelURL().isValid() && + (dummyAvatar.getSkeletonModelURL() != getSkeletonModelURL())) { + // FIXME + //myAvatar->useFullAvatarURL() + } + + if (recordingInterface->getPlayerUseDisplayName() && dummyAvatar.getDisplayName() != getDisplayName()) { + setDisplayName(dummyAvatar.getDisplayName()); + } + + setPosition(dummyAvatar.getPosition()); + setOrientation(dummyAvatar.getOrientation()); + + // FIXME attachments + // FIXME joints + // FIXME head lean + // FIXME head orientation + }); } MyAvatar::~MyAvatar() { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index d4e571ade5..2cf42f9b85 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -62,6 +62,7 @@ extern "C" { #include #include #include +#include #include "AudioInjector.h" #include "AudioConstants.h" @@ -839,93 +840,27 @@ void AudioClient::handleAudioInput() { _inputRingBuffer.shiftReadPosition(inputSamplesRequired); } - emitAudioPacket(networkAudioSamples); - } -} + auto packetType = _shouldEchoToServer ? + PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho; -void AudioClient::emitAudioPacket(const int16_t* audioData, PacketType packetType) { - static std::mutex _mutex; - using Locker = std::unique_lock; - - // FIXME recorded audio isn't guaranteed to have the same stereo state - // as the current system - const int numNetworkBytes = _isStereoInput - ? AudioConstants::NETWORK_FRAME_BYTES_STEREO - : AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; - const int numNetworkSamples = _isStereoInput - ? AudioConstants::NETWORK_FRAME_SAMPLES_STEREO - : AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; - - auto nodeList = DependencyManager::get(); - SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - - if (audioMixer && audioMixer->getActiveSocket()) { - Locker lock(_mutex); - if (!_audioPacket) { - // we don't have an audioPacket yet - set that up now - _audioPacket = NLPacket::create(PacketType::MicrophoneAudioWithEcho); + if (_lastInputLoudness == 0) { + packetType = PacketType::SilentAudioFrame; } - - glm::vec3 headPosition = _positionGetter(); - glm::quat headOrientation = _orientationGetter(); - quint8 isStereo = _isStereoInput ? 1 : 0; - - if (packetType == PacketType::Unknown) { - if (_lastInputLoudness == 0) { - _audioPacket->setType(PacketType::SilentAudioFrame); - } else { - if (_shouldEchoToServer) { - _audioPacket->setType(PacketType::MicrophoneAudioWithEcho); - } else { - _audioPacket->setType(PacketType::MicrophoneAudioNoEcho); - } - } - } else { - _audioPacket->setType(packetType); - } - - // reset the audio packet so we can start writing - _audioPacket->reset(); - - // write sequence number - _audioPacket->writePrimitive(_outgoingAvatarAudioSequenceNumber); - - if (_audioPacket->getType() == PacketType::SilentAudioFrame) { - // pack num silent samples - quint16 numSilentSamples = numNetworkSamples; - _audioPacket->writePrimitive(numSilentSamples); - } else { - // set the mono/stereo byte - _audioPacket->writePrimitive(isStereo); - } - - // pack the three float positions - _audioPacket->writePrimitive(headPosition); - - // pack the orientation - _audioPacket->writePrimitive(headOrientation); - - if (_audioPacket->getType() != PacketType::SilentAudioFrame) { - // audio samples have already been packed (written to networkAudioSamples) - _audioPacket->setPayloadSize(_audioPacket->getPayloadSize() + numNetworkBytes); - } - - static const int leadingBytes = sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8); - int16_t* const networkAudioSamples = (int16_t*)(_audioPacket->getPayload() + leadingBytes); - memcpy(networkAudioSamples, audioData, numNetworkBytes); - + Transform audioTransform; + audioTransform.setTranslation(_positionGetter()); + audioTransform.setRotation(_orientationGetter()); + // FIXME find a way to properly handle both playback audio and user audio concurrently + emitAudioPacket(networkAudioSamples, numNetworkBytes, _outgoingAvatarAudioSequenceNumber, audioTransform, packetType); _stats.sentPacket(); - - nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); - - nodeList->sendUnreliablePacket(*_audioPacket, *audioMixer); - - _outgoingAvatarAudioSequenceNumber++; } } void AudioClient::handleRecordedAudioInput(const QByteArray& audio) { - emitAudioPacket((int16_t*)audio.data(), PacketType::MicrophoneAudioWithEcho); + Transform audioTransform; + audioTransform.setTranslation(_positionGetter()); + audioTransform.setRotation(_orientationGetter()); + // FIXME check a flag to see if we should echo audio? + emitAudioPacket(audio.data(), audio.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho); } void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 9d46ad9d26..fe04de4bdc 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -74,9 +74,10 @@ class QAudioInput; class QAudioOutput; class QIODevice; + typedef struct ty_gverb ty_gverb; - +class Transform; class NLPacket; class AudioClient : public AbstractAudioInterface, public Dependency { @@ -212,7 +213,6 @@ protected: } private: - void emitAudioPacket(const int16_t* audioData, PacketType packetType = PacketType::Unknown); void outputFormatChanged(); QByteArray firstInputFrame; @@ -319,8 +319,6 @@ private: void checkDevices(); bool _hasReceivedFirstPacket = false; - - std::unique_ptr _audioPacket; }; diff --git a/libraries/audio/src/AbstractAudioInterface.cpp b/libraries/audio/src/AbstractAudioInterface.cpp new file mode 100644 index 0000000000..d6c87aa20b --- /dev/null +++ b/libraries/audio/src/AbstractAudioInterface.cpp @@ -0,0 +1,61 @@ +// +// Created by Bradley Austin Davis on 2015/11/18 +// 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 "AbstractAudioInterface.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "AudioConstants.h" + +void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, PacketType packetType) { + static std::mutex _mutex; + using Locker = std::unique_lock; + auto nodeList = DependencyManager::get(); + SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); + if (audioMixer && audioMixer->getActiveSocket()) { + Locker lock(_mutex); + static std::unique_ptr audioPacket = NLPacket::create(PacketType::Unknown); + quint8 isStereo = bytes == AudioConstants::NETWORK_FRAME_BYTES_STEREO ? 1 : 0; + audioPacket->setType(packetType); + // reset the audio packet so we can start writing + audioPacket->reset(); + // write sequence number + audioPacket->writePrimitive(sequenceNumber++); + if (audioPacket->getType() == PacketType::SilentAudioFrame) { + // pack num silent samples + quint16 numSilentSamples = isStereo ? + AudioConstants::NETWORK_FRAME_SAMPLES_STEREO : + AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; + audioPacket->writePrimitive(numSilentSamples); + } else { + // set the mono/stereo byte + audioPacket->writePrimitive(isStereo); + } + + // pack the three float positions + audioPacket->writePrimitive(transform.getTranslation()); + // pack the orientation + audioPacket->writePrimitive(transform.getRotation()); + + if (audioPacket->getType() != PacketType::SilentAudioFrame) { + // audio samples have already been packed (written to networkAudioSamples) + audioPacket->setPayloadSize(audioPacket->getPayloadSize() + bytes); + static const int leadingBytes = sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8); + memcpy(audioPacket->getPayload() + leadingBytes, audioData, bytes); + } + nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); + nodeList->sendUnreliablePacket(*audioPacket, *audioMixer); + } +} diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 4961e9b58c..1a58c5640c 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -15,16 +15,21 @@ #include #include +#include + #include "AudioInjectorOptions.h" class AudioInjector; class AudioInjectorLocalBuffer; +class Transform; class AbstractAudioInterface : public QObject { Q_OBJECT public: AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {}; + static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform, PacketType packetType); + public slots: virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) = 0; diff --git a/libraries/audio/src/AudioConstants.h b/libraries/audio/src/AudioConstants.h index 6c4e582117..e252a5354d 100644 --- a/libraries/audio/src/AudioConstants.h +++ b/libraries/audio/src/AudioConstants.h @@ -20,7 +20,9 @@ namespace AudioConstants { const int SAMPLE_RATE = 24000; typedef int16_t AudioSample; - + + static const char* AUDIO_FRAME_NAME = "com.highfidelity.recording.Audio"; + const int NETWORK_FRAME_BYTES_STEREO = 1024; const int NETWORK_FRAME_SAMPLES_STEREO = NETWORK_FRAME_BYTES_STEREO / sizeof(AudioSample); const int NETWORK_FRAME_BYTES_PER_CHANNEL = 512; diff --git a/libraries/recording/src/recording/Frame.cpp b/libraries/recording/src/recording/Frame.cpp index bff85ea872..2e8a9379ef 100644 --- a/libraries/recording/src/recording/Frame.cpp +++ b/libraries/recording/src/recording/Frame.cpp @@ -131,6 +131,11 @@ Frame::Handler Frame::registerFrameHandler(FrameType type, Handler handler) { return result; } +Frame::Handler Frame::registerFrameHandler(const QString& frameTypeName, Handler handler) { + auto frameType = registerFrameType(frameTypeName); + return registerFrameHandler(frameType, handler); +} + void Frame::handleFrame(const Frame::ConstPointer& frame) { Handler handler; { diff --git a/libraries/recording/src/recording/Frame.h b/libraries/recording/src/recording/Frame.h index 3cc999f505..b5f11737bb 100644 --- a/libraries/recording/src/recording/Frame.h +++ b/libraries/recording/src/recording/Frame.h @@ -55,9 +55,10 @@ public: : FrameHeader(type, timeOffset), data(data) { } static FrameType registerFrameType(const QString& frameTypeName); + static Handler registerFrameHandler(FrameType type, Handler handler); + static Handler registerFrameHandler(const QString& frameTypeName, Handler handler); static QMap getFrameTypes(); static QMap getFrameTypeNames(); - static Handler registerFrameHandler(FrameType type, Handler handler); static void handleFrame(const ConstPointer& frame); }; diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 7d933e983c..33e67e1b43 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -15,43 +15,16 @@ #include #include #include -// FiXME -//#include -#include #include #include "ScriptEngineLogging.h" -typedef int16_t AudioSample; - - using namespace recording; -// FIXME move to somewhere audio related? -static const QString AUDIO_FRAME_NAME = "com.highfidelity.recording.Audio"; RecordingScriptingInterface::RecordingScriptingInterface() { - static const recording::FrameType AVATAR_FRAME_TYPE = recording::Frame::registerFrameType(AvatarData::FRAME_NAME); - // FIXME how to deal with driving multiple avatars locally? - Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this](Frame::ConstPointer frame) { - processAvatarFrame(frame); - }); - - static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AUDIO_FRAME_NAME); - Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this](Frame::ConstPointer frame) { - processAudioFrame(frame); - }); - _player = DependencyManager::get(); _recorder = DependencyManager::get(); - - // FIXME : Disabling Sound -// auto audioClient = DependencyManager::get(); - // connect(audioClient.data(), &AudioClient::inputReceived, this, &RecordingScriptingInterface::processAudioInput); -} - -void RecordingScriptingInterface::setControlledAvatar(AvatarData* avatar) { - _controlledAvatar = avatar; } bool RecordingScriptingInterface::isPlaying() const { @@ -92,12 +65,6 @@ void RecordingScriptingInterface::startPlaying() { return; } - // Playback from the current position - if (_playFromCurrentLocation && _controlledAvatar) { - _dummyAvatar.setRecordingBasis(std::make_shared(_controlledAvatar->getTransform())); - } else { - _dummyAvatar.clearRecordingBasis(); - } _player->play(); } @@ -176,12 +143,6 @@ void RecordingScriptingInterface::startRecording() { return; } - _recordingEpoch = Frame::epochForFrameTime(0); - - if (_controlledAvatar) { - _controlledAvatar->setRecordingBasis(); - } - _recorder->start(); } @@ -189,10 +150,6 @@ void RecordingScriptingInterface::stopRecording() { _recorder->stop(); _lastClip = _recorder->getClip(); _lastClip->seek(0); - - if (_controlledAvatar) { - _controlledAvatar->clearRecordingBasis(); - } } void RecordingScriptingInterface::saveRecording(const QString& filename) { @@ -225,50 +182,3 @@ void RecordingScriptingInterface::loadLastRecording() { _player->play(); } -void RecordingScriptingInterface::processAvatarFrame(const Frame::ConstPointer& frame) { - Q_ASSERT(QThread::currentThread() == thread()); - - if (!_controlledAvatar) { - return; - } - - AvatarData::fromFrame(frame->data, _dummyAvatar); - - - - if (_useHeadModel && _dummyAvatar.getFaceModelURL().isValid() && - (_dummyAvatar.getFaceModelURL() != _controlledAvatar->getFaceModelURL())) { - // FIXME - //myAvatar->setFaceModelURL(_dummyAvatar.getFaceModelURL()); - } - - if (_useSkeletonModel && _dummyAvatar.getSkeletonModelURL().isValid() && - (_dummyAvatar.getSkeletonModelURL() != _controlledAvatar->getSkeletonModelURL())) { - // FIXME - //myAvatar->useFullAvatarURL() - } - - if (_useDisplayName && _dummyAvatar.getDisplayName() != _controlledAvatar->getDisplayName()) { - _controlledAvatar->setDisplayName(_dummyAvatar.getDisplayName()); - } - - _controlledAvatar->setPosition(_dummyAvatar.getPosition()); - _controlledAvatar->setOrientation(_dummyAvatar.getOrientation()); - - // FIXME attachments - // FIXME joints - // FIXME head lean - // FIXME head orientation -} - -void RecordingScriptingInterface::processAudioInput(const QByteArray& audio) { - if (_recorder->isRecording()) { - static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AUDIO_FRAME_NAME); - _recorder->recordFrame(AUDIO_FRAME_TYPE, audio); - } -} - -void RecordingScriptingInterface::processAudioFrame(const recording::FrameConstPointer& frame) { - // auto audioClient = DependencyManager::get(); - // audioClient->handleRecordedAudioInput(frame->data); -} diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index abb090e09a..483ead3ca3 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -10,13 +10,13 @@ #define hifi_RecordingScriptingInterface_h #include +#include #include #include #include #include -#include class RecordingScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -24,8 +24,6 @@ class RecordingScriptingInterface : public QObject, public Dependency { public: RecordingScriptingInterface(); - void setControlledAvatar(AvatarData* avatar); - public slots: void loadRecording(const QString& filename); @@ -41,12 +39,19 @@ public slots: void setPlayerVolume(float volume); void setPlayerAudioOffset(float audioOffset); void setPlayerTime(float time); - void setPlayFromCurrentLocation(bool playFromCurrentLocation); void setPlayerLoop(bool loop); + void setPlayerUseDisplayName(bool useDisplayName); void setPlayerUseAttachments(bool useAttachments); void setPlayerUseHeadModel(bool useHeadModel); void setPlayerUseSkeletonModel(bool useSkeletonModel); + void setPlayFromCurrentLocation(bool playFromCurrentLocation); + + bool getPlayerUseDisplayName() { return _useDisplayName; } + bool getPlayerUseAttachments() { return _useAttachments; } + bool getPlayerUseHeadModel() { return _useHeadModel; } + bool getPlayerUseSkeletonModel() { return _useSkeletonModel; } + bool getPlayFromCurrentLocation() { return _playFromCurrentLocation; } void startRecording(); void stopRecording(); @@ -57,22 +62,13 @@ public slots: void saveRecording(const QString& filename); void loadLastRecording(); -signals: - void playbackStateChanged(); - // Should this occur for any frame or just for seek calls? - void playbackPositionChanged(); - void looped(); - -private: +protected: using Mutex = std::recursive_mutex; using Locker = std::unique_lock; using Flag = std::atomic; - void processAvatarFrame(const recording::FrameConstPointer& frame); - void processAudioFrame(const recording::FrameConstPointer& frame); - void processAudioInput(const QByteArray& audioData); + QSharedPointer _player; QSharedPointer _recorder; - quint64 _recordingEpoch { 0 }; Flag _playFromCurrentLocation { true }; Flag _useDisplayName { false }; @@ -80,8 +76,6 @@ private: Flag _useAttachments { false }; Flag _useSkeletonModel { false }; recording::ClipPointer _lastClip; - AvatarData _dummyAvatar; - AvatarData* _controlledAvatar; }; #endif // hifi_RecordingScriptingInterface_h From 65142a3963a166403b0ce5cedec088cb6a2118a5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 14:20:56 -0800 Subject: [PATCH 02/15] clean up agent comment for domain settings request --- libraries/networking/src/DomainHandler.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index f7d26f25c5..cd3c3e7b58 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -250,8 +250,7 @@ void DomainHandler::requestDomainSettings() { NodeType_t owningNodeType = DependencyManager::get()->getOwnerType(); if (owningNodeType == NodeType::Agent) { - // for now the agent nodes don't need any settings - this allows local assignment-clients - // to connect to a domain that is using automatic networking (since we don't have TCP hole punch yet) + // for now the agent nodes don't need any domain settings _settingsObject = QJsonObject(); emit settingsReceived(_settingsObject); } else { From fd3599acc5f177d0520825626b0392727be3e167 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 18 Nov 2015 14:32:56 -0800 Subject: [PATCH 03/15] PR comments --- libraries/recording/src/recording/Frame.cpp | 14 ++++++++++++++ libraries/recording/src/recording/Frame.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/libraries/recording/src/recording/Frame.cpp b/libraries/recording/src/recording/Frame.cpp index 2e8a9379ef..7f6f6247d9 100644 --- a/libraries/recording/src/recording/Frame.cpp +++ b/libraries/recording/src/recording/Frame.cpp @@ -136,6 +136,20 @@ Frame::Handler Frame::registerFrameHandler(const QString& frameTypeName, Handler return registerFrameHandler(frameType, handler); } +void Frame::clearFrameHandler(FrameType type) { + Locker lock(mutex); + auto iterator = handlerMap.find(type); + if (iterator != handlerMap.end()) { + handlerMap.erase(iterator); + } +} + +void Frame::clearFrameHandler(const QString& frameTypeName) { + auto frameType = registerFrameType(frameTypeName); + clearFrameHandler(frameType); +} + + void Frame::handleFrame(const Frame::ConstPointer& frame) { Handler handler; { diff --git a/libraries/recording/src/recording/Frame.h b/libraries/recording/src/recording/Frame.h index b5f11737bb..6c407063e3 100644 --- a/libraries/recording/src/recording/Frame.h +++ b/libraries/recording/src/recording/Frame.h @@ -57,6 +57,8 @@ public: static FrameType registerFrameType(const QString& frameTypeName); static Handler registerFrameHandler(FrameType type, Handler handler); static Handler registerFrameHandler(const QString& frameTypeName, Handler handler); + static void clearFrameHandler(FrameType type); + static void clearFrameHandler(const QString& frameTypeName); static QMap getFrameTypes(); static QMap getFrameTypeNames(); static void handleFrame(const ConstPointer& frame); From 1a066abb2625f14b4561a1e319733cb8f1850f19 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 14:43:56 -0800 Subject: [PATCH 04/15] fix some indentation for UDT_CONNECTION_DEBUG lines --- libraries/networking/src/udt/SendQueue.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index fd11b0f41e..ba99efdcea 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -395,7 +395,7 @@ bool SendQueue::isInactive(bool sentAPacket) { #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "SendQueue to" << _destination << "reached" << NUM_TIMEOUTS_BEFORE_INACTIVE << "timeouts" - << "and 5s before receiving any ACK/NAK and is now inactive. Stopping."; + << "and 5s before receiving any ACK/NAK and is now inactive. Stopping."; #endif deactivate(); @@ -427,9 +427,9 @@ bool SendQueue::isInactive(bool sentAPacket) { if (cvStatus == std::cv_status::timeout) { #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "SendQueue to" << _destination << "has been empty for" - << EMPTY_QUEUES_INACTIVE_TIMEOUT.count() - << "seconds and receiver has ACKed all packets." - << "The queue is now inactive and will be stopped."; + << EMPTY_QUEUES_INACTIVE_TIMEOUT.count() + << "seconds and receiver has ACKed all packets." + << "The queue is now inactive and will be stopped."; #endif // Deactivate queue From f2ecce6043c4b4ba026176b179064dfa40553eca Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 15:13:21 -0800 Subject: [PATCH 05/15] use safer domain settings request in audio-mixer --- assignment-client/src/audio/AudioMixer.cpp | 117 +++++++++--------- assignment-client/src/audio/AudioMixer.h | 5 +- assignment-client/src/avatars/AvatarMixer.cpp | 1 - libraries/networking/src/DomainHandler.cpp | 42 ++++--- libraries/networking/src/DomainHandler.h | 2 +- .../networking/src/ThreadedAssignment.cpp | 8 ++ libraries/networking/src/ThreadedAssignment.h | 5 +- 7 files changed, 101 insertions(+), 79 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 1d8908845f..d4de3dbd8d 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -644,188 +644,189 @@ void AudioMixer::sendStatsPacket() { } void AudioMixer::run() { - + + qDebug() << "Waiting for connection to domain to request settings from domain-server."; + ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); auto nodeList = DependencyManager::get(); - nodeList->addNodeTypeToInterestSet(NodeType::Agent); + // wait until we have the domain-server settings, otherwise we bail + DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); + connect(&domainHandler, &DomainHandler::settingsReceived, this, &AudioMixer::domainSettingsRequestComplete); + connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AudioMixer::domainSettingsRequestFailed); +} +void AudioMixer::domainSettingsRequestComplete() { + auto nodeList = DependencyManager::get(); + + nodeList->addNodeTypeToInterestSet(NodeType::Agent); + nodeList->linkedDataCreateCallback = [](Node* node) { node->setLinkedData(new AudioMixerClientData()); }; - - // wait until we have the domain-server settings, otherwise we bail - DomainHandler& domainHandler = nodeList->getDomainHandler(); - - qDebug() << "Waiting for domain settings from domain-server."; - - // block until we get the settingsRequestComplete signal - QEventLoop loop; - connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); - connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); - domainHandler.requestDomainSettings(); - loop.exec(); - if (domainHandler.getSettingsObject().isEmpty()) { - qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; - setFinished(true); - return; - } - + DomainHandler& domainHandler = nodeList->getDomainHandler(); const QJsonObject& settingsObject = domainHandler.getSettingsObject(); - + // check the settings object to see if we have anything we can parse out parseSettingsObject(settingsObject); + + // queue up a connection to start broadcasting mixes now that we're ready to go + QMetaObject::invokeMethod(this, "broadcastMixes", Qt::QueuedConnection); +} +void AudioMixer::broadcastMixes() { + auto nodeList = DependencyManager::get(); + int nextFrame = 0; QElapsedTimer timer; timer.start(); - + int usecToSleep = AudioConstants::NETWORK_FRAME_USECS; - + const int TRAILING_AVERAGE_FRAMES = 100; int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; - + while (!_isFinished) { const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; - + const float RATIO_BACK_OFF = 0.02f; - + const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; - + if (usecToSleep < 0) { usecToSleep = 0; } - + _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) + (usecToSleep * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS); - + float lastCutoffRatio = _performanceThrottlingRatio; bool hasRatioChanged = false; - + if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { // we're struggling - change our min required loudness to reduce some load _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); - + qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; hasRatioChanged = true; } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { // we've recovered and can back off the required loudness _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; - + if (_performanceThrottlingRatio < 0) { _performanceThrottlingRatio = 0; } - + qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; hasRatioChanged = true; } - + if (hasRatioChanged) { // set out min audability threshold from the new ratio _minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio)); qDebug() << "Minimum audability required to be mixed is now" << _minAudibilityThreshold; - + framesSinceCutoffEvent = 0; } } - + if (!hasRatioChanged) { ++framesSinceCutoffEvent; } - + quint64 now = usecTimestampNow(); if (now - _lastPerSecondCallbackTime > USECS_PER_SECOND) { perSecondActions(); _lastPerSecondCallbackTime = now; } - + nodeList->eachNode([&](const SharedNodePointer& node) { - + if (node->getLinkedData()) { AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData(); - + // this function will attempt to pop a frame from each audio stream. // a pointer to the popped data is stored as a member in InboundAudioStream. // That's how the popped audio data will be read for mixing (but only if the pop was successful) nodeData->checkBuffersBeforeFrameSend(); - + // if the stream should be muted, send mute packet if (nodeData->getAvatarAudioStream() && shouldMute(nodeData->getAvatarAudioStream()->getQuietestFrameLoudness())) { auto mutePacket = NLPacket::create(PacketType::NoisyMute, 0); nodeList->sendPacket(std::move(mutePacket), *node); } - + if (node->getType() == NodeType::Agent && node->getActiveSocket() && nodeData->getAvatarAudioStream()) { - + int streamsMixed = prepareMixForListeningNode(node.data()); - + std::unique_ptr mixPacket; - + if (streamsMixed > 0) { int mixPacketBytes = sizeof(quint16) + AudioConstants::NETWORK_FRAME_BYTES_STEREO; mixPacket = NLPacket::create(PacketType::MixedAudio, mixPacketBytes); - + // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); mixPacket->writePrimitive(sequence); - + // pack mixed audio samples mixPacket->write(reinterpret_cast(_mixSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO); } else { int silentPacketBytes = sizeof(quint16) + sizeof(quint16); mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes); - + // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); mixPacket->writePrimitive(sequence); - + // pack number of silent audio samples quint16 numSilentSamples = AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; mixPacket->writePrimitive(numSilentSamples); } - + // Send audio environment sendAudioEnvironmentPacket(node); - + // send mixed audio packet nodeList->sendPacket(std::move(mixPacket), *node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); - + // send an audio stream stats packet if it's time if (_sendAudioStreamStats) { nodeData->sendAudioStreamStatsPackets(node); _sendAudioStreamStats = false; } - + ++_sumListeners; } } }); - + ++_numStatFrames; - + // since we're a while loop we need to help Qt's event processing QCoreApplication::processEvents(); - + if (_isFinished) { // at this point the audio-mixer is done // check if we have a deferred delete event to process (which we should once finished) QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete); break; } - + usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - timer.nsecsElapsed() / 1000; // ns to us - + if (usecToSleep > 0) { usleep(usecToSleep); } diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 9e7a010f61..a09e67f4ba 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -40,10 +40,13 @@ public slots: static const InboundAudioStream::Settings& getStreamSettings() { return _streamSettings; } private slots: + void broadcastMixes(); void handleNodeAudioPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleMuteEnvironmentPacket(QSharedPointer packet, SharedNodePointer sendingNode); -private: +private: + void domainSettingsRequestComplete(); + /// adds one stream to the mix for a listening node int addStreamToMixForListeningNodeWithStream(AudioMixerClientData* listenerNodeData, const QUuid& streamUUID, diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 1e17467c3b..a54ee84b88 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -537,7 +537,6 @@ void AvatarMixer::run() { qDebug() << "Waiting for domain settings from domain-server."; // block until we get the settingsRequestComplete signal - QEventLoop loop; connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index cd3c3e7b58..2f200ff1e3 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -38,12 +38,17 @@ DomainHandler::DomainHandler(QObject* parent) : _icePeer(this), _isConnected(false), _settingsObject(), - _failedSettingsRequests(0) + _settingsTimer(this) { _sockAddr.setObjectName("DomainServer"); // if we get a socket that make sure our NetworkPeer ping timer stops connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer); + + // setup a timeout for failure on settings requests + static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000; + _settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS); + connect(&_settingsTimer, &QTimer::timeout, this, &DomainHandler::settingsReceiveFail); } void DomainHandler::disconnect() { @@ -80,13 +85,16 @@ void DomainHandler::sendDisconnectPacket() { void DomainHandler::clearSettings() { _settingsObject = QJsonObject(); - _failedSettingsRequests = 0; } void DomainHandler::softReset() { qCDebug(networking) << "Resetting current domain connection information."; disconnect(); + clearSettings(); + + // cancel the failure timeout for any pending requests for settings + QMetaObject::invokeMethod(&_settingsTimer, "stop", Qt::AutoConnection); } void DomainHandler::hardReset() { @@ -254,29 +262,29 @@ void DomainHandler::requestDomainSettings() { _settingsObject = QJsonObject(); emit settingsReceived(_settingsObject); } else { - if (_settingsObject.isEmpty()) { - qCDebug(networking) << "Requesting settings from domain server"; - - Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); - - auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); - packet->writePrimitive(assignmentType); - - auto nodeList = DependencyManager::get(); - nodeList->sendPacket(std::move(packet), _sockAddr); - } + qCDebug(networking) << "Requesting settings from domain server"; + + Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); + + auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); + packet->writePrimitive(assignmentType); + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(packet), _sockAddr); + + _settingsTimer.start(); } } void DomainHandler::processSettingsPacketList(QSharedPointer packetList) { + // stop our settings timer since we successfully requested the settings we need + _settingsTimer.stop(); + auto data = packetList->getMessage(); _settingsObject = QJsonDocument::fromJson(data).object(); - qCDebug(networking) << "Received domain settings: \n" << QString(data); - - // reset failed settings requests to 0, we got them - _failedSettingsRequests = 0; + qCDebug(networking) << "Received domain settings: \n" << qPrintable(data); emit settingsReceived(_settingsObject); } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 49bab6dc28..25d1c910cd 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -127,8 +127,8 @@ private: NetworkPeer _icePeer; bool _isConnected; QJsonObject _settingsObject; - int _failedSettingsRequests; QString _pendingPath; + QTimer _settingsTimer; }; #endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 6855c2eec3..5433ed0ece 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -77,6 +77,9 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); _domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); + // send a domain-server check in immediately + checkInWithDomainServerOrExit(); + // move the domain server time to the NL so check-ins fire from there _domainServerTimer->moveToThread(nodeList->thread()); @@ -130,3 +133,8 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { DependencyManager::get()->sendDomainServerCheckIn(); } } + +void ThreadedAssignment::domainSettingsRequestFailed() { + qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; + setFinished(true); +} diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 9ff3b34add..33dca969df 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -40,7 +40,10 @@ protected: bool _isFinished; QTimer* _domainServerTimer = nullptr; QTimer* _statsTimer = nullptr; - + +protected slots: + void domainSettingsRequestFailed(); + private slots: void startSendingStats(); void stopSendingStats(); From 1f854784e133bf19689203246f459ec9a6e86bf4 Mon Sep 17 00:00:00 2001 From: AlessandroSigna Date: Wed, 18 Nov 2015 11:24:17 -0800 Subject: [PATCH 06/15] not load the last recording --- examples/entityScripts/createRecorder.js | 4 +- .../entityScripts/recordingEntityScript.js | 56 +++++++------------ examples/entityScripts/recordingMaster.js | 49 ++++------------ 3 files changed, 36 insertions(+), 73 deletions(-) diff --git a/examples/entityScripts/createRecorder.js b/examples/entityScripts/createRecorder.js index 7f89898ceb..946d4e1071 100644 --- a/examples/entityScripts/createRecorder.js +++ b/examples/entityScripts/createRecorder.js @@ -1,3 +1,4 @@ +var PARAMS_SCRIPT_URL = Script.resolvePath('recordingEntityScript.js'); var rotation = Quat.safeEulerAngles(Camera.getOrientation()); rotation = Quat.fromPitchYawRollDegrees(0, rotation.y, 0); var center = Vec3.sum(MyAvatar.position, Vec3.multiply(6, Quat.getFront(rotation))); @@ -17,5 +18,6 @@ var recordAreaEntity = Entities.addEntity({ blue: 255 }, visible: true, - script: "https://hifi-public.s3.amazonaws.com/sam/record/recordingEntityScript.js", + script: PARAMS_SCRIPT_URL, + ignoreForCollision: true, }); \ No newline at end of file diff --git a/examples/entityScripts/recordingEntityScript.js b/examples/entityScripts/recordingEntityScript.js index 1b74466c4c..e423de9afd 100644 --- a/examples/entityScripts/recordingEntityScript.js +++ b/examples/entityScripts/recordingEntityScript.js @@ -12,31 +12,25 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - (function () { - HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; - Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/utils.js"); - - var insideRecorderArea = false; - var enteredInTime = false; - var isAvatarRecording = false; var _this; + var isAvatarRecording = false; + var channel = "groupRecordingChannel"; + var startMessage = "RECONDING STARTED"; + var stopMessage = "RECONDING ENDED"; function recordingEntity() { _this = this; return; } - function update() { - var isRecordingStarted = getEntityCustomData("recordingKey", _this.entityID, { isRecordingStarted: false }).isRecordingStarted; - if (isRecordingStarted && !isAvatarRecording) { + function receivingMessage(channel, message, senderID) { + print("message received on channel:" + channel + ", message:" + message + ", senderID:" + senderID); + if(message === startMessage) { _this.startRecording(); - } else if ((!isRecordingStarted && isAvatarRecording) || (isAvatarRecording && !insideRecorderArea)) { + } else if(message === stopMessage) { _this.stopRecording(); - } else if (!isRecordingStarted && insideRecorderArea && !enteredInTime) { - //if an avatar enters the zone while a recording is started he will be able to participate to the next group recording - enteredInTime = true; } }; @@ -45,37 +39,29 @@ preload: function (entityID) { print("RECORDING ENTITY PRELOAD"); this.entityID = entityID; - + var entityProperties = Entities.getEntityProperties(_this.entityID); if (!entityProperties.ignoreForCollisions) { Entities.editEntity(_this.entityID, { ignoreForCollisions: true }); } - //print(JSON.stringify(entityProperties)); - var recordingKey = getEntityCustomData("recordingKey", _this.entityID, undefined); - if (recordingKey === undefined) { - setEntityCustomData("recordingKey", _this.entityID, { isRecordingStarted: false }); - } - - Script.update.connect(update); + Messages.messageReceived.connect(receivingMessage); }, + enterEntity: function (entityID) { print("entering in the recording area"); - insideRecorderArea = true; - var isRecordingStarted = getEntityCustomData("recordingKey", _this.entityID, { isRecordingStarted: false }).isRecordingStarted; - if (!isRecordingStarted) { - //i'm in the recording area in time (before the event starts) - enteredInTime = true; - } + Messages.subscribe(channel); + }, + leaveEntity: function (entityID) { print("leaving the recording area"); - insideRecorderArea = false; - enteredInTime = false; + _this.stopRecording(); + Messages.unsubscribe(channel); }, startRecording: function (entityID) { - if (enteredInTime && !isAvatarRecording) { + if (!isAvatarRecording) { print("RECORDING STARTED"); Recording.startRecording(); isAvatarRecording = true; @@ -86,7 +72,6 @@ if (isAvatarRecording) { print("RECORDING ENDED"); Recording.stopRecording(); - Recording.loadLastRecording(); isAvatarRecording = false; recordingFile = Window.save("Save recording to file", "./groupRecording", "Recordings (*.hfr)"); if (!(recordingFile === "null" || recordingFile === null || recordingFile === "")) { @@ -94,13 +79,14 @@ } } }, + unload: function (entityID) { print("RECORDING ENTITY UNLOAD"); - Script.update.disconnect(update); + _this.stopRecording(); + Messages.unsubscribe(channel); + Messages.messageReceived.disconnect(receivingMessage); } } - - return new recordingEntity(); }); \ No newline at end of file diff --git a/examples/entityScripts/recordingMaster.js b/examples/entityScripts/recordingMaster.js index 71a92a05f3..680d364eb1 100644 --- a/examples/entityScripts/recordingMaster.js +++ b/examples/entityScripts/recordingMaster.js @@ -5,18 +5,15 @@ // Created by Alessandro Signa on 11/12/15. // Copyright 2015 High Fidelity, Inc. // -// Run this script to find the recorder (created by crateRecorder.js) and drive the start/end of the recording for anyone who is inside the box +// Run this script to spawn a box (recorder) and drive the start/end of the recording for anyone who is inside the box // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/toolBars.js"); Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/utils.js"); - - var rotation = Quat.safeEulerAngles(Camera.getOrientation()); rotation = Quat.fromPitchYawRollDegrees(0, rotation.y, 0); var center = Vec3.sum(MyAvatar.position, Vec3.multiply(1, Quat.getFront(rotation))); @@ -28,27 +25,9 @@ var COLOR_TOOL_BAR = { red: 0, green: 0, blue: 0 }; var toolBar = null; var recordIcon; - -var isRecordingEntityFound = false; - var isRecording = false; - -var recordAreaEntity = null; -findRecorder(); - -function findRecorder() { - foundEntities = Entities.findEntities(MyAvatar.position, 50); - for (var i = 0; i < foundEntities.length; i++) { - var name = Entities.getEntityProperties(foundEntities[i], "name").name; - if (name === "recorderEntity") { - recordAreaEntity = foundEntities[i]; - isRecordingEntityFound = true; - print("Found recorder Entity!"); - return; - } - } -} - +var channel = "groupRecordingChannel"; +Messages.subscribe(channel); setupToolBar(); function setupToolBar() { @@ -58,9 +37,7 @@ function setupToolBar() { } Tool.IMAGE_HEIGHT /= 2; Tool.IMAGE_WIDTH /= 2; - toolBar = new ToolBar(0, 100, ToolBar.HORIZONTAL); //put the button in the up-left corner - toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF); recordIcon = toolBar.addTool({ @@ -70,7 +47,7 @@ function setupToolBar() { width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT, alpha: Recording.isPlaying() ? ALPHA_OFF : ALPHA_ON, - visible: isRecordingEntityFound, + visible: true, }, true, isRecording); } @@ -79,24 +56,22 @@ function mousePressEvent(event) { if (recordIcon === toolBar.clicked(clickedOverlay, false)) { if (!isRecording) { print("I'm the master. I want to start recording"); + var message = "RECONDING STARTED"; + Messages.sendMessage(channel, message); isRecording = true; - setEntityCustomData("recordingKey", recordAreaEntity, {isRecordingStarted: true}); - } else { print("I want to stop recording"); + var message = "RECONDING ENDED"; + Messages.sendMessage(channel, message); isRecording = false; - setEntityCustomData("recordingKey", recordAreaEntity, {isRecordingStarted: false}); - } - } + } } - function cleanup() { toolBar.cleanup(); + Messages.unsubscribe(channel); } - - - Script.scriptEnding.connect(cleanup); - Controller.mousePressEvent.connect(mousePressEvent); \ No newline at end of file +Script.scriptEnding.connect(cleanup); +Controller.mousePressEvent.connect(mousePressEvent); From eafc11e0543198bcdb3998b2e5581485b77c290a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 15:18:16 -0800 Subject: [PATCH 07/15] use safer domain settings request in AvatarMixer --- assignment-client/src/avatars/AvatarMixer.cpp | 43 ++++++++----------- assignment-client/src/avatars/AvatarMixer.h | 1 + 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index a54ee84b88..e70e1414df 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -71,7 +71,6 @@ const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. void AvatarMixer::broadcastAvatarData() { - int idleTime = QDateTime::currentMSecsSinceEpoch() - _lastFrameTimestamp; ++_numStatFrames; @@ -513,15 +512,10 @@ void AvatarMixer::sendStatsPacket() { } void AvatarMixer::run() { + qDebug() << "Waiting for connection to domain to request settings from domain-server."; + ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); - auto nodeList = DependencyManager::get(); - nodeList->addNodeTypeToInterestSet(NodeType::Agent); - - nodeList->linkedDataCreateCallback = [] (Node* node) { - node->setLinkedData(new AvatarMixerClientData()); - }; - // setup the timer that will be fired on the broadcast thread _broadcastTimer = new QTimer; _broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS); @@ -532,30 +526,27 @@ void AvatarMixer::run() { connect(&_broadcastThread, SIGNAL(started()), _broadcastTimer, SLOT(start())); // wait until we have the domain-server settings, otherwise we bail - DomainHandler& domainHandler = nodeList->getDomainHandler(); - - qDebug() << "Waiting for domain settings from domain-server."; - - // block until we get the settingsRequestComplete signal - QEventLoop loop; - connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); - connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); - domainHandler.requestDomainSettings(); - loop.exec(); - - if (domainHandler.getSettingsObject().isEmpty()) { - qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; - setFinished(true); - return; - } + DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); + connect(&domainHandler, &DomainHandler::settingsReceived, this, &AvatarMixer::domainSettingsRequestComplete); + connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AvatarMixer::domainSettingsRequestFailed); +} +void AvatarMixer::domainSettingsRequestComplete() { + auto nodeList = DependencyManager::get(); + nodeList->addNodeTypeToInterestSet(NodeType::Agent); + + nodeList->linkedDataCreateCallback = [] (Node* node) { + node->setLinkedData(new AvatarMixerClientData()); + }; + // parse the settings to pull out the values we need - parseDomainServerSettings(domainHandler.getSettingsObject()); - + parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); + // start the broadcastThread _broadcastThread.start(); } + void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer"; const QString NODE_SEND_BANDWIDTH_KEY = "max_node_send_bandwidth"; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 67fdbf5285..6e87bd6a43 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -36,6 +36,7 @@ private slots: void handleAvatarIdentityPacket(QSharedPointer packet, SharedNodePointer senderNode); void handleAvatarBillboardPacket(QSharedPointer packet, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer packet); + void domainSettingsRequestComplete(); private: void broadcastAvatarData(); From 20485b817c33f607799fa80828678463e89129f5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 15:27:57 -0800 Subject: [PATCH 08/15] use safer domain-server settings request in entity-server --- assignment-client/src/audio/AudioMixer.cpp | 6 +- assignment-client/src/avatars/AvatarMixer.cpp | 10 +- .../src/entities/EntityServer.cpp | 2 +- assignment-client/src/entities/EntityServer.h | 2 +- assignment-client/src/octree/OctreeServer.cpp | 92 ++++++++----------- assignment-client/src/octree/OctreeServer.h | 5 +- 6 files changed, 49 insertions(+), 68 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index d4de3dbd8d..b854e3c801 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -647,14 +647,12 @@ void AudioMixer::run() { qDebug() << "Waiting for connection to domain to request settings from domain-server."; - ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); - - auto nodeList = DependencyManager::get(); - // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &AudioMixer::domainSettingsRequestComplete); connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AudioMixer::domainSettingsRequestFailed); + + ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); } void AudioMixer::domainSettingsRequestComplete() { diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index e70e1414df..b52a4a7e45 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -514,6 +514,11 @@ void AvatarMixer::sendStatsPacket() { void AvatarMixer::run() { qDebug() << "Waiting for connection to domain to request settings from domain-server."; + // wait until we have the domain-server settings, otherwise we bail + DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); + connect(&domainHandler, &DomainHandler::settingsReceived, this, &AvatarMixer::domainSettingsRequestComplete); + connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AvatarMixer::domainSettingsRequestFailed); + ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); // setup the timer that will be fired on the broadcast thread @@ -524,11 +529,6 @@ void AvatarMixer::run() { // connect appropriate signals and slots connect(_broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection); connect(&_broadcastThread, SIGNAL(started()), _broadcastTimer, SLOT(start())); - - // wait until we have the domain-server settings, otherwise we bail - DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); - connect(&domainHandler, &DomainHandler::settingsReceived, this, &AvatarMixer::domainSettingsRequestComplete); - connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AvatarMixer::domainSettingsRequestFailed); } void AvatarMixer::domainSettingsRequestComplete() { diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 2fafaa6731..38a2ac035a 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -253,7 +253,7 @@ void EntityServer::pruneDeletedEntities() { } } -bool EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { +void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { bool wantEditLogging = false; readOptionBool(QString("wantEditLogging"), settingsSectionObject, wantEditLogging); qDebug("wantEditLogging=%s", debug::valueOf(wantEditLogging)); diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 065834cbc2..7ccf5eb0f1 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -41,7 +41,7 @@ public: virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) override; virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) override; - virtual bool readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override; + virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override; public slots: void pruneDeletedEntities(); diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index ad3df11474..5386c1be14 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -932,32 +932,13 @@ bool OctreeServer::readOptionString(const QString& optionName, const QJsonObject return optionAvailable; } -bool OctreeServer::readConfiguration() { +void OctreeServer::readConfiguration() { // if the assignment had a payload, read and parse that if (getPayload().size() > 0) { parsePayload(); } - - // wait until we have the domain-server settings, otherwise we bail - auto nodeList = DependencyManager::get(); - DomainHandler& domainHandler = nodeList->getDomainHandler(); - - qDebug() << "Waiting for domain settings from domain-server."; - - // block until we get the settingsRequestComplete signal - QEventLoop loop; - connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); - connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); - domainHandler.requestDomainSettings(); - loop.exec(); - - if (domainHandler.getSettingsObject().isEmpty()) { - qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; - setFinished(true); - return false; - } - - const QJsonObject& settingsObject = domainHandler.getSettingsObject(); + + const QJsonObject& settingsObject = DependencyManager::get()->getDomainHandler().getSettingsObject(); QString settingsKey = getMyDomainSettingsKey(); QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject(); _settings = settingsSectionObject; // keep this for later @@ -1065,79 +1046,79 @@ bool OctreeServer::readConfiguration() { packetsPerSecondTotalMax, _packetsTotalPerInterval); - return readAdditionalConfiguration(settingsSectionObject); + readAdditionalConfiguration(settingsSectionObject); } void OctreeServer::run() { - - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); - packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); - packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket"); - _safeServerName = getMyServerName(); // Before we do anything else, create our tree... OctreeElement::resetPopulationStatistics(); _tree = createTree(); _tree->setIsServer(true); - - // make sure our NodeList knows what type we are - auto nodeList = DependencyManager::get(); - nodeList->setOwnerType(getMyNodeType()); - + + qDebug() << "Waiting for connection to domain to request settings from domain-server."; + + // wait until we have the domain-server settings, otherwise we bail + DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); + connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete); + connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &OctreeServer::domainSettingsRequestFailed); // use common init to setup common timers and logging commonInit(getMyLoggingServerTargetName(), getMyNodeType()); +} - // read the configuration from either the payload or the domain server configuration - if (!readConfiguration()) { - return; // bailing on run, because readConfiguration failed - } - - beforeRun(); // after payload has been processed - - connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); - connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); - - +void OctreeServer::domainSettingsRequestComplete() { + + auto nodeList = DependencyManager::get(); + // we need to ask the DS about agents so we can ping/reply with them nodeList->addNodeTypeToInterestSet(NodeType::Agent); - + + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); + packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); + packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket"); + + readConfiguration(); + + beforeRun(); // after payload has been processed + + connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); + connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); + #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif - + nodeList->linkedDataCreateCallback = [] (Node* node) { OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode(); newQueryNodeData->init(); node->setLinkedData(newQueryNodeData); }; - + srand((unsigned)time(0)); - + // if we want Persistence, set up the local file and persist thread if (_wantPersist) { - + // now set up PersistThread _persistThread = new OctreePersistThread(_tree, _persistFilename, _persistInterval, _wantBackup, _settings, _debugTimestampNow, _persistAsFileType); _persistThread->initialize(true); } - - HifiSockAddr senderSockAddr; - + // set up our jurisdiction broadcaster... if (_jurisdiction) { _jurisdiction->setNodeType(getMyNodeType()); } _jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType()); _jurisdictionSender->initialize(true); - + // set up our OctreeServerPacketProcessor _octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this); _octreeInboundPacketProcessor->initialize(true); - + // Convert now to tm struct for local timezone tm* localtm = localtime(&_started); const int MAX_TIME_LENGTH = 128; @@ -1149,6 +1130,7 @@ void OctreeServer::run() { if (gmtm) { strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm); } + qDebug() << "Now running... started at: " << localBuffer << utcBuffer; } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index b8e4a5c261..88abb87675 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -126,6 +126,7 @@ public slots: void sendStatsPacket(); private slots: + void domainSettingsRequestComplete(); void handleOctreeQueryPacket(QSharedPointer packet, SharedNodePointer senderNode); void handleOctreeDataNackPacket(QSharedPointer packet, SharedNodePointer senderNode); void handleJurisdictionRequestPacket(QSharedPointer packet, SharedNodePointer senderNode); @@ -135,8 +136,8 @@ protected: bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result); bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result); bool readOptionString(const QString& optionName, const QJsonObject& settingsSectionObject, QString& result); - bool readConfiguration(); - virtual bool readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { return true; }; + void readConfiguration(); + virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { }; void parsePayload(); void initHTTPManager(int port); void resetSendingStats(); From 3773995bb7f6a4fa1bc1836a0e28ac6fbc5e57bd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 15:41:27 -0800 Subject: [PATCH 09/15] remove true return for readAdditionalConfiguration --- assignment-client/src/entities/EntityServer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 38a2ac035a..5126b5ab71 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -265,6 +265,4 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio EntityTreePointer tree = std::static_pointer_cast(_tree); tree->setWantEditLogging(wantEditLogging); tree->setWantTerseEditLogging(wantTerseEditLogging); - - return true; } From 08e6b63090fe3a157f365070e5b80d184dc67623 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 15:43:31 -0800 Subject: [PATCH 10/15] cleanup debug of received domain-server settings --- libraries/networking/src/DomainHandler.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 2f200ff1e3..681f971ef5 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -283,8 +283,10 @@ void DomainHandler::processSettingsPacketList(QSharedPointer packe auto data = packetList->getMessage(); _settingsObject = QJsonDocument::fromJson(data).object(); - - qCDebug(networking) << "Received domain settings: \n" << qPrintable(data); + + if (!_settingsObject.isEmpty()) { + qCDebug(networking) << "Received domain settings: \n" << _settingsObject; + } emit settingsReceived(_settingsObject); } From 98cc7408b119e14a718346953dbb4918eae4cbb2 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 18 Nov 2015 17:15:53 -0800 Subject: [PATCH 11/15] Fixing issues found in AC playback test --- assignment-client/src/Agent.cpp | 67 +++++++++---------- assignment-client/src/Agent.h | 2 - assignment-client/src/AssignmentClient.cpp | 2 + .../src/avatars/ScriptableAvatar.cpp | 4 -- .../src/avatars/ScriptableAvatar.h | 6 +- examples/tests/playbackAcTest.js | 24 +++++++ .../recording/src/recording/impl/FileClip.cpp | 9 +-- libraries/shared/src/Transform.cpp | 2 +- 8 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 examples/tests/playbackAcTest.js diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 1b019034e9..62f9b28f8b 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -177,16 +177,24 @@ void Agent::run() { _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 - ScriptableAvatar scriptedAvatar(_scriptEngine.get()); - scriptedAvatar.setForceFaceTrackerConnected(true); + auto scriptedAvatar = DependencyManager::get(); + connect(_scriptEngine.get(), SIGNAL(update(float)), scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); + scriptedAvatar->setForceFaceTrackerConnected(true); // call model URL setters with empty URLs so our avatar, if user, will have the default models - scriptedAvatar.setFaceModelURL(QUrl()); - scriptedAvatar.setSkeletonModelURL(QUrl()); - + scriptedAvatar->setFaceModelURL(QUrl()); + scriptedAvatar->setSkeletonModelURL(QUrl()); // give this AvatarData object to the script engine - auto scriptedAvatarPtr = &scriptedAvatar; - setAvatarData(&scriptedAvatar, "Avatar"); + _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); + + + using namespace recording; + static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); + // FIXME how to deal with driving multiple avatars locally? + Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this, scriptedAvatar](Frame::ConstPointer frame) { + AvatarData::fromFrame(frame->data, *scriptedAvatar); + }); + using namespace recording; static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::AUDIO_FRAME_NAME); @@ -194,8 +202,8 @@ void Agent::run() { const QByteArray& audio = frame->data; static quint16 audioSequenceNumber{ 0 }; Transform audioTransform; - audioTransform.setTranslation(scriptedAvatar.getPosition()); - audioTransform.setRotation(scriptedAvatar.getOrientation()); + audioTransform.setTranslation(scriptedAvatar->getPosition()); + audioTransform.setRotation(scriptedAvatar->getOrientation()); AbstractAudioInterface::emitAudioPacket(audio.data(), audio.size(), audioSequenceNumber, audioTransform, PacketType::MicrophoneAudioNoEcho); }); @@ -240,7 +248,8 @@ void Agent::run() { _scriptEngine->run(); - Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [](Frame::ConstPointer frame) {}); + Frame::clearFrameHandler(AUDIO_FRAME_TYPE); + Frame::clearFrameHandler(AVATAR_FRAME_TYPE); setFinished(true); } @@ -278,40 +287,30 @@ void Agent::setIsAvatar(bool isAvatar) { } } -void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) { - _avatarData = avatarData; - _scriptEngine->registerGlobalObject(objectName, avatarData); - - using namespace recording; - static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); - // FIXME how to deal with driving multiple avatars locally? - Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this](Frame::ConstPointer frame) { - AvatarData::fromFrame(frame->data, *_avatarData); - }); -} - void Agent::sendAvatarIdentityPacket() { - if (_isAvatar && _avatarData) { - _avatarData->sendIdentityPacket(); + if (_isAvatar) { + auto scriptedAvatar = DependencyManager::get(); + scriptedAvatar->sendIdentityPacket(); } } void Agent::sendAvatarBillboardPacket() { - if (_isAvatar && _avatarData) { - _avatarData->sendBillboardPacket(); + if (_isAvatar) { + auto scriptedAvatar = DependencyManager::get(); + scriptedAvatar->sendBillboardPacket(); } } void Agent::processAgentAvatarAndAudio(float deltaTime) { - if (!_scriptEngine->isFinished() && _isAvatar && _avatarData) { - + if (!_scriptEngine->isFinished() && _isAvatar) { + auto scriptedAvatar = DependencyManager::get(); const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE) / (1000 * 1000)) + 0.5); const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t); - QByteArray avatarByteArray = _avatarData->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); - _avatarData->doneEncoding(true); + QByteArray avatarByteArray = scriptedAvatar->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); + scriptedAvatar->doneEncoding(true); static AvatarDataSequenceNumber sequenceNumber = 0; auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber)); @@ -376,8 +375,8 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) { audioPacket->writePrimitive(SCRIPT_AUDIO_BUFFER_SAMPLES); // use the orientation and position of this avatar for the source of this audio - audioPacket->writePrimitive(_avatarData->getPosition()); - glm::quat headOrientation = _avatarData->getHeadOrientation(); + audioPacket->writePrimitive(scriptedAvatar->getPosition()); + glm::quat headOrientation = scriptedAvatar->getHeadOrientation(); audioPacket->writePrimitive(headOrientation); }else if (nextSoundOutput) { @@ -385,8 +384,8 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) { audioPacket->writePrimitive((quint8)0); // use the orientation and position of this avatar for the source of this audio - audioPacket->writePrimitive(_avatarData->getPosition()); - glm::quat headOrientation = _avatarData->getHeadOrientation(); + audioPacket->writePrimitive(scriptedAvatar->getPosition()); + glm::quat headOrientation = scriptedAvatar->getHeadOrientation(); audioPacket->writePrimitive(headOrientation); // write the raw audio data diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index ab000015d5..e8381d26af 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -70,13 +70,11 @@ private: MixedAudioStream _receivedAudioStream; float _lastReceivedAudioLoudness; - void setAvatarData(AvatarData* avatarData, const QString& objectName); void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } void sendAvatarIdentityPacket(); void sendAvatarBillboardPacket(); - AvatarData* _avatarData = nullptr; bool _isListeningToAudioStream = false; Sound* _avatarSound = nullptr; int _numAvatarSoundSentBytes = 0; diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 2d11f4d289..674b317812 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -35,6 +35,7 @@ #include "AssignmentActionFactory.h" #include "AssignmentClient.h" +#include "avatars/ScriptableAvatar.h" const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; @@ -48,6 +49,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri QSettings::setDefaultFormat(QSettings::IniFormat); + auto scriptableAvatar = DependencyManager::set(); auto addressManager = DependencyManager::set(); // create a NodeList as an unassigned client, must be after addressManager diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index a78939256d..9b3d2ed24e 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -15,10 +15,6 @@ #include "ScriptableAvatar.h" -ScriptableAvatar::ScriptableAvatar(ScriptEngine* scriptEngine) : _scriptEngine(scriptEngine), _animation(NULL) { - connect(_scriptEngine, SIGNAL(update(float)), this, SLOT(update(float))); -} - // hold and priority unused but kept so that client side JS can run. void ScriptableAvatar::startAnimation(const QString& url, float fps, float priority, bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 5a99c8c8da..78b2be1057 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -16,11 +16,10 @@ #include #include -class ScriptableAvatar : public AvatarData { +class ScriptableAvatar : public AvatarData, public Dependency{ Q_OBJECT public: - ScriptableAvatar(ScriptEngine* scriptEngine); - + /// Allows scripts to run animations. Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); @@ -31,7 +30,6 @@ private slots: void update(float deltatime); private: - ScriptEngine* _scriptEngine; AnimationPointer _animation; AnimationDetails _animationDetails; QStringList _maskedJoints; diff --git a/examples/tests/playbackAcTest.js b/examples/tests/playbackAcTest.js new file mode 100644 index 0000000000..5630b17ed8 --- /dev/null +++ b/examples/tests/playbackAcTest.js @@ -0,0 +1,24 @@ +"use strict"; + +var origin = {x: 512, y: 512, z: 512}; +var millisecondsToWaitBeforeStarting = 2 * 1000; // To give the various servers a chance to start. +var millisecondsToWaitBeforeEnding = 30 * 1000; + +Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/dd03b8e3-52fb-4ab3-9ac9-3b17e00cd85d/98baa90b3b66803c5d7bd4537fca6993.fst"; //lovejoy +Avatar.displayName = "AC Avatar"; +Agent.isAvatar = true; + +Script.setTimeout(function () { + Avatar.position = origin; + Recording.loadRecording("d:/hifi.rec"); + Recording.setPlayerLoop(true); + Recording.startPlaying(); +}, millisecondsToWaitBeforeStarting); + + +Script.setTimeout(function () { + print("Stopping script"); + Agent.isAvatar = false; + Recording.stopPlaying(); + Script.stop(); +}, millisecondsToWaitBeforeEnding); \ No newline at end of file diff --git a/libraries/recording/src/recording/impl/FileClip.cpp b/libraries/recording/src/recording/impl/FileClip.cpp index 80aaac4c87..fcc22452e0 100644 --- a/libraries/recording/src/recording/impl/FileClip.cpp +++ b/libraries/recording/src/recording/impl/FileClip.cpp @@ -72,16 +72,17 @@ FileFrameHeaderList parseFrameHeaders(uchar* const start, const qint64& size) { results.push_back(header); } qDebug() << "Parsed source data into " << results.size() << " frames"; - int i = 0; - for (const auto& frameHeader : results) { - qDebug() << "Frame " << i++ << " time " << frameHeader.timeOffset; - } +// int i = 0; +// for (const auto& frameHeader : results) { +// qDebug() << "Frame " << i++ << " time " << frameHeader.timeOffset << " Type " << frameHeader.type; +// } return results; } FileClip::FileClip(const QString& fileName) : _file(fileName) { auto size = _file.size(); + qDebug() << "Opening file of size: " << size; bool opened = _file.open(QIODevice::ReadOnly); if (!opened) { qCWarning(recordingLog) << "Unable to open file " << fileName; diff --git a/libraries/shared/src/Transform.cpp b/libraries/shared/src/Transform.cpp index 1358f396d9..d0aef8a9be 100644 --- a/libraries/shared/src/Transform.cpp +++ b/libraries/shared/src/Transform.cpp @@ -117,7 +117,7 @@ Transform Transform::fromJson(const QJsonValue& json) { result.setTranslation(vec3FromJsonValue(obj[JSON_TRANSLATION])); } if (obj.contains(JSON_SCALE)) { - result.setScale(vec3FromJsonValue(obj[JSON_TRANSLATION])); + result.setScale(vec3FromJsonValue(obj[JSON_SCALE])); } return result; } From 456da661fff27eeac3432cb3d42b0b7cd923a27b Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 18 Nov 2015 17:16:31 -0800 Subject: [PATCH 12/15] implement support to download the persist file from entity server status page --- assignment-client/src/octree/OctreeServer.cpp | 22 ++++++++++++++++++- assignment-client/src/octree/OctreeServer.h | 4 ++++ .../resources/describe-settings.json | 8 +++++++ .../embedded-webserver/src/HTTPConnection.cpp | 2 ++ .../embedded-webserver/src/HTTPConnection.h | 2 ++ libraries/octree/src/OctreePersistThread.cpp | 18 +++++++++++++++ libraries/octree/src/OctreePersistThread.h | 4 ++++ 7 files changed, 59 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 84749bd975..b6a52358e5 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -317,6 +317,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url #endif bool showStats = false; + QString persistFile = "/" + getPersistFilename(); if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (url.path() == "/") { @@ -326,6 +327,18 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url _tree->resetEditStats(); resetSendingStats(); showStats = true; + } else if ((url.path() == persistFile) || (url.path() == persistFile + "/")) { + if (_persistFileDownload) { + QByteArray persistFileContents = getPersistFileContents(); + if (persistFileContents.length() > 0) { + connection->respond(HTTPConnection::StatusCode200, persistFileContents, qPrintable(getPersistFileMimeType())); + } else { + connection->respond(HTTPConnection::StatusCode500, HTTPConnection::StatusCode500); + } + } else { + connection->respond(HTTPConnection::StatusCode403, HTTPConnection::StatusCode403); // not allowed + } + return true; } } @@ -367,6 +380,12 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url statsString += getFileLoadTime(); statsString += "\r\n"; + if (_persistFileDownload) { + statsString += QString("Persist file: %1\r\n").arg(persistFile); + } else { + statsString += QString("Persist file: %1\r\n").arg(persistFile); + } + } else { statsString += "Octree file not yet loaded...\r\n"; } @@ -1026,7 +1045,8 @@ bool OctreeServer::readConfiguration() { _wantBackup = !noBackup; qDebug() << "wantBackup=" << _wantBackup; - //qDebug() << "settingsSectionObject:" << settingsSectionObject; + readOptionBool(QString("persistFileDownload"), settingsSectionObject, _persistFileDownload); + qDebug() << "persistFileDownload=" << _persistFileDownload; } else { qDebug("persistFilename= DISABLED"); diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index b8e4a5c261..6d4eb89f66 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -59,6 +59,9 @@ public: bool isInitialLoadComplete() const { return (_persistThread) ? _persistThread->isInitialLoadComplete() : true; } bool isPersistEnabled() const { return (_persistThread) ? true : false; } quint64 getLoadElapsedTime() const { return (_persistThread) ? _persistThread->getLoadElapsedTime() : 0; } + QString getPersistFilename() const { return (_persistThread) ? _persistThread->getPersistFilename() : ""; } + QString getPersistFileMimeType() const { return (_persistThread) ? _persistThread->getPersistFileMimeType() : "text/plain"; } + QByteArray getPersistFileContents() const { return (_persistThread) ? _persistThread->getPersistFileContents() : QByteArray(); } // Subclasses must implement these methods virtual OctreeQueryNode* createOctreeQueryNode() = 0; @@ -173,6 +176,7 @@ protected: int _persistInterval; bool _wantBackup; + bool _persistFileDownload; QString _backupExtensionFormat; int _backupInterval; int _maxBackupVersions; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index e0038117f0..2332f25dfa 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -476,6 +476,14 @@ "default": "", "advanced": true }, + { + "name": "persistFileDownload", + "type": "checkbox", + "label": "Persist File Download", + "help": "Includes a download link to the persist file in the server status page.", + "default": false, + "advanced": true + }, { "name": "wantEditLogging", "type": "checkbox", diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index fb69499059..b0cd55c085 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -23,7 +23,9 @@ const char* HTTPConnection::StatusCode301 = "301 Moved Permanently"; const char* HTTPConnection::StatusCode302 = "302 Found"; const char* HTTPConnection::StatusCode400 = "400 Bad Request"; const char* HTTPConnection::StatusCode401 = "401 Unauthorized"; +const char* HTTPConnection::StatusCode403 = "403 Forbidden"; const char* HTTPConnection::StatusCode404 = "404 Not Found"; +const char* HTTPConnection::StatusCode500 = "500 Internal server error"; const char* HTTPConnection::DefaultContentType = "text/plain; charset=ISO-8859-1"; HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager) : diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index c981537c15..23a4db5bc2 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -47,7 +47,9 @@ public: static const char* StatusCode302; static const char* StatusCode400; static const char* StatusCode401; + static const char* StatusCode403; static const char* StatusCode404; + static const char* StatusCode500; static const char* DefaultContentType; /// WebSocket close status codes. diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 5cbb30ef49..03794e2223 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -52,6 +52,15 @@ OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& file _filename = sansExt + "." + _persistAsFileType; } +QString OctreePersistThread::getPersistFileMimeType() const { + if (_persistAsFileType == "json") { + return "application/json"; + } if (_persistAsFileType == "json.gz") { + return "application/zip"; + } + return ""; +} + void OctreePersistThread::parseSettings(const QJsonObject& settings) { if (settings["backups"].isArray()) { const QJsonArray& backupRules = settings["backups"].toArray(); @@ -229,6 +238,15 @@ void OctreePersistThread::aboutToFinish() { _stopThread = true; } +QByteArray OctreePersistThread::getPersistFileContents() const { + QByteArray fileContents; + QFile file(_filename); + if (file.open(QIODevice::ReadOnly)) { + fileContents = file.readAll(); + } + return fileContents; +} + void OctreePersistThread::persist() { if (_tree->isDirty()) { diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index d3aa3d3968..061a7a0e15 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -42,6 +42,10 @@ public: void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist + QString getPersistFilename() const { return _filename; } + QString getPersistFileMimeType() const; + QByteArray getPersistFileContents() const; + signals: void loadCompleted(); From dc093159a1d706509c1611670de095f42a4a4ab7 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 18 Nov 2015 17:54:51 -0800 Subject: [PATCH 13/15] make preferences dialog resizable --- interface/src/ui/PreferencesDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index d4bab86126..5e45adadad 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -56,7 +56,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : // move dialog to left side move(parentWidget()->geometry().topLeft()); - setFixedHeight(parentWidget()->size().height() - PREFERENCES_HEIGHT_PADDING); + resize(sizeHint().width(), parentWidget()->size().height() - PREFERENCES_HEIGHT_PADDING); UIUtil::scaleWidgetFontSizes(this); } From 8adf456b91832c22af363c0f073c67f7f4f71134 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Nov 2015 11:18:05 -0800 Subject: [PATCH 14/15] Update range of LOD to go up to 20:2 --- libraries/octree/src/OctreeConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index a98f042006..0b05a8606c 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -24,7 +24,7 @@ const int HALF_TREE_SCALE = TREE_SCALE / 2; const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f; // This is used in the LOD Tools to translate between the size scale slider and the values used to set the OctreeSizeScale -const float MAX_LOD_SIZE_MULTIPLIER = 800.0f; +const float MAX_LOD_SIZE_MULTIPLIER = 4000.0f; const int NUMBER_OF_CHILDREN = 8; From f523cad0a9e9650570daa359259c733ed374cb61 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 19 Nov 2015 09:24:29 -0800 Subject: [PATCH 15/15] Add lodTest.js for testing LOD settings --- examples/tests/lodTest.js | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 examples/tests/lodTest.js diff --git a/examples/tests/lodTest.js b/examples/tests/lodTest.js new file mode 100644 index 0000000000..4b6706cd70 --- /dev/null +++ b/examples/tests/lodTest.js @@ -0,0 +1,40 @@ +// +// lodTest.js +// examples/tests +// +// Created by Ryan Huffman on 11/19/15. +// Copyright 2015 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 +// + +var MIN_DIM = 0.001; +var MAX_DIM = 2.0; +var NUM_SPHERES = 20; + +// Rough estimate of the width the spheres will span, not taking into account MIN_DIM +var WIDTH = MAX_DIM * NUM_SPHERES; + +var entities = []; +var right = Quat.getRight(Camera.orientation); +// Starting position will be 30 meters in front of the camera +var position = Vec3.sum(Camera.position, Vec3.multiply(30, Quat.getFront(Camera.orientation))); +position = Vec3.sum(position, Vec3.multiply(-WIDTH/2, right)); + +for (var i = 0; i < NUM_SPHERES; ++i) { + var dim = (MAX_DIM - MIN_DIM) * ((i + 1) / NUM_SPHERES); + entities.push(Entities.addEntity({ + type: "Sphere", + dimensions: { x: dim, y: dim, z: dim }, + position: position, + })); + + position = Vec3.sum(position, Vec3.multiply(dim * 2, right)); +} + +Script.scriptEnding.connect(function() { + for (var i = 0; i < entities.length; ++i) { + Entities.deleteEntity(entities[i]); + } +})