From 531c32fdd35bf7038cd6763a077c9c03874e02ba Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 18 Jun 2014 18:07:50 -0700 Subject: [PATCH 1/7] More comments, stubbing out incremental streaming. --- libraries/metavoxels/src/Bitstream.h | 6 +++--- libraries/metavoxels/src/MetavoxelData.cpp | 8 ++++++++ libraries/metavoxels/src/MetavoxelData.h | 11 ++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 0d9e516640..b116bd4b49 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -1104,7 +1104,7 @@ private: Q_DECLARE_METATYPE(const QMetaObject*) /// Macro for registering streamable meta-objects. Typically, one would use this macro at the top level of the source file -/// associated with the class. +/// associated with the class. The class should have a no-argument constructor flagged with Q_INVOKABLE. #define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject); /// Contains a value along with a pointer to its streamer. This is stored in QVariants when using fallback generics and @@ -1563,8 +1563,8 @@ public: Bitstream::registerTypeStreamer(qMetaTypeId(), new CollectionTypeStreamer()); /// Declares the metatype and the streaming operators. Typically, one would use this immediately after the definition of a -/// type flagged as STREAMABLE in its header file. The last lines ensure that the generated file will be included in the link -/// phase. +/// type flagged as STREAMABLE in its header file. The type should have a no-argument constructor. The last lines of this +/// macro ensure that the generated file will be included in the link phase. #ifdef _WIN32 #define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \ Bitstream& operator<<(Bitstream& out, const X& obj); \ diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 0d52fc5ed6..503dcce8d2 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -602,6 +602,14 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, const MetavoxelLO } } +void MetavoxelData::readIncrementalDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, + Bitstream& in, const MetavoxelLOD& lod) { +} + +void MetavoxelData::writeIncrementalDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, + Bitstream& out, const MetavoxelLOD& lod) const { +} + MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) { MetavoxelNode*& root = _roots[attribute]; if (root) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 2e6f6c4437..199b6a8aff 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -54,7 +54,8 @@ public: DECLARE_STREAMABLE_METATYPE(MetavoxelLOD) -/// The base metavoxel representation shared between server and client. +/// The base metavoxel representation shared between server and client. Contains a size (for all dimensions) and a set of +/// octrees for different attributes. class MetavoxelData { public: @@ -64,11 +65,14 @@ public: MetavoxelData& operator=(const MetavoxelData& other); + /// Sets the size in all dimensions. void setSize(float size) { _size = size; } float getSize() const { return _size; } + /// Returns the minimum extent of the octrees (which are centered about the origin). glm::vec3 getMinimum() const { return glm::vec3(_size, _size, _size) * -0.5f; } + /// Returns the bounds of the octrees. Box getBounds() const; /// Applies the specified visitor to the contained voxels. @@ -107,6 +111,11 @@ public: void writeDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, Bitstream& out, const MetavoxelLOD& lod) const; + void readIncrementalDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, + Bitstream& in, const MetavoxelLOD& lod); + void writeIncrementalDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, + Bitstream& out, const MetavoxelLOD& lod) const; + MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); } MetavoxelNode* createRoot(const AttributePointer& attribute); From 7a9c0f0e6a504038bf7d19ef616ab4c5d3054945 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 19 Jun 2014 14:50:19 -0700 Subject: [PATCH 2/7] 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; From 24f535e02d263502680f0b04ca5628de77023251 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 19 Jun 2014 18:15:17 -0700 Subject: [PATCH 3/7] Working on metavoxel streaming tests. --- libraries/metavoxels/src/MetavoxelData.cpp | 46 +++++ libraries/metavoxels/src/MetavoxelData.h | 11 ++ tests/metavoxels/src/MetavoxelTests.cpp | 193 +++++++++++++++------ 3 files changed, 196 insertions(+), 54 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index cf645e6f5e..d7528d1959 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -687,6 +687,23 @@ MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) { return root = new MetavoxelNode(attribute); } +bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod) const { + if (_size != other._size) { + return false; + } + if (_roots.size() != other._roots.size()) { + return false; + } + glm::vec3 minimum = getMinimum(); + for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { + MetavoxelNode* otherNode = other._roots.value(it.key()); + if (!(otherNode && it.value()->deepEquals(it.key(), *otherNode, minimum, _size, lod))) { + return false; + } + } + return true; +} + bool MetavoxelData::operator==(const MetavoxelData& other) const { return _size == other._size && _roots == other._roots; } @@ -1083,6 +1100,31 @@ void MetavoxelNode::clearChildren(const AttributePointer& attribute) { } } +bool MetavoxelNode::deepEquals(const AttributePointer& attribute, const MetavoxelNode& other, + const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const { + if (!attribute->equal(_attributeValue, other._attributeValue)) { + return false; + } + if (!lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) { + return true; + } + bool leaf = isLeaf(), otherLeaf = other.isLeaf(); + if (leaf && otherLeaf) { + return true; + } + if (leaf || otherLeaf) { + return false; + } + float nextSize = size * 0.5f; + for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i); + if (!_children[i]->deepEquals(attribute, *(other._children[i]), nextMinimum, nextSize, lod)) { + return false; + } + } + return true; +} + int MetavoxelVisitor::encodeOrder(int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eighth) { return first | (second << 3) | (third << 6) | (fourth << 9) | @@ -1111,6 +1153,10 @@ int MetavoxelVisitor::encodeOrder(const glm::vec3& direction) { indexDistances.at(6).index, indexDistances.at(7).index); } +int MetavoxelVisitor::encodeRandomOrder() { + return DEFAULT_ORDER; +} + const int MetavoxelVisitor::DEFAULT_ORDER = encodeOrder(0, 1, 2, 3, 4, 5, 6, 7); const int MetavoxelVisitor::STOP_RECURSION = 0; const int MetavoxelVisitor::SHORT_CIRCUIT = -1; diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 6b262d6892..0df97a7a4c 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -126,6 +126,10 @@ public: MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); } MetavoxelNode* createRoot(const AttributePointer& attribute); + /// Performs a deep comparison between this data and the specified other (as opposed to the == operator, which does a + /// shallow comparison). + bool deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod = MetavoxelLOD()) const; + bool operator==(const MetavoxelData& other) const; bool operator!=(const MetavoxelData& other) const; @@ -214,6 +218,10 @@ public: void clearChildren(const AttributePointer& attribute); + /// Performs a deep comparison between this and the specified other node. + bool deepEquals(const AttributePointer& attribute, const MetavoxelNode& other, + const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const; + private: Q_DISABLE_COPY(MetavoxelNode) @@ -250,6 +258,9 @@ public: /// Encodes a visitation order sequence that visits each child as sorted along the specified direction. static int encodeOrder(const glm::vec3& direction); + /// Returns a random visitation order sequence. + static int encodeRandomOrder(); + /// The default visitation order. static const int DEFAULT_ORDER; diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index daee1165f6..d261d9d926 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -32,6 +32,8 @@ static int datagramsSent = 0; static int datagramsReceived = 0; static int bytesSent = 0; static int bytesReceived = 0; +static int maxDatagramsPerPacket = 0; +static int maxBytesPerPacket = 0; static int highPriorityMessagesSent = 0; static int highPriorityMessagesReceived = 0; static int unreliableMessagesSent = 0; @@ -353,6 +355,7 @@ bool MetavoxelTests::run() { qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived; qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << datagramsReceived << "with" << bytesReceived << "bytes"; + qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet"; qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed; qDebug() << "Performed" << objectMutationsPerformed << "object mutations"; qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed; @@ -373,7 +376,7 @@ bool MetavoxelTests::run() { qDebug(); // clear the stats - datagramsSent = bytesSent = datagramsReceived = bytesReceived = 0; + datagramsSent = bytesSent = datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0; // create client and server endpoints Endpoint client(datagramHeader, Endpoint::METAVOXEL_CLIENT_MODE); @@ -391,6 +394,7 @@ bool MetavoxelTests::run() { qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << datagramsReceived << "with" << bytesReceived << "bytes"; + qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet"; } qDebug() << "All tests passed!"; @@ -407,6 +411,34 @@ static SharedObjectPointer createRandomSharedObject() { } } +class RandomVisitor : public MetavoxelVisitor { +public: + + int leafCount; + + RandomVisitor(); + virtual int visit(MetavoxelInfo& info); +}; + +RandomVisitor::RandomVisitor() : + MetavoxelVisitor(QVector(), + QVector() << AttributeRegistry::getInstance()->getColorAttribute()), + leafCount(0) { +} + +const float MAXIMUM_LEAF_SIZE = 0.5f; +const float MINIMUM_LEAF_SIZE = 0.25f; + +int RandomVisitor::visit(MetavoxelInfo& info) { + if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) { + return DEFAULT_ORDER; + } + info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline(qRgb(randIntInRange(0, 255), + randIntInRange(0, 255), randIntInRange(0, 255)))); + leafCount++; + return STOP_RECURSION; +} + Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) : _mode(mode), _sequencer(new DatagramSequencer(datagramHeader, this)), @@ -434,6 +466,12 @@ Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) : return; } if (mode == METAVOXEL_SERVER_MODE) { + _data.expand(); + _data.expand(); + + RandomVisitor visitor; + _data.guide(visitor); + qDebug() << "Created" << visitor.leafCount << "base leaves"; return; } // create the object that represents out delta-encoded state @@ -552,6 +590,36 @@ static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMe } } +class MutateVisitor : public MetavoxelVisitor { +public: + + MutateVisitor(); + virtual int visit(MetavoxelInfo& info); + +private: + + bool _finished; +}; + +MutateVisitor::MutateVisitor() : + MetavoxelVisitor(QVector(), + QVector() << AttributeRegistry::getInstance()->getColorAttribute()), + _finished(false) { +} + +int MutateVisitor::visit(MetavoxelInfo& info) { + if (_finished) { + return STOP_RECURSION; + } + if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) { + return encodeRandomOrder(); + } + info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline(qRgb(randIntInRange(0, 255), + randIntInRange(0, 255), randIntInRange(0, 255)))); + _finished = true; + return STOP_RECURSION; +} + bool Endpoint::simulate(int iterationNumber) { // update/send our delayed datagrams for (QList >::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) { @@ -565,6 +633,8 @@ bool Endpoint::simulate(int iterationNumber) { } } + int oldDatagramsSent = datagramsSent; + int oldBytesSent = bytesSent; if (_mode == METAVOXEL_CLIENT_MODE) { Bitstream& out = _sequencer->startPacket(); @@ -575,69 +645,74 @@ bool Endpoint::simulate(int iterationNumber) { // 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 + + } else if (_mode == METAVOXEL_SERVER_MODE) { + // make a random change + MutateVisitor visitor; + _data.guide(visitor); + + // wait until we have a valid lod before sending 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 }; + SendRecord record = { _sequencer->getOutgoingPacketNumber() + 1, 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; - _highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES); - while (_highPriorityMessagesToSend >= 1.0f) { - QVariant message = createRandomMessage(); - _highPriorityMessagesSent.append(message); - _sequencer->sendHighPriorityMessage(message); - highPriorityMessagesSent++; - _highPriorityMessagesToSend -= 1.0f; - } - - // and some number of reliable messages - const float MIN_RELIABLE_MESSAGES = 0.0f; - const float MAX_RELIABLE_MESSAGES = 4.0f; - _reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES); - while (_reliableMessagesToSend >= 1.0f) { - QVariant message = createRandomMessage(); - _reliableMessagesSent.append(message); - _sequencer->getReliableOutputChannel()->sendMessage(message); - reliableMessagesSent++; - _reliableMessagesToSend -= 1.0f; - } - - // tweak the local state - _localState = mutate(_localState); - - // send a packet - try { - Bitstream& out = _sequencer->startPacket(); - SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState }; - _unreliableMessagesSent.append(message); - unreliableMessagesSent++; - out << message; + _sequencer->endPacket(); + + } else { + // enqueue some number of high priority messages + const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f; + const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f; + _highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES); + while (_highPriorityMessagesToSend >= 1.0f) { + QVariant message = createRandomMessage(); + _highPriorityMessagesSent.append(message); + _sequencer->sendHighPriorityMessage(message); + highPriorityMessagesSent++; + _highPriorityMessagesToSend -= 1.0f; + } + + // and some number of reliable messages + const float MIN_RELIABLE_MESSAGES = 0.0f; + const float MAX_RELIABLE_MESSAGES = 4.0f; + _reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES); + while (_reliableMessagesToSend >= 1.0f) { + QVariant message = createRandomMessage(); + _reliableMessagesSent.append(message); + _sequencer->getReliableOutputChannel()->sendMessage(message); + reliableMessagesSent++; + _reliableMessagesToSend -= 1.0f; + } + + // tweak the local state + _localState = mutate(_localState); + + // send a packet + try { + Bitstream& out = _sequencer->startPacket(); + SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState }; + _unreliableMessagesSent.append(message); + unreliableMessagesSent++; + out << message; + _sequencer->endPacket(); + + } catch (const QString& message) { + qDebug() << message; + return true; + } - } catch (const QString& message) { - qDebug() << message; - return true; + // record the send + SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState }; + _sendRecords.append(record); } - - // record the send - SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState }; - _sendRecords.append(record); - + maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent); + maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent); return false; } @@ -692,9 +767,19 @@ void Endpoint::readMessage(Bitstream& in) { in >> message; handleMessage(message, in); + // deep-compare data to sent version + int packetNumber = _sequencer->getIncomingPacketNumber(); + foreach (const SendRecord& sendRecord, _other->_sendRecords) { + if (sendRecord.packetNumber == packetNumber) { + if (!sendRecord.data.deepEquals(_data, _sendRecords.first().lod)) { + throw QString("Sent/received metavoxel data mismatch."); + } + break; + } + } + // record the receipt - ReceiveRecord record = { _sequencer->getIncomingPacketNumber(), SharedObjectPointer(), - _data, _sendRecords.first().lod }; + ReceiveRecord record = { packetNumber, SharedObjectPointer(), _data, _sendRecords.first().lod }; _receiveRecords.append(record); return; } From ce778f47b1c6fc29999a5f9a865ba1c1514f866f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 20 Jun 2014 14:08:39 -0700 Subject: [PATCH 4/7] More work on stream testing, fixed bug with differently-ordered edits. --- libraries/metavoxels/src/MetavoxelData.cpp | 21 +++++++++++++++++---- tests/metavoxels/src/MetavoxelTests.cpp | 8 ++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index d7528d1959..6d1031d3cf 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -1153,8 +1153,23 @@ int MetavoxelVisitor::encodeOrder(const glm::vec3& direction) { indexDistances.at(6).index, indexDistances.at(7).index); } +const int ORDER_ELEMENT_BITS = 3; +const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1; + int MetavoxelVisitor::encodeRandomOrder() { - return DEFAULT_ORDER; + // see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_.22inside-out.22_algorithm + int order; + int randomValues = rand(); + for (int i = 0, iShift = 0; i < MetavoxelNode::CHILD_COUNT; i++, iShift += ORDER_ELEMENT_BITS) { + int j = (randomValues >> iShift) % (i + 1); + int jShift = j * ORDER_ELEMENT_BITS; + if (j != i) { + int jValue = (order >> jShift) & ORDER_ELEMENT_MASK; + order = (order & ~(ORDER_ELEMENT_MASK << iShift)) | (jValue << iShift); + } + order = (order & ~(ORDER_ELEMENT_MASK << jShift)) | (i << jShift); + } + return order; } const int MetavoxelVisitor::DEFAULT_ORDER = encodeOrder(0, 1, 2, 3, 4, 5, 6, 7); @@ -1350,8 +1365,6 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { QVector(visitation.outputNodes.size()) } }; for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) { // the encoded order tells us the child indices for each iteration - const int ORDER_ELEMENT_BITS = 3; - const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1; int index = encodedOrder & ORDER_ELEMENT_MASK; encodedOrder >>= ORDER_ELEMENT_BITS; for (int j = 0; j < visitation.inputNodes.size(); j++) { @@ -1392,7 +1405,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { } } MetavoxelNode* node = visitation.outputNodes.at(j); - MetavoxelNode* child = node->getChild(i); + MetavoxelNode* child = node->getChild(index); if (child) { child->decrementReferenceCount(value.getAttribute()); } else { diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index d261d9d926..6aef40eab1 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -598,17 +598,17 @@ public: private: - bool _finished; + int _mutationsRemaining; }; MutateVisitor::MutateVisitor() : MetavoxelVisitor(QVector(), QVector() << AttributeRegistry::getInstance()->getColorAttribute()), - _finished(false) { + _mutationsRemaining(randIntInRange(2, 4)) { } int MutateVisitor::visit(MetavoxelInfo& info) { - if (_finished) { + if (_mutationsRemaining <= 0) { return STOP_RECURSION; } if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) { @@ -616,7 +616,7 @@ int MutateVisitor::visit(MetavoxelInfo& info) { } info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline(qRgb(randIntInRange(0, 255), randIntInRange(0, 255), randIntInRange(0, 255)))); - _finished = true; + _mutationsRemaining--; return STOP_RECURSION; } From de0c45a919b96b55a670069c469469e813d29fe1 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 20 Jun 2014 18:09:51 -0700 Subject: [PATCH 5/7] Spanner mutation in test. --- .../metavoxels/src/AttributeRegistry.cpp | 49 +++++++++++++++++++ libraries/metavoxels/src/AttributeRegistry.h | 13 +++++ libraries/metavoxels/src/Bitstream.cpp | 24 +++++++++ libraries/metavoxels/src/Bitstream.h | 6 +++ libraries/metavoxels/src/MetavoxelData.cpp | 17 ++++++- libraries/metavoxels/src/MetavoxelData.h | 4 ++ libraries/metavoxels/src/SharedObject.cpp | 14 ++++-- libraries/shared/src/SharedUtil.h | 2 +- tests/metavoxels/src/MetavoxelTests.cpp | 42 +++++++++++++--- tests/metavoxels/src/MetavoxelTests.h | 2 + 10 files changed, 161 insertions(+), 12 deletions(-) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index e7a7f41850..33ce298859 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -211,6 +211,11 @@ void Attribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelSt root.writeSubdivision(state); } +bool Attribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot, + const glm::vec3& minimum, float size, const MetavoxelLOD& lod) { + return firstRoot.deepEquals(this, secondRoot, minimum, size, lod); +} + FloatAttribute::FloatAttribute(const QString& name, float defaultValue) : SimpleInlineAttribute(name, defaultValue) { } @@ -449,6 +454,12 @@ void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) cons } } +bool SharedObjectAttribute::deepEqual(void* first, void* second) const { + SharedObjectPointer firstObject = decodeInline(first); + SharedObjectPointer secondObject = decodeInline(second); + return firstObject ? firstObject->equals(secondObject) : !secondObject; +} + bool SharedObjectAttribute::merge(void*& parent, void* children[], bool postRead) const { SharedObjectPointer firstChild = decodeInline(children[0]); for (int i = 1; i < MERGE_COUNT; i++) { @@ -489,6 +500,35 @@ MetavoxelNode* SharedObjectSetAttribute::createMetavoxelNode( return new MetavoxelNode(value, original); } +static bool setsEqual(const SharedObjectSet& firstSet, const SharedObjectSet& secondSet) { + if (firstSet.size() != secondSet.size()) { + return false; + } + // some hackiness here: we assume that the local ids of the first set correspond to the remote ids of the second, + // so that this will work with the tests + foreach (const SharedObjectPointer& firstObject, firstSet) { + int id = firstObject->getID(); + bool found = false; + foreach (const SharedObjectPointer& secondObject, secondSet) { + if (secondObject->getRemoteID() == id) { + if (!firstObject->equals(secondObject)) { + return false; + } + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +bool SharedObjectSetAttribute::deepEqual(void* first, void* second) const { + return setsEqual(decodeInline(first), decodeInline(second)); +} + bool SharedObjectSetAttribute::merge(void*& parent, void* children[], bool postRead) const { for (int i = 0; i < MERGE_COUNT; i++) { if (!decodeInline(children[i]).isEmpty()) { @@ -563,3 +603,12 @@ void SpannerSetAttribute::writeMetavoxelSubdivision(const MetavoxelNode& root, M state.stream << SharedObjectPointer(); } +bool SpannerSetAttribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot, + const glm::vec3& minimum, float size, const MetavoxelLOD& lod) { + + SharedObjectSet firstSet; + firstRoot.getSpanners(this, minimum, size, lod, firstSet); + SharedObjectSet secondSet; + secondRoot.getSpanners(this, minimum, size, lod, secondSet); + return setsEqual(firstSet, secondSet); +} diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 00d974b8b6..7dc2e110b8 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -27,6 +27,7 @@ class QScriptValue; class Attribute; class MetavoxelData; +class MetavoxelLOD; class MetavoxelNode; class MetavoxelStreamState; @@ -213,6 +214,11 @@ public: virtual bool equal(void* first, void* second) const = 0; + virtual bool deepEqual(void* first, void* second) const { return equal(first, second); } + + virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot, + const glm::vec3& minimum, float size, const MetavoxelLOD& lod); + /// Merges the value of a parent and its children. /// \param postRead whether or not the merge is happening after a read /// \return whether or not the children and parent values are all equal @@ -406,6 +412,8 @@ public: virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; + virtual bool deepEqual(void* first, void* second) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual void* createFromVariant(const QVariant& value) const; @@ -434,6 +442,8 @@ public: virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; + virtual bool deepEqual(void* first, void* second) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual AttributeValue inherit(const AttributeValue& parentValue) const; @@ -462,6 +472,9 @@ public: virtual void readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state); virtual void writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state); + + virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot, + const glm::vec3& minimum, float size, const MetavoxelLOD& lod); }; #endif // hifi_AttributeRegistry_h diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index 44342abe33..0ef86e85c0 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -110,6 +110,10 @@ const TypeStreamer* Bitstream::getTypeStreamer(int type) { return getTypeStreamers().value(type); } +const ObjectStreamer* Bitstream::getObjectStreamer(const QMetaObject* metaObject) { + return getObjectStreamers().value(metaObject); +} + const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) { return getMetaObjects().value(className); } @@ -2316,6 +2320,15 @@ QObject* MappedObjectStreamer::putJSONData(JSONReader& reader, const QJsonObject return object; } +bool MappedObjectStreamer::equal(const QObject* first, const QObject* second) const { + foreach (const StreamerPropertyPair& property, _properties) { + if (!property.first->equal(property.second.read(first), property.second.read(second))) { + return false; + } + } + return true; +} + void MappedObjectStreamer::write(Bitstream& out, const QObject* object) const { foreach (const StreamerPropertyPair& property, _properties) { property.first->write(out, property.second.read(object)); @@ -2433,6 +2446,17 @@ QObject* GenericObjectStreamer::putJSONData(JSONReader& reader, const QJsonObjec return object; } +bool GenericObjectStreamer::equal(const QObject* first, const QObject* second) const { + const QVariantList& firstValues = static_cast(first)->getValues(); + const QVariantList& secondValues = static_cast(second)->getValues(); + for (int i = 0; i < _properties.size(); i++) { + if (!_properties.at(i).first->equal(firstValues.at(i), secondValues.at(i))) { + return false; + } + } + return true; +} + void GenericObjectStreamer::write(Bitstream& out, const QObject* object) const { const QVariantList& values = static_cast(object)->getValues(); for (int i = 0; i < _properties.size(); i++) { diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index b116bd4b49..e32f93dbe2 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -278,6 +278,9 @@ public: /// Returns the streamer registered for the supplied type, if any. static const TypeStreamer* getTypeStreamer(int type); + /// Returns the streamer registered for the supplied object, if any. + static const ObjectStreamer* getObjectStreamer(const QMetaObject* metaObject); + /// Returns the meta-object registered under the supplied class name, if any. static const QMetaObject* getMetaObject(const QByteArray& className); @@ -1022,6 +1025,7 @@ public: virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const = 0; virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const = 0; + virtual bool equal(const QObject* first, const QObject* second) const = 0; virtual void write(Bitstream& out, const QObject* object) const = 0; virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const = 0; virtual QObject* read(Bitstream& in, QObject* object = NULL) const = 0; @@ -1047,6 +1051,7 @@ public: virtual QJsonObject getJSONMetadata(JSONWriter& writer) const; virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const; virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const; + virtual bool equal(const QObject* first, const QObject* second) const; virtual void write(Bitstream& out, const QObject* object) const; virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const; virtual QObject* read(Bitstream& in, QObject* object = NULL) const; @@ -1070,6 +1075,7 @@ public: virtual QJsonObject getJSONMetadata(JSONWriter& writer) const; virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const; virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const; + virtual bool equal(const QObject* first, const QObject* second) const; virtual void write(Bitstream& out, const QObject* object) const; virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const; virtual QObject* read(Bitstream& in, QObject* object = NULL) const; diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 6d1031d3cf..7caa336cd2 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -697,7 +697,7 @@ bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& l glm::vec3 minimum = getMinimum(); for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { MetavoxelNode* otherNode = other._roots.value(it.key()); - if (!(otherNode && it.value()->deepEquals(it.key(), *otherNode, minimum, _size, lod))) { + if (!(otherNode && it.key()->metavoxelRootsEqual(*it.value(), *otherNode, minimum, _size, lod))) { return false; } } @@ -1102,7 +1102,7 @@ void MetavoxelNode::clearChildren(const AttributePointer& attribute) { bool MetavoxelNode::deepEquals(const AttributePointer& attribute, const MetavoxelNode& other, const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const { - if (!attribute->equal(_attributeValue, other._attributeValue)) { + if (!attribute->deepEqual(_attributeValue, other._attributeValue)) { return false; } if (!lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) { @@ -1125,6 +1125,19 @@ bool MetavoxelNode::deepEquals(const AttributePointer& attribute, const Metavoxe return true; } +void MetavoxelNode::getSpanners(const AttributePointer& attribute, const glm::vec3& minimum, + float size, const MetavoxelLOD& lod, SharedObjectSet& results) const { + results.unite(decodeInline(_attributeValue)); + if (isLeaf() || !lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) { + return; + } + float nextSize = size * 0.5f; + for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i); + _children[i]->getSpanners(attribute, nextMinimum, nextSize, lod, results); + } +} + int MetavoxelVisitor::encodeOrder(int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eighth) { return first | (second << 3) | (third << 6) | (fourth << 9) | diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 0df97a7a4c..7496bf567d 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -221,6 +221,10 @@ public: /// Performs a deep comparison between this and the specified other node. bool deepEquals(const AttributePointer& attribute, const MetavoxelNode& other, const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const; + + /// Retrieves all spanners satisfying the LOD constraint, placing them in the provided set. + void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum, + float size, const MetavoxelLOD& lod, SharedObjectSet& results) const; private: Q_DISABLE_COPY(MetavoxelNode) diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index 05af5f1bf8..d0a1842d31 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -84,11 +84,19 @@ bool SharedObject::equals(const SharedObject* other, bool sharedAncestry) const if (metaObject != other->metaObject() && !sharedAncestry) { return false; } - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (property.isStored() && property.read(this) != property.read(other)) { + // use the streamer, if we have one + const ObjectStreamer* streamer = Bitstream::getObjectStreamer(metaObject); + if (streamer) { + if (!streamer->equal(this, other)) { return false; } + } else { + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (property.isStored() && property.read(this) != property.read(other)) { + return false; + } + } } QList dynamicPropertyNames = this->dynamicPropertyNames(); if (dynamicPropertyNames.size() != other->dynamicPropertyNames().size()) { diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index dbbfb02365..e5c2a0afc9 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -68,7 +68,7 @@ float randFloat(); int randIntInRange (int min, int max); float randFloatInRange (float min,float max); float randomSign(); /// \return -1.0 or 1.0 -unsigned char randomColorValue(int minimum); +unsigned char randomColorValue(int minimum = 0); bool randomBoolean(); glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha); diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 6aef40eab1..f22a7ef189 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -47,6 +47,8 @@ static int sharedObjectsDestroyed = 0; static int objectMutationsPerformed = 0; static int scriptObjectsCreated = 0; static int scriptMutationsPerformed = 0; +static int metavoxelMutationsPerformed = 0; +static int spannerMutationsPerformed = 0; static QByteArray createRandomBytes(int minimumSize, int maximumSize) { QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0); @@ -395,6 +397,8 @@ bool MetavoxelTests::run() { qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << datagramsReceived << "with" << bytesReceived << "bytes"; qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet"; + qDebug() << "Performed" << metavoxelMutationsPerformed << "metavoxel mutations," << spannerMutationsPerformed << + "spanner mutations"; } qDebug() << "All tests passed!"; @@ -472,6 +476,12 @@ Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) : RandomVisitor visitor; _data.guide(visitor); qDebug() << "Created" << visitor.leafCount << "base leaves"; + + _data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), new Sphere()); + + _sphere = new Sphere(); + static_cast(_sphere.data())->setScale(0.01f); + _data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), _sphere); return; } // create the object that represents out delta-encoded state @@ -614,9 +624,10 @@ int MutateVisitor::visit(MetavoxelInfo& info) { if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) { return encodeRandomOrder(); } - info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline(qRgb(randIntInRange(0, 255), - randIntInRange(0, 255), randIntInRange(0, 255)))); + info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline(qRgb(randomColorValue(), + randomColorValue(), randomColorValue()))); _mutationsRemaining--; + metavoxelMutationsPerformed++; return STOP_RECURSION; } @@ -651,6 +662,21 @@ bool Endpoint::simulate(int iterationNumber) { MutateVisitor visitor; _data.guide(visitor); + // perhaps mutate the spanner + if (randomBoolean()) { + SharedObjectPointer oldSphere = _sphere; + _sphere = _sphere->clone(true); + Sphere* newSphere = static_cast(_sphere.data()); + if (randomBoolean()) { + newSphere->setColor(QColor(randomColorValue(), randomColorValue(), randomColorValue())); + } else { + newSphere->setTranslation(newSphere->getTranslation() + glm::vec3(randFloatInRange(-0.01f, 0.01f), + randFloatInRange(-0.01f, 0.01f), randFloatInRange(-0.01f, 0.01f))); + } + _data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), oldSphere, _sphere); + spannerMutationsPerformed++; + } + // wait until we have a valid lod before sending if (!_lod.isValid()) { return false; @@ -772,7 +798,8 @@ void Endpoint::readMessage(Bitstream& in) { foreach (const SendRecord& sendRecord, _other->_sendRecords) { if (sendRecord.packetNumber == packetNumber) { if (!sendRecord.data.deepEquals(_data, _sendRecords.first().lod)) { - throw QString("Sent/received metavoxel data mismatch."); + qDebug() << "Sent/received metavoxel data mismatch."; + exit(true); } break; } @@ -807,17 +834,20 @@ void Endpoint::readMessage(Bitstream& in) { it != _other->_unreliableMessagesSent.end(); it++) { if (it->sequenceNumber == message.sequenceNumber) { if (!messagesEqual(it->submessage, message.submessage)) { - throw QString("Sent/received unreliable message mismatch."); + qDebug() << "Sent/received unreliable message mismatch."; + exit(true); } if (!it->state->equals(message.state)) { - throw QString("Delta-encoded object mismatch."); + qDebug() << "Delta-encoded object mismatch."; + exit(true); } _other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1); unreliableMessagesReceived++; return; } } - throw QString("Received unsent/already sent unreliable message."); + qDebug() << "Received unsent/already sent unreliable message."; + exit(true); } void Endpoint::handleReliableMessage(const QVariant& message) { diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 1de355661d..c340d78963 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -93,6 +93,8 @@ private: MetavoxelData _data; MetavoxelLOD _lod; + SharedObjectPointer _sphere; + Endpoint* _other; QList > _delayedDatagrams; float _highPriorityMessagesToSend; From 49f1d427eca90cbc3fff15fae45fe8f64b0cbe5b Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 20 Jun 2014 18:12:08 -0700 Subject: [PATCH 6/7] Removed incremental streaming stubs. --- libraries/metavoxels/src/MetavoxelData.cpp | 77 ---------------------- libraries/metavoxels/src/MetavoxelData.h | 5 -- 2 files changed, 82 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 7caa336cd2..3e70e0f09b 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -602,83 +602,6 @@ 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) { MetavoxelNode*& root = _roots[attribute]; if (root) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 7496bf567d..6a7ba33eb5 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -118,11 +118,6 @@ public: void writeDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, Bitstream& out, const MetavoxelLOD& lod) const; - void readIncrementalDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, - Bitstream& in, const MetavoxelLOD& lod); - void writeIncrementalDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, - Bitstream& out, const MetavoxelLOD& lod) const; - MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); } MetavoxelNode* createRoot(const AttributePointer& attribute); From 626bd42b7b60a29e1a838aa19dc6e6a62e525b9f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 20 Jun 2014 18:22:36 -0700 Subject: [PATCH 7/7] Use randomColorValue, since we have it. --- tests/metavoxels/src/MetavoxelTests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index f22a7ef189..287d3a648c 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -437,8 +437,8 @@ int RandomVisitor::visit(MetavoxelInfo& info) { if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) { return DEFAULT_ORDER; } - info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline(qRgb(randIntInRange(0, 255), - randIntInRange(0, 255), randIntInRange(0, 255)))); + info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline(qRgb(randomColorValue(), + randomColorValue(), randomColorValue()))); leafCount++; return STOP_RECURSION; }