From 2f3cf82202b3418456916ae22d8616242770cc8e Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 18 Nov 2015 14:22:02 -0800 Subject: [PATCH 1/7] don't apply out of order edits to entities that have been deleted --- libraries/entities/src/EntityItem.cpp | 9 +++++++ libraries/entities/src/EntityTree.cpp | 4 ++++ libraries/entities/src/EntityTree.h | 25 +++++++++++++++++--- libraries/entities/src/EntityTreeElement.cpp | 19 ++++++++++----- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 100f6dfe22..75bc26a560 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -500,6 +500,15 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } } + // before proceeding, check to see if this is an entity that we know has been deleted, which + // might happen in the case of out-of-order and/or recorvered packets, if we've deleted the entity + // we can confidently ignore this packet + EntityTreePointer tree = getTree(); + if (tree && tree->isDeletedEntity(_id)) { + qDebug() << "Recieved packet for previously deleted entity [" << _id << "] ignoring. (inside " << __FUNCTION__ << ")"; + ignoreServerPacket = true; + } + if (ignoreServerPacket) { overwriteLocalData = false; #ifdef WANT_DEBUG diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index dc7c19056a..9eac14e4b4 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -68,6 +68,7 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { Octree::eraseAllOctreeElements(createNewRoot); resetClientEditStats(); + clearDeletedEntities(); } bool EntityTree::handlesEditPacketType(PacketType packetType) const { @@ -398,6 +399,9 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) // set up the deleted entities ID QWriteLocker locker(&_recentlyDeletedEntitiesLock); _recentlyDeletedEntityItemIDs.insert(deletedAt, theEntity->getEntityItemID()); + } else { + // on the client side, we also remember that we deleted this entity, we don't care about the time + trackDeletedEntity(theEntity->getEntityItemID()); } if (_simulation) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 645e3f4f76..d1e0462f64 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -228,6 +228,11 @@ public: EntityTreePointer getThisPointer() { return std::static_pointer_cast(shared_from_this()); } + bool isDeletedEntity(const QUuid& id) { + QReadLocker locker(&_deletedEntitiesLock); + return _deletedEntityItemIDs.contains(id); + } + signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); @@ -235,7 +240,7 @@ signals: void newCollisionSoundURL(const QUrl& url); void clearingEntities(); -private: +protected: void processRemovedEntities(const DeleteEntityOperator& theOperator); bool updateEntityWithElement(EntityItemPointer entity, const EntityItemProperties& properties, @@ -252,8 +257,22 @@ private: QReadWriteLock _newlyCreatedHooksLock; QVector _newlyCreatedHooks; - mutable QReadWriteLock _recentlyDeletedEntitiesLock; - QMultiMap _recentlyDeletedEntityItemIDs; + mutable QReadWriteLock _recentlyDeletedEntitiesLock; /// lock of server side recent deletes + QMultiMap _recentlyDeletedEntityItemIDs; /// server side recent deletes + + mutable QReadWriteLock _deletedEntitiesLock; /// lock of client side recent deletes + QSet _deletedEntityItemIDs; /// client side recent deletes + + void clearDeletedEntities() { + QWriteLocker locker(&_deletedEntitiesLock); + _deletedEntityItemIDs.clear(); + } + + void trackDeletedEntity(const QUuid& id) { + QWriteLocker locker(&_deletedEntitiesLock); + _deletedEntityItemIDs << id; + } + EntityItemFBXService* _fbxService; QHash _entityToElementMap; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 7ada138d02..02552ef488 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -894,12 +894,19 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args); if (entityItem) { bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); - addEntityItem(entityItem); // add this new entity to this elements entities - entityItemID = entityItem->getEntityItemID(); - _myTree->setContainingElement(entityItemID, getThisPointer()); - _myTree->postAddEntity(entityItem); - if (entityItem->getCreated() == UNKNOWN_CREATED_TIME) { - entityItem->recordCreationTime(); + + // don't add if we've recently deleted.... + if (!_myTree->isDeletedEntity(entityItem->getID())) { + addEntityItem(entityItem); // add this new entity to this elements entities + entityItemID = entityItem->getEntityItemID(); + _myTree->setContainingElement(entityItemID, getThisPointer()); + _myTree->postAddEntity(entityItem); + if (entityItem->getCreated() == UNKNOWN_CREATED_TIME) { + entityItem->recordCreationTime(); + } + } else { + qDebug() << "Recieved packet for previously deleted entity [" << + entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")"; } } } From 6719092094fb4a1ba8fde4ea38cd23a87e6e9324 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 18 Nov 2015 14:56:31 -0800 Subject: [PATCH 2/7] add an example of an entityScript that subscribes to messages --- .../messagesReceiverEntityExample.js | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 examples/entityScripts/messagesReceiverEntityExample.js diff --git a/examples/entityScripts/messagesReceiverEntityExample.js b/examples/entityScripts/messagesReceiverEntityExample.js new file mode 100644 index 0000000000..c10319e1cc --- /dev/null +++ b/examples/entityScripts/messagesReceiverEntityExample.js @@ -0,0 +1,56 @@ +// +// messagesReceiverEntityExample.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/18/15. +// Copyright 2015 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to an entity, will detect when the entity is being grabbed by the hydraGrab script +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { + + var _this; + + var messageReceived = function (channel, message, senderID) { + print("message received on channel:" + channel + ", message:" + message + ", senderID:" + senderID); + }; + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + MessagesReceiver = function () { + _this = this; + }; + + MessagesReceiver.prototype = { + + // preload() will be called when the entity has become visible (or known) to the interface + // it gives us a chance to set our local JavaScript object up. In this case it means: + // * remembering our entityID, so we can access it in cases where we're called without an entityID + // * unsubscribing from messages + // * connectingf to the messageReceived signal + preload: function (entityID) { + this.entityID = entityID; + + print("---- subscribing ----"); + Messages.subscribe("example"); + Messages.messageReceived.connect(messageReceived); + }, + + // unload() will be called when the entity has become no longer known to the interface + // it gives us a chance to clean up our local JavaScript object. In this case it means: + // * unsubscribing from messages + // * disconnecting from the messageReceived signal + unload: function (entityID) { + print("---- unsubscribing ----"); + Messages.unsubscribe("example"); + Messages.messageReceived.disconnect(messageReceived); + }, + }; + + // entity scripts always need to return a newly constructed object of our type + return new MessagesReceiver(); +}) From f4cf1c0291b1f3b202570c0fd67c694e131b2036 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 17:33:22 -0800 Subject: [PATCH 3/7] only broadcast removed node messages to interested nodes --- domain-server/src/DomainServer.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d7bcec2431..653f0e84cd 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1835,14 +1835,25 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer p qDebug() << "Received a disconnect request from node with UUID" << nodeUUID; - if (limitedNodeList->killNodeWithUUID(nodeUUID)) { + // we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode + // packet to nodes that don't care about this type + auto node = limitedNodeList->nodeWithUUID(nodeUUID); + + if (node) { + auto nodeType = node->getType(); + limitedNodeList->killNodeWithUUID(nodeUUID); + static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID); removedNodePacket->reset(); removedNodePacket->write(nodeUUID.toRfc4122()); // broadcast out the DomainServerRemovedNode message - limitedNodeList->eachNode([&limitedNodeList](const SharedNodePointer& otherNode){ + limitedNodeList->eachMatchingNode([&nodeType](const SharedNodePointer& otherNode) -> bool { + // only send the removed node packet to nodes that care about the type of node this was + auto nodeLinkedData = dynamic_cast(otherNode->getLinkedData()); + return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType); + }, [&limitedNodeList](const SharedNodePointer& otherNode){ limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode); }); } From 3fba1f8445422e3a639dc4a93c7c0fb776ffd8fd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 17:46:32 -0800 Subject: [PATCH 4/7] better variable naming for nodeToKill --- domain-server/src/DomainServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 653f0e84cd..b4243ef8a0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1837,10 +1837,10 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer p // we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode // packet to nodes that don't care about this type - auto node = limitedNodeList->nodeWithUUID(nodeUUID); + auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID); - if (node) { - auto nodeType = node->getType(); + if (nodeToKill) { + auto nodeType = nodeToKill->getType(); limitedNodeList->killNodeWithUUID(nodeUUID); static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID); From b7f501c0b3aee31c9fba328b536e235b90cb2fac Mon Sep 17 00:00:00 2001 From: "U-GAPOS\\andrew" Date: Thu, 19 Nov 2015 10:42:53 -0800 Subject: [PATCH 5/7] use glm::quat_cast instead of extractRotation() --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1c151bcd3f..989a81fc0c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -428,7 +428,7 @@ void MyAvatar::updateHMDFollowVelocity() { } if (_followSpeed > 0.0f) { // to compute new velocity we must rotate offset into the world-frame - glm::quat sensorToWorldRotation = extractRotation(_sensorToWorldMatrix); + glm::quat sensorToWorldRotation = glm::normalize(glm::quat_cast(_sensorToWorldMatrix)); _followVelocity = _followSpeed * glm::normalize(sensorToWorldRotation * offset); } } From 28e349f421225fb3ec085f86a29291705b9b1f23 Mon Sep 17 00:00:00 2001 From: "U-GAPOS\\andrew" Date: Thu, 19 Nov 2015 10:43:40 -0800 Subject: [PATCH 6/7] remeasure sensor after recentering --- plugins/oculus/src/OculusBaseDisplayPlugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 4c80b9a51d..7fd956a08f 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -29,6 +29,7 @@ glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseP void OculusBaseDisplayPlugin::resetSensors() { #if (OVR_MAJOR_VERSION >= 6) ovr_RecenterPose(_hmd); + preRender(); #endif } From e187aaedcbf60fb72f5d475da2e373d63db6ccf6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 19 Nov 2015 15:56:37 -0800 Subject: [PATCH 7/7] Load recorded clips from URLs, not file paths --- libraries/recording/CMakeLists.txt | 2 +- .../recording/src/recording/ClipCache.cpp | 40 +++++ libraries/recording/src/recording/ClipCache.h | 57 ++++++ .../recording/src/recording/impl/FileClip.cpp | 137 +-------------- .../recording/src/recording/impl/FileClip.h | 27 +-- .../src/recording/impl/PointerClip.cpp | 163 ++++++++++++++++++ .../src/recording/impl/PointerClip.h | 61 +++++++ .../src/RecordingScriptingInterface.cpp | 18 +- .../src/RecordingScriptingInterface.h | 4 +- 9 files changed, 338 insertions(+), 171 deletions(-) create mode 100644 libraries/recording/src/recording/ClipCache.cpp create mode 100644 libraries/recording/src/recording/ClipCache.h create mode 100644 libraries/recording/src/recording/impl/PointerClip.cpp create mode 100644 libraries/recording/src/recording/impl/PointerClip.h diff --git a/libraries/recording/CMakeLists.txt b/libraries/recording/CMakeLists.txt index a0beae4496..b42a4018f8 100644 --- a/libraries/recording/CMakeLists.txt +++ b/libraries/recording/CMakeLists.txt @@ -4,6 +4,6 @@ set(TARGET_NAME recording) setup_hifi_library(Script) # use setup_hifi_library macro to setup our project and link appropriate Qt modules -link_hifi_libraries(shared) +link_hifi_libraries(shared networking) GroupSources("src/recording") diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp new file mode 100644 index 0000000000..fb09245bf9 --- /dev/null +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis on 2015/11/19 +// 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 +// +#include "ClipCache.h" +#include "impl/PointerClip.h" + +using namespace recording; +NetworkClipLoader::NetworkClipLoader(const QUrl& url, bool delayLoad) + : Resource(url, delayLoad), _clip(std::make_shared(url)) +{ + +} + + +void NetworkClip::init(const QByteArray& clipData) { + _clipData = clipData; + PointerClip::init((uchar*)_clipData.data(), _clipData.size()); +} + +void NetworkClipLoader::downloadFinished(const QByteArray& data) { + _clip->init(data); +} + +ClipCache& ClipCache::instance() { + static ClipCache _instance; + return _instance; +} + +NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { + return ResourceCache::getResource(url, QUrl(), false, nullptr).staticCast(); +} + +QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { + return QSharedPointer(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared); +} + diff --git a/libraries/recording/src/recording/ClipCache.h b/libraries/recording/src/recording/ClipCache.h new file mode 100644 index 0000000000..c72d45648d --- /dev/null +++ b/libraries/recording/src/recording/ClipCache.h @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis on 2015/11/19 +// 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 +// +#pragma once +#ifndef hifi_Recording_ClipCache_h +#define hifi_Recording_ClipCache_h + +#include + +#include "Forward.h" +#include "impl/PointerClip.h" + +namespace recording { + +class NetworkClip : public PointerClip { +public: + using Pointer = std::shared_ptr; + + NetworkClip(const QUrl& url) : _url(url) {} + virtual void init(const QByteArray& clipData); + virtual QString getName() const override { return _url.toString(); } + +private: + QByteArray _clipData; + QUrl _url; +}; + +class NetworkClipLoader : public Resource { +public: + NetworkClipLoader(const QUrl& url, bool delayLoad); + virtual void downloadFinished(const QByteArray& data) override; + ClipPointer getClip() { return _clip; } + bool completed() { return _failedToLoad || isLoaded(); } + +private: + const NetworkClip::Pointer _clip; +}; + +using NetworkClipLoaderPointer = QSharedPointer; + +class ClipCache : public ResourceCache { +public: + static ClipCache& instance(); + + NetworkClipLoaderPointer getClipLoader(const QUrl& url); + +protected: + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) override; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/impl/FileClip.cpp b/libraries/recording/src/recording/impl/FileClip.cpp index fcc22452e0..ce2705a76c 100644 --- a/libraries/recording/src/recording/impl/FileClip.cpp +++ b/libraries/recording/src/recording/impl/FileClip.cpp @@ -23,63 +23,6 @@ using namespace recording; -static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize); -static const QString FRAME_TYPE_MAP = QStringLiteral("frameTypes"); -static const QString FRAME_COMREPSSION_FLAG = QStringLiteral("compressed"); - -using FrameTranslationMap = QMap; - -FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) { - FrameTranslationMap results; - auto headerObj = doc.object(); - if (headerObj.contains(FRAME_TYPE_MAP)) { - auto frameTypeObj = headerObj[FRAME_TYPE_MAP].toObject(); - auto currentFrameTypes = Frame::getFrameTypes(); - for (auto frameTypeName : frameTypeObj.keys()) { - qDebug() << frameTypeName; - if (!currentFrameTypes.contains(frameTypeName)) { - continue; - } - FrameType currentTypeEnum = currentFrameTypes[frameTypeName]; - FrameType storedTypeEnum = static_cast(frameTypeObj[frameTypeName].toInt()); - results[storedTypeEnum] = currentTypeEnum; - } - } - return results; -} - - -FileFrameHeaderList parseFrameHeaders(uchar* const start, const qint64& size) { - FileFrameHeaderList results; - auto current = start; - auto end = current + size; - // Read all the frame headers - // FIXME move to Frame::readHeader? - while (end - current >= MINIMUM_FRAME_SIZE) { - FileFrameHeader header; - memcpy(&(header.type), current, sizeof(FrameType)); - current += sizeof(FrameType); - memcpy(&(header.timeOffset), current, sizeof(Frame::Time)); - current += sizeof(Frame::Time); - memcpy(&(header.size), current, sizeof(FrameSize)); - current += sizeof(FrameSize); - header.fileOffset = current - start; - if (end - current < header.size) { - current = end; - break; - } - current += header.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 << " Type " << frameHeader.type; -// } - return results; -} - - FileClip::FileClip(const QString& fileName) : _file(fileName) { auto size = _file.size(); qDebug() << "Opening file of size: " << size; @@ -88,58 +31,8 @@ FileClip::FileClip(const QString& fileName) : _file(fileName) { qCWarning(recordingLog) << "Unable to open file " << fileName; return; } - _map = _file.map(0, size, QFile::MapPrivateOption); - if (!_map) { - qCWarning(recordingLog) << "Unable to map file " << fileName; - return; - } - - auto parsedFrameHeaders = parseFrameHeaders(_map, size); - - // Verify that at least one frame exists and that the first frame is a header - if (0 == parsedFrameHeaders.size()) { - qWarning() << "No frames found, invalid file"; - return; - } - - // Grab the file header - { - auto fileHeaderFrameHeader = *parsedFrameHeaders.begin(); - parsedFrameHeaders.pop_front(); - if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) { - qWarning() << "Missing header frame, invalid file"; - return; - } - - QByteArray fileHeaderData((char*)_map + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size); - _fileHeader = QJsonDocument::fromBinaryData(fileHeaderData); - } - - // Check for compression - { - _compressed = _fileHeader.object()[FRAME_COMREPSSION_FLAG].toBool(); - } - - // Find the type enum translation map and fix up the frame headers - { - FrameTranslationMap translationMap = parseTranslationMap(_fileHeader); - if (translationMap.empty()) { - qWarning() << "Header missing frame type map, invalid file"; - return; - } - qDebug() << translationMap; - - // Update the loaded headers with the frame data - _frames.reserve(parsedFrameHeaders.size()); - for (auto& frameHeader : parsedFrameHeaders) { - if (!translationMap.contains(frameHeader.type)) { - continue; - } - frameHeader.type = translationMap[frameHeader.type]; - _frames.push_back(frameHeader); - } - } - + auto mappedFile = _file.map(0, size, QFile::MapPrivateOption); + init(mappedFile, size); } @@ -228,31 +121,9 @@ bool FileClip::write(const QString& fileName, Clip::Pointer clip) { FileClip::~FileClip() { Locker lock(_mutex); - _file.unmap(_map); - _map = nullptr; + _file.unmap(_data); if (_file.isOpen()) { _file.close(); } -} - -// Internal only function, needs no locking -FrameConstPointer FileClip::readFrame(size_t frameIndex) const { - FramePointer result; - if (frameIndex < _frames.size()) { - result = std::make_shared(); - const auto& header = _frames[frameIndex]; - result->type = header.type; - result->timeOffset = header.timeOffset; - if (header.size) { - result->data.insert(0, reinterpret_cast(_map)+header.fileOffset, header.size); - if (_compressed) { - result->data = qUncompress(result->data); - } - } - } - return result; -} - -void FileClip::addFrame(FrameConstPointer) { - throw std::runtime_error("File clips are read only"); + reset(); } diff --git a/libraries/recording/src/recording/impl/FileClip.h b/libraries/recording/src/recording/impl/FileClip.h index f103a9aca6..71ae414721 100644 --- a/libraries/recording/src/recording/impl/FileClip.h +++ b/libraries/recording/src/recording/impl/FileClip.h @@ -10,27 +10,13 @@ #ifndef hifi_Recording_Impl_FileClip_h #define hifi_Recording_Impl_FileClip_h -#include "ArrayClip.h" - -#include +#include "PointerClip.h" #include -#include - -#include "../Frame.h" namespace recording { -struct FileFrameHeader : public FrameHeader { - FrameType type; - Frame::Time timeOffset; - uint16_t size; - quint64 fileOffset; -}; - -using FileFrameHeaderList = std::list; - -class FileClip : public ArrayClip { +class FileClip : public PointerClip { public: using Pointer = std::shared_ptr; @@ -38,20 +24,11 @@ public: virtual ~FileClip(); virtual QString getName() const override; - virtual void addFrame(FrameConstPointer) override; - - const QJsonDocument& getHeader() { - return _fileHeader; - } static bool write(const QString& filePath, Clip::Pointer clip); private: - virtual FrameConstPointer readFrame(size_t index) const override; - QJsonDocument _fileHeader; QFile _file; - uchar* _map { nullptr }; - bool _compressed { true }; }; } diff --git a/libraries/recording/src/recording/impl/PointerClip.cpp b/libraries/recording/src/recording/impl/PointerClip.cpp new file mode 100644 index 0000000000..48132c066d --- /dev/null +++ b/libraries/recording/src/recording/impl/PointerClip.cpp @@ -0,0 +1,163 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// 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 +// + +#include "PointerClip.h" + +#include + +#include +#include +#include + +#include + +#include "../Frame.h" +#include "../Logging.h" +#include "BufferClip.h" + + +using namespace recording; + +const QString PointerClip::FRAME_TYPE_MAP = QStringLiteral("frameTypes"); +const QString PointerClip::FRAME_COMREPSSION_FLAG = QStringLiteral("compressed"); + +using FrameTranslationMap = QMap; + +FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) { + FrameTranslationMap results; + auto headerObj = doc.object(); + if (headerObj.contains(PointerClip::FRAME_TYPE_MAP)) { + auto frameTypeObj = headerObj[PointerClip::FRAME_TYPE_MAP].toObject(); + auto currentFrameTypes = Frame::getFrameTypes(); + for (auto frameTypeName : frameTypeObj.keys()) { + qDebug() << frameTypeName; + if (!currentFrameTypes.contains(frameTypeName)) { + continue; + } + FrameType currentTypeEnum = currentFrameTypes[frameTypeName]; + FrameType storedTypeEnum = static_cast(frameTypeObj[frameTypeName].toInt()); + results[storedTypeEnum] = currentTypeEnum; + } + } + return results; +} + + +PointerFrameHeaderList parseFrameHeaders(uchar* const start, const size_t& size) { + PointerFrameHeaderList results; + auto current = start; + auto end = current + size; + // Read all the frame headers + // FIXME move to Frame::readHeader? + while (end - current >= PointerClip::MINIMUM_FRAME_SIZE) { + PointerFrameHeader header; + memcpy(&(header.type), current, sizeof(FrameType)); + current += sizeof(FrameType); + memcpy(&(header.timeOffset), current, sizeof(Frame::Time)); + current += sizeof(Frame::Time); + memcpy(&(header.size), current, sizeof(FrameSize)); + current += sizeof(FrameSize); + header.fileOffset = current - start; + if (end - current < header.size) { + current = end; + break; + } + current += header.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 << " Type " << frameHeader.type; +// } + return results; +} + +void PointerClip::reset() { + _frames.clear(); + _data = nullptr; + _size = 0; + _header = QJsonDocument(); +} + +void PointerClip::init(uchar* data, size_t size) { + reset(); + + _data = data; + _size = size; + + auto parsedFrameHeaders = parseFrameHeaders(data, size); + // Verify that at least one frame exists and that the first frame is a header + if (0 == parsedFrameHeaders.size()) { + qWarning() << "No frames found, invalid file"; + reset(); + return; + } + + // Grab the file header + { + auto fileHeaderFrameHeader = *parsedFrameHeaders.begin(); + parsedFrameHeaders.pop_front(); + if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) { + qWarning() << "Missing header frame, invalid file"; + reset(); + return; + } + + QByteArray fileHeaderData((char*)_data + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size); + _header = QJsonDocument::fromBinaryData(fileHeaderData); + } + + // Check for compression + { + _compressed = _header.object()[FRAME_COMREPSSION_FLAG].toBool(); + } + + // Find the type enum translation map and fix up the frame headers + { + FrameTranslationMap translationMap = parseTranslationMap(_header); + if (translationMap.empty()) { + qWarning() << "Header missing frame type map, invalid file"; + reset(); + return; + } + + // Update the loaded headers with the frame data + _frames.reserve(parsedFrameHeaders.size()); + for (auto& frameHeader : parsedFrameHeaders) { + if (!translationMap.contains(frameHeader.type)) { + continue; + } + frameHeader.type = translationMap[frameHeader.type]; + _frames.push_back(frameHeader); + } + } + +} + +// Internal only function, needs no locking +FrameConstPointer PointerClip::readFrame(size_t frameIndex) const { + FramePointer result; + if (frameIndex < _frames.size()) { + result = std::make_shared(); + const auto& header = _frames[frameIndex]; + result->type = header.type; + result->timeOffset = header.timeOffset; + if (header.size) { + result->data.insert(0, reinterpret_cast(_data)+header.fileOffset, header.size); + if (_compressed) { + result->data = qUncompress(result->data); + } + } + } + return result; +} + +void PointerClip::addFrame(FrameConstPointer) { + throw std::runtime_error("Pointer clips are read only, use duplicate to create a read/write clip"); +} diff --git a/libraries/recording/src/recording/impl/PointerClip.h b/libraries/recording/src/recording/impl/PointerClip.h new file mode 100644 index 0000000000..5a7a3499fe --- /dev/null +++ b/libraries/recording/src/recording/impl/PointerClip.h @@ -0,0 +1,61 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// 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 +// + +#pragma once +#ifndef hifi_Recording_Impl_PointerClip_h +#define hifi_Recording_Impl_PointerClip_h + +#include "ArrayClip.h" + +#include + +#include + +#include "../Frame.h" + +namespace recording { + +struct PointerFrameHeader : public FrameHeader { + FrameType type; + Frame::Time timeOffset; + uint16_t size; + quint64 fileOffset; +}; + +using PointerFrameHeaderList = std::list; + +class PointerClip : public ArrayClip { +public: + using Pointer = std::shared_ptr; + + PointerClip() {}; + PointerClip(uchar* data, size_t size) { init(data, size); } + + void init(uchar* data, size_t size); + virtual void addFrame(FrameConstPointer) override; + const QJsonDocument& getHeader() const { + return _header; + } + + // FIXME move to frame? + static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize); + static const QString FRAME_TYPE_MAP; + static const QString FRAME_COMREPSSION_FLAG; + +protected: + void reset(); + virtual FrameConstPointer readFrame(size_t index) const override; + QJsonDocument _header; + uchar* _data { nullptr }; + size_t _size { 0 }; + bool _compressed { true }; +}; + +} + +#endif diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 33e67e1b43..d82d471d79 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -8,14 +8,15 @@ #include "RecordingScriptingInterface.h" -#include +#include +#include +#include #include #include #include #include -#include -#include +#include #include "ScriptEngineLogging.h" @@ -43,20 +44,17 @@ float RecordingScriptingInterface::playerLength() const { return _player->length(); } -void RecordingScriptingInterface::loadRecording(const QString& filename) { +void RecordingScriptingInterface::loadRecording(const QString& url) { using namespace recording; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection, - Q_ARG(QString, filename)); + Q_ARG(QString, url)); return; } - ClipPointer clip = Clip::fromFile(filename); - if (!clip) { - qWarning() << "Unable to load clip data from " << filename; - } - _player->queueClip(clip); + // FIXME make blocking and force off main thread? + _player->queueClip(ClipCache::instance().getClipLoader(url)->getClip()); } void RecordingScriptingInterface::startPlaying() { diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 483ead3ca3..3834089177 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -25,7 +25,7 @@ public: RecordingScriptingInterface(); public slots: - void loadRecording(const QString& filename); + void loadRecording(const QString& url); void startPlaying(); void pausePlayer();