From 7a9c0f0e6a504038bf7d19ef616ab4c5d3054945 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 19 Jun 2014 14:50:19 -0700 Subject: [PATCH] Working on tests for metavoxel streaming. --- libraries/metavoxels/src/MetavoxelData.cpp | 69 +++++++++ libraries/metavoxels/src/MetavoxelData.h | 13 +- tests/metavoxels/src/MetavoxelTests.cpp | 164 +++++++++++++++++---- tests/metavoxels/src/MetavoxelTests.h | 16 +- 4 files changed, 229 insertions(+), 33 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 503dcce8d2..cf645e6f5e 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -604,10 +604,79 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, const MetavoxelLO void MetavoxelData::readIncrementalDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, Bitstream& in, const MetavoxelLOD& lod) { + + // shallow copy the reference + *this = reference; + + // if nothing changed, return + bool changed; + in >> changed; + if (!changed) { + return; + } + + // expand if the size has changed + bool sizeChanged; + in >> sizeChanged; + if (sizeChanged) { + float size; + in >> size; + while (_size < size) { + expand(); + } + } + + // read the nodes removed + forever { + AttributePointer attribute; + in >> attribute; + if (!attribute) { + break; + } + _roots.take(attribute)->decrementReferenceCount(attribute); + } } void MetavoxelData::writeIncrementalDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, Bitstream& out, const MetavoxelLOD& lod) const { + + // first things first: there might be no change whatsoever + glm::vec3 minimum = getMinimum(); + bool becameSubdivided = lod.becameSubdivided(minimum, _size, referenceLOD); + if (_size == reference._size && _roots == reference._roots && !becameSubdivided) { + out << false; + return; + } + out << true; + + // compare the size; if changed (rare), we must compare to the expanded reference + const MetavoxelData* expandedReference = &reference; + if (_size == reference._size) { + out << false; + } else { + out << true; + out << _size; + + MetavoxelData* expanded = new MetavoxelData(reference); + while (expanded->_size < _size) { + expanded->expand(); + } + expandedReference = expanded; + } + + // write the nodes removed + for (QHash::const_iterator it = expandedReference->_roots.constBegin(); + it != expandedReference->_roots.constEnd(); it++) { + if (!_roots.contains(it.key())) { + out << it.key(); + } + } + out << AttributePointer(); + + // delete the expanded reference if we had to expand + if (expandedReference != &reference) { + delete expandedReference; + } } MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 199b6a8aff..6b262d6892 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -34,7 +34,8 @@ class NetworkValue; class Spanner; class SpannerRenderer; -/// Determines whether to subdivide each node when traversing. +/// Determines whether to subdivide each node when traversing. Contains the position (presumed to be of the viewer) and a +/// threshold value, where lower thresholds cause smaller/more distant voxels to be subdivided. class MetavoxelLOD { STREAMABLE @@ -46,6 +47,7 @@ public: bool isValid() const { return threshold > 0.0f; } + /// Checks whether, according to this LOD, we should subdivide the described voxel. bool shouldSubdivide(const glm::vec3& minimum, float size, float multiplier = 1.0f) const; /// Checks whether the node or any of the nodes underneath it have had subdivision enabled as compared to the reference. @@ -78,20 +80,25 @@ public: /// Applies the specified visitor to the contained voxels. void guide(MetavoxelVisitor& visitor); + /// Inserts a spanner into the specified attribute layer. void insert(const AttributePointer& attribute, const SharedObjectPointer& object); void insert(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object); + /// Removes a spanner from the specified attribute layer. void remove(const AttributePointer& attribute, const SharedObjectPointer& object); void remove(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object); + /// Toggles the existence of a spanner in the specified attribute layer (removes if present, adds if not). void toggle(const AttributePointer& attribute, const SharedObjectPointer& object); void toggle(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object); + /// Replaces a spanner in the specified attribute layer. void replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject, const SharedObjectPointer& newObject); void replace(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& oldObject, const SharedObjectPointer& newObject); - + + /// Clears all data in the specified attribute layer. void clear(const AttributePointer& attribute); /// Convenience function that finds the first spanner intersecting the provided ray. @@ -101,7 +108,7 @@ public: /// Sets part of the data. void set(const glm::vec3& minimum, const MetavoxelData& data, bool blend = false); - /// Expands the tree, increasing its capacity in all dimensions. + /// Expands the tree, doubling its size in all dimensions (that is, increasing its volume eightfold). void expand(); void read(Bitstream& in, const MetavoxelLOD& lod = MetavoxelLOD()); diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index c9bce27cc3..daee1165f6 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -321,44 +321,76 @@ static bool testSerialization(Bitstream::MetadataType metadataType) { } bool MetavoxelTests::run() { - - qDebug() << "Running transmission tests..."; - qDebug(); - // seed the random number generator so that our tests are reproducible srand(0xBAAAAABE); - // create two endpoints with the same header + // check for an optional command line argument specifying a single test + QStringList arguments = this->arguments(); + int test = (arguments.size() > 1) ? arguments.at(1).toInt() : 0; + QByteArray datagramHeader("testheader"); - Endpoint alice(datagramHeader), bob(datagramHeader); - - alice.setOther(&bob); - bob.setOther(&alice); - - // perform a large number of simulation iterations const int SIMULATION_ITERATIONS = 10000; - for (int i = 0; i < SIMULATION_ITERATIONS; i++) { - if (alice.simulate(i) || bob.simulate(i)) { + if (test == 0 || test == 1) { + qDebug() << "Running transmission tests..."; + qDebug(); + + // create two endpoints with the same header + Endpoint alice(datagramHeader), bob(datagramHeader); + + alice.setOther(&bob); + bob.setOther(&alice); + + // perform a large number of simulation iterations + for (int i = 0; i < SIMULATION_ITERATIONS; i++) { + if (alice.simulate(i) || bob.simulate(i)) { + return true; + } + } + + qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived; + qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived; + qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived; + qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived; + qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << + datagramsReceived << "with" << bytesReceived << "bytes"; + qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed; + qDebug() << "Performed" << objectMutationsPerformed << "object mutations"; + qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed; + qDebug(); + } + + if (test == 0 || test == 2) { + qDebug() << "Running serialization tests..."; + qDebug(); + + if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) { return true; } } - qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived; - qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived; - qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived; - qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived; - qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << - datagramsReceived << "with" << bytesReceived << "bytes"; - qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed; - qDebug() << "Performed" << objectMutationsPerformed << "object mutations"; - qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed; - qDebug(); + if (test == 0 || test == 3) { + qDebug() << "Running metavoxel data tests..."; + qDebug(); - qDebug() << "Running serialization tests..."; - qDebug(); + // clear the stats + datagramsSent = bytesSent = datagramsReceived = bytesReceived = 0; - if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) { - return true; + // create client and server endpoints + Endpoint client(datagramHeader, Endpoint::METAVOXEL_CLIENT_MODE); + Endpoint server(datagramHeader, Endpoint::METAVOXEL_SERVER_MODE); + + client.setOther(&server); + server.setOther(&client); + + // simulate + for (int i = 0; i < SIMULATION_ITERATIONS; i++) { + if (client.simulate(i) || server.simulate(i)) { + return true; + } + } + + qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << + datagramsReceived << "with" << bytesReceived << "bytes"; } qDebug() << "All tests passed!"; @@ -375,7 +407,8 @@ static SharedObjectPointer createRandomSharedObject() { } } -Endpoint::Endpoint(const QByteArray& datagramHeader) : +Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) : + _mode(mode), _sequencer(new DatagramSequencer(datagramHeader, this)), _highPriorityMessagesToSend(0.0f), _reliableMessagesToSend(0.0f) { @@ -396,6 +429,13 @@ Endpoint::Endpoint(const QByteArray& datagramHeader) : ReceiveRecord receiveRecord = { 0 }; _receiveRecords.append(receiveRecord); + if (mode == METAVOXEL_CLIENT_MODE) { + _lod = MetavoxelLOD(glm::vec3(), 0.01f); + return; + } + if (mode == METAVOXEL_SERVER_MODE) { + return; + } // create the object that represents out delta-encoded state _localState = new TestSharedObjectA(); @@ -415,7 +455,7 @@ Endpoint::Endpoint(const QByteArray& datagramHeader) : QByteArray bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES); _dataStreamed.append(bytes); output->getBuffer().write(bytes); - streamedBytesSent += bytes.size(); + streamedBytesSent += bytes.size(); } static QVariant createRandomMessage() { @@ -525,6 +565,34 @@ bool Endpoint::simulate(int iterationNumber) { } } + if (_mode == METAVOXEL_CLIENT_MODE) { + Bitstream& out = _sequencer->startPacket(); + + ClientStateMessage state = { _lod }; + out << QVariant::fromValue(state); + _sequencer->endPacket(); + + // record the send + SendRecord record = { _sequencer->getOutgoingPacketNumber(), SharedObjectPointer(), MetavoxelData(), _lod }; + _sendRecords.append(record); + return false; + } + if (_mode == METAVOXEL_SERVER_MODE) { + // wait until we have a valid lod + if (!_lod.isValid()) { + return false; + } + Bitstream& out = _sequencer->startPacket(); + out << QVariant::fromValue(MetavoxelDeltaMessage()); + _data.writeDelta(_sendRecords.first().data, _sendRecords.first().lod, out, _lod); + _sequencer->endPacket(); + + // record the send + SendRecord record = { _sequencer->getOutgoingPacketNumber(), SharedObjectPointer(), _data, _lod }; + _sendRecords.append(record); + return false; + } + // enqueue some number of high priority messages const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f; const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f; @@ -619,6 +687,28 @@ void Endpoint::handleHighPriorityMessage(const QVariant& message) { } void Endpoint::readMessage(Bitstream& in) { + if (_mode == METAVOXEL_CLIENT_MODE) { + QVariant message; + in >> message; + handleMessage(message, in); + + // record the receipt + ReceiveRecord record = { _sequencer->getIncomingPacketNumber(), SharedObjectPointer(), + _data, _sendRecords.first().lod }; + _receiveRecords.append(record); + return; + } + if (_mode == METAVOXEL_SERVER_MODE) { + QVariant message; + in >> message; + handleMessage(message, in); + + // record the receipt + ReceiveRecord record = { _sequencer->getIncomingPacketNumber() }; + _receiveRecords.append(record); + return; + } + SequencedTestMessage message; in >> message; @@ -682,6 +772,22 @@ void Endpoint::clearReceiveRecordsBefore(int index) { _receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1); } +void Endpoint::handleMessage(const QVariant& message, Bitstream& in) { + int userType = message.userType(); + if (userType == ClientStateMessage::Type) { + ClientStateMessage state = message.value(); + _lod = state.lod; + + } else if (userType == MetavoxelDeltaMessage::Type) { + _data.readDelta(_receiveRecords.first().data, _receiveRecords.first().lod, in, _sendRecords.first().lod); + + } else if (userType == QMetaType::QVariantList) { + foreach (const QVariant& element, message.toList()) { + handleMessage(element, in); + } + } +} + TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) : _foo(foo), _baz(baz), diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index ac9eda2659..1de355661d 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -16,6 +16,7 @@ #include #include +#include #include class SequencedTestMessage; @@ -39,7 +40,9 @@ class Endpoint : public QObject { public: - Endpoint(const QByteArray& datagramHeader); + enum Mode { BASIC_PEER_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE }; + + Endpoint(const QByteArray& datagramHeader, Mode mode = BASIC_PEER_MODE); void setOther(Endpoint* other) { _other = other; } @@ -60,18 +63,26 @@ private slots: private: + void handleMessage(const QVariant& message, Bitstream& in); + class SendRecord { public: int packetNumber; SharedObjectPointer localState; + MetavoxelData data; + MetavoxelLOD lod; }; class ReceiveRecord { public: int packetNumber; SharedObjectPointer remoteState; + MetavoxelData data; + MetavoxelLOD lod; }; + Mode _mode; + DatagramSequencer* _sequencer; QList _sendRecords; QList _receiveRecords; @@ -79,6 +90,9 @@ private: SharedObjectPointer _localState; SharedObjectPointer _remoteState; + MetavoxelData _data; + MetavoxelLOD _lod; + Endpoint* _other; QList > _delayedDatagrams; float _highPriorityMessagesToSend;