mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge branch 'master' of ssh://github.com/highfidelity/hifi into avatar-interaction
This commit is contained in:
commit
4edc6cd88b
27 changed files with 931 additions and 102 deletions
|
@ -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)
|
||||
|
|
BIN
interface/resources/meshes/defaultAvatar_body.fbx
Normal file
BIN
interface/resources/meshes/defaultAvatar_body.fbx
Normal file
Binary file not shown.
BIN
interface/resources/meshes/defaultAvatar_head.fbx
Normal file
BIN
interface/resources/meshes/defaultAvatar_head.fbx
Normal file
Binary file not shown.
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -57,6 +57,9 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
case PacketTypeDataServerConfirm:
|
||||
case PacketTypeDataServerSend:
|
||||
return 1;
|
||||
case PacketTypeVoxelSet:
|
||||
case PacketTypeVoxelSetDestructive:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -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
10
tests/CMakeLists.txt
Normal 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()
|
||||
|
32
tests/metavoxels/CMakeLists.txt
Normal file
32
tests/metavoxels/CMakeLists.txt
Normal 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})
|
||||
|
||||
|
272
tests/metavoxels/src/MetavoxelTests.cpp
Normal file
272
tests/metavoxels/src/MetavoxelTests.cpp
Normal 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();
|
||||
}
|
113
tests/metavoxels/src/MetavoxelTests.h
Normal file
113
tests/metavoxels/src/MetavoxelTests.h
Normal 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__) */
|
14
tests/metavoxels/src/main.cpp
Normal file
14
tests/metavoxels/src/main.cpp
Normal 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();
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue