From 074a11306c02e850774a237f588d75296f63e099 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 1 May 2017 13:21:52 -0700 Subject: [PATCH 01/13] Add support for atp and file urls in OBJReader --- libraries/fbx/src/OBJReader.cpp | 314 +++++++++--------- libraries/fbx/src/OBJReader.h | 1 - .../networking/src/AssetResourceRequest.cpp | 6 +- .../networking/src/AssetResourceRequest.h | 2 +- libraries/networking/src/ResourceManager.cpp | 2 +- 5 files changed, 167 insertions(+), 158 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 7b46556530..167cb8caac 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -24,6 +24,7 @@ #include #include +#include #include "FBXReader.h" #include "ModelFormatLogging.h" @@ -165,6 +166,7 @@ bool OBJFace::add(const QByteArray& vertexIndex, const QByteArray& textureIndex, } return true; } + QVector OBJFace::triangulate() { QVector newFaces; const int nVerticesInATriangle = 3; @@ -183,6 +185,7 @@ QVector OBJFace::triangulate() { } return newFaces; } + void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f at index i vertexIndices.append(face->vertexIndices[index]); if (face->textureUVIndices.count() > 0) { // Any at all. Runtime error if not consistent. @@ -193,24 +196,13 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f } } -static bool replyOK(QNetworkReply* netReply, QUrl url) { // This will be reworked when we make things asynchronous - return (netReply && netReply->isFinished() && - (url.toString().startsWith("file", Qt::CaseInsensitive) ? // file urls don't have http status codes - netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString().isEmpty() : - (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200))); -} - bool OBJReader::isValidTexture(const QByteArray &filename) { if (_url.isEmpty()) { return false; } QUrl candidateUrl = _url.resolved(QUrl(filename)); - QNetworkReply *netReply = request(candidateUrl, true); - bool isValid = replyOK(netReply, candidateUrl); - if (netReply) { - netReply->deleteLater(); - } - return isValid; + + return ResourceManager::resourceExists(candidateUrl); } void OBJReader::parseMaterialLibrary(QIODevice* device) { @@ -274,7 +266,28 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } } -QNetworkReply* OBJReader::request(QUrl& url, bool isTest) { +std::tuple requestData(QUrl& url) { + auto request = ResourceManager::createResourceRequest(nullptr, url); + + if (!request) { + return std::make_tuple(false, QByteArray()); + } + + request->send(); + + QEventLoop loop; + QObject::connect(request, &ResourceRequest::finished, &loop, &QEventLoop::quit); + loop.exec(); + + if (request->getResult() == ResourceRequest::Success) { + return std::make_tuple(true, request->getData()); + } else { + return std::make_tuple(false, QByteArray()); + } +} + + +QNetworkReply* request(QUrl& url, bool isTest) { if (!qApp) { return nullptr; } @@ -293,10 +306,7 @@ QNetworkReply* OBJReader::request(QUrl& url, bool isTest) { QEventLoop loop; // Create an event loop that will quit when we get the finished signal QObject::connect(netReply, SIGNAL(finished()), &loop, SLOT(quit())); loop.exec(); // Nothing is going to happen on this whole run thread until we get this - static const int WAIT_TIMEOUT_MS = 500; - while (!aboutToQuit && qApp && !netReply->isReadable()) { - netReply->waitForReadyRead(WAIT_TIMEOUT_MS); // so we might as well block this thread waiting for the response, rather than - } + QObject::disconnect(connection); return netReply; // trying to sync later on. } @@ -446,142 +456,142 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, // add a new meshPart to the geometry's single mesh. while (parseOBJGroup(tokenizer, mapping, geometry, scaleGuess, combineParts)) {} - FBXMesh& mesh = geometry.meshes[0]; - mesh.meshIndex = 0; + FBXMesh& mesh = geometry.meshes[0]; + mesh.meshIndex = 0; - geometry.joints.resize(1); - geometry.joints[0].isFree = false; - geometry.joints[0].parentIndex = -1; - geometry.joints[0].distanceToParent = 0; - geometry.joints[0].translation = glm::vec3(0, 0, 0); - geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); - geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); - geometry.joints[0].name = "OBJ"; - geometry.joints[0].isSkeletonJoint = true; + geometry.joints.resize(1); + geometry.joints[0].isFree = false; + geometry.joints[0].parentIndex = -1; + geometry.joints[0].distanceToParent = 0; + geometry.joints[0].translation = glm::vec3(0, 0, 0); + geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); + geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); + geometry.joints[0].name = "OBJ"; + geometry.joints[0].isSkeletonJoint = true; - geometry.jointIndices["x"] = 1; + geometry.jointIndices["x"] = 1; - FBXCluster cluster; - cluster.jointIndex = 0; - cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - mesh.clusters.append(cluster); + FBXCluster cluster; + cluster.jointIndex = 0; + cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + mesh.clusters.append(cluster); - QMap materialMeshIdMap; - QVector fbxMeshParts; - for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) { - FBXMeshPart& meshPart = mesh.parts[i]; - FaceGroup faceGroup = faceGroups[meshPartCount]; + QMap materialMeshIdMap; + QVector fbxMeshParts; + for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) { + FBXMeshPart& meshPart = mesh.parts[i]; + FaceGroup faceGroup = faceGroups[meshPartCount]; bool specifiesUV = false; - foreach(OBJFace face, faceGroup) { - // Go through all of the OBJ faces and determine the number of different materials necessary (each different material will be a unique mesh). - // NOTE (trent/mittens 3/30/17): this seems hardcore wasteful and is slowed down a bit by iterating through the face group twice, but it's the best way I've thought of to hack multi-material support in an OBJ into this pipeline. - if (!materialMeshIdMap.contains(face.materialName)) { - // Create a new FBXMesh for this material mapping. - materialMeshIdMap.insert(face.materialName, materialMeshIdMap.count()); + foreach(OBJFace face, faceGroup) { + // Go through all of the OBJ faces and determine the number of different materials necessary (each different material will be a unique mesh). + // NOTE (trent/mittens 3/30/17): this seems hardcore wasteful and is slowed down a bit by iterating through the face group twice, but it's the best way I've thought of to hack multi-material support in an OBJ into this pipeline. + if (!materialMeshIdMap.contains(face.materialName)) { + // Create a new FBXMesh for this material mapping. + materialMeshIdMap.insert(face.materialName, materialMeshIdMap.count()); - fbxMeshParts.append(FBXMeshPart()); - FBXMeshPart& meshPartNew = fbxMeshParts.last(); - meshPartNew.quadIndices = QVector(meshPart.quadIndices); // Copy over quad indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. - meshPartNew.quadTrianglesIndices = QVector(meshPart.quadTrianglesIndices); // Copy over quad triangulated indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. - meshPartNew.triangleIndices = QVector(meshPart.triangleIndices); // Copy over triangle indices. + fbxMeshParts.append(FBXMeshPart()); + FBXMeshPart& meshPartNew = fbxMeshParts.last(); + meshPartNew.quadIndices = QVector(meshPart.quadIndices); // Copy over quad indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. + meshPartNew.quadTrianglesIndices = QVector(meshPart.quadTrianglesIndices); // Copy over quad triangulated indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. + meshPartNew.triangleIndices = QVector(meshPart.triangleIndices); // Copy over triangle indices. - // Do some of the material logic (which previously lived below) now. - // All the faces in the same group will have the same name and material. - QString groupMaterialName = face.materialName; - if (groupMaterialName.isEmpty() && specifiesUV) { + // Do some of the material logic (which previously lived below) now. + // All the faces in the same group will have the same name and material. + QString groupMaterialName = face.materialName; + if (groupMaterialName.isEmpty() && specifiesUV) { #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader WARNING: " << url - << " needs a texture that isn't specified. Using default mechanism."; + qCDebug(modelformat) << "OBJ Reader WARNING: " << url + << " needs a texture that isn't specified. Using default mechanism."; #endif - groupMaterialName = SMART_DEFAULT_MATERIAL_NAME; - } - if (!groupMaterialName.isEmpty()) { - OBJMaterial& material = materials[groupMaterialName]; - if (specifiesUV) { - material.userSpecifiesUV = true; // Note might not be true in a later usage. - } - if (specifiesUV || (groupMaterialName.compare("none", Qt::CaseInsensitive) != 0)) { - // Blender has a convention that a material named "None" isn't really used (or defined). - material.used = true; - needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME; - } - materials[groupMaterialName] = material; - meshPartNew.materialID = groupMaterialName; - } - } - } - } - - // clean up old mesh parts. - int unmodifiedMeshPartCount = mesh.parts.count(); - mesh.parts.clear(); - mesh.parts = QVector(fbxMeshParts); - - for (int i = 0, meshPartCount = 0; i < unmodifiedMeshPartCount; i++, meshPartCount++) { - FaceGroup faceGroup = faceGroups[meshPartCount]; - - // Now that each mesh has been created with its own unique material mappings, fill them with data (vertex data is duplicated, face data is not). - foreach(OBJFace face, faceGroup) { - FBXMeshPart& meshPart = mesh.parts[materialMeshIdMap[face.materialName]]; - - glm::vec3 v0 = checked_at(vertices, face.vertexIndices[0]); - glm::vec3 v1 = checked_at(vertices, face.vertexIndices[1]); - glm::vec3 v2 = checked_at(vertices, face.vertexIndices[2]); - - // Scale the vertices if the OBJ file scale is specified as non-one. - if (scaleGuess != 1.0f) { - v0 *= scaleGuess; - v1 *= scaleGuess; - v2 *= scaleGuess; - } - - // Add the vertices. - meshPart.triangleIndices.append(mesh.vertices.count()); // not face.vertexIndices into vertices - mesh.vertices << v0; - meshPart.triangleIndices.append(mesh.vertices.count()); - mesh.vertices << v1; - meshPart.triangleIndices.append(mesh.vertices.count()); - mesh.vertices << v2; - - glm::vec3 n0, n1, n2; - if (face.normalIndices.count()) { - n0 = checked_at(normals, face.normalIndices[0]); - n1 = checked_at(normals, face.normalIndices[1]); - n2 = checked_at(normals, face.normalIndices[2]); - } else { - // generate normals from triangle plane if not provided - n0 = n1 = n2 = glm::cross(v1 - v0, v2 - v0); - } - - mesh.normals.append(n0); - mesh.normals.append(n1); - mesh.normals.append(n2); - - if (face.textureUVIndices.count()) { - mesh.texCoords << - checked_at(textureUVs, face.textureUVIndices[0]) << - checked_at(textureUVs, face.textureUVIndices[1]) << - checked_at(textureUVs, face.textureUVIndices[2]); - } else { - glm::vec2 corner(0.0f, 1.0f); - mesh.texCoords << corner << corner << corner; - } - } + groupMaterialName = SMART_DEFAULT_MATERIAL_NAME; + } + if (!groupMaterialName.isEmpty()) { + OBJMaterial& material = materials[groupMaterialName]; + if (specifiesUV) { + material.userSpecifiesUV = true; // Note might not be true in a later usage. + } + if (specifiesUV || (groupMaterialName.compare("none", Qt::CaseInsensitive) != 0)) { + // Blender has a convention that a material named "None" isn't really used (or defined). + material.used = true; + needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME; + } + materials[groupMaterialName] = material; + meshPartNew.materialID = groupMaterialName; + } + } + } } - mesh.meshExtents.reset(); - foreach(const glm::vec3& vertex, mesh.vertices) { - mesh.meshExtents.addPoint(vertex); - geometry.meshExtents.addPoint(vertex); - } + // clean up old mesh parts. + int unmodifiedMeshPartCount = mesh.parts.count(); + mesh.parts.clear(); + mesh.parts = QVector(fbxMeshParts); - // Build the single mesh. - FBXReader::buildModelMesh(mesh, url.toString()); + for (int i = 0, meshPartCount = 0; i < unmodifiedMeshPartCount; i++, meshPartCount++) { + FaceGroup faceGroup = faceGroups[meshPartCount]; - // fbxDebugDump(geometry); + // Now that each mesh has been created with its own unique material mappings, fill them with data (vertex data is duplicated, face data is not). + foreach(OBJFace face, faceGroup) { + FBXMeshPart& meshPart = mesh.parts[materialMeshIdMap[face.materialName]]; + + glm::vec3 v0 = checked_at(vertices, face.vertexIndices[0]); + glm::vec3 v1 = checked_at(vertices, face.vertexIndices[1]); + glm::vec3 v2 = checked_at(vertices, face.vertexIndices[2]); + + // Scale the vertices if the OBJ file scale is specified as non-one. + if (scaleGuess != 1.0f) { + v0 *= scaleGuess; + v1 *= scaleGuess; + v2 *= scaleGuess; + } + + // Add the vertices. + meshPart.triangleIndices.append(mesh.vertices.count()); // not face.vertexIndices into vertices + mesh.vertices << v0; + meshPart.triangleIndices.append(mesh.vertices.count()); + mesh.vertices << v1; + meshPart.triangleIndices.append(mesh.vertices.count()); + mesh.vertices << v2; + + glm::vec3 n0, n1, n2; + if (face.normalIndices.count()) { + n0 = checked_at(normals, face.normalIndices[0]); + n1 = checked_at(normals, face.normalIndices[1]); + n2 = checked_at(normals, face.normalIndices[2]); + } else { + // generate normals from triangle plane if not provided + n0 = n1 = n2 = glm::cross(v1 - v0, v2 - v0); + } + + mesh.normals.append(n0); + mesh.normals.append(n1); + mesh.normals.append(n2); + + if (face.textureUVIndices.count()) { + mesh.texCoords << + checked_at(textureUVs, face.textureUVIndices[0]) << + checked_at(textureUVs, face.textureUVIndices[1]) << + checked_at(textureUVs, face.textureUVIndices[2]); + } else { + glm::vec2 corner(0.0f, 1.0f); + mesh.texCoords << corner << corner << corner; + } + } + } + + mesh.meshExtents.reset(); + foreach(const glm::vec3& vertex, mesh.vertices) { + mesh.meshExtents.addPoint(vertex); + geometry.meshExtents.addPoint(vertex); + } + + // Build the single mesh. + FBXReader::buildModelMesh(mesh, url.toString()); + + // fbxDebugDump(geometry); } catch(const std::exception& e) { qCDebug(modelformat) << "OBJ reader fail: " << e.what(); } @@ -624,15 +634,15 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, // Throw away any path part of libraryName, and merge against original url. QUrl libraryUrl = _url.resolved(QUrl(libraryName).fileName()); qCDebug(modelformat) << "OBJ Reader material library" << libraryName << "used in" << _url; - QNetworkReply* netReply = request(libraryUrl, false); - if (replyOK(netReply, libraryUrl)) { - parseMaterialLibrary(netReply); + bool success; + QByteArray data; + std::tie(success, data) = requestData(libraryUrl); + if (success) { + QBuffer buffer { &data }; + buffer.open(QIODevice::ReadOnly); + parseMaterialLibrary(&buffer); } else { - qCDebug(modelformat) << "OBJ Reader WARNING:" << libraryName << "did not answer. Got" - << (!netReply ? "aborted" : netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()); - } - if (netReply) { - netReply->deleteLater(); + qCDebug(modelformat) << "OBJ Reader WARNING:" << libraryName << "did not answer"; } } } @@ -655,9 +665,9 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, if (!objMaterial.diffuseTextureFilename.isEmpty()) { fbxMaterial.albedoTexture.filename = objMaterial.diffuseTextureFilename; } - if (!objMaterial.specularTextureFilename.isEmpty()) { - fbxMaterial.specularTexture.filename = objMaterial.specularTextureFilename; - } + if (!objMaterial.specularTextureFilename.isEmpty()) { + fbxMaterial.specularTexture.filename = objMaterial.specularTextureFilename; + } modelMaterial->setEmissive(fbxMaterial.emissiveColor); modelMaterial->setAlbedo(fbxMaterial.diffuseColor); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 4be5705f9a..18a4b89f1e 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -72,7 +72,6 @@ public: QString currentMaterialName; QHash materials; - QNetworkReply* request(QUrl& url, bool isTest); FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); private: diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 092e0ccb3d..a4d5d66923 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -40,16 +40,16 @@ AssetResourceRequest::~AssetResourceRequest() { } } -bool AssetResourceRequest::urlIsAssetHash() const { +bool AssetResourceRequest::urlIsAssetHash(const QUrl& url) { static const QString ATP_HASH_REGEX_STRING { "^atp:([A-Fa-f0-9]{64})(\\.[\\w]+)?$" }; QRegExp hashRegex { ATP_HASH_REGEX_STRING }; - return hashRegex.exactMatch(_url.toString()); + return hashRegex.exactMatch(url.toString()); } void AssetResourceRequest::doSend() { // We'll either have a hash or an ATP path to a file (that maps to a hash) - if (urlIsAssetHash()) { + if (urlIsAssetHash(_url)) { // We've detected that this is a hash - simply use AssetClient to request that asset auto parts = _url.path().split(".", QString::SkipEmptyParts); auto hash = parts.length() > 0 ? parts[0] : ""; diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index 3f110fae17..18b82f2573 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -32,7 +32,7 @@ private slots: void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); private: - bool urlIsAssetHash() const; + static bool urlIsAssetHash(const QUrl& url); void requestMappingForPath(const AssetPath& path); void requestHash(const AssetHash& hash); diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 439d44c940..6492e9171e 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -14,10 +14,10 @@ #include #include #include +#include #include - #include "AssetResourceRequest.h" #include "FileResourceRequest.h" #include "HTTPResourceRequest.h" From c839118c6be5de2ac17366a97f42175a83e91e7a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 1 May 2017 13:22:23 -0700 Subject: [PATCH 02/13] Add ResourceManager::resourceExists --- libraries/networking/src/ResourceManager.cpp | 48 ++++++++++++++++++++ libraries/networking/src/ResourceManager.h | 4 ++ 2 files changed, 52 insertions(+) diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 6492e9171e..e2c1cf2431 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -116,3 +116,51 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q request->moveToThread(&_thread); return request; } + + +bool ResourceManager::resourceExists(const QUrl& url) { + auto scheme = url.scheme(); + if (scheme == URL_SCHEME_FILE) { + QFileInfo file { url.toString() }; + return file.exists(); + } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { + auto& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest request { url }; + + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + + auto reply = networkAccessManager.head(request); + + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + + reply->deleteLater(); + + return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200; + } else if (scheme == URL_SCHEME_ATP) { + auto request = new AssetResourceRequest(url); + ByteRange range; + range.fromInclusive = 1; + range.toExclusive = 1; + request->setByteRange(range); + request->setCacheEnabled(false); + + QEventLoop loop; + + QObject::connect(request, &AssetResourceRequest::finished, &loop, &QEventLoop::quit); + + request->send(); + + loop.exec(); + + request->deleteLater(); + + return request->getResult() == ResourceRequest::Success; + } + + qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url(); + return false; +} + diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index d193c39cae..41da892701 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -36,6 +36,10 @@ public: static void init(); static void cleanup(); + // Blocking call to check if a resource exists. This function uses a QEventLoop internally + // to return to the calling thread so that events can still be processed. + static bool resourceExists(const QUrl& url); + private: static QThread _thread; From 9b117165bd21d7a3ff21c47fa5190ab4dcd2590d Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 2 May 2017 11:44:16 -0700 Subject: [PATCH 03/13] expire our announcements when we leave, and turn off messagesWaiting when everything is expired. --- scripts/system/tablet-goto.js | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index fec7a6de90..b9ca6f7407 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -143,9 +143,22 @@ button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); - var stories = {}; - var DEBUG = false; + var stories = {}, pingPong = false; + var DEBUG = true; //fixme + function expire(id) { + request({ + uri: location.metaverseServerUrl + '/api/v1/user_stories/' + id, + method: 'PUT', + body: {expired: true} + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR expiring story: ", error || response.status); + return; + } + }); + } function pollForAnnouncements() { + // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments? var actions = DEBUG ? 'snapshot' : 'announcement'; var count = DEBUG ? 10 : 100; var options = [ @@ -164,9 +177,16 @@ print("Error: unable to get", url, error || data.status); return; } - var didNotify = false; + var didNotify = false, key; + pingPong = !pingPong; data.user_stories.forEach(function (story) { - if (stories[story.id]) { // already seen + var stored = stories[story.id], storedOrNew = stored || story; + if ((storedOrNew.username === Account.username) && (storyOrNew.place_name !== location.placename)) { + expire(story.id); + return; // before marking + } + storedOrNew.pingPong = pingPong; + if (stored) { // already seen return; } stories[story.id] = story; @@ -174,12 +194,19 @@ Window.displayAnnouncement(message); didNotify = true; }); + for (key in stories) { // Any story we were tracking that was not marked, has expired. + if (stories[key].pingPong !== pingPong) { + delete stories[key]; + } + } if (didNotify) { messagesWaiting(true); if (HMD.isHandControllerAvailable()) { var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND); } + } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired. + messagesWaiting(false); } }); } From 84305c20498fe5ceda8fea22b214770c3b5f83cc Mon Sep 17 00:00:00 2001 From: samcake Date: Tue, 2 May 2017 17:30:31 -0700 Subject: [PATCH 04/13] Fixing the by region update of the compressed texture to match the 4 x 4 tiles alignment --- libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp | 8 ++++++-- .../gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 5db924dd5c..9fe6bbb772 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -429,9 +429,13 @@ void GL41VariableAllocationTexture::populateTransferQueue() { // consuming more than X bandwidth auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); const auto lines = mipDimensions.y; - auto bytesPerLine = mipSize / lines; + const uint32_t CHUNK_NUM_LINES { 4 }; + const auto numChunks = (lines + (CHUNK_NUM_LINES - 1)) / CHUNK_NUM_LINES; + auto bytesPerChunk = mipSize / numChunks; + //auto bytesPerLine = mipSize / lines; Q_ASSERT(0 == (mipSize % lines)); - uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine); + // uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine); + uint32_t linesPerTransfer = CHUNK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerChunk); uint32_t lineOffset = 0; while (lineOffset < lines) { uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 92d820e5f0..192b7f3088 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -197,9 +197,11 @@ void GL45ResourceTexture::populateTransferQueue() { // consuming more than X bandwidth auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); const auto lines = mipDimensions.y; - auto bytesPerLine = mipSize / lines; + const uint32_t CHUNK_NUM_LINES { 4 }; + const auto numChunks = (lines + (CHUNK_NUM_LINES - 1)) / CHUNK_NUM_LINES; + auto bytesPerChunk = mipSize / numChunks; Q_ASSERT(0 == (mipSize % lines)); - uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine); + uint32_t linesPerTransfer = CHUNK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerChunk); uint32_t lineOffset = 0; while (lineOffset < lines) { uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); From c583ffbac447d865914442817eaa5323b1fc67fd Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 3 May 2017 11:30:09 -0700 Subject: [PATCH 05/13] Clean up names and comments --- libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp | 11 +++++------ .../src/gpu/gl45/GL45BackendVariableTexture.cpp | 9 +++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 9fe6bbb772..a9d1ddb914 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -427,15 +427,14 @@ void GL41VariableAllocationTexture::populateTransferQueue() { // break down the transfers into chunks so that no single transfer is // consuming more than X bandwidth + // For compressed format, regions must be a multiple of the 4x4 tiles, so enforce 4 lines as the minimum block auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); const auto lines = mipDimensions.y; - const uint32_t CHUNK_NUM_LINES { 4 }; - const auto numChunks = (lines + (CHUNK_NUM_LINES - 1)) / CHUNK_NUM_LINES; - auto bytesPerChunk = mipSize / numChunks; - //auto bytesPerLine = mipSize / lines; + const uint32_t BLOCK_NUM_LINES { 4 }; + const auto numBlocks = (lines + (BLOCK_NUM_LINES - 1)) / BLOCK_NUM_LINES; + auto bytesPerBlock = mipSize / numBlocks; Q_ASSERT(0 == (mipSize % lines)); - // uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine); - uint32_t linesPerTransfer = CHUNK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerChunk); + uint32_t linesPerTransfer = BLOCK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerBlock); uint32_t lineOffset = 0; while (lineOffset < lines) { uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 192b7f3088..d46f38d88f 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -195,13 +195,14 @@ void GL45ResourceTexture::populateTransferQueue() { // break down the transfers into chunks so that no single transfer is // consuming more than X bandwidth + // For compressed format, regions must be a multiple of the 4x4 tiles, so enforce 4 lines as the minimum block auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); const auto lines = mipDimensions.y; - const uint32_t CHUNK_NUM_LINES { 4 }; - const auto numChunks = (lines + (CHUNK_NUM_LINES - 1)) / CHUNK_NUM_LINES; - auto bytesPerChunk = mipSize / numChunks; + const uint32_t BLOCK_NUM_LINES { 4 }; + const auto numBlocks = (lines + (BLOCK_NUM_LINES - 1)) / BLOCK_NUM_LINES; + auto bytesPerBlock = mipSize / numBlocks; Q_ASSERT(0 == (mipSize % lines)); - uint32_t linesPerTransfer = CHUNK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerChunk); + uint32_t linesPerTransfer = BLOCK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerBlock); uint32_t lineOffset = 0; while (lineOffset < lines) { uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); From 42887d41c464846877265298600731dbc8538ad7 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 3 May 2017 13:45:48 -0700 Subject: [PATCH 06/13] debugging --- scripts/system/tablet-goto.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index b9ca6f7407..6c18d0a681 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -145,15 +145,22 @@ var stories = {}, pingPong = false; var DEBUG = true; //fixme + function debug() { + if (!DEBUG) { + return; + } + print([].map.call(arguments, JSON.stringify)); + } + function expire(id) { request({ uri: location.metaverseServerUrl + '/api/v1/user_stories/' + id, method: 'PUT', body: {expired: true} }, function (error, response) { + debug('expired story', id, 'error:', error, 'response:', response); if (error || (response.status !== 'success')) { print("ERROR expiring story: ", error || response.status); - return; } }); } @@ -170,9 +177,11 @@ 'per_page=' + count ]; var url = location.metaverseServerUrl + '/api/v1/user_stories?' + options.join('&'); + url = 'https://highfidelity.com/api/v1/user_stories?include_actions=announcement'; //fixme remove request({ uri: url }, function (error, data) { + debug(url, error, data); if (error || (data.status !== 'success')) { print("Error: unable to get", url, error || data.status); return; @@ -181,6 +190,7 @@ pingPong = !pingPong; data.user_stories.forEach(function (story) { var stored = stories[story.id], storedOrNew = stored || story; + debug('story exists:', !!stored, storedOrNew); if ((storedOrNew.username === Account.username) && (storyOrNew.place_name !== location.placename)) { expire(story.id); return; // before marking From aa74dda9f36bb77a83b9e3f460291d246a91f3ef Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 3 May 2017 16:29:37 -0700 Subject: [PATCH 07/13] cleanup --- scripts/system/tablet-goto.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 13c4830d31..2cdfecede4 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -16,6 +16,13 @@ (function () { // BEGIN LOCAL_SCOPE var request = Script.require('request').request; + var DEBUG = false; + function debug() { + if (!DEBUG) { + return; + } + print([].map.call(arguments, JSON.stringify)); + } var gotoQmlSource = "TabletAddressDialog.qml"; var buttonName = "GOTO"; @@ -101,14 +108,6 @@ tablet.screenChanged.connect(onScreenChanged); var stories = {}, pingPong = false; - var DEBUG = true; //fixme - function debug() { - if (!DEBUG) { - return; - } - print([].map.call(arguments, JSON.stringify)); - } - function expire(id) { request({ uri: location.metaverseServerUrl + '/api/v1/user_stories/' + id, @@ -123,7 +122,7 @@ } function pollForAnnouncements() { // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments? - var actions = DEBUG ? 'snapshot' : 'announcement'; + var actions = 'announcement'; var count = DEBUG ? 10 : 100; var options = [ 'now=' + new Date().toISOString(), @@ -134,7 +133,6 @@ 'per_page=' + count ]; var url = location.metaverseServerUrl + '/api/v1/user_stories?' + options.join('&'); - url = 'https://highfidelity.com/api/v1/user_stories?include_actions=announcement'; //fixme remove request({ uri: url }, function (error, data) { @@ -148,7 +146,7 @@ data.user_stories.forEach(function (story) { var stored = stories[story.id], storedOrNew = stored || story; debug('story exists:', !!stored, storedOrNew); - if ((storedOrNew.username === Account.username) && (storyOrNew.place_name !== location.placename)) { + if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) { expire(story.id); return; // before marking } From 3e57e804655d5d5043b985c4d16e6ba48ae0f519 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 3 May 2017 16:39:34 -0700 Subject: [PATCH 08/13] use new request module. --- scripts/system/snapshot.js | 49 +------------------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index b93595f0b4..762dad1f7e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -38,54 +38,7 @@ var METAVERSE_BASE = location.metaverseServerUrl; // It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story, // POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS -function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. - var httpRequest = new XMLHttpRequest(), key; - // QT bug: apparently doesn't handle onload. Workaround using readyState. - httpRequest.onreadystatechange = function () { - var READY_STATE_DONE = 4; - var HTTP_OK = 200; - if (httpRequest.readyState >= READY_STATE_DONE) { - var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText, - response = !error && httpRequest.responseText, - contentType = !error && httpRequest.getResponseHeader('content-type'); - if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc. - try { - response = JSON.parse(response); - } catch (e) { - error = e; - } - } - callback(error, response); - } - }; - if (typeof options === 'string') { - options = { uri: options }; - } - if (options.url) { - options.uri = options.url; - } - if (!options.method) { - options.method = 'GET'; - } - if (options.body && (options.method === 'GET')) { // add query parameters - var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&'; - for (key in options.body) { - params.push(key + '=' + options.body[key]); - } - options.uri += appender + params.join('&'); - delete options.body; - } - if (options.json) { - options.headers = options.headers || {}; - options.headers["Content-type"] = "application/json"; - options.body = JSON.stringify(options.body); - } - for (key in options.headers || {}) { - httpRequest.setRequestHeader(key, options.headers[key]); - } - httpRequest.open(options.method, options.uri, true); - httpRequest.send(options.body); -} +var request = Script.require('request').request; function openLoginWindow() { if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) From 5e36bebc969fc31e15ea871849527407f9946125 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 3 May 2017 19:23:21 -0700 Subject: [PATCH 09/13] Store irradiance in the KTX files. --- libraries/gpu/src/gpu/Texture_ktx.cpp | 82 +++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index d2f93c0036..d5710bf84b 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -99,6 +99,61 @@ struct GPUKTXPayload { }; const std::string GPUKTXPayload::KEY { "hifi.gpu" }; + +struct IrradianceKTXPayload { + using Version = uint8; + + static const std::string KEY; + static const Version CURRENT_VERSION{ 0 }; + static const size_t PADDING{ 3 }; + static const size_t SIZE{ sizeof(Version) + sizeof(SphericalHarmonics) + PADDING }; + static_assert(IrradianceKTXPayload::SIZE == 148, "Packing size may differ between platforms"); + static_assert(IrradianceKTXPayload::SIZE % 4 == 0, "IrradianceKTXPayload is not 4 bytes aligned"); + + SphericalHarmonics _irradianceSH; + + Byte* serialize(Byte* data) const { + *(Version*)data = CURRENT_VERSION; + data += sizeof(Version); + + memcpy(data, &_irradianceSH, sizeof(SphericalHarmonics)); + data += sizeof(SphericalHarmonics); + + return data + PADDING; + } + + bool unserialize(const Byte* data, size_t size) { + if (size != SIZE) { + return false; + } + + Version version = *(const Version*)data; + if (version != CURRENT_VERSION) { + return false; + } + data += sizeof(Version); + + memcpy(&_irradianceSH, data, sizeof(SphericalHarmonics)); + data += sizeof(SphericalHarmonics); + + return true; + } + + static bool isIrradianceKTX(const ktx::KeyValue& val) { + return (val._key.compare(KEY) == 0); + } + + static bool findInKeyValues(const ktx::KeyValues& keyValues, IrradianceKTXPayload& payload) { + auto found = std::find_if(keyValues.begin(), keyValues.end(), isIrradianceKTX); + if (found != keyValues.end()) { + auto value = found->_value; + return payload.unserialize(value.data(), value.size()); + } + return false; + } +}; +const std::string IrradianceKTXPayload::KEY{ "hifi.irradianceSH" }; + KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) { { // We are doing a lot of work here just to get descriptor data @@ -304,16 +359,27 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { } } - GPUKTXPayload keyval; - keyval._samplerDesc = texture.getSampler().getDesc(); - keyval._usage = texture.getUsage(); - keyval._usageType = texture.getUsageType(); + GPUKTXPayload gpuKeyval; + gpuKeyval._samplerDesc = texture.getSampler().getDesc(); + gpuKeyval._usage = texture.getUsage(); + gpuKeyval._usageType = texture.getUsageType(); + Byte keyvalPayload[GPUKTXPayload::SIZE]; - keyval.serialize(keyvalPayload); + gpuKeyval.serialize(keyvalPayload); ktx::KeyValues keyValues; keyValues.emplace_back(GPUKTXPayload::KEY, (uint32)GPUKTXPayload::SIZE, (ktx::Byte*) &keyvalPayload); + if (texture.getIrradiance()) { + IrradianceKTXPayload irradianceKeyval; + irradianceKeyval._irradianceSH = *texture.getIrradiance(); + + Byte irradianceKeyvalPayload[IrradianceKTXPayload::SIZE]; + irradianceKeyval.serialize(irradianceKeyvalPayload); + + keyValues.emplace_back(IrradianceKTXPayload::KEY, (uint32)IrradianceKTXPayload::SIZE, (ktx::Byte*) &irradianceKeyvalPayload); + } + auto hash = texture.sourceHash(); if (!hash.empty()) { // the sourceHash is an std::string in hex @@ -409,6 +475,12 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDe // Assing the mips availables texture->setStoredMipFormat(mipFormat); texture->setKtxBacking(ktxfile); + + IrradianceKTXPayload irradianceKtxKeyValue; + if (IrradianceKTXPayload::findInKeyValues(descriptor.keyValues, irradianceKtxKeyValue)) { + texture->overrideIrradiance(std::make_shared(irradianceKtxKeyValue._irradianceSH)); + } + return texture; } From e87f8dfe45fd76e8d16e30e3ea5ec0cf5430caec Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 4 May 2017 13:31:24 -0700 Subject: [PATCH 10/13] fix issue with not seeing your own attachments --- interface/src/avatar/Avatar.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 3bd4c663d2..49ab862acf 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -525,13 +525,11 @@ static TextRenderer3D* textRenderer(TextRendererType type) { void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { auto avatarPayload = new render::Payload(self); auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload); - if (_skeletonModel->addToScene(scene, transaction)) { - _renderItemID = scene->allocateID(); - transaction.resetItem(_renderItemID, avatarPayloadPointer); - - for (auto& attachmentModel : _attachmentModels) { - attachmentModel->addToScene(scene, transaction); - } + _renderItemID = scene->allocateID(); + transaction.resetItem(_renderItemID, avatarPayloadPointer); + _skeletonModel->addToScene(scene, transaction); + for (auto& attachmentModel : _attachmentModels) { + attachmentModel->addToScene(scene, transaction); } } From 9954380ce33d36bf79ba2af8f8df02ae681b3073 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 4 May 2017 14:44:48 -0700 Subject: [PATCH 11/13] don't filter out the place you're at anymore --- interface/resources/qml/hifi/Feed.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index fd3472b7be..fc108f47e3 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -156,10 +156,8 @@ Column { function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches var words = filter.toUpperCase().split(/\s+/).filter(identity); function suggestable(story) { - if (story.action === 'snapshot') { - return true; - } - return story.place_name !== AddressManager.placename; // Not our entry, but do show other entry points to current domain. + // We could filter out places we don't want to suggest, such as those where (story.place_name === AddressManager.placename) or (story.username === Account.username). + return true; } function matches(story) { if (!words.length) { From 25ab8e7e9443632253e5a69b392998025a88e09f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 4 May 2017 14:45:58 -0700 Subject: [PATCH 12/13] debugging on call, but mostly, specify json headers, etc. --- scripts/system/tablet-goto.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 2cdfecede4..fb842d1314 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -21,7 +21,7 @@ if (!DEBUG) { return; } - print([].map.call(arguments, JSON.stringify)); + print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); } var gotoQmlSource = "TabletAddressDialog.qml"; @@ -46,6 +46,7 @@ switch (message.method) { case 'request': request(message.params, function (error, data) { + debug('rpc', request, 'error:', error, 'data:', data); response.error = error; response.result = data; tablet.sendToQml(response); @@ -109,12 +110,14 @@ var stories = {}, pingPong = false; function expire(id) { - request({ + var options = { uri: location.metaverseServerUrl + '/api/v1/user_stories/' + id, method: 'PUT', - body: {expired: true} - }, function (error, response) { - debug('expired story', id, 'error:', error, 'response:', response); + json: true, + body: {expire: "true"} + }; + request(options, function (error, response) { + debug('expired story', options, 'error:', error, 'response:', response); if (error || (response.status !== 'success')) { print("ERROR expiring story: ", error || response.status); } @@ -147,7 +150,9 @@ var stored = stories[story.id], storedOrNew = stored || story; debug('story exists:', !!stored, storedOrNew); if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) { - expire(story.id); + if (storedOrNew.audience == 'for_connections') { // Only expire if we haven't already done so. + expire(story.id); + } return; // before marking } storedOrNew.pingPong = pingPong; @@ -161,6 +166,7 @@ }); for (key in stories) { // Any story we were tracking that was not marked, has expired. if (stories[key].pingPong !== pingPong) { + debug('removing story', key); delete stories[key]; } } From 43a177cf9a3c3d9e271d1d7c7cac4ee3023384d3 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 4 May 2017 15:22:44 -0700 Subject: [PATCH 13/13] CR feedback --- libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 98e4299a13..be55653f64 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -502,7 +502,10 @@ static TextRenderer3D* textRenderer(TextRendererType type) { void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { auto avatarPayload = new render::Payload(self); auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload); - _renderItemID = scene->allocateID(); + + if (_renderItemID == render::Item::INVALID_ITEM_ID) { + _renderItemID = scene->allocateID(); + } transaction.resetItem(_renderItemID, avatarPayloadPointer); _skeletonModel->addToScene(scene, transaction); for (auto& attachmentModel : _attachmentModels) {