Merge branch 'master' of ssh://github.com/highfidelity/hifi into avatar-interaction

This commit is contained in:
Andrew Meadows 2014-02-10 15:42:56 -08:00
commit 4edc6cd88b
27 changed files with 931 additions and 102 deletions

View file

@ -39,4 +39,5 @@ add_subdirectory(assignment-client)
add_subdirectory(domain-server)
add_subdirectory(interface)
add_subdirectory(pairing-server)
add_subdirectory(voxel-edit)
add_subdirectory(tests)
add_subdirectory(voxel-edit)

Binary file not shown.

Binary file not shown.

View file

@ -1827,6 +1827,9 @@ void Application::init() {
_audio.setJitterBufferSamples(Menu::getInstance()->getAudioJitterBufferSamples());
}
qDebug("Loaded settings");
// fire off an immediate domain-server check in now that settings are loaded
NodeList::getInstance()->sendDomainServerCheckIn();
// Set up VoxelSystem after loading preferences so we can get the desired max voxel count
_voxels.setMaxVoxels(Menu::getInstance()->getMaxVoxels());

View file

@ -778,11 +778,13 @@ void Menu::editPreferences() {
QString faceURLString = applicationInstance->getAvatar()->getHead().getFaceModel().getURL().toString();
QLineEdit* faceURLEdit = new QLineEdit(faceURLString);
faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
faceURLEdit->setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
form->addRow("Face URL:", faceURLEdit);
QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString();
QLineEdit* skeletonURLEdit = new QLineEdit(skeletonURLString);
skeletonURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
skeletonURLEdit->setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
form->addRow("Skeleton URL:", skeletonURLEdit);
QSlider* pupilDilation = new QSlider(Qt::Horizontal);

View file

@ -345,12 +345,12 @@ bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, floa
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
_head.getFaceModel().setURL(faceModelURL);
_head.getFaceModel().setURL(_faceModelURL);
}
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
_skeletonModel.setURL(skeletonModelURL);
_skeletonModel.setURL(_skeletonModelURL);
}
int Avatar::parseData(const QByteArray& packet) {

View file

@ -601,6 +601,7 @@ public:
FBXMesh mesh;
QMultiHash<int, int> newIndices;
QVector<QHash<int, int> > blendshapeIndexMaps;
QVector<QPair<int, int> > partMaterialTextures;
};
class MeshData {
@ -667,6 +668,7 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
ExtractedMesh extractMesh(const FBXNode& object) {
MeshData data;
QVector<int> materials;
QVector<int> textures;
foreach (const FBXNode& child, object.children) {
if (child.name == "Vertices") {
data.vertices = createVec3Vector(getDoubleVector(child.properties, 0));
@ -703,19 +705,32 @@ ExtractedMesh extractMesh(const FBXNode& object) {
materials = getIntVector(subdata.properties, 0);
}
}
} else if (child.name == "LayerElementTexture") {
foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "TextureId") {
textures = getIntVector(subdata.properties, 0);
}
}
}
}
// convert the polygons to quads and triangles
int polygonIndex = 0;
QHash<QPair<int, int>, int> materialTextureParts;
for (int beginIndex = 0; beginIndex < data.polygonIndices.size(); polygonIndex++) {
int endIndex = beginIndex;
while (data.polygonIndices.at(endIndex++) >= 0);
int materialIndex = (polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0;
data.extracted.mesh.parts.resize(max(data.extracted.mesh.parts.size(), materialIndex + 1));
FBXMeshPart& part = data.extracted.mesh.parts[materialIndex];
QPair<int, int> materialTexture((polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0,
(polygonIndex < textures.size()) ? textures.at(polygonIndex) : 0);
int& partIndex = materialTextureParts[materialTexture];
if (partIndex == 0) {
data.extracted.partMaterialTextures.append(materialTexture);
data.extracted.mesh.parts.resize(data.extracted.mesh.parts.size() + 1);
partIndex = data.extracted.mesh.parts.size();
}
FBXMeshPart& part = data.extracted.mesh.parts[partIndex - 1];
if (endIndex - beginIndex == 4) {
appendIndex(data, part.quadIndices, beginIndex++);
appendIndex(data, part.quadIndices, beginIndex++);
@ -1265,41 +1280,60 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID);
// look for textures, material properties
int partIndex = extracted.mesh.parts.size() - 1;
int materialIndex = 0;
int textureIndex = 0;
bool generateTangents = false;
foreach (const QString& childID, childMap.values(modelID)) {
if (partIndex < 0) {
break;
}
FBXMeshPart& part = extracted.mesh.parts[partIndex];
if (textureFilenames.contains(childID)) {
part.diffuseFilename = textureFilenames.value(childID);
continue;
}
if (!materials.contains(childID)) {
continue;
}
Material material = materials.value(childID);
part.diffuseColor = material.diffuse;
part.specularColor = material.specular;
part.shininess = material.shininess;
QString diffuseTextureID = diffuseTextures.value(childID);
if (!diffuseTextureID.isNull()) {
part.diffuseFilename = textureFilenames.value(diffuseTextureID);
QList<QString> children = childMap.values(modelID);
for (int i = children.size() - 1; i >= 0; i--) {
const QString& childID = children.at(i);
if (materials.contains(childID)) {
Material material = materials.value(childID);
QByteArray diffuseFilename;
QString diffuseTextureID = diffuseTextures.value(childID);
if (!diffuseTextureID.isNull()) {
diffuseFilename = textureFilenames.value(diffuseTextureID);
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) {
if (textureFilenames.contains(childTextureID)) {
part.diffuseFilename = textureFilenames.value(childTextureID);
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) {
if (textureFilenames.contains(childTextureID)) {
diffuseFilename = textureFilenames.value(childTextureID);
}
}
}
QByteArray normalFilename;
QString bumpTextureID = bumpTextures.value(childID);
if (!bumpTextureID.isNull()) {
normalFilename = textureFilenames.value(bumpTextureID);
generateTangents = true;
}
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
if (extracted.partMaterialTextures.at(j).first == materialIndex) {
FBXMeshPart& part = extracted.mesh.parts[j];
part.diffuseColor = material.diffuse;
part.specularColor = material.specular;
part.shininess = material.shininess;
if (!diffuseFilename.isNull()) {
part.diffuseFilename = diffuseFilename;
}
if (!normalFilename.isNull()) {
part.normalFilename = normalFilename;
}
}
}
materialIndex++;
} else if (textureFilenames.contains(childID)) {
QByteArray filename = textureFilenames.value(childID);
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
if (extracted.partMaterialTextures.at(j).second == textureIndex) {
extracted.mesh.parts[j].diffuseFilename = filename;
}
}
textureIndex++;
}
QString bumpTextureID = bumpTextures.value(childID);
if (!bumpTextureID.isNull()) {
part.normalFilename = textureFilenames.value(bumpTextureID);
generateTangents = true;
}
partIndex--;
}
// if we have a normal map (and texture coordinates), we must compute tangents

View file

@ -301,13 +301,15 @@ QByteArray AvatarData::identityByteArray() {
}
void AvatarData::setFaceModelURL(const QUrl& faceModelURL) {
qDebug() << "Changing face model for avatar to" << faceModelURL.toString();
_faceModelURL = faceModelURL;
_faceModelURL = faceModelURL.isEmpty() ? DEFAULT_HEAD_MODEL_URL : faceModelURL;
qDebug() << "Changing face model for avatar to" << _faceModelURL.toString();
}
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
qDebug() << "Changing skeleton model for avatar to" << skeletonModelURL.toString();
_skeletonModelURL = skeletonModelURL;
_skeletonModelURL = skeletonModelURL.isEmpty() ? DEFAULT_BODY_MODEL_URL : skeletonModelURL;
qDebug() << "Changing skeleton model for avatar to" << _skeletonModelURL.toString();
}
void AvatarData::setClampedTargetScale(float targetScale) {

View file

@ -52,6 +52,9 @@ static const float MIN_AVATAR_SCALE = .005f;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fbx");
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fbx");
enum KeyState {
NO_KEY_DOWN = 0,
INSERT_KEY_DOWN,

View file

@ -82,6 +82,14 @@ bool AttributeValue::operator==(void* other) const {
return _attribute && _attribute->equal(_value, other);
}
bool AttributeValue::operator!=(const AttributeValue& other) const {
return _attribute != other._attribute || (_attribute && !_attribute->equal(_value, other._value));
}
bool AttributeValue::operator!=(void* other) const {
return !_attribute || !_attribute->equal(_value, other);
}
OwnedAttributeValue::OwnedAttributeValue(const AttributePointer& attribute, void* value) :
AttributeValue(attribute, value) {
}

View file

@ -105,6 +105,9 @@ public:
bool operator==(const AttributeValue& other) const;
bool operator==(void* other) const;
bool operator!=(const AttributeValue& other) const;
bool operator!=(void* other) const;
protected:
AttributePointer _attribute;

View file

@ -415,12 +415,16 @@ public:
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
Bitstream& operator<<(Bitstream& out, const X& obj); \
Bitstream& operator>>(Bitstream& in, X& obj); \
bool operator==(const X& first, const X& second); \
bool operator!=(const X& first, const X& second); \
static const int* _TypePtr##X = &X::Type;
#else
#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); \
bool operator==(const X& first, const X& second); \
bool operator!=(const X& first, const X& second); \
static const int* _TypePtr##X = &X::Type; \
_Pragma(STRINGIFY(unused(_TypePtr##X)))
#endif

View file

@ -20,7 +20,8 @@ const int MAX_DATAGRAM_SIZE = MAX_PACKET_SIZE;
const int DEFAULT_MAX_PACKET_SIZE = 3000;
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader) :
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject* parent) :
QObject(parent),
_outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly),
_outputStream(_outgoingPacketStream),
_incomingDatagramStream(&_incomingDatagramBuffer),
@ -174,10 +175,10 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
}
// read and dispatch the high-priority messages
int highPriorityMessageCount;
quint32 highPriorityMessageCount;
_incomingPacketStream >> highPriorityMessageCount;
int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages;
for (int i = 0; i < highPriorityMessageCount; i++) {
for (quint32 i = 0; i < highPriorityMessageCount; i++) {
QVariant data;
_inputStream >> data;
if (i >= _receivedHighPriorityMessages) {
@ -192,10 +193,10 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
// read the reliable data, if any
quint32 reliableChannels;
_incomingPacketStream >> reliableChannels;
for (int i = 0; i < reliableChannels; i++) {
for (quint32 i = 0; i < reliableChannels; i++) {
quint32 channelIndex;
_incomingPacketStream >> channelIndex;
getReliableOutputChannel(channelIndex)->readData(_incomingPacketStream);
getReliableInputChannel(channelIndex)->readData(_incomingPacketStream);
}
_incomingPacketStream.device()->seek(0);
@ -311,6 +312,178 @@ void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
}
}
const int INITIAL_CIRCULAR_BUFFER_CAPACITY = 16;
CircularBuffer::CircularBuffer(QObject* parent) :
QIODevice(parent),
_data(INITIAL_CIRCULAR_BUFFER_CAPACITY, 0),
_position(0),
_size(0),
_offset(0) {
}
void CircularBuffer::append(const char* data, int length) {
// resize to fit
int oldSize = _size;
resize(_size + length);
// write our data in up to two segments: one from the position to the end, one from the beginning
int end = (_position + oldSize) % _data.size();
int firstSegment = qMin(length, _data.size() - end);
memcpy(_data.data() + end, data, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
memcpy(_data.data(), data + firstSegment, secondSegment);
}
}
void CircularBuffer::remove(int length) {
_position = (_position + length) % _data.size();
_size -= length;
}
QByteArray CircularBuffer::readBytes(int offset, int length) const {
// write in up to two segments
QByteArray array;
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
array.append(_data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
array.append(_data.constData(), secondSegment);
}
return array;
}
void CircularBuffer::writeToStream(int offset, int length, QDataStream& out) const {
// write in up to two segments
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
out.writeRawData(_data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
out.writeRawData(_data.constData(), secondSegment);
}
}
void CircularBuffer::readFromStream(int offset, int length, QDataStream& in) {
// resize to fit
int requiredSize = offset + length;
if (requiredSize > _size) {
resize(requiredSize);
}
// read in up to two segments
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
in.readRawData(_data.data() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
in.readRawData(_data.data(), secondSegment);
}
}
void CircularBuffer::appendToBuffer(int offset, int length, CircularBuffer& buffer) const {
// append in up to two segments
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
buffer.append(_data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
buffer.append(_data.constData(), secondSegment);
}
}
bool CircularBuffer::atEnd() const {
return _offset >= _size;
}
qint64 CircularBuffer::bytesAvailable() const {
return _size - _offset + QIODevice::bytesAvailable();
}
bool CircularBuffer::canReadLine() const {
for (int offset = _offset; offset < _size; offset++) {
if (_data.at((_position + offset) % _data.size()) == '\n') {
return true;
}
}
return false;
}
bool CircularBuffer::open(OpenMode flags) {
return QIODevice::open(flags | QIODevice::Unbuffered);
}
qint64 CircularBuffer::pos() const {
return _offset;
}
bool CircularBuffer::seek(qint64 pos) {
if (pos < 0 || pos > _size) {
return false;
}
_offset = pos;
return true;
}
qint64 CircularBuffer::size() const {
return _size;
}
qint64 CircularBuffer::readData(char* data, qint64 length) {
int readable = qMin((int)length, _size - _offset);
// read in up to two segments
int start = (_position + _offset) % _data.size();
int firstSegment = qMin((int)length, _data.size() - start);
memcpy(data, _data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
memcpy(data + firstSegment, _data.constData(), secondSegment);
}
_offset += readable;
return readable;
}
qint64 CircularBuffer::writeData(const char* data, qint64 length) {
// resize to fit
int requiredSize = _offset + length;
if (requiredSize > _size) {
resize(requiredSize);
}
// write in up to two segments
int start = (_position + _offset) % _data.size();
int firstSegment = qMin((int)length, _data.size() - start);
memcpy(_data.data() + start, data, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
memcpy(_data.data(), data + firstSegment, secondSegment);
}
_offset += length;
return length;
}
void CircularBuffer::resize(int size) {
if (size > _data.size()) {
// double our capacity until we can fit the desired length
int newCapacity = _data.size();
do {
newCapacity *= 2;
} while (size > newCapacity);
int oldCapacity = _data.size();
_data.resize(newCapacity);
int trailing = _position + _size - oldCapacity;
if (trailing > 0) {
memcpy(_data.data() + oldCapacity, _data.constData(), trailing);
}
}
_size = size;
}
SpanList::SpanList() : _totalSet(0) {
}
@ -369,7 +542,7 @@ int SpanList::set(int offset, int length) {
int SpanList::setSpans(QList<Span>::iterator it, int length) {
int remainingLength = length;
int totalRemoved = 0;
for (; it != _spans.end(); it++) {
for (; it != _spans.end(); it = _spans.erase(it)) {
if (remainingLength < it->unset) {
it->unset -= remainingLength;
totalRemoved += remainingLength;
@ -378,7 +551,6 @@ int SpanList::setSpans(QList<Span>::iterator it, int length) {
int combined = it->unset + it->set;
remainingLength = qMax(remainingLength - combined, 0);
totalRemoved += combined;
it = _spans.erase(it);
_totalSet -= it->set;
}
return qMax(length, totalRemoved);
@ -424,14 +596,13 @@ void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSeq
break;
}
spanCount++;
remainingBytes -= getBytesToWrite(first, span.unset);
remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, span.unset));
position += (span.unset + span.set);
}
int leftover = _buffer.pos() - position;
if (remainingBytes > 0 && leftover > 0) {
spanCount++;
remainingBytes -= getBytesToWrite(first, leftover);
remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, leftover));
}
}
@ -448,8 +619,9 @@ void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSeq
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, span.unset), spans);
position += (span.unset + span.set);
}
if (remainingBytes > 0 && position < _buffer.pos()) {
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, (int)(_buffer.pos() - position)), spans);
int leftover = _buffer.pos() - position;
if (remainingBytes > 0 && leftover > 0) {
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, leftover), spans);
}
}
}
@ -473,16 +645,16 @@ int ReliableChannel::writeSpan(QDataStream& out, bool& first, int position, int
spans.append(span);
out << (quint32)span.offset;
out << (quint32)length;
out.writeRawData(_buffer.data().constData() + position, length);
_buffer.writeToStream(position, length, out);
return length;
}
void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& span) {
int advancement = _acknowledged.set(span.offset - _offset, span.length);
if (advancement > 0) {
// TODO: better way of pruning buffer
_buffer.buffer() = _buffer.buffer().right(_buffer.size() - advancement);
_buffer.remove(advancement);
_buffer.seek(_buffer.size());
_offset += advancement;
_writePosition = qMax(_writePosition - advancement, 0);
}
@ -491,38 +663,40 @@ void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& spa
void ReliableChannel::readData(QDataStream& in) {
quint32 segments;
in >> segments;
for (int i = 0; i < segments; i++) {
bool readSome = false;
for (quint32 i = 0; i < segments; i++) {
quint32 offset, size;
in >> offset >> size;
int position = offset - _offset;
int end = position + size;
if (_assemblyBuffer.size() < end) {
_assemblyBuffer.resize(end);
}
if (end <= 0) {
in.skipRawData(size);
} else if (position < 0) {
in.skipRawData(-position);
in.readRawData(_assemblyBuffer.data(), size + position);
_assemblyBuffer.readFromStream(0, end, in);
} else {
in.readRawData(_assemblyBuffer.data() + position, size);
_assemblyBuffer.readFromStream(position, size, in);
}
int advancement = _acknowledged.set(position, size);
if (advancement > 0) {
// TODO: better way of pruning buffer
_buffer.buffer().append(_assemblyBuffer.constData(), advancement);
emit _buffer.readyRead();
_assemblyBuffer = _assemblyBuffer.right(_assemblyBuffer.size() - advancement);
_assemblyBuffer.appendToBuffer(0, advancement, _buffer);
_assemblyBuffer.remove(advancement);
_offset += advancement;
readSome = true;
}
}
// when the read head is sufficiently advanced into the buffer, prune it off. this along
// with other buffer usages should be replaced with a circular buffer
const int PRUNE_SIZE = 8192;
if (_buffer.pos() > PRUNE_SIZE) {
_buffer.buffer() = _buffer.buffer().right(_buffer.size() - _buffer.pos());
// let listeners know that there's data to read
if (readSome) {
emit _buffer.readyRead();
}
// prune any read data from the buffer
if (_buffer.pos() > 0) {
_buffer.remove((int)_buffer.pos());
_buffer.seek(0);
}
}

View file

@ -32,7 +32,7 @@ public:
int firstPacketNumber;
};
DatagramSequencer(const QByteArray& datagramHeader = QByteArray());
DatagramSequencer(const QByteArray& datagramHeader = QByteArray(), QObject* parent = NULL);
/// Returns the packet number of the last packet sent.
int getOutgoingPacketNumber() const { return _outgoingPacketNumber; }
@ -58,7 +58,7 @@ public:
/// Returns the output channel at the specified index, creating it if necessary.
ReliableChannel* getReliableOutputChannel(int index = 0);
/// Returns the intput channel at the
/// Returns the intput channel at the specified index, creating it if necessary.
ReliableChannel* getReliableInputChannel(int index = 0);
/// Starts a new packet for transmission.
@ -167,6 +167,56 @@ private:
QHash<int, ReliableChannel*> _reliableInputChannels;
};
/// A circular buffer, where one may efficiently append data to the end or remove data from the beginning.
class CircularBuffer : public QIODevice {
public:
CircularBuffer(QObject* parent = NULL);
/// Appends data to the end of the buffer.
void append(const QByteArray& data) { append(data.constData(), data.size()); }
/// Appends data to the end of the buffer.
void append(const char* data, int length);
/// Removes data from the beginning of the buffer.
void remove(int length);
/// Reads part of the data from the buffer.
QByteArray readBytes(int offset, int length) const;
/// Writes part of the buffer to the supplied stream.
void writeToStream(int offset, int length, QDataStream& out) const;
/// Reads part of the buffer from the supplied stream.
void readFromStream(int offset, int length, QDataStream& in);
/// Appends part of the buffer to the supplied other buffer.
void appendToBuffer(int offset, int length, CircularBuffer& buffer) const;
virtual bool atEnd() const;
virtual qint64 bytesAvailable() const;
virtual bool canReadLine() const;
virtual bool open(OpenMode flags);
virtual qint64 pos() const;
virtual bool seek(qint64 pos);
virtual qint64 size() const;
protected:
virtual qint64 readData(char* data, qint64 length);
virtual qint64 writeData(const char* data, qint64 length);
private:
void resize(int size);
QByteArray _data;
int _position;
int _size;
int _offset;
};
/// A list of contiguous spans, alternating between set and unset. Conceptually, the list is preceeded by a set
/// span of infinite length and followed by an unset span of infinite length. Within those bounds, it alternates
/// between unset and set.
@ -208,6 +258,7 @@ public:
int getIndex() const { return _index; }
CircularBuffer& getBuffer() { return _buffer; }
QDataStream& getDataStream() { return _dataStream; }
Bitstream& getBitstream() { return _bitstream; }
@ -237,8 +288,8 @@ private:
void readData(QDataStream& in);
int _index;
QBuffer _buffer;
QByteArray _assemblyBuffer;
CircularBuffer _buffer;
CircularBuffer _assemblyBuffer;
QDataStream _dataStream;
Bitstream _bitstream;
float _priority;

View file

@ -11,12 +11,12 @@
#include <QDoubleSpinBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QItemEditorCreatorBase>
#include <QItemEditorFactory>
#include <QLineEdit>
#include <QMetaType>
#include <QPushButton>
#include <QScriptEngine>
#include <QStandardItemEditorCreator>
#include <QVBoxLayout>
#include <QtDebug>
@ -76,27 +76,48 @@ static QItemEditorFactory* getItemEditorFactory() {
return factory;
}
/// Because Windows doesn't necessarily have the staticMetaObject available when we want to create,
/// this class simply delays the value property name lookup until actually requested.
template<class T> class LazyItemEditorCreator : public QItemEditorCreatorBase {
public:
virtual QWidget* createWidget(QWidget* parent) const { return new T(parent); }
virtual QByteArray valuePropertyName() const;
protected:
QByteArray _valuePropertyName;
};
template<class T> QByteArray LazyItemEditorCreator<T>::valuePropertyName() const {
if (_valuePropertyName.isNull()) {
const_cast<LazyItemEditorCreator<T>*>(this)->_valuePropertyName = T::staticMetaObject.userProperty().name();
}
return _valuePropertyName;
}
static QItemEditorCreatorBase* createDoubleEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<DoubleEditor>();
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<DoubleEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<double>(), creator);
getItemEditorFactory()->registerEditor(qMetaTypeId<float>(), creator);
return creator;
}
static QItemEditorCreatorBase* createQColorEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<QColorEditor>();
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QColorEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<QColor>(), creator);
return creator;
}
static QItemEditorCreatorBase* createVec3EditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<Vec3Editor>();
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<Vec3Editor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<glm::vec3>(), creator);
return creator;
}
static QItemEditorCreatorBase* createParameterizedURLEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<ParameterizedURLEditor>();
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<ParameterizedURLEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<ParameterizedURL>(), creator);
return creator;
}
@ -120,6 +141,12 @@ QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode
return QUuid::fromRfc4122(QByteArray::fromRawData(data.constData() + headerSize, UUID_BYTES));
}
QByteArray signal(const char* signature) {
static QByteArray prototype = SIGNAL(dummyMethod());
QByteArray signal = prototype;
return signal.replace("dummyMethod()", signature);
}
bool Box::contains(const Box& other) const {
return other.minimum.x >= minimum.x && other.maximum.x <= maximum.x &&
other.minimum.y >= minimum.y && other.maximum.y <= maximum.y &&
@ -301,8 +328,7 @@ void ParameterizedURLEditor::continueUpdatingParameters() {
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
widgetProperty.write(widget, _url.getParameters().value(parameter.name));
if (widgetProperty.hasNotifySignal()) {
connect(widget, QByteArray(SIGNAL()).append(widgetProperty.notifySignal().methodSignature()),
SLOT(updateURL()));
connect(widget, signal(widgetProperty.notifySignal().methodSignature()), SLOT(updateURL()));
}
}
}

View file

@ -33,6 +33,9 @@ class NetworkProgram;
/// \return the session ID, or a null ID if invalid (in which case a warning will be logged)
QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize);
/// Performs the runtime equivalent of Qt's SIGNAL macro, which is to attach a prefix to the signature.
QByteArray signal(const char* signature);
/// A streamable axis-aligned bounding box.
class Box {
STREAMABLE

View file

@ -13,6 +13,7 @@
#include <QVBoxLayout>
#include "Bitstream.h"
#include "MetavoxelUtil.h"
#include "SharedObject.h"
SharedObject::SharedObject() : _referenceCount(0) {
@ -204,8 +205,7 @@ void SharedObjectEditor::updateType() {
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
widgetProperty.write(widget, property.read(newObject));
if (widgetProperty.hasNotifySignal()) {
connect(widget, QByteArray(SIGNAL()).append(widgetProperty.notifySignal().methodSignature()),
SLOT(propertyChanged()));
connect(widget, signal(widgetProperty.notifySignal().methodSignature()), SLOT(propertyChanged()));
}
}
}

View file

@ -98,6 +98,9 @@ const char* stringForLogType(QtMsgType msgType) {
const char DATE_STRING_FORMAT[] = "%F %H:%M:%S %z";
void Logging::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (message.isEmpty()) {
return;
}
// log prefix is in the following format
// [DEBUG] [TIMESTAMP] [PID:PARENT_PID] [TARGET] logged string

View file

@ -27,7 +27,7 @@ const char SOLO_NODE_TYPES[2] = {
NodeType::AudioMixer
};
const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io";
const QString DEFAULT_DOMAIN_HOSTNAME = "root.highfidelity.io";
const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102;
NodeList* NodeList::_sharedInstance = NULL;
@ -59,7 +59,7 @@ NodeList* NodeList::getInstance() {
NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) :
_nodeHash(),
_nodeHashMutex(QMutex::Recursive),
_domainHostname(DEFAULT_DOMAIN_HOSTNAME),
_domainHostname(),
_domainSockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_nodeSocket(this),
_ownerType(newOwnerType),
@ -81,13 +81,10 @@ NodeList::~NodeList() {
}
bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) {
// currently this just checks if the version in the packet matches our return from versionForPacketType
// may need to be expanded in the future for types and versions that take > than 1 byte
if (packet[1] != versionForPacketType(packetTypeForPacket(packet))
&& packetTypeForPacket(packet) != PacketTypeStunResponse) {
PacketType mismatchType = packetTypeForPacket(packet);
int numPacketTypeBytes = arithmeticCodingValueFromBuffer(packet.data());
int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender"
<< uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but"
@ -498,7 +495,7 @@ void NodeList::sendDomainServerCheckIn() {
static bool printedDomainServerIP = false;
// Lookup the IP address of the domain server if we need to
if (_domainSockAddr.getAddress().isNull()) {
if (_domainSockAddr.getAddress().isNull() && !_domainHostname.isEmpty()) {
qDebug("Looking up DS hostname %s.", _domainHostname.toLocal8Bit().constData());
QHostInfo domainServerHostInfo = QHostInfo::fromName(_domainHostname);
@ -527,8 +524,8 @@ void NodeList::sendDomainServerCheckIn() {
// we don't know our public socket and we need to send it to the domain server
// send a STUN request to figure it out
sendSTUNRequest();
} else {
// construct the DS check in packet if we need to
} else if (!_domainSockAddr.getAddress().isNull()) {
// construct the DS check in packet if we can
// check in packet has header, optional UUID, node type, port, IP, node types of interest, null termination
QByteArray domainServerPacket = byteArrayWithPopluatedHeader(PacketTypeDomainListRequest);
@ -807,7 +804,8 @@ void NodeList::loadData(QSettings *settings) {
if (domainServerHostname.size() > 0) {
_domainHostname = domainServerHostname;
emit domainChanged(_domainHostname);
} else {
_domainHostname = DEFAULT_DOMAIN_HOSTNAME;
}
settings->endGroup();

View file

@ -57,6 +57,9 @@ PacketVersion versionForPacketType(PacketType type) {
case PacketTypeDataServerConfirm:
case PacketTypeDataServerSend:
return 1;
case PacketTypeVoxelSet:
case PacketTypeVoxelSetDestructive:
return 1;
default:
return 0;
}

View file

@ -521,6 +521,8 @@ bool VoxelTree::handlesEditPacketType(PacketType packetType) const {
}
}
const unsigned int REPORT_OVERFLOW_WARNING_INTERVAL = 100;
unsigned int overflowWarnings = 0;
int VoxelTree::processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& node) {
@ -532,9 +534,17 @@ int VoxelTree::processEditPacketData(PacketType packetType, const unsigned char*
int octets = numberOfThreeBitSectionsInCode(editData, maxLength);
if (octets == OVERFLOWED_OCTCODE_BUFFER) {
printf("WARNING! Got voxel edit record that would overflow buffer in numberOfThreeBitSectionsInCode(), ");
printf("bailing processing of packet!\n");
return 0;
overflowWarnings++;
if (overflowWarnings % REPORT_OVERFLOW_WARNING_INTERVAL == 1) {
qDebug() << "WARNING! Got voxel edit record that would overflow buffer in numberOfThreeBitSectionsInCode()"
" [NOTE: this is warning number" << overflowWarnings << ", the next" <<
(REPORT_OVERFLOW_WARNING_INTERVAL-1) << "will be suppressed.]";
QDebug debug = qDebug();
debug << "edit data contents:";
outputBufferBits(editData, maxLength, &debug);
}
return maxLength;
}
const int COLOR_SIZE_IN_BYTES = 3;
@ -542,9 +552,17 @@ int VoxelTree::processEditPacketData(PacketType packetType, const unsigned char*
int voxelDataSize = voxelCodeSize + COLOR_SIZE_IN_BYTES;
if (voxelDataSize > maxLength) {
printf("WARNING! Got voxel edit record that would overflow buffer, bailing processing of packet!\n");
printf("bailing processing of packet!\n");
return 0;
overflowWarnings++;
if (overflowWarnings % REPORT_OVERFLOW_WARNING_INTERVAL == 1) {
qDebug() << "WARNING! Got voxel edit record that would overflow buffer."
" [NOTE: this is warning number" << overflowWarnings << ", the next" <<
(REPORT_OVERFLOW_WARNING_INTERVAL-1) << "will be suppressed.]";
QDebug debug = qDebug();
debug << "edit data contents:";
outputBufferBits(editData, maxLength, &debug);
}
return maxLength;
}
readCodeColorBufferToTree(editData, destructive);

10
tests/CMakeLists.txt Normal file
View file

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 2.8)
# add the test directories
file(GLOB TEST_SUBDIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
foreach(DIR ${TEST_SUBDIRS})
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${DIR})
add_subdirectory(${DIR})
endif()
endforeach()

View file

@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 2.8)
set(TARGET_NAME metavoxel-tests)
set(ROOT_DIR ../..)
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
# setup for find modules
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)
include(${MACRO_DIR}/AutoMTC.cmake)
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
qt5_use_modules(${TARGET_NAME} Network Script Widgets)
#include glm
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})
# link in the shared libraries
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(metavoxels ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})

View file

@ -0,0 +1,272 @@
//
// MetavoxelTests.cpp
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <stdlib.h>
#include <SharedUtil.h>
#include "MetavoxelTests.h"
MetavoxelTests::MetavoxelTests(int& argc, char** argv) :
QCoreApplication(argc, argv) {
}
static int datagramsSent = 0;
static int datagramsReceived = 0;
static int highPriorityMessagesSent = 0;
static int highPriorityMessagesReceived = 0;
static int unreliableMessagesSent = 0;
static int unreliableMessagesReceived = 0;
static int streamedBytesSent = 0;
static int streamedBytesReceived = 0;
static int lowPriorityStreamedBytesSent = 0;
static int lowPriorityStreamedBytesReceived = 0;
bool MetavoxelTests::run() {
qDebug() << "Running metavoxel tests...";
// seed the random number generator so that our tests are reproducible
srand(0xBAAAAABE);
// create two endpoints with the same header
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 = 100000;
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" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
qDebug() << "Sent" << lowPriorityStreamedBytesSent << "low-priority streamed bytes, received" <<
lowPriorityStreamedBytesReceived;
qDebug() << "Sent" << datagramsSent << "datagrams, received" << datagramsReceived;
qDebug() << "All tests passed!";
return false;
}
static QByteArray createRandomBytes(int minimumSize, int maximumSize) {
QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0);
for (int i = 0; i < bytes.size(); i++) {
bytes[i] = rand();
}
return bytes;
}
static QByteArray createRandomBytes() {
const int MIN_BYTES = 4;
const int MAX_BYTES = 16;
return createRandomBytes(MIN_BYTES, MAX_BYTES);
}
Endpoint::Endpoint(const QByteArray& datagramHeader) :
_sequencer(new DatagramSequencer(datagramHeader, this)),
_highPriorityMessagesToSend(0.0f) {
connect(_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
connect(_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
connect(_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
SLOT(handleHighPriorityMessage(const QVariant&)));
connect(&_sequencer->getReliableInputChannel()->getBuffer(), SIGNAL(readyRead()), SLOT(readReliableChannel()));
connect(&_sequencer->getReliableInputChannel(1)->getBuffer(), SIGNAL(readyRead()), SLOT(readLowPriorityReliableChannel()));
// enqueue a large amount of data in a low-priority channel
ReliableChannel* output = _sequencer->getReliableOutputChannel(1);
output->setPriority(0.25f);
const int MIN_LOW_PRIORITY_DATA = 100000;
const int MAX_LOW_PRIORITY_DATA = 200000;
QByteArray bytes = createRandomBytes(MIN_LOW_PRIORITY_DATA, MAX_LOW_PRIORITY_DATA);
_lowPriorityDataStreamed.append(bytes);
output->getBuffer().write(bytes);
lowPriorityStreamedBytesSent += bytes.size();
}
static QVariant createRandomMessage() {
switch (randIntInRange(0, 2)) {
case 0: {
TestMessageA message = { randomBoolean(), rand(), randFloat() };
return QVariant::fromValue(message);
}
case 1: {
TestMessageB message = { createRandomBytes() };
return QVariant::fromValue(message);
}
case 2:
default: {
TestMessageC message;
message.foo = randomBoolean();
message.bar = rand();
message.baz = randFloat();
message.bong.foo = createRandomBytes();
return QVariant::fromValue(message);
}
}
}
static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMessage) {
int type = firstMessage.userType();
if (secondMessage.userType() != type) {
return false;
}
if (type == TestMessageA::Type) {
return firstMessage.value<TestMessageA>() == secondMessage.value<TestMessageA>();
} else if (type == TestMessageB::Type) {
return firstMessage.value<TestMessageB>() == secondMessage.value<TestMessageB>();
} else if (type == TestMessageC::Type) {
return firstMessage.value<TestMessageC>() == secondMessage.value<TestMessageC>();
} else {
return firstMessage == secondMessage;
}
}
bool Endpoint::simulate(int iterationNumber) {
// update/send our delayed datagrams
for (QList<QPair<QByteArray, int> >::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
if (it->second-- == 1) {
_other->_sequencer->receivedDatagram(it->first);
datagramsReceived++;
it = _delayedDatagrams.erase(it);
} else {
it++;
}
}
// 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;
}
// stream some random data
const int MIN_BYTES_TO_STREAM = 10;
const int MAX_BYTES_TO_STREAM = 100;
QByteArray bytes = createRandomBytes(MIN_BYTES_TO_STREAM, MAX_BYTES_TO_STREAM);
_dataStreamed.append(bytes);
streamedBytesSent += bytes.size();
_sequencer->getReliableOutputChannel()->getDataStream().writeRawData(bytes.constData(), bytes.size());
// send a packet
try {
Bitstream& out = _sequencer->startPacket();
SequencedTestMessage message = { iterationNumber, createRandomMessage() };
_unreliableMessagesSent.append(message);
unreliableMessagesSent++;
out << message;
_sequencer->endPacket();
} catch (const QString& message) {
qDebug() << message;
return true;
}
return false;
}
void Endpoint::sendDatagram(const QByteArray& datagram) {
datagramsSent++;
// some datagrams are dropped
const float DROP_PROBABILITY = 0.1f;
if (randFloat() < DROP_PROBABILITY) {
return;
}
// some are received out of order
const float REORDER_PROBABILITY = 0.1f;
if (randFloat() < REORDER_PROBABILITY) {
const int MIN_DELAY = 1;
const int MAX_DELAY = 5;
// have to copy the datagram; the one we're passed is a reference to a shared buffer
_delayedDatagrams.append(QPair<QByteArray, int>(QByteArray(datagram.constData(), datagram.size()),
randIntInRange(MIN_DELAY, MAX_DELAY)));
// and some are duplicated
const float DUPLICATE_PROBABILITY = 0.01f;
if (randFloat() > DUPLICATE_PROBABILITY) {
return;
}
}
_other->_sequencer->receivedDatagram(datagram);
datagramsReceived++;
}
void Endpoint::handleHighPriorityMessage(const QVariant& message) {
if (_other->_highPriorityMessagesSent.isEmpty()) {
throw QString("Received unsent/already sent high priority message.");
}
QVariant sentMessage = _other->_highPriorityMessagesSent.takeFirst();
if (!messagesEqual(message, sentMessage)) {
throw QString("Sent/received high priority message mismatch.");
}
highPriorityMessagesReceived++;
}
void Endpoint::readMessage(Bitstream& in) {
SequencedTestMessage message;
in >> message;
for (QList<SequencedTestMessage>::iterator it = _other->_unreliableMessagesSent.begin();
it != _other->_unreliableMessagesSent.end(); it++) {
if (it->sequenceNumber == message.sequenceNumber) {
if (!messagesEqual(it->submessage, message.submessage)) {
throw QString("Sent/received unreliable message mismatch.");
}
_other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1);
unreliableMessagesReceived++;
return;
}
}
throw QString("Received unsent/already sent unreliable message.");
}
void Endpoint::readReliableChannel() {
CircularBuffer& buffer = _sequencer->getReliableInputChannel()->getBuffer();
QByteArray bytes = buffer.read(buffer.bytesAvailable());
if (_other->_dataStreamed.size() < bytes.size()) {
throw QString("Received unsent/already sent streamed data.");
}
QByteArray compare = _other->_dataStreamed.readBytes(0, bytes.size());
_other->_dataStreamed.remove(bytes.size());
if (compare != bytes) {
throw QString("Sent/received streamed data mismatch.");
}
streamedBytesReceived += bytes.size();
}
void Endpoint::readLowPriorityReliableChannel() {
CircularBuffer& buffer = _sequencer->getReliableInputChannel(1)->getBuffer();
QByteArray bytes = buffer.read(buffer.bytesAvailable());
if (_other->_lowPriorityDataStreamed.size() < bytes.size()) {
throw QString("Received unsent/already sent low-priority streamed data.");
}
QByteArray compare = _other->_lowPriorityDataStreamed.readBytes(0, bytes.size());
_other->_lowPriorityDataStreamed.remove(bytes.size());
if (compare != bytes) {
throw QString("Sent/received low-priority streamed data mismatch.");
}
lowPriorityStreamedBytesReceived += bytes.size();
}

View file

@ -0,0 +1,113 @@
//
// MetavoxelTests.h
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__MetavoxelTests__
#define __interface__MetavoxelTests__
#include <QCoreApplication>
#include <QVariantList>
#include <DatagramSequencer.h>
class SequencedTestMessage;
/// Tests various aspects of the metavoxel library.
class MetavoxelTests : public QCoreApplication {
Q_OBJECT
public:
MetavoxelTests(int& argc, char** argv);
/// Performs our various tests.
/// \return true if any of the tests failed.
bool run();
};
/// Represents a simulated endpoint.
class Endpoint : public QObject {
Q_OBJECT
public:
Endpoint(const QByteArray& datagramHeader);
void setOther(Endpoint* other) { _other = other; }
/// Perform a simulation step.
/// \return true if failure was detected
bool simulate(int iterationNumber);
private slots:
void sendDatagram(const QByteArray& datagram);
void handleHighPriorityMessage(const QVariant& message);
void readMessage(Bitstream& in);
void readReliableChannel();
void readLowPriorityReliableChannel();
private:
DatagramSequencer* _sequencer;
Endpoint* _other;
QList<QPair<QByteArray, int> > _delayedDatagrams;
float _highPriorityMessagesToSend;
QVariantList _highPriorityMessagesSent;
QList<SequencedTestMessage> _unreliableMessagesSent;
CircularBuffer _dataStreamed;
CircularBuffer _lowPriorityDataStreamed;
};
/// A simple test message.
class TestMessageA {
STREAMABLE
public:
STREAM bool foo;
STREAM int bar;
STREAM float baz;
};
DECLARE_STREAMABLE_METATYPE(TestMessageA)
// Another simple test message.
class TestMessageB {
STREAMABLE
public:
STREAM QByteArray foo;
};
DECLARE_STREAMABLE_METATYPE(TestMessageB)
// A test message that demonstrates inheritance and composition.
class TestMessageC : public TestMessageA {
STREAMABLE
public:
STREAM TestMessageB bong;
};
DECLARE_STREAMABLE_METATYPE(TestMessageC)
/// Combines a sequence number with a submessage; used for testing unreliable transport.
class SequencedTestMessage {
STREAMABLE
public:
STREAM int sequenceNumber;
STREAM QVariant submessage;
};
DECLARE_STREAMABLE_METATYPE(SequencedTestMessage)
#endif /* defined(__interface__MetavoxelTests__) */

View file

@ -0,0 +1,14 @@
//
// main.cpp
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
#include <QDebug>
#include "MetavoxelTests.h"
int main(int argc, char** argv) {
return MetavoxelTests(argc, argv).run();
}

View file

@ -90,7 +90,7 @@ 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";
out << "Bitstream& operator<<(Bitstream& out, const " << name << "& obj) {\n";
foreach (const QString& base, str.clazz.bases) {
out << " out << static_cast<const " << base << "&>(obj);\n";
}
@ -100,7 +100,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << " return out;\n";
out << "}\n";
out << "Bitstream& operator>> (Bitstream& in, " << name << "& obj) {\n";
out << "Bitstream& operator>>(Bitstream& in, " << name << "& obj) {\n";
foreach (const QString& base, str.clazz.bases) {
out << " in >> static_cast<" << base << "&>(obj);\n";
}
@ -110,6 +110,58 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << " return in;\n";
out << "}\n";
out << "bool operator==(const " << name << "& first, const " << name << "& second) {\n";
if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) {
out << " return true";
} else {
out << " return ";
bool first = true;
foreach (const QString& base, str.clazz.bases) {
if (!first) {
out << " &&\n";
out << " ";
}
out << "static_cast<" << base << "&>(first) == static_cast<" << base << "&>(second)";
first = false;
}
foreach (const QString& field, str.fields) {
if (!first) {
out << " &&\n";
out << " ";
}
out << "first." << field << " == second." << field;
first = false;
}
}
out << ";\n";
out << "}\n";
out << "bool operator!=(const " << name << "& first, const " << name << "& second) {\n";
if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) {
out << " return false";
} else {
out << " return ";
bool first = true;
foreach (const QString& base, str.clazz.bases) {
if (!first) {
out << " ||\n";
out << " ";
}
out << "static_cast<" << base << "&>(first) != static_cast<" << base << "&>(second)";
first = false;
}
foreach (const QString& field, str.fields) {
if (!first) {
out << " ||\n";
out << " ";
}
out << "first." << field << " != second." << field;
first = false;
}
}
out << ";\n";
out << "}\n";
out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n\n";
}
}