From d28ed70ca99c6b23fac9063d49ed7a4e9cb3d6e9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 10 Jan 2014 15:55:38 -0800 Subject: [PATCH] merge pull request 1446 --- assignment-client/CMakeLists.txt | 4 +- assignment-client/src/AssignmentFactory.cpp | 14 +- assignment-client/src/audio/AudioMixer.cpp | 21 +- assignment-client/src/avatars/AvatarMixer.cpp | 17 +- .../src/metavoxels/MetavoxelServer.cpp | 156 ++++++++++ .../src/metavoxels/MetavoxelServer.h | 102 ++++++ cmake/macros/AutoMTC.cmake | 21 ++ domain-server/src/DomainServer.cpp | 18 +- domain-server/src/DomainServer.h | 1 + interface/src/Application.cpp | 10 +- interface/src/Application.h | 2 +- interface/src/MetavoxelSystem.cpp | 244 +++++++++++++++ interface/src/MetavoxelSystem.h | 124 ++++++++ interface/src/renderer/MetavoxelSystem.cpp | 122 -------- interface/src/renderer/MetavoxelSystem.h | 61 ---- libraries/metavoxels/CMakeLists.txt | 3 + .../metavoxels/src/AttributeRegistry.cpp | 9 +- libraries/metavoxels/src/AttributeRegistry.h | 51 ++- libraries/metavoxels/src/Bitstream.cpp | 266 +++++++++++++++- libraries/metavoxels/src/Bitstream.h | 293 +++++++++++++++++- .../metavoxels/src/DatagramSequencer.cpp | 179 +++++++++++ libraries/metavoxels/src/DatagramSequencer.h | 109 +++++++ libraries/metavoxels/src/MetavoxelData.cpp | 184 ++++++++++- libraries/metavoxels/src/MetavoxelData.h | 37 ++- libraries/metavoxels/src/MetavoxelMessages.h | 32 ++ libraries/metavoxels/src/MetavoxelUtil.cpp | 29 ++ libraries/metavoxels/src/MetavoxelUtil.h | 23 ++ libraries/shared/src/Assignment.cpp | 4 + libraries/shared/src/Assignment.h | 1 + libraries/shared/src/HifiSockAddr.cpp | 2 + libraries/shared/src/Node.cpp | 3 + libraries/shared/src/NodeList.cpp | 3 +- libraries/shared/src/NodeTypes.h | 1 + libraries/shared/src/PacketHeaders.h | 1 + libraries/shared/src/ThreadedAssignment.cpp | 23 ++ libraries/shared/src/ThreadedAssignment.h | 1 + tools/mtc/CMakeLists.txt | 11 + tools/mtc/src/main.cpp | 180 +++++++++++ 38 files changed, 2099 insertions(+), 263 deletions(-) create mode 100644 assignment-client/src/metavoxels/MetavoxelServer.cpp create mode 100644 assignment-client/src/metavoxels/MetavoxelServer.h create mode 100644 cmake/macros/AutoMTC.cmake create mode 100644 interface/src/MetavoxelSystem.cpp create mode 100644 interface/src/MetavoxelSystem.h delete mode 100644 interface/src/renderer/MetavoxelSystem.cpp delete mode 100644 interface/src/renderer/MetavoxelSystem.h create mode 100644 libraries/metavoxels/src/DatagramSequencer.cpp create mode 100644 libraries/metavoxels/src/DatagramSequencer.h create mode 100644 libraries/metavoxels/src/MetavoxelMessages.h create mode 100644 libraries/metavoxels/src/MetavoxelUtil.cpp create mode 100644 libraries/metavoxels/src/MetavoxelUtil.h create mode 100644 tools/mtc/CMakeLists.txt create mode 100644 tools/mtc/src/main.cpp diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index c7b8ab5732..491485b264 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -9,12 +9,13 @@ set(MACRO_DIR ${ROOT_DIR}/cmake/macros) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") find_package(Qt5Network REQUIRED) +find_package(Qt5Script REQUIRED) find_package(Qt5Widgets REQUIRED) include(${MACRO_DIR}/SetupHifiProject.cmake) setup_hifi_project(${TARGET_NAME} TRUE) -qt5_use_modules(${TARGET_NAME} Network Widgets) +qt5_use_modules(${TARGET_NAME} Network Script Widgets) # include glm include(${MACRO_DIR}/IncludeGLM.cmake) @@ -28,6 +29,7 @@ link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR}) +link_hifi_library(metavoxels ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(particle-server ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(voxel-server ${TARGET_NAME} ${ROOT_DIR}) diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp index 815186c4f5..b32a909bfe 100644 --- a/assignment-client/src/AssignmentFactory.cpp +++ b/assignment-client/src/AssignmentFactory.cpp @@ -8,13 +8,15 @@ #include -#include "Agent.h" -#include "audio/AudioMixer.h" -#include "avatars/AvatarMixer.h" -#include #include +#include + +#include "Agent.h" #include "AssignmentFactory.h" +#include "audio/AudioMixer.h" +#include "avatars/AvatarMixer.h" +#include "metavoxels/MetavoxelServer.h" ThreadedAssignment* AssignmentFactory::unpackAssignment(const unsigned char* dataBuffer, int numBytes) { int headerBytes = numBytesForPacketHeader(dataBuffer); @@ -33,7 +35,9 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(const unsigned char* dat return new VoxelServer(dataBuffer, numBytes); case Assignment::ParticleServerType: return new ParticleServer(dataBuffer, numBytes); + case Assignment::MetavoxelServerType: + return new MetavoxelServer(dataBuffer, numBytes); default: return NULL; } -} \ No newline at end of file +} diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index d8e0580cc9..e315d366f8 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -239,30 +239,15 @@ void AudioMixer::processDatagram(const QByteArray& dataByteArray, const HifiSock void AudioMixer::run() { + commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NODE_TYPE_AUDIO_MIXER); + NodeList* nodeList = NodeList::getInstance(); - // change the logging target name while this is running - Logging::setTargetName(AUDIO_MIXER_LOGGING_TARGET_NAME); - - nodeList->setOwnerType(NODE_TYPE_AUDIO_MIXER); - const char AUDIO_MIXER_NODE_TYPES_OF_INTEREST[2] = { NODE_TYPE_AGENT, NODE_TYPE_AUDIO_INJECTOR }; nodeList->setNodeTypesOfInterest(AUDIO_MIXER_NODE_TYPES_OF_INTEREST, sizeof(AUDIO_MIXER_NODE_TYPES_OF_INTEREST)); nodeList->linkedDataCreateCallback = attachNewBufferToNode; - QTimer* domainServerTimer = new QTimer(this); - connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); - - QTimer* silentNodeTimer = new QTimer(this); - connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); - - QTimer* pingNodesTimer = new QTimer(this); - connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes())); - pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000); - int nextFrame = 0; timeval startTime; @@ -314,4 +299,4 @@ void AudioMixer::run() { } } -} \ No newline at end of file +} diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 0f72ae9680..f6096fd18a 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -162,28 +162,13 @@ void AvatarMixer::processDatagram(const QByteArray& dataByteArray, const HifiSoc } void AvatarMixer::run() { - // change the logging target name while AvatarMixer is running - Logging::setTargetName(AVATAR_MIXER_LOGGING_NAME); + commonInit(AVATAR_MIXER_LOGGING_NAME, NODE_TYPE_AVATAR_MIXER); NodeList* nodeList = NodeList::getInstance(); - nodeList->setOwnerType(NODE_TYPE_AVATAR_MIXER); - nodeList->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1); nodeList->linkedDataCreateCallback = attachAvatarDataToNode; - QTimer* domainServerTimer = new QTimer(this); - connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); - - QTimer* pingNodesTimer = new QTimer(this); - connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes())); - pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000); - - QTimer* silentNodeTimer = new QTimer(this); - connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); - int nextFrame = 0; timeval startTime; diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp new file mode 100644 index 0000000000..fc586599ce --- /dev/null +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -0,0 +1,156 @@ +// +// MetavoxelServer.cpp +// hifi +// +// Created by Andrzej Kapolka on 12/18/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include + +#include + +#include +#include + +#include "MetavoxelServer.h" + +const int SEND_INTERVAL = 50; + +MetavoxelServer::MetavoxelServer(const unsigned char* dataBuffer, int numBytes) : + ThreadedAssignment(dataBuffer, numBytes), + _data(new MetavoxelData()) { + + _sendTimer.setSingleShot(true); + connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas())); +} + +void MetavoxelServer::removeSession(const QUuid& sessionId) { + delete _sessions.take(sessionId); +} + +void MetavoxelServer::run() { + commonInit("metavoxel-server", NODE_TYPE_METAVOXEL_SERVER); + + _lastSend = QDateTime::currentMSecsSinceEpoch(); + _sendTimer.start(SEND_INTERVAL); +} + +void MetavoxelServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) { + switch (dataByteArray.at(0)) { + case PACKET_TYPE_METAVOXEL_DATA: + processData(dataByteArray, senderSockAddr); + break; + + default: + NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*)dataByteArray.data(), dataByteArray.size()); + break; + } +} + +void MetavoxelServer::sendDeltas() { + // send deltas for all sessions + foreach (MetavoxelSession* session, _sessions) { + session->sendDelta(); + } + + // restart the send timer + qint64 now = QDateTime::currentMSecsSinceEpoch(); + int elapsed = now - _lastSend; + _lastSend = now; + + _sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed)); +} + +void MetavoxelServer::processData(const QByteArray& data, const HifiSockAddr& sender) { + // read the session id + int headerPlusIDSize; + QUuid sessionID = readSessionID(data, sender, headerPlusIDSize); + if (sessionID.isNull()) { + return; + } + + // forward to session, creating if necessary + MetavoxelSession*& session = _sessions[sessionID]; + if (!session) { + session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize)); + } + session->receivedData(data, sender); +} + +MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader) : + QObject(server), + _server(server), + _sessionId(sessionId), + _sequencer(datagramHeader) { + + const int TIMEOUT_INTERVAL = 30 * 1000; + _timeoutTimer.setInterval(TIMEOUT_INTERVAL); + _timeoutTimer.setSingleShot(true); + connect(&_timeoutTimer, SIGNAL(timeout()), SLOT(timedOut())); + + connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&))); + connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&))); + connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int))); + + // insert the baseline send record + SendRecord record = { 0, MetavoxelDataPointer(new MetavoxelData()) }; + _sendRecords.append(record); +} + +void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr& sender) { + // reset the timeout timer + _timeoutTimer.start(); + + // save the most recent sender + _sender = sender; + + // process through sequencer + _sequencer.receivedDatagram(data); +} + +void MetavoxelSession::sendDelta() { + Bitstream& out = _sequencer.startPacket(); + out << QVariant::fromValue(MetavoxelDeltaMessage()); + writeDelta(_server->getData(), _sendRecords.first().data, out); + _sequencer.endPacket(); + + // record the send + SendRecord record = { _sequencer.getOutgoingPacketNumber(), _server->getData() }; + _sendRecords.append(record); +} + +void MetavoxelSession::timedOut() { + qDebug() << "Session timed out [sessionId=" << _sessionId << ", sender=" << _sender << "]\n"; + _server->removeSession(_sessionId); +} + +void MetavoxelSession::sendData(const QByteArray& data) { + NodeList::getInstance()->getNodeSocket().writeDatagram(data, _sender.getAddress(), _sender.getPort()); +} + +void MetavoxelSession::readPacket(Bitstream& in) { + QVariant message; + in >> message; + handleMessage(message, in); +} + +void MetavoxelSession::clearSendRecordsBefore(int index) { + _sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1); +} + +void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) { + int userType = message.userType(); + if (userType == ClientStateMessage::Type) { + ClientStateMessage state = message.value(); + _position = state.position; + + } else if (userType == MetavoxelDeltaMessage::Type) { + + + } else if (userType == QMetaType::QVariantList) { + foreach (const QVariant& element, message.toList()) { + handleMessage(element, in); + } + } +} diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h new file mode 100644 index 0000000000..407c520116 --- /dev/null +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -0,0 +1,102 @@ +// +// MetavoxelServer.h +// hifi +// +// Created by Andrzej Kapolka on 12/18/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__MetavoxelServer__ +#define __hifi__MetavoxelServer__ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +class MetavoxelSession; + +/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates. +class MetavoxelServer : public ThreadedAssignment { + Q_OBJECT + +public: + + MetavoxelServer(const unsigned char* dataBuffer, int numBytes); + + const MetavoxelDataPointer& getData() const { return _data; } + + void removeSession(const QUuid& sessionId); + + virtual void run(); + + virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr); + +private slots: + + void sendDeltas(); + +private: + + void processData(const QByteArray& data, const HifiSockAddr& sender); + + QTimer _sendTimer; + qint64 _lastSend; + + QHash _sessions; + + MetavoxelDataPointer _data; +}; + +/// Contains the state of a single client session. +class MetavoxelSession : public QObject { + Q_OBJECT + +public: + + MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader); + + void receivedData(const QByteArray& data, const HifiSockAddr& sender); + + void sendDelta(); + +private slots: + + void timedOut(); + + void sendData(const QByteArray& data); + + void readPacket(Bitstream& in); + + void clearSendRecordsBefore(int index); + +private: + + void handleMessage(const QVariant& message, Bitstream& in); + + class SendRecord { + public: + int packetNumber; + MetavoxelDataPointer data; + }; + + MetavoxelServer* _server; + QUuid _sessionId; + + QTimer _timeoutTimer; + DatagramSequencer _sequencer; + + HifiSockAddr _sender; + + glm::vec3 _position; + + QList _sendRecords; +}; + +#endif /* defined(__hifi__MetavoxelServer__) */ diff --git a/cmake/macros/AutoMTC.cmake b/cmake/macros/AutoMTC.cmake new file mode 100644 index 0000000000..fca8170e64 --- /dev/null +++ b/cmake/macros/AutoMTC.cmake @@ -0,0 +1,21 @@ +macro(AUTO_MTC TARGET ROOT_DIR) + if (NOT TARGET mtc) + add_subdirectory(${ROOT_DIR}/tools/mtc ${ROOT_DIR}/tools/mtc) + endif (NOT TARGET mtc) + + file(GLOB INCLUDE_FILES src/*.h) + + add_custom_command(OUTPUT ${TARGET}_automtc.cpp COMMAND mtc -o ${TARGET}_automtc.cpp + ${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES}) + + find_package(Qt5Core REQUIRED) + + add_library(${TARGET}_automtc STATIC ${TARGET}_automtc.cpp) + + qt5_use_modules(${TARGET}_automtc Core) + + target_link_libraries(${TARGET} ${TARGET}_automtc) + +endmacro() + + diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ee6caaaf9b..ec1875668a 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -37,6 +37,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())), _staticAssignmentFileData(NULL), _voxelServerConfig(NULL), + _metavoxelServerConfig(NULL), _hasCompletedRestartHold(false) { DomainServer::setDomainServerInstance(this); @@ -55,6 +56,9 @@ DomainServer::DomainServer(int argc, char* argv[]) : const char PARTICLE_CONFIG_OPTION[] = "--particleServerConfig"; _particleServerConfig = getCmdOption(argc, (const char**) argv, PARTICLE_CONFIG_OPTION); + const char METAVOXEL_CONFIG_OPTION[] = "--metavoxelServerConfig"; + _metavoxelServerConfig = getCmdOption(argc, (const char**)argv, METAVOXEL_CONFIG_OPTION); + // setup the mongoose web server struct mg_callbacks callbacks = {}; @@ -152,10 +156,11 @@ void DomainServer::readAvailableDatagrams() { int numBytesPublicSocket = HifiSockAddr::unpackSockAddr(packetData + packetIndex, nodeLocalAddress); packetIndex += numBytesPublicSocket; - const char STATICALLY_ASSIGNED_NODES[3] = { + const char STATICALLY_ASSIGNED_NODES[] = { NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, - NODE_TYPE_VOXEL_SERVER + NODE_TYPE_VOXEL_SERVER, + NODE_TYPE_METAVOXEL_SERVER }; Assignment* matchingStaticAssignment = NULL; @@ -612,6 +617,13 @@ void DomainServer::prepopulateStaticAssignmentFile() { freshStaticAssignments[numFreshStaticAssignments++] = rootParticleServerAssignment; } + // handle metavoxel configuration command line argument + Assignment& metavoxelAssignment = (freshStaticAssignments[numFreshStaticAssignments++] = + Assignment(Assignment::CreateCommand, Assignment::MetavoxelServerType)); + if (_metavoxelServerConfig) { + metavoxelAssignment.setPayload((const unsigned char*)_metavoxelServerConfig, strlen(_metavoxelServerConfig)); + } + qDebug() << "Adding" << numFreshStaticAssignments << "static assignments to fresh file.\n"; _staticAssignmentFile.open(QIODevice::WriteOnly); @@ -787,4 +799,4 @@ void DomainServer::addStaticAssignmentsBackToQueueAfterRestart() { void DomainServer::cleanup() { _staticAssignmentFile.unmap(_staticAssignmentFileData); _staticAssignmentFile.close(); -} \ No newline at end of file +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index e6047fed9a..10cb58b786 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -62,6 +62,7 @@ private: const char* _voxelServerConfig; const char* _particleServerConfig; + const char* _metavoxelServerConfig; bool _hasCompletedRestartHold; private slots: diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5332f20b21..f24366f268 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -208,8 +208,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : #endif // tell the NodeList instance who to tell the domain server we care about - const char nodeTypesOfInterest[] = {NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, NODE_TYPE_VOXEL_SERVER, - NODE_TYPE_PARTICLE_SERVER}; + const char nodeTypesOfInterest[] = {NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, NODE_TYPE_VOXEL_SERVER, + NODE_TYPE_PARTICLE_SERVER, NODE_TYPE_METAVOXEL_SERVER}; nodeList->setNodeTypesOfInterest(nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); QTimer* silentNodeTimer = new QTimer(this); @@ -1281,7 +1281,7 @@ void Application::wheelEvent(QWheelEvent* event) { void Application::sendPingPackets() { const char nodesToPing[] = {NODE_TYPE_VOXEL_SERVER, NODE_TYPE_PARTICLE_SERVER, - NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER}; + NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, NODE_TYPE_METAVOXEL_SERVER}; unsigned char pingPacket[MAX_PACKET_SIZE]; int length = NodeList::getInstance()->fillPingPacket(pingPacket); @@ -4411,6 +4411,10 @@ void* Application::networkReceive(void* args) { app->_voxelProcessor.queueReceivedPacket(senderSockAddr, app->_incomingPacket, bytesReceived); break; } + case PACKET_TYPE_METAVOXEL_DATA: + app->_metavoxels.processData(QByteArray((const char*)app->_incomingPacket, bytesReceived), + senderSockAddr); + break; case PACKET_TYPE_BULK_AVATAR_DATA: NodeList::getInstance()->processBulkNodeData(senderSockAddr, app->_incomingPacket, diff --git a/interface/src/Application.h b/interface/src/Application.h index 6f0463bc88..f8aea3f808 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -37,6 +37,7 @@ #include "Cloud.h" #include "Environment.h" #include "GLCanvas.h" +#include "MetavoxelSystem.h" #include "PacketHeaders.h" #include "PieMenu.h" #include "Stars.h" @@ -59,7 +60,6 @@ #include "renderer/AmbientOcclusionEffect.h" #include "renderer/GeometryCache.h" #include "renderer/GlowEffect.h" -#include "renderer/MetavoxelSystem.h" #include "renderer/PointShader.h" #include "renderer/TextureCache.h" #include "renderer/VoxelShader.h" diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp new file mode 100644 index 0000000000..355b027e93 --- /dev/null +++ b/interface/src/MetavoxelSystem.cpp @@ -0,0 +1,244 @@ +// +// MetavoxelSystem.cpp +// interface +// +// Created by Andrzej Kapolka on 12/10/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include + +#include + +#include +#include + +#include "Application.h" +#include "MetavoxelSystem.h" + +ProgramObject MetavoxelSystem::_program; +int MetavoxelSystem::_pointScaleLocation; + +MetavoxelSystem::MetavoxelSystem() : + _pointVisitor(_points), + _buffer(QOpenGLBuffer::VertexBuffer) { +} + +MetavoxelSystem::~MetavoxelSystem() { + NodeList::getInstance()->removeHook(this); +} + +void MetavoxelSystem::init() { + if (!_program.isLinked()) { + switchToResourcesParentIfRequired(); + _program.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/metavoxel_point.vert"); + _program.link(); + + _pointScaleLocation = _program.uniformLocation("pointScale"); + } + + NodeList::getInstance()->addHook(this); + + AttributeRegistry::getInstance()->configureScriptEngine(&_scriptEngine); + + QFile scriptFile("resources/scripts/sphere.js"); + scriptFile.open(QIODevice::ReadOnly); + QScriptValue guideFunction = _scriptEngine.evaluate(QTextStream(&scriptFile).readAll()); + _data.setAttributeValue(MetavoxelPath(), AttributeValue(AttributeRegistry::getInstance()->getGuideAttribute(), + encodeInline(PolymorphicDataPointer(new ScriptedMetavoxelGuide(guideFunction))))); + + _buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); + _buffer.create(); +} + +void MetavoxelSystem::processData(const QByteArray& data, const HifiSockAddr& sender) { + QMetaObject::invokeMethod(this, "receivedData", Q_ARG(const QByteArray&, data), Q_ARG(const HifiSockAddr&, sender)); +} + +void MetavoxelSystem::simulate(float deltaTime) { + // simulate the clients + foreach (MetavoxelClient* client, _clients) { + client->simulate(deltaTime); + } + + _points.clear(); + _data.guide(_pointVisitor); + + _buffer.bind(); + int bytes = _points.size() * sizeof(Point); + if (_buffer.size() < bytes) { + _buffer.allocate(_points.constData(), bytes); + } else { + _buffer.write(0, _points.constData(), bytes); + } + _buffer.release(); +} + +void MetavoxelSystem::render() { + int viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + const int VIEWPORT_WIDTH_INDEX = 2; + const int VIEWPORT_HEIGHT_INDEX = 3; + float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX]; + float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX]; + float viewportDiagonal = sqrtf(viewportWidth*viewportWidth + viewportHeight*viewportHeight); + float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(), + Application::getInstance()->getViewFrustum()->getNearTopRight()); + + _program.bind(); + _program.setUniformValue(_pointScaleLocation, viewportDiagonal * + Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal); + + _buffer.bind(); + + Point* pt = 0; + glVertexPointer(4, GL_FLOAT, sizeof(Point), &pt->vertex); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Point), &pt->color); + glNormalPointer(GL_BYTE, sizeof(Point), &pt->normal); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); + + glDrawArrays(GL_POINTS, 0, _points.size()); + + glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + + _buffer.release(); + + _program.release(); +} + +void MetavoxelSystem::nodeAdded(Node* node) { + if (node->getType() == NODE_TYPE_METAVOXEL_SERVER) { + QMetaObject::invokeMethod(this, "addClient", Q_ARG(const QUuid&, node->getUUID()), + Q_ARG(const HifiSockAddr&, node->getLocalSocket())); + } +} + +void MetavoxelSystem::nodeKilled(Node* node) { + if (node->getType() == NODE_TYPE_METAVOXEL_SERVER) { + QMetaObject::invokeMethod(this, "removeClient", Q_ARG(const QUuid&, node->getUUID())); + } +} + +void MetavoxelSystem::addClient(const QUuid& uuid, const HifiSockAddr& address) { + MetavoxelClient* client = new MetavoxelClient(address); + _clients.insert(uuid, client); + _clientsBySessionID.insert(client->getSessionID(), client); +} + +void MetavoxelSystem::removeClient(const QUuid& uuid) { + MetavoxelClient* client = _clients.take(uuid); + _clientsBySessionID.remove(client->getSessionID()); + delete client; +} + +void MetavoxelSystem::receivedData(const QByteArray& data, const HifiSockAddr& sender) { + int headerPlusIDSize; + QUuid sessionID = readSessionID(data, sender, headerPlusIDSize); + if (sessionID.isNull()) { + return; + } + MetavoxelClient* client = _clientsBySessionID.value(sessionID); + if (client) { + client->receivedData(data, sender); + } +} + +MetavoxelSystem::PointVisitor::PointVisitor(QVector& points) : + MetavoxelVisitor(QVector() << + AttributeRegistry::getInstance()->getColorAttribute() << + AttributeRegistry::getInstance()->getNormalAttribute()), + _points(points) { +} + +bool MetavoxelSystem::PointVisitor::visit(const MetavoxelInfo& info) { + if (!info.isLeaf) { + return true; + } + QRgb color = info.attributeValues.at(0).getInlineValue(); + QRgb normal = info.attributeValues.at(1).getInlineValue(); + int alpha = qAlpha(color); + if (alpha > 0) { + Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size), + { qRed(color), qGreen(color), qBlue(color), alpha }, { qRed(normal), qGreen(normal), qBlue(normal) } }; + _points.append(point); + } + return false; +} + +static QByteArray createDatagramHeader(const QUuid& sessionID) { + QByteArray header(MAX_PACKET_HEADER_BYTES, 0); + populateTypeAndVersion(reinterpret_cast(header.data()), PACKET_TYPE_METAVOXEL_DATA); + header += sessionID.toRfc4122(); + return header; +} + +MetavoxelClient::MetavoxelClient(const HifiSockAddr& address) : + _address(address), + _sessionID(QUuid::createUuid()), + _sequencer(createDatagramHeader(_sessionID)), + _data(new MetavoxelData()) { + + connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&))); + connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&))); + connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int))); + + // insert the baseline receive record + ReceiveRecord record = { 0, _data }; + _receiveRecords.append(record); +} + +void MetavoxelClient::simulate(float deltaTime) { + Bitstream& out = _sequencer.startPacket(); + ClientStateMessage state = { Application::getInstance()->getCamera()->getPosition() }; + out << QVariant::fromValue(state); + _sequencer.endPacket(); +} + +void MetavoxelClient::receivedData(const QByteArray& data, const HifiSockAddr& sender) { + // save the most recent sender + _address = sender; + + // process through sequencer + _sequencer.receivedDatagram(data); +} + +void MetavoxelClient::sendData(const QByteArray& data) { + NodeList::getInstance()->getNodeSocket().writeDatagram(data, _address.getAddress(), _address.getPort()); +} + +void MetavoxelClient::readPacket(Bitstream& in) { + QVariant message; + in >> message; + handleMessage(message, in); + + // record the receipt + ReceiveRecord record = { _sequencer.getIncomingPacketNumber(), _data }; + _receiveRecords.append(record); +} + +void MetavoxelClient::clearReceiveRecordsBefore(int index) { + _receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1); +} + +void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) { + int userType = message.userType(); + if (userType == MetavoxelDeltaMessage::Type) { + readDelta(_data, _receiveRecords.first().data, in); + + } else if (userType == QMetaType::QVariantList) { + foreach (const QVariant& element, message.toList()) { + handleMessage(element, in); + } + } +} diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h new file mode 100644 index 0000000000..ddb69644d5 --- /dev/null +++ b/interface/src/MetavoxelSystem.h @@ -0,0 +1,124 @@ +// +// MetavoxelSystem.h +// interface +// +// Created by Andrzej Kapolka on 12/10/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__MetavoxelSystem__ +#define __interface__MetavoxelSystem__ + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "renderer/ProgramObject.h" + +class MetavoxelClient; + +/// Renders a metavoxel tree. +class MetavoxelSystem : public QObject, public NodeListHook { + Q_OBJECT + +public: + + MetavoxelSystem(); + ~MetavoxelSystem(); + + void init(); + + void processData(const QByteArray& data, const HifiSockAddr& sender); + + void simulate(float deltaTime); + void render(); + + virtual void nodeAdded(Node* node); + virtual void nodeKilled(Node* node); + +private: + + Q_INVOKABLE void addClient(const QUuid& uuid, const HifiSockAddr& address); + Q_INVOKABLE void removeClient(const QUuid& uuid); + Q_INVOKABLE void receivedData(const QByteArray& data, const HifiSockAddr& sender); + + class Point { + public: + glm::vec4 vertex; + quint8 color[4]; + quint8 normal[3]; + }; + + class PointVisitor : public MetavoxelVisitor { + public: + PointVisitor(QVector& points); + virtual bool visit(const MetavoxelInfo& info); + + private: + QVector& _points; + }; + + static ProgramObject _program; + static int _pointScaleLocation; + + QScriptEngine _scriptEngine; + MetavoxelData _data; + QVector _points; + PointVisitor _pointVisitor; + QOpenGLBuffer _buffer; + + QHash _clients; + QHash _clientsBySessionID; +}; + +/// A client session associated with a single server. +class MetavoxelClient : public QObject { + Q_OBJECT + +public: + + MetavoxelClient(const HifiSockAddr& address); + + const QUuid& getSessionID() const { return _sessionID; } + + void simulate(float deltaTime); + + void receivedData(const QByteArray& data, const HifiSockAddr& sender); + +private slots: + + void sendData(const QByteArray& data); + + void readPacket(Bitstream& in); + + void clearReceiveRecordsBefore(int index); + +private: + + void handleMessage(const QVariant& message, Bitstream& in); + + class ReceiveRecord { + public: + int packetNumber; + MetavoxelDataPointer data; + }; + + HifiSockAddr _address; + QUuid _sessionID; + + DatagramSequencer _sequencer; + + MetavoxelDataPointer _data; + + QList _receiveRecords; +}; + +#endif /* defined(__interface__MetavoxelSystem__) */ diff --git a/interface/src/renderer/MetavoxelSystem.cpp b/interface/src/renderer/MetavoxelSystem.cpp deleted file mode 100644 index 543a8b6301..0000000000 --- a/interface/src/renderer/MetavoxelSystem.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// -// MetavoxelSystem.cpp -// interface -// -// Created by Andrzej Kapolka on 12/10/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// - -#include -#include -#include - -#include - -#include "Application.h" -#include "MetavoxelSystem.h" - -ProgramObject MetavoxelSystem::_program; -int MetavoxelSystem::_pointScaleLocation; - -MetavoxelSystem::MetavoxelSystem() : - _pointVisitor(_points), - _buffer(QOpenGLBuffer::VertexBuffer) { -} - -void MetavoxelSystem::init() { - if (!_program.isLinked()) { - switchToResourcesParentIfRequired(); - _program.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/metavoxel_point.vert"); - _program.link(); - - _pointScaleLocation = _program.uniformLocation("pointScale"); - } - - AttributeRegistry::getInstance()->configureScriptEngine(&_scriptEngine); - - QFile scriptFile("resources/scripts/sphere.js"); - scriptFile.open(QIODevice::ReadOnly); - QScriptValue guideFunction = _scriptEngine.evaluate(QTextStream(&scriptFile).readAll()); - _data.setAttributeValue(MetavoxelPath(), AttributeValue(AttributeRegistry::getInstance()->getGuideAttribute(), - encodeInline(PolymorphicDataPointer(new ScriptedMetavoxelGuide(guideFunction))))); - - _buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); - _buffer.create(); -} - -void MetavoxelSystem::simulate(float deltaTime) { - _points.clear(); - _data.guide(_pointVisitor); - - _buffer.bind(); - int bytes = _points.size() * sizeof(Point); - if (_buffer.size() < bytes) { - _buffer.allocate(_points.constData(), bytes); - } else { - _buffer.write(0, _points.constData(), bytes); - } - _buffer.release(); -} - -void MetavoxelSystem::render() { - int viewport[4]; - glGetIntegerv(GL_VIEWPORT, viewport); - const int VIEWPORT_WIDTH_INDEX = 2; - const int VIEWPORT_HEIGHT_INDEX = 3; - float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX]; - float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX]; - float viewportDiagonal = sqrtf(viewportWidth*viewportWidth + viewportHeight*viewportHeight); - float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(), - Application::getInstance()->getViewFrustum()->getNearTopRight()); - - _program.bind(); - _program.setUniformValue(_pointScaleLocation, viewportDiagonal * - Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal); - - _buffer.bind(); - - Point* pt = 0; - glVertexPointer(4, GL_FLOAT, sizeof(Point), &pt->vertex); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Point), &pt->color); - glNormalPointer(GL_BYTE, sizeof(Point), &pt->normal); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - - glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); - - glDrawArrays(GL_POINTS, 0, _points.size()); - - glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_NORMAL_ARRAY); - - _buffer.release(); - - _program.release(); -} - -MetavoxelSystem::PointVisitor::PointVisitor(QVector& points) : - MetavoxelVisitor(QVector() << - AttributeRegistry::getInstance()->getColorAttribute() << - AttributeRegistry::getInstance()->getNormalAttribute()), - _points(points) { -} - -bool MetavoxelSystem::PointVisitor::visit(const MetavoxelInfo& info) { - if (!info.isLeaf) { - return true; - } - QRgb color = info.attributeValues.at(0).getInlineValue(); - QRgb normal = info.attributeValues.at(1).getInlineValue(); - int alpha = qAlpha(color); - if (alpha > 0) { - Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size), - { qRed(color), qGreen(color), qBlue(color), alpha }, { qRed(normal), qGreen(normal), qBlue(normal) } }; - _points.append(point); - } - return false; -} diff --git a/interface/src/renderer/MetavoxelSystem.h b/interface/src/renderer/MetavoxelSystem.h deleted file mode 100644 index b8617d99a4..0000000000 --- a/interface/src/renderer/MetavoxelSystem.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// MetavoxelSystem.h -// interface -// -// Created by Andrzej Kapolka on 12/10/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// - -#ifndef __interface__MetavoxelSystem__ -#define __interface__MetavoxelSystem__ - -#include -#include -#include - -#include - -#include - -#include "ProgramObject.h" - -/// Renders a metavoxel tree. -class MetavoxelSystem { -public: - - MetavoxelSystem(); - - void init(); - - void simulate(float deltaTime); - void render(); - -private: - - class Point { - public: - glm::vec4 vertex; - quint8 color[4]; - quint8 normal[3]; - }; - - class PointVisitor : public MetavoxelVisitor { - public: - PointVisitor(QVector& points); - virtual bool visit(const MetavoxelInfo& info); - - private: - QVector& _points; - }; - - static ProgramObject _program; - static int _pointScaleLocation; - - QScriptEngine _scriptEngine; - MetavoxelData _data; - QVector _points; - PointVisitor _pointVisitor; - QOpenGLBuffer _buffer; -}; - -#endif /* defined(__interface__MetavoxelSystem__) */ diff --git a/libraries/metavoxels/CMakeLists.txt b/libraries/metavoxels/CMakeLists.txt index 0f9c1c695c..98b2baf7ac 100644 --- a/libraries/metavoxels/CMakeLists.txt +++ b/libraries/metavoxels/CMakeLists.txt @@ -13,6 +13,9 @@ find_package(Qt5Widgets REQUIRED) include(${MACRO_DIR}/SetupHifiLibrary.cmake) setup_hifi_library(${TARGET_NAME}) +include(${MACRO_DIR}/AutoMTC.cmake) +auto_mtc(${TARGET_NAME} ${ROOT_DIR}) + qt5_use_modules(${TARGET_NAME} Widgets Script) include(${MACRO_DIR}/IncludeGLM.cmake) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 5fb03035da..9a1f220034 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -11,7 +11,12 @@ #include "AttributeRegistry.h" #include "MetavoxelData.h" -AttributeRegistry AttributeRegistry::_instance; +REGISTER_META_OBJECT(QRgbAttribute) + +AttributeRegistry* AttributeRegistry::getInstance() { + static AttributeRegistry registry; + return ®istry; +} AttributeRegistry::AttributeRegistry() : _guideAttribute(registerAttribute(new PolymorphicAttribute("guide", PolymorphicDataPointer(new DefaultMetavoxelGuide())))), @@ -36,7 +41,7 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute } QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) { - return engine->newQObject(_instance.getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership, + return engine->newQObject(getInstance()->getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership, QScriptEngine::PreferExistingWrapperObject); } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index a5f9c08f8b..4f2f1d79b2 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -32,7 +32,7 @@ class AttributeRegistry { public: /// Returns a pointer to the singleton registry instance. - static AttributeRegistry* getInstance() { return &_instance; } + static AttributeRegistry* getInstance(); AttributeRegistry(); @@ -65,8 +65,6 @@ private: static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine); - static AttributeRegistry _instance; - QHash _attributes; AttributePointer _guideAttribute; AttributePointer _colorAttribute; @@ -140,8 +138,11 @@ public: virtual void* create(void* copy) const = 0; virtual void destroy(void* value) const = 0; - virtual bool read(Bitstream& in, void*& value) const = 0; - virtual bool write(Bitstream& out, void* value) const = 0; + virtual void read(Bitstream& in, void*& value, bool isLeaf) const = 0; + virtual void write(Bitstream& out, void* value, bool isLeaf) const = 0; + + virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); } + virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { write(out, value, isLeaf); } virtual bool equal(void* first, void* second) const = 0; @@ -163,18 +164,31 @@ public: virtual void* create(void* copy) const { void* value; new (&value) T(*(T*)©); return value; } virtual void destroy(void* value) const { ((T*)&value)->~T(); } - virtual bool read(Bitstream& in, void*& value) const { value = getDefaultValue(); in.read(&value, bits); return false; } - virtual bool write(Bitstream& out, void* value) const { out.write(&value, bits); return false; } + virtual void read(Bitstream& in, void*& value, bool isLeaf) const; + virtual void write(Bitstream& out, void* value, bool isLeaf) const; virtual bool equal(void* first, void* second) const { return decodeInline(first) == decodeInline(second); } virtual void* getDefaultValue() const { return encodeInline(_defaultValue); } -private: +protected: T _defaultValue; }; +template inline void InlineAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { + if (isLeaf) { + value = getDefaultValue(); + in.read(&value, bits); + } +} + +template inline void InlineAttribute::write(Bitstream& out, void* value, bool isLeaf) const { + if (isLeaf) { + out.write(&value, bits); + } +} + /// Provides merging using the =, ==, += and /= operators. template class SimpleInlineAttribute : public InlineAttribute { public: @@ -198,9 +212,12 @@ template inline bool SimpleInlineAttribute::merge(vo /// Provides appropriate averaging for RGBA values. class QRgbAttribute : public InlineAttribute { + Q_OBJECT + Q_PROPERTY(int defaultValue MEMBER _defaultValue) + public: - QRgbAttribute(const QString& name, QRgb defaultValue = QRgb()); + Q_INVOKABLE QRgbAttribute(const QString& name = QString(), QRgb defaultValue = QRgb()); virtual bool merge(void*& parent, void* children[]) const; @@ -216,8 +233,8 @@ public: virtual void* create(void* copy) const { new T(*static_cast(copy)); } virtual void destroy(void* value) const { delete static_cast(value); } - virtual bool read(Bitstream& in, void*& value) const { in >> *static_cast(value); return true; } - virtual bool write(Bitstream& out, void* value) const { out << *static_cast(value); return true; } + virtual void read(Bitstream& in, void*& value, bool isLeaf) const; + virtual void write(Bitstream& out, void* value, bool isLeaf) const; virtual bool equal(void* first, void* second) const { return *static_cast(first) == *static_cast(second); } @@ -228,6 +245,18 @@ private: T _defaultValue; }; +template inline void PointerAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { + if (isLeaf) { + in.read(value, sizeof(T) * 8); + } +} + +template inline void PointerAttribute::write(Bitstream& out, void* value, bool isLeaf) const { + if (isLeaf) { + out.write(value, sizeof(T) * 8); + } +} + /// Provides merging using the =, ==, += and /= operators. template class SimplePointerAttribute : public PointerAttribute { public: diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index ac91bdf767..e1ab79c974 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -6,12 +6,68 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // -#include +#include +#include +#include +#include + +#include "AttributeRegistry.h" #include "Bitstream.h" -Bitstream::Bitstream(QDataStream& underlying) - : _underlying(underlying), _byte(0), _position(0) { +REGISTER_SIMPLE_TYPE_STREAMER(QByteArray) +REGISTER_SIMPLE_TYPE_STREAMER(QString) +REGISTER_SIMPLE_TYPE_STREAMER(QVariantList) +REGISTER_SIMPLE_TYPE_STREAMER(bool) +REGISTER_SIMPLE_TYPE_STREAMER(int) + +IDStreamer::IDStreamer(Bitstream& stream) : + _stream(stream), + _bits(1) { +} + +void IDStreamer::setBitsFromValue(int value) { + _bits = 1; + while (value >= (1 << _bits) - 1) { + _bits++; + } +} + +IDStreamer& IDStreamer::operator<<(int value) { + _stream.write(&value, _bits); + if (value == (1 << _bits) - 1) { + _bits++; + } + return *this; +} + +IDStreamer& IDStreamer::operator>>(int& value) { + value = 0; + _stream.read(&value, _bits); + if (value == (1 << _bits) - 1) { + _bits++; + } + return *this; +} + +int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) { + getMetaObjects().insert(className, metaObject); + return 0; +} + +int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) { + streamer->setType(type); + getTypeStreamers().insert(type, streamer); + return 0; +} + +Bitstream::Bitstream(QDataStream& underlying) : + _underlying(underlying), + _byte(0), + _position(0), + _metaObjectStreamer(*this), + _typeStreamerStreamer(*this), + _attributeStreamer(*this) { } const int BITS_IN_BYTE = 8; @@ -41,7 +97,8 @@ Bitstream& Bitstream::read(void* data, int bits, int offset) { _underlying >> _byte; } int bitsToRead = qMin(BITS_IN_BYTE - _position, qMin(BITS_IN_BYTE - offset, bits)); - *dest |= ((_byte >> _position) & ((1 << bitsToRead) - 1)) << offset; + int mask = ((1 << bitsToRead) - 1) << offset; + *dest = (*dest & ~mask) | (((_byte >> _position) << offset) & mask); _position = (_position + bitsToRead) & LAST_BIT_POSITION; if ((offset += bitsToRead) == BITS_IN_BYTE) { dest++; @@ -55,11 +112,40 @@ Bitstream& Bitstream::read(void* data, int bits, int offset) { void Bitstream::flush() { if (_position != 0) { _underlying << _byte; - _byte = 0; - _position = 0; + reset(); } } +void Bitstream::reset() { + _byte = 0; + _position = 0; +} + +Bitstream::WriteMappings Bitstream::getAndResetWriteMappings() { + WriteMappings mappings = { _metaObjectStreamer.getAndResetTransientOffsets(), + _typeStreamerStreamer.getAndResetTransientOffsets(), + _attributeStreamer.getAndResetTransientOffsets() }; + return mappings; +} + +void Bitstream::persistWriteMappings(const WriteMappings& mappings) { + _metaObjectStreamer.persistTransientOffsets(mappings.metaObjectOffsets); + _typeStreamerStreamer.persistTransientOffsets(mappings.typeStreamerOffsets); + _attributeStreamer.persistTransientOffsets(mappings.attributeOffsets); +} + +Bitstream::ReadMappings Bitstream::getAndResetReadMappings() { + ReadMappings mappings = { _metaObjectStreamer.getAndResetTransientValues(), + _typeStreamerStreamer.getAndResetTransientValues(), + _attributeStreamer.getAndResetTransientValues() }; + return mappings; +} + +void Bitstream::persistReadMappings(const ReadMappings& mappings) { + _metaObjectStreamer.persistTransientValues(mappings.metaObjectValues); + _typeStreamerStreamer.persistTransientValues(mappings.typeStreamerValues); + _attributeStreamer.persistTransientValues(mappings.attributeValues); +} Bitstream& Bitstream::operator<<(bool value) { if (value) { @@ -79,3 +165,171 @@ Bitstream& Bitstream::operator>>(bool& value) { _position = (_position + 1) & LAST_BIT_POSITION; return *this; } + +Bitstream& Bitstream::operator<<(int value) { + return write(&value, 32); +} + +Bitstream& Bitstream::operator>>(int& value) { + qint32 sizedValue; + read(&sizedValue, 32); + value = sizedValue; + return *this; +} + +Bitstream& Bitstream::operator<<(float value) { + return write(&value, 32); +} + +Bitstream& Bitstream::operator>>(float& value) { + return read(&value, 32); +} + +Bitstream& Bitstream::operator<<(const glm::vec3& value) { + return *this << value.x << value.y << value.z; +} + +Bitstream& Bitstream::operator>>(glm::vec3& value) { + return *this >> value.x >> value.y >> value.z; +} + +Bitstream& Bitstream::operator<<(const QByteArray& string) { + *this << string.size(); + return write(string.constData(), string.size() * BITS_IN_BYTE); +} + +Bitstream& Bitstream::operator>>(QByteArray& string) { + int size; + *this >> size; + string.resize(size); + return read(string.data(), size * BITS_IN_BYTE); +} + +Bitstream& Bitstream::operator<<(const QString& string) { + *this << string.size(); + return write(string.constData(), string.size() * sizeof(QChar) * BITS_IN_BYTE); +} + +Bitstream& Bitstream::operator>>(QString& string) { + int size; + *this >> size; + string.resize(size); + return read(string.data(), size * sizeof(QChar) * BITS_IN_BYTE); +} + +Bitstream& Bitstream::operator<<(const QVariant& value) { + const TypeStreamer* streamer = getTypeStreamers().value(value.userType()); + if (streamer) { + _typeStreamerStreamer << streamer; + streamer->write(*this, value); + } else { + qWarning() << "Non-streamable type: " << value.typeName() << "\n"; + } + return *this; +} + +Bitstream& Bitstream::operator>>(QVariant& value) { + const TypeStreamer* streamer; + _typeStreamerStreamer >> streamer; + if (streamer) { + value = streamer->read(*this); + } + return *this; +} + +Bitstream& Bitstream::operator<<(const QObject* object) { + if (!object) { + _metaObjectStreamer << NULL; + return *this; + } + const QMetaObject* metaObject = object->metaObject(); + _metaObjectStreamer << metaObject; + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored(object)) { + continue; + } + const TypeStreamer* streamer = getTypeStreamers().value(property.userType()); + if (streamer) { + streamer->write(*this, property.read(object)); + } + } + return *this; +} + +Bitstream& Bitstream::operator>>(QObject*& object) { + const QMetaObject* metaObject; + _metaObjectStreamer >> metaObject; + if (!metaObject) { + object = NULL; + return *this; + } + object = metaObject->newInstance(); + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored(object)) { + continue; + } + const TypeStreamer* streamer = getTypeStreamers().value(property.userType()); + if (streamer) { + property.write(object, streamer->read(*this)); + } + } + return *this; +} + +Bitstream& Bitstream::operator<<(const QMetaObject* metaObject) { + return *this << (metaObject ? QByteArray::fromRawData( + metaObject->className(), strlen(metaObject->className())) : QByteArray()); +} + +Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) { + QByteArray className; + *this >> className; + if (className.isEmpty()) { + metaObject = NULL; + return *this; + } + metaObject = getMetaObjects().value(className); + if (!metaObject) { + qWarning() << "Unknown class name: " << className << "\n"; + } + return *this; +} + +Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) { + const char* typeName = QMetaType::typeName(streamer->getType()); + return *this << QByteArray::fromRawData(typeName, strlen(typeName)); +} + +Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) { + QByteArray typeName; + *this >> typeName; + streamer = getTypeStreamers().value(QMetaType::type(typeName.constData())); + if (!streamer) { + qWarning() << "Unknown type name: " << typeName << "\n"; + } + return *this; +} + +Bitstream& Bitstream::operator<<(const AttributePointer& attribute) { + return *this << (QObject*)attribute.data(); +} + +Bitstream& Bitstream::operator>>(AttributePointer& attribute) { + QObject* object; + *this >> object; + attribute = AttributeRegistry::getInstance()->registerAttribute(static_cast(object)); + return *this; +} + +QHash& Bitstream::getMetaObjects() { + static QHash metaObjects; + return metaObjects; +} + +QHash& Bitstream::getTypeStreamers() { + static QHash typeStreamers; + return typeStreamers; +} + diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 12a1b88886..6709d4d03b 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -9,14 +9,174 @@ #ifndef __interface__Bitstream__ #define __interface__Bitstream__ -#include +#include +#include +#include +#include +#include +#include + +class QByteArray; class QDataStream; +struct QMetaObject; +class QObject; + +class Attribute; +class Bitstream; +class TypeStreamer; + +typedef QSharedPointer AttributePointer; + +/// Streams integer identifiers that conform to the following pattern: each ID encountered in the stream is either one that +/// has been sent (received) before, or is one more than the highest previously encountered ID (starting at zero). This allows +/// us to use the minimum number of bits to encode the IDs. +class IDStreamer { +public: + + IDStreamer(Bitstream& stream); + + void setBitsFromValue(int value); + + IDStreamer& operator<<(int value); + IDStreamer& operator>>(int& value); + +private: + + Bitstream& _stream; + int _bits; +}; + +/// Provides a means to stream repeated values efficiently. The value is first streamed along with a unique ID. When +/// subsequently streamed, only the ID is sent. +template class RepeatedValueStreamer { +public: + + RepeatedValueStreamer(Bitstream& stream) : _stream(stream), _idStreamer(stream), + _lastPersistentID(0), _lastTransientOffset(0) { } + + QHash getAndResetTransientOffsets(); + + void persistTransientOffsets(const QHash& transientOffsets); + + QHash getAndResetTransientValues(); + + void persistTransientValues(const QHash& transientValues); + + RepeatedValueStreamer& operator<<(T value); + RepeatedValueStreamer& operator>>(T& value); + +private: + + Bitstream& _stream; + IDStreamer _idStreamer; + int _lastPersistentID; + int _lastTransientOffset; + QHash _persistentIDs; + QHash _transientOffsets; + QHash _persistentValues; + QHash _transientValues; +}; + +template inline QHash RepeatedValueStreamer::getAndResetTransientOffsets() { + QHash transientOffsets; + _transientOffsets.swap(transientOffsets); + _lastTransientOffset = 0; + _idStreamer.setBitsFromValue(_lastPersistentID); + return transientOffsets; +} + +template inline void RepeatedValueStreamer::persistTransientOffsets(const QHash& transientOffsets) { + int oldLastPersistentID = _lastPersistentID; + for (typename QHash::const_iterator it = transientOffsets.constBegin(); it != transientOffsets.constEnd(); it++) { + int id = oldLastPersistentID + it.value(); + _lastPersistentID = qMax(_lastPersistentID, id); + _persistentIDs.insert(it.key(), id); + } + _idStreamer.setBitsFromValue(_lastPersistentID); +} + +template inline QHash RepeatedValueStreamer::getAndResetTransientValues() { + QHash transientValues; + _transientValues.swap(transientValues); + _idStreamer.setBitsFromValue(_lastPersistentID); + return transientValues; +} + +template inline void RepeatedValueStreamer::persistTransientValues(const QHash& transientValues) { + int oldLastPersistentID = _lastPersistentID; + for (typename QHash::const_iterator it = transientValues.constBegin(); it != transientValues.constEnd(); it++) { + int id = oldLastPersistentID + it.key(); + _lastPersistentID = qMax(_lastPersistentID, id); + _persistentValues.insert(id, it.value()); + } + _idStreamer.setBitsFromValue(_lastPersistentID); +} + +template inline RepeatedValueStreamer& RepeatedValueStreamer::operator<<(T value) { + int id = _persistentIDs.value(value); + if (id == 0) { + int& offset = _transientOffsets[value]; + if (offset == 0) { + _idStreamer << (_lastPersistentID + (offset = ++_lastTransientOffset)); + _stream << value; + + } else { + _idStreamer << (_lastPersistentID + offset); + } + } else { + _idStreamer << id; + } + return *this; +} + +template inline RepeatedValueStreamer& RepeatedValueStreamer::operator>>(T& value) { + int id; + _idStreamer >> id; + if (id <= _lastPersistentID) { + value = _persistentValues.value(id); + + } else { + int offset = id - _lastPersistentID; + typename QHash::iterator it = _transientValues.find(offset); + if (it == _transientValues.end()) { + _stream >> value; + _transientValues.insert(offset, value); + + } else { + value = *it; + } + } + return *this; +} /// A stream for bit-aligned data. class Bitstream { public: + class WriteMappings { + public: + QHash metaObjectOffsets; + QHash typeStreamerOffsets; + QHash attributeOffsets; + }; + + class ReadMappings { + public: + QHash metaObjectValues; + QHash typeStreamerValues; + QHash attributeValues; + }; + + /// Registers a metaobject under its name so that instances of it can be streamed. + /// \return zero; the function only returns a value so that it can be used in static initialization + static int registerMetaObject(const char* className, const QMetaObject* metaObject); + + /// Registers a streamer for the specified Qt-registered type. + /// \return zero; the function only returns a value so that it can be used in static initialization + static int registerTypeStreamer(int type, TypeStreamer* streamer); + + /// Creates a new bitstream. Note: the stream may be used for reading or writing, but not both. Bitstream(QDataStream& underlying); /// Writes a set of bits to the underlying stream. @@ -32,14 +192,145 @@ public: /// Flushes any unwritten bits to the underlying stream. void flush(); + /// Resets to the initial state. + void reset(); + + /// Returns a reference to the attribute streamer. + RepeatedValueStreamer& getAttributeStreamer() { return _attributeStreamer; } + + /// Returns the set of transient mappings gathered during writing and resets them. + WriteMappings getAndResetWriteMappings(); + + /// Persists a set of write mappings recorded earlier. + void persistWriteMappings(const WriteMappings& mappings); + + /// Returns the set of transient mappings gathered during reading and resets them. + ReadMappings getAndResetReadMappings(); + + /// Persists a set of read mappings recorded earlier. + void persistReadMappings(const ReadMappings& mappings); + Bitstream& operator<<(bool value); Bitstream& operator>>(bool& value); + Bitstream& operator<<(int value); + Bitstream& operator>>(int& value); + + Bitstream& operator<<(float value); + Bitstream& operator>>(float& value); + + Bitstream& operator<<(const glm::vec3& value); + Bitstream& operator>>(glm::vec3& value); + + Bitstream& operator<<(const QByteArray& string); + Bitstream& operator>>(QByteArray& string); + + Bitstream& operator<<(const QString& string); + Bitstream& operator>>(QString& string); + + Bitstream& operator<<(const QVariant& value); + Bitstream& operator>>(QVariant& value); + + template Bitstream& operator<<(const QList& list); + template Bitstream& operator>>(QList& list); + + Bitstream& operator<<(const QObject* object); + Bitstream& operator>>(QObject*& object); + + Bitstream& operator<<(const QMetaObject* metaObject); + Bitstream& operator>>(const QMetaObject*& metaObject); + + Bitstream& operator<<(const TypeStreamer* streamer); + Bitstream& operator>>(const TypeStreamer*& streamer); + + Bitstream& operator<<(const AttributePointer& attribute); + Bitstream& operator>>(AttributePointer& attribute); + private: QDataStream& _underlying; quint8 _byte; int _position; + + RepeatedValueStreamer _metaObjectStreamer; + RepeatedValueStreamer _typeStreamerStreamer; + RepeatedValueStreamer _attributeStreamer; + + static QHash& getMetaObjects(); + static QHash& getTypeStreamers(); }; +template inline Bitstream& Bitstream::operator<<(const QList& list) { + *this << list.size(); + foreach (const T& entry, list) { + *this << entry; + } + return *this; +} + +template inline Bitstream& Bitstream::operator>>(QList& list) { + int size; + *this >> size; + list.clear(); + list.reserve(size); + for (int i = 0; i < size; i++) { + T entry; + *this >> entry; + list.append(entry); + } + return *this; +} + +/// Macro for registering streamable meta-objects. +#define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject); + +/// Interface for objects that can write values to and read values from bitstreams. +class TypeStreamer { +public: + + void setType(int type) { _type = type; } + int getType() const { return _type; } + + virtual void write(Bitstream& out, const QVariant& value) const = 0; + virtual QVariant read(Bitstream& in) const = 0; + +private: + + int _type; +}; + +/// A streamer that works with Bitstream's operators. +template class SimpleTypeStreamer : public TypeStreamer { +public: + + virtual void write(Bitstream& out, const QVariant& value) const { out << value.value(); } + virtual QVariant read(Bitstream& in) const { T value; in >> value; return QVariant::fromValue(value); } +}; + +/// Macro for registering simple type streamers. +#define REGISTER_SIMPLE_TYPE_STREAMER(x) static int x##Streamer = \ + Bitstream::registerTypeStreamer(QMetaType::type(#x), new SimpleTypeStreamer()); + +/// Declares the metatype and the streaming operators. The last lines +/// ensure that the generated file will be included in the link phase. +#define STRINGIFY(x) #x +#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \ + Bitstream& operator<<(Bitstream& out, const X& obj); \ + Bitstream& operator>>(Bitstream& in, X& obj); \ + static const int* _TypePtr##X = &X::Type; \ + _Pragma(STRINGIFY(unused(_TypePtr##X))) + +/// Registers a streamable type and its streamer. +template int registerStreamableMetaType() { + int type = qRegisterMetaType(); + Bitstream::registerTypeStreamer(type, new SimpleTypeStreamer()); + return type; +} + +/// Flags a class as streamable (use as you would Q_OBJECT). +#define STREAMABLE public: static const int Type; private: + +/// Flags a field or base class as streaming. +#define STREAM + #endif /* defined(__interface__Bitstream__) */ diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp new file mode 100644 index 0000000000..82ea27dfab --- /dev/null +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -0,0 +1,179 @@ +// +// DatagramSequencer.cpp +// metavoxels +// +// Created by Andrzej Kapolka on 12/20/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include + +#include + +#include "DatagramSequencer.h" + +const int MAX_DATAGRAM_SIZE = 1500; + +DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader) : + _outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly), + _outputStream(_outgoingPacketStream), + _incomingDatagramStream(&_incomingDatagramBuffer), + _datagramHeaderSize(datagramHeader.size()), + _outgoingPacketNumber(0), + _outgoingDatagram(MAX_DATAGRAM_SIZE, 0), + _outgoingDatagramBuffer(&_outgoingDatagram), + _outgoingDatagramStream(&_outgoingDatagramBuffer), + _incomingPacketNumber(0), + _incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly), + _inputStream(_incomingPacketStream) { + + _outgoingPacketStream.setByteOrder(QDataStream::LittleEndian); + _incomingDatagramStream.setByteOrder(QDataStream::LittleEndian); + _incomingPacketStream.setByteOrder(QDataStream::LittleEndian); + _outgoingDatagramStream.setByteOrder(QDataStream::LittleEndian); + + memcpy(_outgoingDatagram.data(), datagramHeader.constData(), _datagramHeaderSize); +} + +/// Simple RAII-style object to keep a device open when in scope. +class QIODeviceOpener { +public: + + QIODeviceOpener(QIODevice* device, QIODevice::OpenMode mode) : _device(device) { _device->open(mode); } + ~QIODeviceOpener() { _device->close(); } + +private: + + QIODevice* _device; +}; + +Bitstream& DatagramSequencer::startPacket() { + // start with the list of acknowledgements + _outgoingPacketStream << (quint32)_receiveRecords.size(); + foreach (const ReceiveRecord& record, _receiveRecords) { + _outgoingPacketStream << (quint32)record.packetNumber; + } + return _outputStream; +} + +void DatagramSequencer::endPacket() { + _outputStream.flush(); + sendPacket(QByteArray::fromRawData(_outgoingPacketData.constData(), _outgoingPacketStream.device()->pos())); + _outgoingPacketStream.device()->seek(0); +} + +void DatagramSequencer::receivedDatagram(const QByteArray& datagram) { + _incomingDatagramBuffer.setData(datagram.constData() + _datagramHeaderSize, datagram.size() - _datagramHeaderSize); + QIODeviceOpener opener(&_incomingDatagramBuffer, QIODevice::ReadOnly); + + // read the sequence number + quint32 sequenceNumber; + _incomingDatagramStream >> sequenceNumber; + + // if it's less than the last, ignore + if (sequenceNumber < _incomingPacketNumber) { + return; + } + + // read the size and offset + quint32 packetSize, offset; + _incomingDatagramStream >> packetSize >> offset; + + // if it's greater, reset + if (sequenceNumber > _incomingPacketNumber) { + _incomingPacketNumber = sequenceNumber; + _incomingPacketData.resize(packetSize); + _offsetsReceived.clear(); + _offsetsReceived.insert(offset); + _remainingBytes = packetSize; + + } else { + // make sure it's not a duplicate + if (_offsetsReceived.contains(offset)) { + return; + } + _offsetsReceived.insert(offset); + } + + // copy in the data + memcpy(_incomingPacketData.data() + offset, _incomingDatagramBuffer.data().constData() + _incomingDatagramBuffer.pos(), + _incomingDatagramBuffer.bytesAvailable()); + + // see if we're done + if ((_remainingBytes -= _incomingDatagramBuffer.bytesAvailable()) > 0) { + return; + } + + // read the list of acknowledged packets + quint32 acknowledgementCount; + _incomingPacketStream >> acknowledgementCount; + for (int i = 0; i < acknowledgementCount; i++) { + quint32 packetNumber; + _incomingPacketStream >> packetNumber; + if (_sendRecords.isEmpty()) { + continue; + } + int index = packetNumber - _sendRecords.first().packetNumber; + if (index < 0 || index >= _sendRecords.size()) { + continue; + } + QList::iterator it = _sendRecords.begin() + index; + sendRecordAcknowledged(*it); + emit sendAcknowledged(index); + _sendRecords.erase(_sendRecords.begin(), it + 1); + } + + emit readyToRead(_inputStream); + _incomingPacketStream.device()->seek(0); + _inputStream.reset(); + + // record the receipt + ReceiveRecord record = { _incomingPacketNumber, _inputStream.getAndResetReadMappings() }; + _receiveRecords.append(record); +} + +void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { + // stop acknowledging the recorded packets (TODO: replace with interpolation search?) + ReceiveRecord compare = { record.lastReceivedPacketNumber }; + QList::iterator it = qBinaryFind(_receiveRecords.begin(), _receiveRecords.end(), compare); + if (it != _receiveRecords.end()) { + _inputStream.persistReadMappings(it->mappings); + emit receiveAcknowledged(it - _receiveRecords.begin()); + _receiveRecords.erase(_receiveRecords.begin(), it + 1); + } + _outputStream.persistWriteMappings(record.mappings); +} + +void DatagramSequencer::sendPacket(const QByteArray& packet) { + QIODeviceOpener opener(&_outgoingDatagramBuffer, QIODevice::WriteOnly); + + // increment the packet number + _outgoingPacketNumber++; + + // record the send + SendRecord record = { _outgoingPacketNumber, _receiveRecords.isEmpty() ? 0 : _receiveRecords.last().packetNumber, + _outputStream.getAndResetWriteMappings() }; + _sendRecords.append(record); + + // write the sequence number and size, which are the same between all fragments + _outgoingDatagramBuffer.seek(_datagramHeaderSize); + _outgoingDatagramStream << (quint32)_outgoingPacketNumber; + _outgoingDatagramStream << (quint32)packet.size(); + int initialPosition = _outgoingDatagramBuffer.pos(); + + // break the packet into MTU-sized datagrams + int offset = 0; + do { + _outgoingDatagramBuffer.seek(initialPosition); + _outgoingDatagramStream << (quint32)offset; + + int payloadSize = qMin((int)(_outgoingDatagram.size() - _outgoingDatagramBuffer.pos()), packet.size() - offset); + memcpy(_outgoingDatagram.data() + _outgoingDatagramBuffer.pos(), packet.constData() + offset, payloadSize); + + emit readyToWrite(QByteArray::fromRawData(_outgoingDatagram.constData(), _outgoingDatagramBuffer.pos() + payloadSize)); + + offset += payloadSize; + + } while(offset < packet.size()); +} + diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h new file mode 100644 index 0000000000..e5e4b8c4be --- /dev/null +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -0,0 +1,109 @@ +// +// DatagramSequencer.h +// metavoxels +// +// Created by Andrzej Kapolka on 12/20/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__DatagramSequencer__ +#define __interface__DatagramSequencer__ + +#include +#include +#include +#include +#include + +#include "Bitstream.h" + +/// Performs simple datagram sequencing, packet fragmentation and reassembly. +class DatagramSequencer : public QObject { + Q_OBJECT + +public: + + DatagramSequencer(const QByteArray& datagramHeader = QByteArray()); + + /// Returns the packet number of the last packet sent. + int getOutgoingPacketNumber() const { return _outgoingPacketNumber; } + + /// Returns the packet number of the last packet received (or the packet currently being assembled). + int getIncomingPacketNumber() const { return _incomingPacketNumber; } + + /// Starts a new packet for transmission. + /// \return a reference to the Bitstream to use for writing to the packet + Bitstream& startPacket(); + + /// Sends the packet currently being written. + void endPacket(); + + /// Processes a datagram received from the other party, emitting readyToRead when the entire packet + /// has been successfully assembled. + void receivedDatagram(const QByteArray& datagram); + +signals: + + /// Emitted when a datagram is ready to be transmitted. + void readyToWrite(const QByteArray& datagram); + + /// Emitted when a packet is available to read. + void readyToRead(Bitstream& input); + + /// Emitted when a sent packet has been acknowledged by the remote side. + /// \param index the index of the packet in our list of send records + void sendAcknowledged(int index); + + /// Emitted when our acknowledgement of a received packet has been acknowledged by the remote side. + /// \param index the index of the packet in our list of receive records + void receiveAcknowledged(int index); + +private: + + class SendRecord { + public: + int packetNumber; + int lastReceivedPacketNumber; + Bitstream::WriteMappings mappings; + }; + + class ReceiveRecord { + public: + int packetNumber; + Bitstream::ReadMappings mappings; + + bool operator<(const ReceiveRecord& other) const { return packetNumber < other.packetNumber; } + }; + + /// Notes that the described send was acknowledged by the other party. + void sendRecordAcknowledged(const SendRecord& record); + + /// Sends a packet to the other party, fragmenting it into multiple datagrams (and emitting + /// readyToWrite) as necessary. + void sendPacket(const QByteArray& packet); + + QList _sendRecords; + QList _receiveRecords; + + QByteArray _outgoingPacketData; + QDataStream _outgoingPacketStream; + Bitstream _outputStream; + + QBuffer _incomingDatagramBuffer; + QDataStream _incomingDatagramStream; + int _datagramHeaderSize; + + int _outgoingPacketNumber; + QByteArray _outgoingDatagram; + QBuffer _outgoingDatagramBuffer; + QDataStream _outgoingDatagramStream; + + int _incomingPacketNumber; + QByteArray _incomingPacketData; + QDataStream _incomingPacketStream; + Bitstream _inputStream; + QSet _offsetsReceived; + int _remainingBytes; +}; + +#endif /* defined(__interface__DatagramSequencer__) */ diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 636982b63a..849d85960e 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -11,11 +11,22 @@ #include "MetavoxelData.h" +MetavoxelData::MetavoxelData() { +} + +MetavoxelData::MetavoxelData(const MetavoxelData& other) : _roots(other._roots) { + incrementRootReferenceCounts(); +} + MetavoxelData::~MetavoxelData() { - for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { - it.value()->destroy(it.key()); - delete it.value(); - } + decrementRootReferenceCounts(); +} + +MetavoxelData& MetavoxelData::operator=(const MetavoxelData& other) { + decrementRootReferenceCounts(); + _roots = other._roots; + incrementRootReferenceCounts(); + return *this; } void MetavoxelData::guide(MetavoxelVisitor& visitor) { @@ -43,8 +54,7 @@ void MetavoxelData::setAttributeValue(const MetavoxelPath& path, const Attribute node = new MetavoxelNode(attributeValue.getAttribute()); } if (node->setAttributeValue(path, 0, attributeValue) && attributeValue.isDefault()) { - node->destroy(attributeValue.getAttribute()); - delete node; + node->decrementReferenceCount(attributeValue.getAttribute()); _roots.remove(attributeValue.getAttribute()); } } @@ -64,7 +74,76 @@ AttributeValue MetavoxelData::getAttributeValue(const MetavoxelPath& path, const return node->getAttributeValue(attribute); } -MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue) { +void MetavoxelData::read(Bitstream& in) { + // save the old roots and clear + QHash oldRoots = _roots; + _roots.clear(); + + // read in the new roots, reusing old ones where appropriate + qint32 rootCount; + in >> rootCount; + for (int i = 0; i < rootCount; i++) { + AttributePointer attribute; + in.getAttributeStreamer() >> attribute; + MetavoxelNode* root = oldRoots.take(attribute); + if (!root) { + root = new MetavoxelNode(attribute); + } + _roots.insert(attribute, root); + root->read(attribute, in); + } + + // clear out the remaining old roots + for (QHash::const_iterator it = oldRoots.constBegin(); it != oldRoots.constEnd(); it++) { + it.value()->decrementReferenceCount(it.key()); + } +} + +void MetavoxelData::write(Bitstream& out) const { + out << (qint32)_roots.size(); + for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { + out.getAttributeStreamer() << it.key(); + it.value()->write(it.key(), out); + } +} + +void MetavoxelData::readDelta(const MetavoxelData& reference, Bitstream& in) { +} + +void MetavoxelData::writeDelta(const MetavoxelData& reference, Bitstream& out) const { +} + +void MetavoxelData::incrementRootReferenceCounts() { + for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { + it.value()->incrementReferenceCount(); + } +} + +void MetavoxelData::decrementRootReferenceCounts() { + for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { + it.value()->decrementReferenceCount(it.key()); + } +} + +void writeDelta(const MetavoxelDataPointer& data, const MetavoxelDataPointer& reference, Bitstream& out) { + if (data == reference) { + out << false; + return; + } + out << true; + data->writeDelta(*reference, out); +} + +void readDelta(MetavoxelDataPointer& data, const MetavoxelDataPointer& reference, Bitstream& in) { + bool changed; + in >> changed; + if (changed) { + data.detach(); + data->readDelta(*reference, in); + } +} + +MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue) : _referenceCount(1) { _attributeValue = attributeValue.copy(); for (int i = 0; i < CHILD_COUNT; i++) { _children[i] = NULL; @@ -118,12 +197,96 @@ bool MetavoxelNode::isLeaf() const { return true; } +void MetavoxelNode::read(const AttributePointer& attribute, Bitstream& in) { + bool leaf; + in >> leaf; + attribute->read(in, _attributeValue, leaf); + if (leaf) { + clearChildren(attribute); + + } else { + void* childValues[CHILD_COUNT]; + for (int i = 0; i < CHILD_COUNT; i++) { + if (!_children[i]) { + _children[i] = new MetavoxelNode(attribute); + } + _children[i]->read(attribute, in); + childValues[i] = _children[i]->_attributeValue; + } + attribute->merge(_attributeValue, childValues); + } +} + +void MetavoxelNode::write(const AttributePointer& attribute, Bitstream& out) const { + bool leaf = isLeaf(); + out << leaf; + attribute->write(out, _attributeValue, leaf); + if (!leaf) { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i]->write(attribute, out); + } + } +} + +void MetavoxelNode::readDelta(const AttributePointer& attribute, const MetavoxelNode& reference, Bitstream& in) { + bool different; + in >> different; + if (!different) { + return; + } + bool leaf; + in >> leaf; + attribute->readDelta(in, _attributeValue, reference._attributeValue, leaf); + if (leaf) { + clearChildren(attribute); + + } else { + if (reference.isLeaf()) { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i]->read(attribute, in); + } + } else { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i]->readDelta(attribute, *reference._children[i], in); + } + } + } +} + +void MetavoxelNode::writeDelta(const AttributePointer& attribute, const MetavoxelNode& reference, Bitstream& out) const { + if (this == &reference) { + out << false; + return; + } + out << true; + bool leaf = isLeaf(); + out << leaf; + attribute->writeDelta(out, _attributeValue, reference._attributeValue, leaf); + if (!leaf) { + if (reference.isLeaf()) { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i]->write(attribute, out); + } + } else { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i]->writeDelta(attribute, *reference._children[i], out); + } + } + } +} + +void MetavoxelNode::decrementReferenceCount(const AttributePointer& attribute) { + if (--_referenceCount == 0) { + destroy(attribute); + delete this; + } +} + void MetavoxelNode::destroy(const AttributePointer& attribute) { attribute->destroy(_attributeValue); for (int i = 0; i < CHILD_COUNT; i++) { if (_children[i]) { - _children[i]->destroy(attribute); - delete _children[i]; + _children[i]->decrementReferenceCount(attribute); } } } @@ -131,8 +294,7 @@ void MetavoxelNode::destroy(const AttributePointer& attribute) { void MetavoxelNode::clearChildren(const AttributePointer& attribute) { for (int i = 0; i < CHILD_COUNT; i++) { if (_children[i]) { - _children[i]->destroy(attribute); - delete _children[i]; + _children[i]->decrementReferenceCount(attribute); _children[i] = NULL; } } diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index fc7045cff4..d588c8a687 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -10,7 +10,9 @@ #define __interface__MetavoxelData__ #include +#include #include +#include #include #include #include @@ -27,11 +29,15 @@ class MetavoxelVisitation; class MetavoxelVisitor; /// The base metavoxel representation shared between server and client. -class MetavoxelData { +class MetavoxelData : public QSharedData { public: + MetavoxelData(); + MetavoxelData(const MetavoxelData& other); ~MetavoxelData(); + MetavoxelData& operator=(const MetavoxelData& other); + /// Applies the specified visitor to the contained voxels. void guide(MetavoxelVisitor& visitor); @@ -41,11 +47,26 @@ public: /// Retrieves the attribute value corresponding to the specified path. AttributeValue getAttributeValue(const MetavoxelPath& path, const AttributePointer& attribute) const; + void read(Bitstream& in); + void write(Bitstream& out) const; + + void readDelta(const MetavoxelData& reference, Bitstream& in); + void writeDelta(const MetavoxelData& reference, Bitstream& out) const; + private: + void incrementRootReferenceCounts(); + void decrementRootReferenceCounts(); + QHash _roots; }; +typedef QExplicitlySharedDataPointer MetavoxelDataPointer; + +void writeDelta(const MetavoxelDataPointer& data, const MetavoxelDataPointer& reference, Bitstream& out); + +void readDelta(MetavoxelDataPointer& data, const MetavoxelDataPointer& reference, Bitstream& in); + /// A single node within a metavoxel layer. class MetavoxelNode { public: @@ -69,6 +90,19 @@ public: bool isLeaf() const; + void read(const AttributePointer& attribute, Bitstream& in); + void write(const AttributePointer& attribute, Bitstream& out) const; + + void readDelta(const AttributePointer& attribute, const MetavoxelNode& reference, Bitstream& in); + void writeDelta(const AttributePointer& attribute, const MetavoxelNode& reference, Bitstream& out) const; + + /// Increments the node's reference count. + void incrementReferenceCount() { _referenceCount++; } + + /// Decrements the node's reference count. If the resulting reference count is zero, destroys the node + /// and calls delete this. + void decrementReferenceCount(const AttributePointer& attribute); + void destroy(const AttributePointer& attribute); private: @@ -76,6 +110,7 @@ private: void clearChildren(const AttributePointer& attribute); + int _referenceCount; void* _attributeValue; MetavoxelNode* _children[CHILD_COUNT]; }; diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h new file mode 100644 index 0000000000..3951e16d22 --- /dev/null +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -0,0 +1,32 @@ +// +// MetavoxelMessages.h +// metavoxels +// +// Created by Andrzej Kapolka on 12/31/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__MetavoxelMessages__ +#define __interface__MetavoxelMessages__ + +#include "Bitstream.h" + +/// A message containing the state of a client. +class ClientStateMessage { + STREAMABLE + +public: + + STREAM glm::vec3 position; +}; + +DECLARE_STREAMABLE_METATYPE(ClientStateMessage) + +/// A message preceding metavoxel delta information. The actual delta will follow it in the stream. +class MetavoxelDeltaMessage { + STREAMABLE +}; + +DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaMessage) + +#endif /* defined(__interface__MetavoxelMessages__) */ diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp new file mode 100644 index 0000000000..29b8c57168 --- /dev/null +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -0,0 +1,29 @@ +// +// MetavoxelUtil.cpp +// metavoxels +// +// Created by Andrzej Kapolka on 12/30/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include + +#include +#include + +#include "MetavoxelUtil.h" + +QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize) { + // get the header size + int headerSize = numBytesForPacketHeader(reinterpret_cast(data.constData())); + + // read the session id + const int UUID_BYTES = 16; + headerPlusIDSize = headerSize + UUID_BYTES; + if (data.size() < headerPlusIDSize) { + qWarning() << "Metavoxel data too short [size=" << data.size() << ", sender=" << sender << "]\n"; + return QUuid(); + } + return QUuid::fromRfc4122(QByteArray::fromRawData(data.constData() + headerSize, UUID_BYTES)); +} diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h new file mode 100644 index 0000000000..f7e0edbadf --- /dev/null +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -0,0 +1,23 @@ +// +// MetavoxelUtil.h +// metavoxels +// +// Created by Andrzej Kapolka on 12/30/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__MetavoxelUtil__ +#define __interface__MetavoxelUtil__ + +#include + +class QByteArray; + +class HifiSockAddr; + +/// Reads and returns the session ID from a datagram. +/// \param[out] headerPlusIDSize the size of the header (including the session ID) within the data +/// \return the session ID, or a null ID if invalid (in which case a warning will be logged) +QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize); + +#endif /* defined(__interface__MetavoxelUtil__) */ diff --git a/libraries/shared/src/Assignment.cpp b/libraries/shared/src/Assignment.cpp index 4943098812..a0168e4bf0 100644 --- a/libraries/shared/src/Assignment.cpp +++ b/libraries/shared/src/Assignment.cpp @@ -29,6 +29,8 @@ Assignment::Type Assignment::typeForNodeType(NODE_TYPE nodeType) { return Assignment::VoxelServerType; case NODE_TYPE_PARTICLE_SERVER: return Assignment::ParticleServerType; + case NODE_TYPE_METAVOXEL_SERVER: + return Assignment::MetavoxelServerType; default: return Assignment::AllTypes; } @@ -180,6 +182,8 @@ const char* Assignment::getTypeName() const { return "voxel-server"; case Assignment::ParticleServerType: return "particle-server"; + case Assignment::MetavoxelServerType: + return "metavoxel-server"; default: return "unknown"; } diff --git a/libraries/shared/src/Assignment.h b/libraries/shared/src/Assignment.h index 1aac273e36..5a7a21c112 100644 --- a/libraries/shared/src/Assignment.h +++ b/libraries/shared/src/Assignment.h @@ -29,6 +29,7 @@ public: AgentType, VoxelServerType, ParticleServerType, + MetavoxelServerType, AllTypes }; diff --git a/libraries/shared/src/HifiSockAddr.cpp b/libraries/shared/src/HifiSockAddr.cpp index 0a6f5dce2d..55c950c580 100644 --- a/libraries/shared/src/HifiSockAddr.cpp +++ b/libraries/shared/src/HifiSockAddr.cpp @@ -11,6 +11,8 @@ #include #include +static int hifiSockAddrMetaTypeId = qMetaTypeId(); + HifiSockAddr::HifiSockAddr() : _address(), _port(0) diff --git a/libraries/shared/src/Node.cpp b/libraries/shared/src/Node.cpp index 70379801c6..739c1c03a9 100644 --- a/libraries/shared/src/Node.cpp +++ b/libraries/shared/src/Node.cpp @@ -52,6 +52,7 @@ Node::~Node() { const char* NODE_TYPE_NAME_DOMAIN = "Domain"; const char* NODE_TYPE_NAME_VOXEL_SERVER = "Voxel Server"; const char* NODE_TYPE_NAME_PARTICLE_SERVER = "Particle Server"; +const char* NODE_TYPE_NAME_METAVOXEL_SERVER = "Metavoxel Server"; const char* NODE_TYPE_NAME_AGENT = "Agent"; const char* NODE_TYPE_NAME_AUDIO_MIXER = "Audio Mixer"; const char* NODE_TYPE_NAME_AVATAR_MIXER = "Avatar Mixer"; @@ -68,6 +69,8 @@ const char* Node::getTypeName() const { return NODE_TYPE_NAME_VOXEL_SERVER; case NODE_TYPE_PARTICLE_SERVER: return NODE_TYPE_NAME_PARTICLE_SERVER; + case NODE_TYPE_METAVOXEL_SERVER: + return NODE_TYPE_NAME_METAVOXEL_SERVER; case NODE_TYPE_AGENT: return NODE_TYPE_NAME_AGENT; case NODE_TYPE_AUDIO_MIXER: diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index c763a52e05..0a50a659b2 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -703,7 +703,8 @@ Node* NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, node->lock(); if (node->getType() == NODE_TYPE_AUDIO_MIXER || - node->getType() == NODE_TYPE_VOXEL_SERVER) { + node->getType() == NODE_TYPE_VOXEL_SERVER || + node->getType() == NODE_TYPE_METAVOXEL_SERVER) { // until the Audio class also uses our nodeList, we need to update // the lastRecvTimeUsecs for the audio mixer so it doesn't get killed and re-added continously node->setLastHeardMicrostamp(usecTimestampNow()); diff --git a/libraries/shared/src/NodeTypes.h b/libraries/shared/src/NodeTypes.h index c9723f4477..37e0503bab 100644 --- a/libraries/shared/src/NodeTypes.h +++ b/libraries/shared/src/NodeTypes.h @@ -20,6 +20,7 @@ typedef char NODE_TYPE; const NODE_TYPE NODE_TYPE_DOMAIN = 'D'; const NODE_TYPE NODE_TYPE_VOXEL_SERVER = 'V'; const NODE_TYPE NODE_TYPE_PARTICLE_SERVER = 'P'; +const NODE_TYPE NODE_TYPE_METAVOXEL_SERVER = 'm'; const NODE_TYPE NODE_TYPE_ENVIRONMENT_SERVER = 'E'; const NODE_TYPE NODE_TYPE_AGENT = 'I'; const NODE_TYPE NODE_TYPE_AUDIO_MIXER = 'M'; diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index a59ae5e5e5..d1a2b501f6 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -51,6 +51,7 @@ const PACKET_TYPE PACKET_TYPE_PARTICLE_DATA = 'v'; const PACKET_TYPE PACKET_TYPE_PARTICLE_ADD_OR_EDIT = 'a'; const PACKET_TYPE PACKET_TYPE_PARTICLE_ERASE = 'x'; const PACKET_TYPE PACKET_TYPE_PARTICLE_ADD_RESPONSE = 'b'; +const PACKET_TYPE PACKET_TYPE_METAVOXEL_DATA = 't'; typedef char PACKET_VERSION; diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/shared/src/ThreadedAssignment.cpp index 095999adca..b16c180c9d 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/shared/src/ThreadedAssignment.cpp @@ -6,6 +6,9 @@ // Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // +#include + +#include "Logging.h" #include "ThreadedAssignment.h" ThreadedAssignment::ThreadedAssignment(const unsigned char* dataBuffer, int numBytes) : @@ -23,6 +26,26 @@ void ThreadedAssignment::setFinished(bool isFinished) { } } +void ThreadedAssignment::commonInit(const char* targetName, NODE_TYPE nodeType) { + // change the logging target name while the assignment is running + Logging::setTargetName(targetName); + + NodeList* nodeList = NodeList::getInstance(); + nodeList->setOwnerType(nodeType); + + QTimer* domainServerTimer = new QTimer(this); + connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); + domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); + + QTimer* pingNodesTimer = new QTimer(this); + connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes())); + pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000); + + QTimer* silentNodeTimer = new QTimer(this); + connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); +} + void ThreadedAssignment::checkInWithDomainServerOrExit() { if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { setFinished(true); diff --git a/libraries/shared/src/ThreadedAssignment.h b/libraries/shared/src/ThreadedAssignment.h index 2d6e49b724..0dfd8a320f 100644 --- a/libraries/shared/src/ThreadedAssignment.h +++ b/libraries/shared/src/ThreadedAssignment.h @@ -23,6 +23,7 @@ public slots: virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) = 0; protected: + void commonInit(const char* targetName, NODE_TYPE nodeType); bool _isFinished; private slots: void checkInWithDomainServerOrExit(); diff --git a/tools/mtc/CMakeLists.txt b/tools/mtc/CMakeLists.txt new file mode 100644 index 0000000000..95cb95d573 --- /dev/null +++ b/tools/mtc/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.8) + +set(TARGET_NAME mtc) + +set(ROOT_DIR ../..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +include(${MACRO_DIR}/SetupHifiProject.cmake) +setup_hifi_project(${TARGET_NAME} TRUE) + + diff --git a/tools/mtc/src/main.cpp b/tools/mtc/src/main.cpp new file mode 100644 index 0000000000..77f0a069b5 --- /dev/null +++ b/tools/mtc/src/main.cpp @@ -0,0 +1,180 @@ +// +// main.cpp +// mtc +// +// Created by Andrzej Kapolka on 12/31/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +class Class { +public: + QString name; + QStringList bases; +}; + +class Streamable { +public: + Class clazz; + QStringList fields; +}; + +void processInput(QTextStream& in, QList* streamables) { + Class clazz; + Streamable currentStreamable; + + QRegExp exp( + "(/\\*.*\\*/)|" // multi-line comments + "(//.*\n)|" // single-line comments + "(\\s*#.*\n)|" // preprocessor definitions + "(\\s*STREAMABLE\\s+)|" // STREAMABLE tag for classes + "(\\s*STREAM\\s+.*;)|" // STREAM tag for fields + "(\\s*class\\s+[^;]+\\{)" // class definition + ); + exp.setMinimal(true); + + QRegExp classExp("class (\\w+) ?:?([^:]*)\\{"); + + // read in the entire input and look for matches with our expression + QString all = in.readAll(); + for (int off = 0; (off = exp.indexIn(all, off)) != -1; off += exp.matchedLength()) { + QString match = exp.cap().simplified(); + if (match.startsWith("/*") || match.startsWith("//") || match.startsWith('#')) { + continue; // comment, preprocessor definition + } + if (match.startsWith("STREAMABLE")) { + if (clazz.name.isEmpty()) { + cerr << "Found STREAMABLE marker before class definition." << endl; + continue; + } + if (!currentStreamable.clazz.name.isEmpty()) { + streamables->append(currentStreamable); + } + currentStreamable.clazz = clazz; + currentStreamable.fields.clear(); + + } else if (match.startsWith("STREAM")) { + match.chop(1); // get rid of the semicolon + match = match.trimmed(); // and any space before it + currentStreamable.fields.append(match.mid(match.lastIndexOf(' ') + 1)); + + } else { // match.startsWith("class") + classExp.exactMatch(match); + clazz.name = classExp.cap(1); + clazz.bases.clear(); + foreach (const QString& bstr, classExp.cap(2).split(',')) { + QString base = bstr.trimmed(); + if (!base.isEmpty() && base.startsWith("STREAM")) { + clazz.bases.append(base.mid(base.lastIndexOf(' ') + 1)); + } + } + } + } + if (!currentStreamable.clazz.name.isEmpty()) { + streamables->append(currentStreamable); + } +} + +void generateOutput (QTextStream& out, const QList& streamables) { + foreach (const Streamable& str, streamables) { + const QString& name = str.clazz.name; + + out << "Bitstream& operator<< (Bitstream& out, const " << name << "& obj) {\n"; + foreach (const QString& base, str.clazz.bases) { + out << " out << static_cast(obj);\n"; + } + foreach (const QString& field, str.fields) { + out << " out << obj." << field << ";\n"; + } + out << " return out;\n"; + out << "}\n"; + + out << "Bitstream& operator>> (Bitstream& in, " << name << "& obj) {\n"; + foreach (const QString& base, str.clazz.bases) { + out << " in >> static_cast<" << base << "&>(obj);\n"; + } + foreach (const QString& field, str.fields) { + out << " in >> obj." << field << ";\n"; + } + out << " return in;\n"; + out << "}\n"; + + out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n\n"; + } +} + +int main (int argc, char** argv) { + // process the command line arguments + QStringList inputs; + QString output; + for (int ii = 1; ii < argc; ii++) { + QString arg(argv[ii]); + if (!arg.startsWith('-')) { + inputs.append(arg); + continue; + } + QStringRef name = arg.midRef(1); + if (name == "o") { + if (++ii == argc) { + cerr << "Missing file name argument for -o" << endl; + return 1; + } + output = argv[ii]; + + } else { + cerr << "Unknown option " << arg.toStdString() << endl; + return 1; + } + } + if (inputs.isEmpty()) { + cerr << "Usage: mtc [OPTION]... input files" << endl; + cerr << "Where options include:" << endl; + cerr << " -o filename: Send output to filename rather than standard output." << endl; + return 0; + } + + QList streamables; + foreach (const QString& input, inputs) { + QFile ifile(input); + if (!ifile.open(QIODevice::ReadOnly | QIODevice::Text)) { + cerr << ("Couldn't open " + input + ": " + ifile.errorString()).toStdString() << endl; + continue; + } + QTextStream istream(&ifile); + int oldSize = streamables.size(); + processInput(istream, &streamables); + if (streamables.size() == oldSize) { + // no streamables; remove from list + inputs.removeOne(input); + } + } + + QFile ofile(output); + if (output.isNull()) { + ofile.open(stdout, QIODevice::WriteOnly | QIODevice::Text); + + } else if (!ofile.open(QIODevice::WriteOnly | QIODevice::Text)) { + cerr << ("Couldn't open " + output + ": " + ofile.errorString()).toStdString() << endl; + return 1; + } + + QTextStream ostream(&ofile); + ostream << "// generated by mtc\n"; + foreach (const QString& input, inputs) { + ostream << "#include \"" << input << "\"\n"; + } + generateOutput(ostream, streamables); + + return 0; +}