mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 08:04:01 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into controllers
This commit is contained in:
commit
bb7ab9b1f8
17 changed files with 457 additions and 183 deletions
|
@ -1835,14 +1835,25 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<NLPacket> 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 nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID);
|
||||
|
||||
if (nodeToKill) {
|
||||
auto nodeType = nodeToKill->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<DomainServerNodeData*>(otherNode->getLinkedData());
|
||||
return (nodeLinkedData != nullptr) && nodeLinkedData->getNodeInterestSet().contains(nodeType);
|
||||
}, [&limitedNodeList](const SharedNodePointer& otherNode){
|
||||
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
|
||||
});
|
||||
}
|
||||
|
|
56
examples/entityScripts/messagesReceiverEntityExample.js
Normal file
56
examples/entityScripts/messagesReceiverEntityExample.js
Normal file
|
@ -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();
|
||||
})
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -228,6 +228,11 @@ public:
|
|||
|
||||
EntityTreePointer getThisPointer() { return std::static_pointer_cast<EntityTree>(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<NewlyCreatedEntityHook*> _newlyCreatedHooks;
|
||||
|
||||
mutable QReadWriteLock _recentlyDeletedEntitiesLock;
|
||||
QMultiMap<quint64, QUuid> _recentlyDeletedEntityItemIDs;
|
||||
mutable QReadWriteLock _recentlyDeletedEntitiesLock; /// lock of server side recent deletes
|
||||
QMultiMap<quint64, QUuid> _recentlyDeletedEntityItemIDs; /// server side recent deletes
|
||||
|
||||
mutable QReadWriteLock _deletedEntitiesLock; /// lock of client side recent deletes
|
||||
QSet<QUuid> _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<EntityItemID, EntityTreeElementPointer> _entityToElementMap;
|
||||
|
|
|
@ -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__ << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
40
libraries/recording/src/recording/ClipCache.cpp
Normal file
40
libraries/recording/src/recording/ClipCache.cpp
Normal file
|
@ -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<NetworkClip>(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<NetworkClipLoader>();
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||
return QSharedPointer<Resource>(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared);
|
||||
}
|
||||
|
57
libraries/recording/src/recording/ClipCache.h
Normal file
57
libraries/recording/src/recording/ClipCache.h
Normal file
|
@ -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 <ResourceCache.h>
|
||||
|
||||
#include "Forward.h"
|
||||
#include "impl/PointerClip.h"
|
||||
|
||||
namespace recording {
|
||||
|
||||
class NetworkClip : public PointerClip {
|
||||
public:
|
||||
using Pointer = std::shared_ptr<NetworkClip>;
|
||||
|
||||
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<NetworkClipLoader>;
|
||||
|
||||
class ClipCache : public ResourceCache {
|
||||
public:
|
||||
static ClipCache& instance();
|
||||
|
||||
NetworkClipLoaderPointer getClipLoader(const QUrl& url);
|
||||
|
||||
protected:
|
||||
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -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<FrameType, FrameType>;
|
||||
|
||||
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<FrameType>(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<Frame>();
|
||||
const auto& header = _frames[frameIndex];
|
||||
result->type = header.type;
|
||||
result->timeOffset = header.timeOffset;
|
||||
if (header.size) {
|
||||
result->data.insert(0, reinterpret_cast<char*>(_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();
|
||||
}
|
||||
|
|
|
@ -10,27 +10,13 @@
|
|||
#ifndef hifi_Recording_Impl_FileClip_h
|
||||
#define hifi_Recording_Impl_FileClip_h
|
||||
|
||||
#include "ArrayClip.h"
|
||||
|
||||
#include <mutex>
|
||||
#include "PointerClip.h"
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include "../Frame.h"
|
||||
|
||||
namespace recording {
|
||||
|
||||
struct FileFrameHeader : public FrameHeader {
|
||||
FrameType type;
|
||||
Frame::Time timeOffset;
|
||||
uint16_t size;
|
||||
quint64 fileOffset;
|
||||
};
|
||||
|
||||
using FileFrameHeaderList = std::list<FileFrameHeader>;
|
||||
|
||||
class FileClip : public ArrayClip<FileFrameHeader> {
|
||||
class FileClip : public PointerClip {
|
||||
public:
|
||||
using Pointer = std::shared_ptr<FileClip>;
|
||||
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
163
libraries/recording/src/recording/impl/PointerClip.cpp
Normal file
163
libraries/recording/src/recording/impl/PointerClip.cpp
Normal file
|
@ -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 <algorithm>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
#include <Finally.h>
|
||||
|
||||
#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<FrameType, FrameType>;
|
||||
|
||||
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<FrameType>(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<Frame>();
|
||||
const auto& header = _frames[frameIndex];
|
||||
result->type = header.type;
|
||||
result->timeOffset = header.timeOffset;
|
||||
if (header.size) {
|
||||
result->data.insert(0, reinterpret_cast<char*>(_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");
|
||||
}
|
61
libraries/recording/src/recording/impl/PointerClip.h
Normal file
61
libraries/recording/src/recording/impl/PointerClip.h
Normal file
|
@ -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 <mutex>
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include "../Frame.h"
|
||||
|
||||
namespace recording {
|
||||
|
||||
struct PointerFrameHeader : public FrameHeader {
|
||||
FrameType type;
|
||||
Frame::Time timeOffset;
|
||||
uint16_t size;
|
||||
quint64 fileOffset;
|
||||
};
|
||||
|
||||
using PointerFrameHeaderList = std::list<PointerFrameHeader>;
|
||||
|
||||
class PointerClip : public ArrayClip<PointerFrameHeader> {
|
||||
public:
|
||||
using Pointer = std::shared_ptr<PointerClip>;
|
||||
|
||||
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
|
|
@ -8,14 +8,15 @@
|
|||
|
||||
#include "RecordingScriptingInterface.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <Transform.h>
|
||||
#include <recording/Deck.h>
|
||||
#include <recording/Recorder.h>
|
||||
#include <recording/Clip.h>
|
||||
#include <recording/Frame.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <Transform.h>
|
||||
#include <recording/ClipCache.h>
|
||||
|
||||
#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() {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <recording/Forward.h>
|
||||
|
@ -25,7 +25,7 @@ public:
|
|||
RecordingScriptingInterface();
|
||||
|
||||
public slots:
|
||||
void loadRecording(const QString& filename);
|
||||
void loadRecording(const QString& url);
|
||||
|
||||
void startPlaying();
|
||||
void pausePlayer();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue