mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-07 13:12:39 +02:00
merge pull request 1446
This commit is contained in:
parent
f0460bac5f
commit
d28ed70ca9
38 changed files with 2099 additions and 263 deletions
|
@ -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})
|
||||
|
|
|
@ -8,13 +8,15 @@
|
|||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "Agent.h"
|
||||
#include "audio/AudioMixer.h"
|
||||
#include "avatars/AvatarMixer.h"
|
||||
#include <VoxelServer.h>
|
||||
#include <ParticleServer.h>
|
||||
|
||||
#include <VoxelServer.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
156
assignment-client/src/metavoxels/MetavoxelServer.cpp
Normal file
156
assignment-client/src/metavoxels/MetavoxelServer.cpp
Normal file
|
@ -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 <QDateTime>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include <MetavoxelMessages.h>
|
||||
#include <MetavoxelUtil.h>
|
||||
|
||||
#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<ClientStateMessage>();
|
||||
_position = state.position;
|
||||
|
||||
} else if (userType == MetavoxelDeltaMessage::Type) {
|
||||
|
||||
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
}
|
||||
}
|
||||
}
|
102
assignment-client/src/metavoxels/MetavoxelServer.h
Normal file
102
assignment-client/src/metavoxels/MetavoxelServer.h
Normal file
|
@ -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 <QHash>
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
|
||||
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<QUuid, MetavoxelSession*> _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<SendRecord> _sendRecords;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__MetavoxelServer__) */
|
21
cmake/macros/AutoMTC.cmake
Normal file
21
cmake/macros/AutoMTC.cmake
Normal file
|
@ -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()
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ private:
|
|||
|
||||
const char* _voxelServerConfig;
|
||||
const char* _particleServerConfig;
|
||||
const char* _metavoxelServerConfig;
|
||||
|
||||
bool _hasCompletedRestartHold;
|
||||
private slots:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
244
interface/src/MetavoxelSystem.cpp
Normal file
244
interface/src/MetavoxelSystem.cpp
Normal file
|
@ -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 <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include <MetavoxelMessages.h>
|
||||
#include <MetavoxelUtil.h>
|
||||
|
||||
#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<Point>& points) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() <<
|
||||
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>();
|
||||
QRgb normal = info.attributeValues.at(1).getInlineValue<QRgb>();
|
||||
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<unsigned char*>(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);
|
||||
}
|
||||
}
|
||||
}
|
124
interface/src/MetavoxelSystem.h
Normal file
124
interface/src/MetavoxelSystem.h
Normal file
|
@ -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 <QList>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QScriptEngine>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <NodeList.h>
|
||||
|
||||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
|
||||
#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<Point>& points);
|
||||
virtual bool visit(const MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
QVector<Point>& _points;
|
||||
};
|
||||
|
||||
static ProgramObject _program;
|
||||
static int _pointScaleLocation;
|
||||
|
||||
QScriptEngine _scriptEngine;
|
||||
MetavoxelData _data;
|
||||
QVector<Point> _points;
|
||||
PointVisitor _pointVisitor;
|
||||
QOpenGLBuffer _buffer;
|
||||
|
||||
QHash<QUuid, MetavoxelClient*> _clients;
|
||||
QHash<QUuid, MetavoxelClient*> _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<ReceiveRecord> _receiveRecords;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__MetavoxelSystem__) */
|
|
@ -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 <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#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<Point>& points) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() <<
|
||||
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>();
|
||||
QRgb normal = info.attributeValues.at(1).getInlineValue<QRgb>();
|
||||
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;
|
||||
}
|
|
@ -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 <QOpenGLBuffer>
|
||||
#include <QScriptEngine>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <MetavoxelData.h>
|
||||
|
||||
#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<Point>& points);
|
||||
virtual bool visit(const MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
QVector<Point>& _points;
|
||||
};
|
||||
|
||||
static ProgramObject _program;
|
||||
static int _pointScaleLocation;
|
||||
|
||||
QScriptEngine _scriptEngine;
|
||||
MetavoxelData _data;
|
||||
QVector<Point> _points;
|
||||
PointVisitor _pointVisitor;
|
||||
QOpenGLBuffer _buffer;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__MetavoxelSystem__) */
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<QString, AttributePointer> _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<T>(first) == decodeInline<T>(second); }
|
||||
|
||||
virtual void* getDefaultValue() const { return encodeInline(_defaultValue); }
|
||||
|
||||
private:
|
||||
protected:
|
||||
|
||||
T _defaultValue;
|
||||
};
|
||||
|
||||
template<class T, int bits> inline void InlineAttribute<T, bits>::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
value = getDefaultValue();
|
||||
in.read(&value, bits);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, int bits> inline void InlineAttribute<T, bits>::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
out.write(&value, bits);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides merging using the =, ==, += and /= operators.
|
||||
template<class T, int bits = 32> class SimpleInlineAttribute : public InlineAttribute<T, bits> {
|
||||
public:
|
||||
|
@ -198,9 +212,12 @@ template<class T, int bits> inline bool SimpleInlineAttribute<T, bits>::merge(vo
|
|||
|
||||
/// Provides appropriate averaging for RGBA values.
|
||||
class QRgbAttribute : public InlineAttribute<QRgb> {
|
||||
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<T*>(copy)); }
|
||||
virtual void destroy(void* value) const { delete static_cast<T*>(value); }
|
||||
|
||||
virtual bool read(Bitstream& in, void*& value) const { in >> *static_cast<T*>(value); return true; }
|
||||
virtual bool write(Bitstream& out, void* value) const { out << *static_cast<T*>(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<T*>(first) == *static_cast<T*>(second); }
|
||||
|
||||
|
@ -228,6 +245,18 @@ private:
|
|||
T _defaultValue;
|
||||
};
|
||||
|
||||
template<class T> inline void PointerAttribute<T>::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
in.read(value, sizeof(T) * 8);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void PointerAttribute<T>::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
out.write(value, sizeof(T) * 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides merging using the =, ==, += and /= operators.
|
||||
template<class T> class SimplePointerAttribute : public PointerAttribute<T> {
|
||||
public:
|
||||
|
|
|
@ -6,12 +6,68 @@
|
|||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QDataStream>
|
||||
#include <cstring>
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QMetaProperty>
|
||||
#include <QtDebug>
|
||||
|
||||
#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<Attribute*>(object));
|
||||
return *this;
|
||||
}
|
||||
|
||||
QHash<QByteArray, const QMetaObject*>& Bitstream::getMetaObjects() {
|
||||
static QHash<QByteArray, const QMetaObject*> metaObjects;
|
||||
return metaObjects;
|
||||
}
|
||||
|
||||
QHash<int, const TypeStreamer*>& Bitstream::getTypeStreamers() {
|
||||
static QHash<int, const TypeStreamer*> typeStreamers;
|
||||
return typeStreamers;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,14 +9,174 @@
|
|||
#ifndef __interface__Bitstream__
|
||||
#define __interface__Bitstream__
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QHash>
|
||||
#include <QMetaType>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariant>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class QByteArray;
|
||||
class QDataStream;
|
||||
struct QMetaObject;
|
||||
class QObject;
|
||||
|
||||
class Attribute;
|
||||
class Bitstream;
|
||||
class TypeStreamer;
|
||||
|
||||
typedef QSharedPointer<Attribute> 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 T> class RepeatedValueStreamer {
|
||||
public:
|
||||
|
||||
RepeatedValueStreamer(Bitstream& stream) : _stream(stream), _idStreamer(stream),
|
||||
_lastPersistentID(0), _lastTransientOffset(0) { }
|
||||
|
||||
QHash<T, int> getAndResetTransientOffsets();
|
||||
|
||||
void persistTransientOffsets(const QHash<T, int>& transientOffsets);
|
||||
|
||||
QHash<int, T> getAndResetTransientValues();
|
||||
|
||||
void persistTransientValues(const QHash<int, T>& transientValues);
|
||||
|
||||
RepeatedValueStreamer& operator<<(T value);
|
||||
RepeatedValueStreamer& operator>>(T& value);
|
||||
|
||||
private:
|
||||
|
||||
Bitstream& _stream;
|
||||
IDStreamer _idStreamer;
|
||||
int _lastPersistentID;
|
||||
int _lastTransientOffset;
|
||||
QHash<T, int> _persistentIDs;
|
||||
QHash<T, int> _transientOffsets;
|
||||
QHash<int, T> _persistentValues;
|
||||
QHash<int, T> _transientValues;
|
||||
};
|
||||
|
||||
template<class T> inline QHash<T, int> RepeatedValueStreamer<T>::getAndResetTransientOffsets() {
|
||||
QHash<T, int> transientOffsets;
|
||||
_transientOffsets.swap(transientOffsets);
|
||||
_lastTransientOffset = 0;
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
return transientOffsets;
|
||||
}
|
||||
|
||||
template<class T> inline void RepeatedValueStreamer<T>::persistTransientOffsets(const QHash<T, int>& transientOffsets) {
|
||||
int oldLastPersistentID = _lastPersistentID;
|
||||
for (typename QHash<T, int>::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<class T> inline QHash<int, T> RepeatedValueStreamer<T>::getAndResetTransientValues() {
|
||||
QHash<int, T> transientValues;
|
||||
_transientValues.swap(transientValues);
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
return transientValues;
|
||||
}
|
||||
|
||||
template<class T> inline void RepeatedValueStreamer<T>::persistTransientValues(const QHash<int, T>& transientValues) {
|
||||
int oldLastPersistentID = _lastPersistentID;
|
||||
for (typename QHash<int, T>::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<class T> inline RepeatedValueStreamer<T>& RepeatedValueStreamer<T>::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<class T> inline RepeatedValueStreamer<T>& RepeatedValueStreamer<T>::operator>>(T& value) {
|
||||
int id;
|
||||
_idStreamer >> id;
|
||||
if (id <= _lastPersistentID) {
|
||||
value = _persistentValues.value(id);
|
||||
|
||||
} else {
|
||||
int offset = id - _lastPersistentID;
|
||||
typename QHash<int, T>::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<const QMetaObject*, int> metaObjectOffsets;
|
||||
QHash<const TypeStreamer*, int> typeStreamerOffsets;
|
||||
QHash<AttributePointer, int> attributeOffsets;
|
||||
};
|
||||
|
||||
class ReadMappings {
|
||||
public:
|
||||
QHash<int, const QMetaObject*> metaObjectValues;
|
||||
QHash<int, const TypeStreamer*> typeStreamerValues;
|
||||
QHash<int, AttributePointer> 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<AttributePointer>& 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<class T> Bitstream& operator<<(const QList<T>& list);
|
||||
template<class T> Bitstream& operator>>(QList<T>& 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<const QMetaObject*> _metaObjectStreamer;
|
||||
RepeatedValueStreamer<const TypeStreamer*> _typeStreamerStreamer;
|
||||
RepeatedValueStreamer<AttributePointer> _attributeStreamer;
|
||||
|
||||
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
|
||||
static QHash<int, const TypeStreamer*>& getTypeStreamers();
|
||||
};
|
||||
|
||||
template<class T> inline Bitstream& Bitstream::operator<<(const QList<T>& list) {
|
||||
*this << list.size();
|
||||
foreach (const T& entry, list) {
|
||||
*this << entry;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T> inline Bitstream& Bitstream::operator>>(QList<T>& 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 T> class SimpleTypeStreamer : public TypeStreamer {
|
||||
public:
|
||||
|
||||
virtual void write(Bitstream& out, const QVariant& value) const { out << value.value<T>(); }
|
||||
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<x>());
|
||||
|
||||
/// 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<class T> int registerStreamableMetaType() {
|
||||
int type = qRegisterMetaType<T>();
|
||||
Bitstream::registerTypeStreamer(type, new SimpleTypeStreamer<T>());
|
||||
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__) */
|
||||
|
|
179
libraries/metavoxels/src/DatagramSequencer.cpp
Normal file
179
libraries/metavoxels/src/DatagramSequencer.cpp
Normal file
|
@ -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 <cstring>
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
#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<SendRecord>::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<ReceiveRecord>::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());
|
||||
}
|
||||
|
109
libraries/metavoxels/src/DatagramSequencer.h
Normal file
109
libraries/metavoxels/src/DatagramSequencer.h
Normal file
|
@ -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 <QBuffer>
|
||||
#include <QDataStream>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
|
||||
#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<SendRecord> _sendRecords;
|
||||
QList<ReceiveRecord> _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<int> _offsetsReceived;
|
||||
int _remainingBytes;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__DatagramSequencer__) */
|
|
@ -11,11 +11,22 @@
|
|||
|
||||
#include "MetavoxelData.h"
|
||||
|
||||
MetavoxelData::MetavoxelData() {
|
||||
}
|
||||
|
||||
MetavoxelData::MetavoxelData(const MetavoxelData& other) : _roots(other._roots) {
|
||||
incrementRootReferenceCounts();
|
||||
}
|
||||
|
||||
MetavoxelData::~MetavoxelData() {
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::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<AttributePointer, MetavoxelNode*> 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<AttributePointer, MetavoxelNode*>::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<AttributePointer, MetavoxelNode*>::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<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
it.value()->incrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelData::decrementRootReferenceCounts() {
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
#define __interface__MetavoxelData__
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QExplicitlySharedDataPointer>
|
||||
#include <QHash>
|
||||
#include <QSharedData>
|
||||
#include <QScriptString>
|
||||
#include <QScriptValue>
|
||||
#include <QVector>
|
||||
|
@ -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<AttributePointer, MetavoxelNode*> _roots;
|
||||
};
|
||||
|
||||
typedef QExplicitlySharedDataPointer<MetavoxelData> 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];
|
||||
};
|
||||
|
|
32
libraries/metavoxels/src/MetavoxelMessages.h
Normal file
32
libraries/metavoxels/src/MetavoxelMessages.h
Normal file
|
@ -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__) */
|
29
libraries/metavoxels/src/MetavoxelUtil.cpp
Normal file
29
libraries/metavoxels/src/MetavoxelUtil.cpp
Normal file
|
@ -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 <QByteArray>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "MetavoxelUtil.h"
|
||||
|
||||
QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize) {
|
||||
// get the header size
|
||||
int headerSize = numBytesForPacketHeader(reinterpret_cast<const unsigned char*>(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));
|
||||
}
|
23
libraries/metavoxels/src/MetavoxelUtil.h
Normal file
23
libraries/metavoxels/src/MetavoxelUtil.h
Normal file
|
@ -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 <QUuid>
|
||||
|
||||
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__) */
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
AgentType,
|
||||
VoxelServerType,
|
||||
ParticleServerType,
|
||||
MetavoxelServerType,
|
||||
AllTypes
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <QtNetwork/QHostInfo>
|
||||
#include <QtNetwork/QNetworkInterface>
|
||||
|
||||
static int hifiSockAddrMetaTypeId = qMetaTypeId<HifiSockAddr>();
|
||||
|
||||
HifiSockAddr::HifiSockAddr() :
|
||||
_address(),
|
||||
_port(0)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#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);
|
||||
|
|
|
@ -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();
|
||||
|
|
11
tools/mtc/CMakeLists.txt
Normal file
11
tools/mtc/CMakeLists.txt
Normal file
|
@ -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)
|
||||
|
||||
|
180
tools/mtc/src/main.cpp
Normal file
180
tools/mtc/src/main.cpp
Normal file
|
@ -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 <iostream>
|
||||
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QRegExp>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
#include <QtDebug>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Class {
|
||||
public:
|
||||
QString name;
|
||||
QStringList bases;
|
||||
};
|
||||
|
||||
class Streamable {
|
||||
public:
|
||||
Class clazz;
|
||||
QStringList fields;
|
||||
};
|
||||
|
||||
void processInput(QTextStream& in, QList<Streamable>* 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<Streamable>& 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<const " << base << "&>(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<Streamable> 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;
|
||||
}
|
Loading…
Reference in a new issue