diff --git a/examples/editVoxels.js b/examples/editVoxels.js index cd9566717a..ed63c12eb9 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -68,7 +68,8 @@ colors[4] = { red: 236, green: 174, blue: 0 }; colors[5] = { red: 234, green: 133, blue: 0 }; colors[6] = { red: 211, green: 115, blue: 0 }; colors[7] = { red: 48, green: 116, blue: 119 }; -var numColors = 8; +colors[8] = { red: 31, green: 64, blue: 64 }; +var numColors = 9; var whichColor = -1; // Starting color is 'Copy' mode // Create sounds for adding, deleting, recoloring voxels @@ -138,34 +139,46 @@ var linePreviewRight = Overlays.addOverlay("line3d", { // these will be used below -var sliderWidth = 158; -var sliderHeight = 35; +var sliderWidth = 154; +var sliderHeight = 37; // These will be our "overlay IDs" var swatches = new Array(); -var swatchHeight = 54; -var swatchWidth = 31; -var swatchesWidth = swatchWidth * numColors; +var swatchExtraPadding = 5; +var swatchHeight = 37; +var swatchWidth = 27; +var swatchesWidth = swatchWidth * numColors + numColors + swatchExtraPadding * 2; var swatchesX = (windowDimensions.x - (swatchesWidth + sliderWidth)) / 2; -var swatchesY = windowDimensions.y - swatchHeight; +var swatchesY = windowDimensions.y - swatchHeight + 1; + +var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; // create the overlays, position them in a row, set their colors, and for the selected one, use a different source image // location so that it displays the "selected" marker for (s = 0; s < numColors; s++) { - var imageFromX = 12 + (s * 27); - var imageFromY = 0; - if (s == whichColor) { - imageFromY = 55; + + var extraWidth = 0; + + if (s == 0) { + extraWidth = swatchExtraPadding; + } + + var imageFromX = swatchExtraPadding - extraWidth + s * swatchWidth; + var imageFromY = swatchHeight + 1; + + var swatchX = swatchExtraPadding - extraWidth + swatchesX + ((swatchWidth - 1) * s); + + if (s == (numColors - 1)) { + extraWidth = swatchExtraPadding; } - var swatchX = swatchesX + (30 * s); swatches[s] = Overlays.addOverlay("image", { x: swatchX, y: swatchesY, - width: swatchWidth, + width: swatchWidth + extraWidth, height: swatchHeight, - subImage: { x: imageFromX, y: imageFromY, width: (swatchWidth - 1), height: swatchHeight }, - imageURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/swatches.svg", + subImage: { x: imageFromX, y: imageFromY, width: swatchWidth + extraWidth, height: swatchHeight }, + imageURL: toolIconUrl + "swatches.svg", color: colors[s], alpha: 1, visible: editToolsOn @@ -174,66 +187,41 @@ for (s = 0; s < numColors; s++) { // These will be our tool palette overlays -var numberOfTools = 5; -var toolHeight = 40; -var toolWidth = 62; -var toolsHeight = toolHeight * numberOfTools; -var toolsX = 0; +var numberOfTools = 3; +var toolHeight = 50; +var toolWidth = 50; +var toolVerticalSpacing = 4; +var toolsHeight = toolHeight * numberOfTools + toolVerticalSpacing * (numberOfTools - 1); +var toolsX = 8; var toolsY = (windowDimensions.y - toolsHeight) / 2; -var addToolAt = 0; -var deleteToolAt = 1; -var recolorToolAt = 2; -var eyedropperToolAt = 3; -var selectToolAt = 4; -var toolSelectedColor = { red: 255, green: 255, blue: 255 }; -var notSelectedColor = { red: 128, green: 128, blue: 128 }; +var voxelToolAt = 0; +var recolorToolAt = 1; +var eyedropperToolAt = 2; -var addTool = Overlays.addOverlay("image", { +var voxelTool = Overlays.addOverlay("image", { x: 0, y: 0, width: toolWidth, height: toolHeight, - subImage: { x: 0, y: toolHeight * addToolAt, width: toolWidth, height: toolHeight }, - imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", - color: toolSelectedColor, - visible: false, - alpha: 0.9 - }); - -var deleteTool = Overlays.addOverlay("image", { - x: 0, y: 0, width: toolWidth, height: toolHeight, - subImage: { x: 0, y: toolHeight * deleteToolAt, width: toolWidth, height: toolHeight }, - imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", - color: toolSelectedColor, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "voxel-tool.svg", visible: false, alpha: 0.9 }); var recolorTool = Overlays.addOverlay("image", { x: 0, y: 0, width: toolWidth, height: toolHeight, - subImage: { x: 0, y: toolHeight * recolorToolAt, width: toolWidth, height: toolHeight }, - imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", - color: toolSelectedColor, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "paint-tool.svg", visible: false, alpha: 0.9 }); var eyedropperTool = Overlays.addOverlay("image", { x: 0, y: 0, width: toolWidth, height: toolHeight, - subImage: { x: 0, y: toolHeight * eyedropperToolAt, width: toolWidth, height: toolHeight }, - imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", - color: toolSelectedColor, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "eyedropper-tool.svg", visible: false, alpha: 0.9 }); - -var selectTool = Overlays.addOverlay("image", { - x: 0, y: 0, width: toolWidth, height: toolHeight, - subImage: { x: 0, y: toolHeight * selectToolAt, width: toolWidth, height: toolHeight }, - imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", - color: toolSelectedColor, - visible: false, - alpha: 0.9 - }); - // This will create a couple of image overlays that make a "slider", we will demonstrate how to trap mouse messages to // move the slider @@ -242,35 +230,34 @@ var selectTool = Overlays.addOverlay("image", { //var sliderWidth = 158; //var sliderHeight = 35; -var sliderX = swatchesX + swatchesWidth; -var sliderY = windowDimensions.y - sliderHeight; +var sliderOffsetX = 17; +var sliderX = swatchesX - swatchWidth - sliderOffsetX; +var sliderY = windowDimensions.y - sliderHeight + 1; var slider = Overlays.addOverlay("image", { // alternate form of expressing bounds bounds: { x: sliderX, y: sliderY, width: sliderWidth, height: sliderHeight}, - imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", - color: { red: 255, green: 255, blue: 255}, + imageURL: toolIconUrl + "voxel-size-slider-bg.svg", alpha: 1, visible: false }); - // The slider is handled in the mouse event callbacks. var isMovingSlider = false; var thumbClickOffsetX = 0; // This is the thumb of our slider -var minThumbX = 30; // relative to the x of the slider -var maxThumbX = minThumbX + 65; +var minThumbX = 20; // relative to the x of the slider +var maxThumbX = minThumbX + 90; var thumbExtents = maxThumbX - minThumbX; var thumbX = (minThumbX + maxThumbX) / 2; -var thumbY = sliderY + 9; +var thumbOffsetY = 11; +var thumbY = sliderY + thumbOffsetY; var thumb = Overlays.addOverlay("image", { x: sliderX + thumbX, y: thumbY, - width: 18, + width: 17, height: 17, - imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", - color: { red: 255, green: 255, blue: 255}, + imageURL: toolIconUrl + "voxel-size-slider-handle.svg", alpha: 1, visible: false }); @@ -347,11 +334,9 @@ var trackAsRecolor = false; var trackAsEyedropper = false; var trackAsOrbitOrPan = false; -var addToolSelected = true; -var deleteToolSelected = false; +var voxelToolSelected = true; var recolorToolSelected = false; var eyedropperToolSelected = false; -var selectToolSelected = false; function playRandomAddSound(audioOptions) { if (Math.random() < 0.33) { @@ -524,51 +509,12 @@ function showPreviewVoxel() { } var guidePosition; - - if (trackAsDelete || deleteToolSelected) { - guidePosition = calculateVoxelFromIntersection(intersection,"delete"); - Overlays.editOverlay(voxelPreview, { - position: guidePosition, - size: guidePosition.s + zFightingSizeAdjust, - visible: true, - color: { red: 255, green: 0, blue: 0 }, - solid: false, - alpha: 1 - }); - } else if (selectToolSelected) { - guidePosition = calculateVoxelFromIntersection(intersection,"select"); - Overlays.editOverlay(voxelPreview, { - position: guidePosition, - size: guidePosition.s + zFightingSizeAdjust, - visible: true, - color: { red: 255, green: 255, blue: 0 }, - solid: false, - alpha: 1 - }); - } else if (trackAsRecolor || recolorToolSelected || trackAsEyedropper|| eyedropperToolSelected) { - guidePosition = calculateVoxelFromIntersection(intersection,"recolor"); - - Overlays.editOverlay(voxelPreview, { - position: guidePosition, - size: guidePosition.s + zFightingSizeAdjust, - visible: true, - color: voxelColor, - solid: true, - alpha: 0.8 - }); + if (trackAsRecolor || recolorToolSelected || trackAsEyedropper || eyedropperToolSelected) { + Overlays.editOverlay(voxelPreview, { visible: true }); } else if (trackAsOrbitOrPan) { Overlays.editOverlay(voxelPreview, { visible: false }); - } else if (addToolSelected && !isExtruding) { - guidePosition = calculateVoxelFromIntersection(intersection,"add"); - - Overlays.editOverlay(voxelPreview, { - position: guidePosition, - size: (guidePosition.s - zFightingSizeAdjust), - visible: true, - color: voxelColor, - solid: true, - alpha: 0.7 - }); + } else if (voxelToolSelected && !isExtruding) { + Overlays.editOverlay(voxelPreview, { visible: true }); } else if (isExtruding) { Overlays.editOverlay(voxelPreview, { visible: false }); } @@ -587,23 +533,11 @@ function showPreviewLines() { } resultVoxel = calculateVoxelFromIntersection(intersection,""); - if (selectToolSelected) { - Overlays.editOverlay(voxelPreview, { - position: resultVoxel, - size: resultVoxel.s + zFightingSizeAdjust, - visible: true, - color: { red: 255, green: 255, blue: 0 }, - lineWidth: previewLineWidth, - solid: false, - alpha: 1 - }); - } else { - Overlays.editOverlay(voxelPreview, { visible: false }); - Overlays.editOverlay(linePreviewTop, { position: resultVoxel.topLeft, end: resultVoxel.topRight, visible: true }); - Overlays.editOverlay(linePreviewBottom, { position: resultVoxel.bottomLeft, end: resultVoxel.bottomRight, visible: true }); - Overlays.editOverlay(linePreviewLeft, { position: resultVoxel.topLeft, end: resultVoxel.bottomLeft, visible: true }); - Overlays.editOverlay(linePreviewRight, { position: resultVoxel.topRight, end: resultVoxel.bottomRight, visible: true }); - } + Overlays.editOverlay(voxelPreview, { visible: false }); + Overlays.editOverlay(linePreviewTop, { position: resultVoxel.topLeft, end: resultVoxel.topRight, visible: true }); + Overlays.editOverlay(linePreviewBottom, { position: resultVoxel.bottomLeft, end: resultVoxel.bottomRight, visible: true }); + Overlays.editOverlay(linePreviewLeft, { position: resultVoxel.topLeft, end: resultVoxel.bottomLeft, visible: true }); + Overlays.editOverlay(linePreviewRight, { position: resultVoxel.topRight, end: resultVoxel.bottomRight, visible: true }); } else { Overlays.editOverlay(voxelPreview, { visible: false }); Overlays.editOverlay(linePreviewTop, { visible: false }); @@ -829,44 +763,39 @@ function mousePressEvent(event) { isMovingSlider = true; thumbClickOffsetX = event.x - (sliderX + thumbX); // this should be the position of the mouse relative to the thumb clickedOnSomething = true; - } else if (clickedOverlay == addTool) { - addToolSelected = true; - deleteToolSelected = false; + + Overlays.editOverlay(thumb, { imageURL: toolIconUrl + "voxel-size-slider-handle.svg", }); + + } else if (clickedOverlay == voxelTool) { + voxelToolSelected = true; recolorToolSelected = false; eyedropperToolSelected = false; - selectToolSelected = false; - moveTools(); - clickedOnSomething = true; - } else if (clickedOverlay == deleteTool) { - addToolSelected = false; - deleteToolSelected = true; - recolorToolSelected = false; - eyedropperToolSelected = false; - selectToolSelected = false; moveTools(); clickedOnSomething = true; } else if (clickedOverlay == recolorTool) { - addToolSelected = false; - deleteToolSelected = false; + voxelToolSelected = false; recolorToolSelected = true; eyedropperToolSelected = false; - selectToolSelected = false; moveTools(); clickedOnSomething = true; } else if (clickedOverlay == eyedropperTool) { - addToolSelected = false; - deleteToolSelected = false; + voxelToolSelected = false; recolorToolSelected = false; eyedropperToolSelected = true; - selectToolSelected = false; moveTools(); clickedOnSomething = true; - } else if (clickedOverlay == selectTool) { - addToolSelected = false; - deleteToolSelected = false; - recolorToolSelected = false; - eyedropperToolSelected = false; - selectToolSelected = true; + } else if (clickedOverlay == slider) { + + if (event.x < sliderX + minThumbX) { + thumbX -= thumbDeltaPerStep; + calcScaleFromThumb(thumbX); + } + + if (event.x > sliderX + maxThumbX) { + thumbX += thumbDeltaPerStep; + calcScaleFromThumb(thumbX); + } + moveTools(); clickedOnSomething = true; } else { @@ -908,7 +837,7 @@ function mousePressEvent(event) { startPanMode(event); isPanning = true; } - } else if (deleteToolSelected || trackAsDelete || (event.isRightButton && !trackAsEyedropper)) { + } else if (trackAsDelete || event.isRightButton && !trackAsEyedropper) { // Delete voxel voxelDetails = calculateVoxelFromIntersection(intersection,"delete"); Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s); @@ -932,7 +861,7 @@ function mousePressEvent(event) { colors[whichColor].red, colors[whichColor].green, colors[whichColor].blue); Audio.playSound(changeColorSound, audioOptions); Overlays.editOverlay(voxelPreview, { visible: false }); - } else if (addToolSelected) { + } else if (voxelToolSelected) { // Add voxel on face if (whichColor == -1) { // Copy mode - use clicked voxel color @@ -968,7 +897,7 @@ function keyPressEvent(event) { // if our tools are off, then don't do anything if (editToolsOn) { var nVal = parseInt(event.text); - if (event.text == "0") { + if (event.text == "`") { print("Color = Copy"); whichColor = -1; Audio.playSound(clickSound, audioOptions); @@ -978,7 +907,7 @@ function keyPressEvent(event) { print("Color = " + (whichColor + 1)); Audio.playSound(clickSound, audioOptions); moveTools(); - } else if (event.text == "9") { + } else if (event.text == "0") { // Create a brand new 1 meter voxel in front of your avatar var color = whichColor; if (color == -1) color = 0; @@ -1010,18 +939,6 @@ function keyPressEvent(event) { function keyReleaseEvent(event) { trackKeyReleaseEvent(event); // used by preview support - - // handle clipboard items - if (selectToolSelected) { - // menu tied to BACKSPACE, so we handle DELETE key here... - if (event.text == "DELETE") { - var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY); - var intersection = Voxels.findRayIntersection(pickRay); - selectedVoxel = calculateVoxelFromIntersection(intersection,"select"); - print("the DELETE key was pressed... delete"); - Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s); - } - } } function setupMenus() { @@ -1186,22 +1103,33 @@ function mouseReleaseEvent(event) { function moveTools() { // move the swatches swatchesX = (windowDimensions.x - (swatchesWidth + sliderWidth)) / 2; - swatchesY = windowDimensions.y - swatchHeight; + swatchesY = windowDimensions.y - swatchHeight + 1; // create the overlays, position them in a row, set their colors, and for the selected one, use a different source image // location so that it displays the "selected" marker for (s = 0; s < numColors; s++) { - var imageFromX = 12 + (s * 27); - var imageFromY = 0; - if (s == whichColor) { - imageFromY = 55; - } - var swatchX = swatchesX + ((swatchWidth - 1) * s); + var extraWidth = 0; + if (s == 0) { + extraWidth = swatchExtraPadding; + } + + var imageFromX = swatchExtraPadding - extraWidth + s * swatchWidth; + var imageFromY = swatchHeight + 1; + if (s == whichColor) { + imageFromY = 0; + } + + var swatchX = swatchExtraPadding - extraWidth + swatchesX + ((swatchWidth - 1) * s); + + if (s == (numColors - 1)) { + extraWidth = swatchExtraPadding; + } + Overlays.editOverlay(swatches[s], { x: swatchX, y: swatchesY, - subImage: { x: imageFromX, y: imageFromY, width: (swatchWidth - 1), height: swatchHeight }, + subImage: { x: imageFromX, y: imageFromY, width: swatchWidth + extraWidth, height: swatchHeight }, color: colors[s], alpha: 1, visible: editToolsOn @@ -1210,62 +1138,45 @@ function moveTools() { // move the tools toolsY = (windowDimensions.y - toolsHeight) / 2; - addToolColor = notSelectedColor; - deleteToolColor = notSelectedColor; - recolorToolColor = notSelectedColor; - eyedropperToolColor = notSelectedColor; - selectToolColor = notSelectedColor; - if (trackAsDelete || deleteToolSelected) { - deleteToolColor = toolSelectedColor; - } else if (trackAsRecolor || recolorToolSelected) { - recolorToolColor = toolSelectedColor; + var voxelToolOffset = 1, + recolorToolOffset = 1, + eyedropperToolOffset = 1; + + if (trackAsRecolor || recolorToolSelected) { + recolorToolOffset = 2; } else if (trackAsEyedropper || eyedropperToolSelected) { - eyedropperToolColor = toolSelectedColor; - } else if (selectToolSelected) { - selectToolColor = toolSelectedColor; + eyedropperToolOffset = 2; } else if (trackAsOrbitOrPan) { // nothing gets selected in this case... } else { - addToolColor = toolSelectedColor; + voxelToolOffset = 2; } - Overlays.editOverlay(addTool, { - x: 0, y: toolsY + (toolHeight * addToolAt), width: toolWidth, height: toolHeight, - color: addToolColor, - visible: editToolsOn - }); - - Overlays.editOverlay(deleteTool, { - x: 0, y: toolsY + (toolHeight * deleteToolAt), width: toolWidth, height: toolHeight, - color: deleteToolColor, + Overlays.editOverlay(voxelTool, { + subImage: { x: 0, y: toolHeight * voxelToolOffset, width: toolWidth, height: toolHeight }, + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * voxelToolAt), width: toolWidth, height: toolHeight, visible: editToolsOn }); Overlays.editOverlay(recolorTool, { - x: 0, y: toolsY + (toolHeight * recolorToolAt), width: toolWidth, height: toolHeight, - color: recolorToolColor, + subImage: { x: 0, y: toolHeight * recolorToolOffset, width: toolWidth, height: toolHeight }, + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * recolorToolAt), width: toolWidth, height: toolHeight, visible: editToolsOn }); Overlays.editOverlay(eyedropperTool, { - x: 0, y: toolsY + (toolHeight * eyedropperToolAt), width: toolWidth, height: toolHeight, - color: eyedropperToolColor, + subImage: { x: 0, y: toolHeight * eyedropperToolOffset, width: toolWidth, height: toolHeight }, + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * eyedropperToolAt), width: toolWidth, height: toolHeight, visible: editToolsOn }); - Overlays.editOverlay(selectTool, { - x: 0, y: toolsY + (toolHeight * selectToolAt), width: toolWidth, height: toolHeight, - color: selectToolColor, - visible: editToolsOn - }); - - sliderX = swatchesX + swatchesWidth; - sliderY = windowDimensions.y - sliderHeight; + sliderX = swatchesX + swatchesWidth - sliderOffsetX; + sliderY = windowDimensions.y - sliderHeight + 1; + thumbY = sliderY + thumbOffsetY; Overlays.editOverlay(slider, { x: sliderX, y: sliderY, visible: editToolsOn }); // This is the thumb of our slider - thumbY = sliderY + 9; Overlays.editOverlay(thumb, { x: sliderX + thumbX, y: thumbY, visible: editToolsOn }); } @@ -1421,11 +1332,9 @@ function scriptEnding() { for (s = 0; s < numColors; s++) { Overlays.deleteOverlay(swatches[s]); } - Overlays.deleteOverlay(addTool); - Overlays.deleteOverlay(deleteTool); + Overlays.deleteOverlay(voxelTool); Overlays.deleteOverlay(recolorTool); Overlays.deleteOverlay(eyedropperTool); - Overlays.deleteOverlay(selectTool); Overlays.deleteOverlay(slider); Overlays.deleteOverlay(thumb); Controller.releaseKeyEvents({ text: "+" }); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 762ada8eea..2efba6f98f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,6 +58,7 @@ #include <PacketHeaders.h> #include <ParticlesScriptingInterface.h> #include <PerfStat.h> +#include <ResourceCache.h> #include <UUID.h> #include <VoxelSceneStats.h> @@ -275,6 +276,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); _networkAccessManager->setCache(cache); + ResourceCache::setNetworkAccessManager(_networkAccessManager); + ResourceCache::setRequestLimit(3); + _window->setCentralWidget(_glWidget); restoreSizeAndPosition(); diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 59a714ece5..eeb92be5ec 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -35,9 +35,6 @@ void MetavoxelSystem::init() { _program.link(); _pointScaleLocation = _program.uniformLocation("pointScale"); - - // let the script cache know to use our common access manager - ScriptCache::getInstance()->setNetworkAccessManager(Application::getInstance()->getNetworkAccessManager()); } _buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); _buffer.create(); diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 13563be200..e9719e7a84 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -8,7 +8,6 @@ #include <cmath> #include <QNetworkReply> -#include <QTimer> #include "Application.h" #include "GeometryCache.h" @@ -287,53 +286,25 @@ void GeometryCache::renderGrid(int xDivisions, int yDivisions) { } QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) { - if (!url.isValid() && fallback.isValid()) { - return getGeometry(fallback, QUrl(), delayLoad); - } - QSharedPointer<NetworkGeometry> geometry = _networkGeometry.value(url); - if (geometry.isNull()) { - geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ? - getGeometry(fallback, QUrl(), true) : QSharedPointer<NetworkGeometry>(), delayLoad)); - geometry->setLODParent(geometry); - _networkGeometry.insert(url, geometry); - } - return geometry; + return getResource(url, fallback, delayLoad).staticCast<NetworkGeometry>(); +} + +QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url, + const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) { + + QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url, fallback.staticCast<NetworkGeometry>(), delayLoad)); + geometry->setLODParent(geometry); + return geometry.staticCast<Resource>(); } const float NetworkGeometry::NO_HYSTERESIS = -1.0f; NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBase) : - _request(url), - _reply(NULL), + Resource(url, delayLoad), _mapping(mapping), _textureBase(textureBase.isValid() ? textureBase : url), - _fallback(fallback), - _startedLoading(false), - _failedToLoad(false), - _attempts(0) { - - if (!url.isValid()) { - return; - } - _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - - // start loading immediately unless instructed otherwise - if (!delayLoad) { - makeRequest(); - } -} - -NetworkGeometry::~NetworkGeometry() { - if (_reply != NULL) { - delete _reply; - } -} - -void NetworkGeometry::ensureLoading() { - if (!_startedLoading) { - makeRequest(); - } + _fallback(fallback) { } QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const { @@ -406,24 +377,60 @@ glm::vec4 NetworkGeometry::computeAverageColor() const { return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles; } -void NetworkGeometry::makeRequest() { - _startedLoading = true; - _reply = Application::getInstance()->getNetworkAccessManager()->get(_request); +void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) { + Resource::setLoadPriority(owner, priority); - connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); - connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); + for (int i = 0; i < _meshes.size(); i++) { + NetworkMesh& mesh = _meshes[i]; + for (int j = 0; j < mesh.parts.size(); j++) { + NetworkMeshPart& part = mesh.parts[j]; + if (part.diffuseTexture) { + part.diffuseTexture->setLoadPriority(owner, priority); + } + if (part.normalTexture) { + part.normalTexture->setLoadPriority(owner, priority); + } + } + } } -void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (!_reply->isFinished()) { - return; - } +void NetworkGeometry::setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities) { + Resource::setLoadPriorities(priorities); - QUrl url = _reply->url(); - QByteArray data = _reply->readAll(); - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; + for (int i = 0; i < _meshes.size(); i++) { + NetworkMesh& mesh = _meshes[i]; + for (int j = 0; j < mesh.parts.size(); j++) { + NetworkMeshPart& part = mesh.parts[j]; + if (part.diffuseTexture) { + part.diffuseTexture->setLoadPriorities(priorities); + } + if (part.normalTexture) { + part.normalTexture->setLoadPriorities(priorities); + } + } + } +} + +void NetworkGeometry::clearLoadPriority(const QPointer<QObject>& owner) { + Resource::clearLoadPriority(owner); + + for (int i = 0; i < _meshes.size(); i++) { + NetworkMesh& mesh = _meshes[i]; + for (int j = 0; j < mesh.parts.size(); j++) { + NetworkMeshPart& part = mesh.parts[j]; + if (part.diffuseTexture) { + part.diffuseTexture->clearLoadPriority(owner); + } + if (part.normalTexture) { + part.normalTexture->clearLoadPriority(owner); + } + } + } +} + +void NetworkGeometry::downloadFinished(QNetworkReply* reply) { + QUrl url = reply->url(); + QByteArray data = reply->readAll(); if (url.path().toLower().endsWith(".fst")) { // it's a mapping file; parse it and get the mesh filename @@ -453,7 +460,7 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT // make the request immediately only if we have no LODs to switch between _startedLoading = false; if (_lods.isEmpty()) { - makeRequest(); + attemptRequest(); } } return; @@ -477,10 +484,12 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT if (!part.diffuseFilename.isEmpty()) { networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture( _textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye); + networkPart.diffuseTexture->setLoadPriorities(_loadPriorities); } if (!part.normalFilename.isEmpty()) { networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture( _textureBase.resolved(QUrl(part.normalFilename)), true); + networkPart.normalTexture->setLoadPriorities(_loadPriorities); } networkMesh.parts.append(networkPart); @@ -560,42 +569,6 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT } } -void NetworkGeometry::handleReplyError() { - QDebug debug = qDebug() << _reply->errorString(); - - QNetworkReply::NetworkError error = _reply->error(); - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; - - // retry for certain types of failures - switch (error) { - case QNetworkReply::RemoteHostClosedError: - case QNetworkReply::TimeoutError: - case QNetworkReply::TemporaryNetworkFailureError: - case QNetworkReply::ProxyConnectionClosedError: - case QNetworkReply::ProxyTimeoutError: - case QNetworkReply::UnknownNetworkError: - case QNetworkReply::UnknownProxyError: - case QNetworkReply::UnknownContentError: - case QNetworkReply::ProtocolFailure: { - // retry with increasing delays - const int MAX_ATTEMPTS = 8; - const int BASE_DELAY_MS = 1000; - if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); - debug << " -- retrying..."; - return; - } - // fall through to final failure - } - default: - _failedToLoad = true; - break; - } - -} - bool NetworkMeshPart::isTranslucent() const { return diffuseTexture && diffuseTexture->isTranslucent(); } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index a1d159be31..e5139088d3 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -12,24 +12,19 @@ // include this before QOpenGLBuffer, which includes an earlier version of OpenGL #include "InterfaceConfig.h" -#include <QHash> #include <QMap> -#include <QNetworkRequest> -#include <QObject> #include <QOpenGLBuffer> -#include <QSharedPointer> -#include <QWeakPointer> + +#include <ResourceCache.h> #include "FBXReader.h" -class QNetworkReply; - class NetworkGeometry; class NetworkMesh; class NetworkTexture; /// Stores cached geometry. -class GeometryCache { +class GeometryCache : public ResourceCache { public: ~GeometryCache(); @@ -44,6 +39,11 @@ public: /// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); +protected: + + virtual QSharedPointer<Resource> createResource(const QUrl& url, + const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra); + private: typedef QPair<int, int> IntPair; @@ -58,7 +58,7 @@ private: }; /// Geometry loaded from the network. -class NetworkGeometry : public QObject { +class NetworkGeometry : public Resource { Q_OBJECT public: @@ -68,14 +68,10 @@ public: NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad, const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); - ~NetworkGeometry(); /// Checks whether the geometry is fulled loaded. bool isLoaded() const { return !_geometry.joints.isEmpty(); } - /// Makes sure that the geometry has started loading. - void ensureLoading(); - /// Returns a pointer to the geometry appropriate for the specified distance. /// \param hysteresis a hysteresis parameter that prevents rapid model switching QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const; @@ -86,11 +82,13 @@ public: /// Returns the average color of all meshes in the geometry. glm::vec4 computeAverageColor() const; -private slots: + virtual void setLoadPriority(const QPointer<QObject>& owner, float priority); + virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities); + virtual void clearLoadPriority(const QPointer<QObject>& owner); - void makeRequest(); - void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleReplyError(); +protected: + + virtual void downloadFinished(QNetworkReply* reply); private: @@ -98,15 +96,10 @@ private: void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; } - QNetworkRequest _request; - QNetworkReply* _reply; QVariantHash _mapping; QUrl _textureBase; QSharedPointer<NetworkGeometry> _fallback; - bool _startedLoading; - bool _failedToLoad; - int _attempts; QMap<float, QSharedPointer<NetworkGeometry> > _lods; FBXGeometry _geometry; QVector<NetworkMesh> _meshes; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 55594fa3dc..599abf29aa 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -139,6 +139,7 @@ void Model::simulate(float deltaTime, bool delayLoad) { _geometry = geometry; } if (!delayLoad) { + _geometry->setLoadPriority(this, -_lodDistance); _geometry->ensureLoading(); } } @@ -833,6 +834,10 @@ void Model::deleteGeometry() { _blendedVertexBufferIDs.clear(); _jointStates.clear(); _meshStates.clear(); + + if (_geometry) { + _geometry->clearLoadPriority(this); + } } void Model::renderMeshes(float alpha, bool translucent) { diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 8bfef5a742..0a6a3fe6f8 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -11,7 +11,6 @@ #include <QGLWidget> #include <QNetworkReply> #include <QOpenGLFramebufferObject> -#include <QTimer> #include <glm/gtc/random.hpp> @@ -124,19 +123,13 @@ GLuint TextureCache::getFileTextureID(const QString& filename) { } QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) { - QSharedPointer<NetworkTexture> texture; - if (dilatable) { - texture = _dilatableNetworkTextures.value(url); - if (texture.isNull()) { - texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url, normalMap)); - _dilatableNetworkTextures.insert(url, texture); - } - } else { - texture = _networkTextures.value(url); - if (texture.isNull()) { - texture = QSharedPointer<NetworkTexture>(new NetworkTexture(url, normalMap)); - _networkTextures.insert(url, texture); - } + if (!dilatable) { + return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>(); + } + QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url); + if (texture.isNull()) { + texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url)); + _dilatableNetworkTextures.insert(url, texture); } return texture; } @@ -233,6 +226,11 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) { return false; } +QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, + const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) { + return QSharedPointer<Resource>(new NetworkTexture(url, *(const bool*)extra)); +} + QOpenGLFramebufferObject* TextureCache::createFramebufferObject() { QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size()); Application::getInstance()->getGLWidget()->installEventFilter(this); @@ -254,9 +252,7 @@ Texture::~Texture() { } NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : - _request(url), - _reply(NULL), - _attempts(0), + Resource(url), _averageColor(1.0f, 1.0f, 1.0f, 1.0f), _translucent(false), _loaded(false) { @@ -265,8 +261,6 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : _loaded = true; return; } - _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - makeRequest(); // default to white/blue glBindTexture(GL_TEXTURE_2D, getID()); @@ -274,35 +268,13 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : glBindTexture(GL_TEXTURE_2D, 0); } -NetworkTexture::~NetworkTexture() { - if (_reply != NULL) { - delete _reply; - } -} - -void NetworkTexture::imageLoaded(const QImage& image) { - // nothing by default -} - -void NetworkTexture::makeRequest() { - _reply = Application::getInstance()->getNetworkAccessManager()->get(_request); - - connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); - connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); -} - -void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (bytesReceived < bytesTotal && !_reply->isFinished()) { - return; - } - - QByteArray entirety = _reply->readAll(); - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; +void NetworkTexture::downloadFinished(QNetworkReply* reply) { _loaded = true; - QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32); + QImage image = QImage::fromData(reply->readAll()); + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } // sum up the colors for the average and check for translucency glm::vec4 accumulated; @@ -334,27 +306,12 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo glBindTexture(GL_TEXTURE_2D, 0); } -void NetworkTexture::handleReplyError() { - QDebug debug = qDebug() << _reply->errorString(); - - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; - - // retry with increasing delays - const int MAX_ATTEMPTS = 8; - const int BASE_DELAY_MS = 1000; - if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); - debug << " -- retrying..."; - - } else { - _loaded = true; - } +void NetworkTexture::imageLoaded(const QImage& image) { + // nothing by default } -DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, bool normalMap) : - NetworkTexture(url, normalMap), +DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) : + NetworkTexture(url, false), _innerRadius(0), _outerRadius(0) { diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index ca7bf67a32..2c8f4d7cf9 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -9,23 +9,19 @@ #ifndef __interface__TextureCache__ #define __interface__TextureCache__ -#include <QHash> #include <QImage> #include <QMap> -#include <QNetworkRequest> -#include <QObject> -#include <QSharedPointer> -#include <QWeakPointer> + +#include <ResourceCache.h> #include "InterfaceConfig.h" -class QNetworkReply; class QOpenGLFramebufferObject; class NetworkTexture; /// Stores cached textures, including render-to-texture targets. -class TextureCache : public QObject { +class TextureCache : public ResourceCache { Q_OBJECT public: @@ -73,6 +69,11 @@ public: virtual bool eventFilter(QObject* watched, QEvent* event); +protected: + + virtual QSharedPointer<Resource> createResource(const QUrl& url, + const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra); + private: QOpenGLFramebufferObject* createFramebufferObject(); @@ -83,7 +84,6 @@ private: QHash<QString, GLuint> _fileTextureIDs; - QHash<QUrl, QWeakPointer<NetworkTexture> > _networkTextures; QHash<QUrl, QWeakPointer<NetworkTexture> > _dilatableNetworkTextures; GLuint _primaryDepthTextureID; @@ -110,13 +110,12 @@ private: }; /// A texture loaded from the network. -class NetworkTexture : public QObject, public Texture { +class NetworkTexture : public Resource, public Texture { Q_OBJECT public: NetworkTexture(const QUrl& url, bool normalMap); - ~NetworkTexture(); bool isLoaded() const { return _loaded; } @@ -129,19 +128,11 @@ public: protected: - virtual void imageLoaded(const QImage& image); - -private slots: - - void makeRequest(); - void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleReplyError(); + virtual void downloadFinished(QNetworkReply* reply); + virtual void imageLoaded(const QImage& image); private: - QNetworkRequest _request; - QNetworkReply* _reply; - int _attempts; glm::vec4 _averageColor; bool _translucent; bool _loaded; @@ -153,7 +144,7 @@ class DilatableNetworkTexture : public NetworkTexture { public: - DilatableNetworkTexture(const QUrl& url, bool normalMap); + DilatableNetworkTexture(const QUrl& url); /// Returns a pointer to a texture with the requested amount of dilation. QSharedPointer<Texture> getDilatedTexture(float dilation); diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp index e7610038f5..da29186541 100644 --- a/libraries/metavoxels/src/ScriptCache.cpp +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -11,8 +11,6 @@ #include <QNetworkReply> #include <QScriptEngine> #include <QTextStream> -#include <QTimer> -#include <QtDebug> #include "AttributeRegistry.h" #include "ScriptCache.h" @@ -23,7 +21,6 @@ ScriptCache* ScriptCache::getInstance() { } ScriptCache::ScriptCache() : - _networkAccessManager(NULL), _engine(NULL) { setEngine(new QScriptEngine(this)); @@ -41,15 +38,6 @@ void ScriptCache::setEngine(QScriptEngine* engine) { _generatorString = engine->toStringHandle("generator"); } -QSharedPointer<NetworkProgram> ScriptCache::getProgram(const QUrl& url) { - QSharedPointer<NetworkProgram> program = _networkPrograms.value(url); - if (program.isNull()) { - program = QSharedPointer<NetworkProgram>(new NetworkProgram(this, url)); - _networkPrograms.insert(url, program); - } - return program; -} - QSharedPointer<NetworkValue> ScriptCache::getValue(const ParameterizedURL& url) { QSharedPointer<NetworkValue> value = _networkValues.value(url); if (value.isNull()) { @@ -61,65 +49,21 @@ QSharedPointer<NetworkValue> ScriptCache::getValue(const ParameterizedURL& url) return value; } +QSharedPointer<Resource> ScriptCache::createResource(const QUrl& url, + const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) { + return QSharedPointer<Resource>(new NetworkProgram(this, url)); +} + NetworkProgram::NetworkProgram(ScriptCache* cache, const QUrl& url) : - _cache(cache), - _request(url), - _reply(NULL), - _attempts(0) { - - if (!url.isValid()) { - return; - } - _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - makeRequest(); + Resource(url), + _cache(cache) { } -NetworkProgram::~NetworkProgram() { - if (_reply != NULL) { - delete _reply; - } -} - -void NetworkProgram::makeRequest() { - QNetworkAccessManager* manager = _cache->getNetworkAccessManager(); - if (manager == NULL) { - return; - } - _reply = manager->get(_request); - - connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); - connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); -} - -void NetworkProgram::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (bytesReceived < bytesTotal && !_reply->isFinished()) { - return; - } - _program = QScriptProgram(QTextStream(_reply).readAll(), _reply->url().toString()); - - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; - +void NetworkProgram::downloadFinished(QNetworkReply* reply) { + _program = QScriptProgram(QTextStream(reply).readAll(), reply->url().toString()); emit loaded(); } -void NetworkProgram::handleReplyError() { - QDebug debug = qDebug() << _reply->errorString(); - - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; - - // retry with increasing delays - const int MAX_ATTEMPTS = 8; - const int BASE_DELAY_MS = 1000; - if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); - debug << " -- retrying..."; - } -} - NetworkValue::~NetworkValue() { } diff --git a/libraries/metavoxels/src/ScriptCache.h b/libraries/metavoxels/src/ScriptCache.h index 3ce525d979..072020d882 100644 --- a/libraries/metavoxels/src/ScriptCache.h +++ b/libraries/metavoxels/src/ScriptCache.h @@ -9,26 +9,20 @@ #ifndef __interface__ScriptCache__ #define __interface__ScriptCache__ -#include <QHash> -#include <QList> -#include <QNetworkRequest> -#include <QObject> #include <QScriptProgram> #include <QScriptValue> -#include <QSharedPointer> -#include <QWeakPointer> + +#include <ResourceCache.h> #include "MetavoxelUtil.h" -class QNetworkAccessManager; -class QNetworkReply; class QScriptEngine; class NetworkProgram; class NetworkValue; /// Maintains a cache of loaded scripts. -class ScriptCache : public QObject { +class ScriptCache : public ResourceCache { Q_OBJECT public: @@ -37,14 +31,11 @@ public: ScriptCache(); - void setNetworkAccessManager(QNetworkAccessManager* manager) { _networkAccessManager = manager; } - QNetworkAccessManager* getNetworkAccessManager() const { return _networkAccessManager; } - void setEngine(QScriptEngine* engine); QScriptEngine* getEngine() const { return _engine; } /// Loads a script program from the specified URL. - QSharedPointer<NetworkProgram> getProgram(const QUrl& url); + QSharedPointer<NetworkProgram> getProgram(const QUrl& url) { return getResource(url).staticCast<NetworkProgram>(); } /// Loads a script value from the specified URL. QSharedPointer<NetworkValue> getValue(const ParameterizedURL& url); @@ -55,11 +46,14 @@ public: const QScriptString& getTypeString() const { return _typeString; } const QScriptString& getGeneratorString() const { return _generatorString; } +protected: + + virtual QSharedPointer<Resource> createResource(const QUrl& url, + const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra); + private: - QNetworkAccessManager* _networkAccessManager; QScriptEngine* _engine; - QHash<QUrl, QWeakPointer<NetworkProgram> > _networkPrograms; QHash<ParameterizedURL, QWeakPointer<NetworkValue> > _networkValues; QScriptString _parametersString; QScriptString _lengthString; @@ -69,13 +63,12 @@ private: }; /// A program loaded from the network. -class NetworkProgram : public QObject { +class NetworkProgram : public Resource { Q_OBJECT public: NetworkProgram(ScriptCache* cache, const QUrl& url); - ~NetworkProgram(); ScriptCache* getCache() const { return _cache; } @@ -87,18 +80,13 @@ signals: void loaded(); -private slots: - - void makeRequest(); - void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleReplyError(); - +protected: + + virtual void downloadFinished(QNetworkReply* reply); + private: ScriptCache* _cache; - QNetworkRequest _request; - QNetworkReply* _reply; - int _attempts; QScriptProgram _program; }; diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp new file mode 100644 index 0000000000..24350794bc --- /dev/null +++ b/libraries/shared/src/ResourceCache.cpp @@ -0,0 +1,199 @@ +// +// ResourceCache.cpp +// shared +// +// Created by Andrzej Kapolka on 2/27/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#include <cfloat> +#include <cmath> + +#include <QNetworkReply> +#include <QTimer> +#include <QtDebug> + +#include "ResourceCache.h" + +ResourceCache::ResourceCache(QObject* parent) : + QObject(parent) { +} + +QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) { + if (!url.isValid() && fallback.isValid()) { + return getResource(fallback, QUrl(), delayLoad); + } + QSharedPointer<Resource> resource = _resources.value(url); + if (resource.isNull()) { + resource = createResource(url, fallback.isValid() ? + getResource(fallback, QUrl(), true) : QSharedPointer<Resource>(), delayLoad, extra); + _resources.insert(url, resource); + } + return resource; +} + +void ResourceCache::attemptRequest(Resource* resource) { + if (_requestLimit <= 0) { + // wait until a slot becomes available + _pendingRequests.append(resource); + return; + } + _requestLimit--; + resource->makeRequest(); +} + +void ResourceCache::requestCompleted() { + _requestLimit++; + + // look for the highest priority pending request + int highestIndex = -1; + float highestPriority = -FLT_MAX; + for (int i = 0; i < _pendingRequests.size(); ) { + Resource* resource = _pendingRequests.at(i).data(); + if (!resource) { + _pendingRequests.removeAt(i); + continue; + } + float priority = resource->getLoadPriority(); + if (priority >= highestPriority) { + highestPriority = priority; + highestIndex = i; + } + i++; + } + if (highestIndex >= 0) { + attemptRequest(_pendingRequests.takeAt(highestIndex)); + } +} + +QNetworkAccessManager* ResourceCache::_networkAccessManager = NULL; + +const int DEFAULT_REQUEST_LIMIT = 10; +int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT; + +QList<QPointer<Resource> > ResourceCache::_pendingRequests; + +Resource::Resource(const QUrl& url, bool delayLoad) : + _request(url), + _startedLoading(false), + _failedToLoad(false), + _attempts(0), + _reply(NULL) { + + if (!url.isValid()) { + _startedLoading = _failedToLoad = true; + return; + } + _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + + // start loading immediately unless instructed otherwise + if (!delayLoad) { + attemptRequest(); + } +} + +Resource::~Resource() { + if (_reply) { + ResourceCache::requestCompleted(); + delete _reply; + } +} + +void Resource::ensureLoading() { + if (!_startedLoading) { + attemptRequest(); + } +} + +void Resource::setLoadPriority(const QPointer<QObject>& owner, float priority) { + _loadPriorities.insert(owner, priority); +} + +void Resource::setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities) { + for (QHash<QPointer<QObject>, float>::const_iterator it = priorities.constBegin(); + it != priorities.constEnd(); it++) { + _loadPriorities.insert(it.key(), it.value()); + } +} + +void Resource::clearLoadPriority(const QPointer<QObject>& owner) { + _loadPriorities.remove(owner); +} + +float Resource::getLoadPriority() { + float highestPriority = -FLT_MAX; + for (QHash<QPointer<QObject>, float>::iterator it = _loadPriorities.begin(); it != _loadPriorities.end(); ) { + if (it.key().isNull()) { + it = _loadPriorities.erase(it); + continue; + } + highestPriority = qMax(highestPriority, it.value()); + it++; + } + return highestPriority; +} + +void Resource::attemptRequest() { + _startedLoading = true; + ResourceCache::attemptRequest(this); +} + +void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + if (!_reply->isFinished()) { + return; + } + _reply->disconnect(this); + _reply->deleteLater(); + QNetworkReply* reply = _reply; + _reply = NULL; + ResourceCache::requestCompleted(); + + downloadFinished(reply); +} + +void Resource::handleReplyError() { + QDebug debug = qDebug() << _reply->errorString(); + + QNetworkReply::NetworkError error = _reply->error(); + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; + ResourceCache::requestCompleted(); + + // retry for certain types of failures + switch (error) { + case QNetworkReply::RemoteHostClosedError: + case QNetworkReply::TimeoutError: + case QNetworkReply::TemporaryNetworkFailureError: + case QNetworkReply::ProxyConnectionClosedError: + case QNetworkReply::ProxyTimeoutError: + case QNetworkReply::UnknownNetworkError: + case QNetworkReply::UnknownProxyError: + case QNetworkReply::UnknownContentError: + case QNetworkReply::ProtocolFailure: { + // retry with increasing delays + const int MAX_ATTEMPTS = 8; + const int BASE_DELAY_MS = 1000; + if (++_attempts < MAX_ATTEMPTS) { + QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest())); + debug << " -- retrying..."; + return; + } + // fall through to final failure + } + default: + _failedToLoad = true; + break; + } +} + +void Resource::makeRequest() { + _reply = ResourceCache::getNetworkAccessManager()->get(_request); + + connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); + connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); +} + +uint qHash(const QPointer<QObject>& value, uint seed) { + return qHash(value.data(), seed); +} diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h new file mode 100644 index 0000000000..ac8ef68a05 --- /dev/null +++ b/libraries/shared/src/ResourceCache.h @@ -0,0 +1,121 @@ +// +// ResourceCache.h +// shared +// +// Created by Andrzej Kapolka on 2/27/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __shared__ResourceCache__ +#define __shared__ResourceCache__ + +#include <QHash> +#include <QList> +#include <QNetworkRequest> +#include <QObject> +#include <QPointer> +#include <QSharedPointer> +#include <QUrl> +#include <QWeakPointer> + +class QNetworkAccessManager; +class QNetworkReply; + +class Resource; + +/// Base class for resource caches. +class ResourceCache : public QObject { + Q_OBJECT + +public: + + static void setNetworkAccessManager(QNetworkAccessManager* manager) { _networkAccessManager = manager; } + static QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; } + + static void setRequestLimit(int limit) { _requestLimit = limit; } + static int getRequestLimit() { return _requestLimit; } + + ResourceCache(QObject* parent = NULL); + +protected: + + /// Loads a resource from the specified URL. + /// \param fallback a fallback URL to load if the desired one is unavailable + /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested + /// \param extra extra data to pass to the creator, if appropriate + QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl(), + bool delayLoad = false, void* extra = NULL); + + /// Creates a new resource. + virtual QSharedPointer<Resource> createResource(const QUrl& url, + const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) = 0; + + static void attemptRequest(Resource* resource); + static void requestCompleted(); + +private: + + friend class Resource; + + QHash<QUrl, QWeakPointer<Resource> > _resources; + + static QNetworkAccessManager* _networkAccessManager; + static int _requestLimit; + static QList<QPointer<Resource> > _pendingRequests; +}; + +/// Base class for resources. +class Resource : public QObject { + Q_OBJECT + +public: + + Resource(const QUrl& url, bool delayLoad = false); + ~Resource(); + + /// Makes sure that the resource has started loading. + void ensureLoading(); + + /// Sets the load priority for one owner. + virtual void setLoadPriority(const QPointer<QObject>& owner, float priority); + + /// Sets a set of priorities at once. + virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities); + + /// Clears the load priority for one owner. + virtual void clearLoadPriority(const QPointer<QObject>& owner); + + /// Returns the highest load priority across all owners. + float getLoadPriority(); + +protected slots: + + void attemptRequest(); + +protected: + + virtual void downloadFinished(QNetworkReply* reply) = 0; + + QNetworkRequest _request; + bool _startedLoading; + bool _failedToLoad; + QHash<QPointer<QObject>, float> _loadPriorities; + +private slots: + + void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void handleReplyError(); + +private: + + void makeRequest(); + + friend class ResourceCache; + + QNetworkReply* _reply; + int _attempts; +}; + +uint qHash(const QPointer<QObject>& value, uint seed = 0); + +#endif /* defined(__shared__ResourceCache__) */