merge pull request 1446

This commit is contained in:
Andrew Meadows 2014-01-10 15:55:38 -08:00
parent f0460bac5f
commit d28ed70ca9
38 changed files with 2099 additions and 263 deletions

View file

@ -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})

View file

@ -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;
}
}
}

View file

@ -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() {
}
}
}
}

View file

@ -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;

View 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);
}
}
}

View 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__) */

View 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()

View file

@ -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();
}
}

View file

@ -62,6 +62,7 @@ private:
const char* _voxelServerConfig;
const char* _particleServerConfig;
const char* _metavoxelServerConfig;
bool _hasCompletedRestartHold;
private slots:

View file

@ -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,

View file

@ -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"

View 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);
}
}
}

View 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__) */

View file

@ -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;
}

View file

@ -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__) */

View file

@ -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)

View file

@ -11,7 +11,12 @@
#include "AttributeRegistry.h"
#include "MetavoxelData.h"
AttributeRegistry AttributeRegistry::_instance;
REGISTER_META_OBJECT(QRgbAttribute)
AttributeRegistry* AttributeRegistry::getInstance() {
static AttributeRegistry registry;
return &registry;
}
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);
}

View file

@ -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*)&copy); 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:

View file

@ -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;
}

View file

@ -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__) */

View 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());
}

View 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__) */

View file

@ -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;
}
}

View file

@ -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];
};

View 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__) */

View 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));
}

View 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__) */

View file

@ -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";
}

View file

@ -29,6 +29,7 @@ public:
AgentType,
VoxelServerType,
ParticleServerType,
MetavoxelServerType,
AllTypes
};

View file

@ -11,6 +11,8 @@
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkInterface>
static int hifiSockAddrMetaTypeId = qMetaTypeId<HifiSockAddr>();
HifiSockAddr::HifiSockAddr() :
_address(),
_port(0)

View file

@ -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:

View file

@ -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());

View file

@ -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';

View file

@ -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;

View file

@ -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);

View file

@ -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
View 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
View 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;
}