diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 779307c19d..cc6c4930ff 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -369,14 +369,6 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) { reverbTime = _zoneReverbSettings[i].reverbTime; wetLevel = _zoneReverbSettings[i].wetLevel; - // Modulate wet level with distance to wall - float MIN_ATTENUATION_DISTANCE = 2.0f; - float MAX_ATTENUATION = -12; // dB - glm::vec3 distanceToWalls = (box.getDimensions() / 2.0f) - glm::abs(streamPosition - box.calcCenter()); - float distanceToClosestWall = glm::min(distanceToWalls.x, distanceToWalls.z); - if (distanceToClosestWall < MIN_ATTENUATION_DISTANCE) { - wetLevel += MAX_ATTENUATION * (1.0f - distanceToClosestWall / MIN_ATTENUATION_DISTANCE); - } break; } } diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 80ee32efa1..44a1796a8d 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -308,7 +308,7 @@ "name": "reverb", "type": "table", "label": "Reverb Settings", - "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet level of -10 db. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet level of -5 db.", + "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, "columns": [ { @@ -325,9 +325,9 @@ }, { "name": "wet_level", - "label": "Wet Level", + "label": "Wet/Dry Mix", "can_set": true, - "placeholder": "(in db)" + "placeholder": "(in percent)" } ] } diff --git a/examples/html/eventBridgeLoader.js b/examples/html/eventBridgeLoader.js index de6553ee1c..ebfb6dc740 100644 --- a/examples/html/eventBridgeLoader.js +++ b/examples/html/eventBridgeLoader.js @@ -10,11 +10,50 @@ var EventBridge; -openEventBridge = function(callback) { - new QWebChannel(qt.webChannelTransport, function(channel) { - console.log("uid " + EventBridgeUid); - EventBridge = channel.objects[EventBridgeUid]; - callback(EventBridge); +EventBridgeConnectionProxy = function(parent) { + this.parent = parent; + this.realSignal = this.parent.realBridge.scriptEventReceived + this.webWindowId = this.parent.webWindow.windowId; +} + +EventBridgeConnectionProxy.prototype.connect = function(callback) { + var that = this; + this.realSignal.connect(function(id, message) { + if (id === that.webWindowId) { callback(message); } }); } +EventBridgeProxy = function(webWindow) { + this.webWindow = webWindow; + this.realBridge = this.webWindow.eventBridge; + this.scriptEventReceived = new EventBridgeConnectionProxy(this); +} + +EventBridgeProxy.prototype.emitWebEvent = function(data) { + this.realBridge.emitWebEvent(data); +} + +openEventBridge = function(callback) { + EVENT_BRIDGE_URI = "ws://localhost:51016"; + socket = new WebSocket(this.EVENT_BRIDGE_URI); + + socket.onclose = function() { + console.error("web channel closed"); + }; + + socket.onerror = function(error) { + console.error("web channel error: " + error); + }; + + socket.onopen = function() { + channel = new QWebChannel(socket, function(channel) { + console.log("Document url is " + document.URL); + var webWindow = channel.objects[document.URL.toLowerCase()]; + console.log("WebWindow is " + webWindow) + eventBridgeProxy = new EventBridgeProxy(webWindow); + EventBridge = eventBridgeProxy; + if (callback) { callback(eventBridgeProxy); } + }); + } +} + diff --git a/examples/html/qmlWebTest.html b/examples/html/qmlWebTest.html index 553ce83417..e59535701d 100644 --- a/examples/html/qmlWebTest.html +++ b/examples/html/qmlWebTest.html @@ -4,17 +4,21 @@ + diff --git a/examples/tests/qmlWebTest.js b/examples/tests/qmlWebTest.js index d29f2ba002..5faa68668d 100644 --- a/examples/tests/qmlWebTest.js +++ b/examples/tests/qmlWebTest.js @@ -8,14 +8,26 @@ webWindow.eventBridge.webEventReceived.connect(function(data) { print("JS Side event received: " + data); }); +var titles = ["A", "B", "C"]; +var titleIndex = 0; + Script.setInterval(function() { - var message = [ Math.random(), Math.random() ]; - print("JS Side sending: " + message); - webWindow.emitScriptEvent(message); -}, 5 * 1000); + webWindow.eventBridge.emitScriptEvent("JS Event sent"); + var size = webWindow.size; + var position = webWindow.position; + print("Window url: " + webWindow.url) + print("Window visible: " + webWindow.visible) + print("Window size: " + size.x + "x" + size.y) + print("Window pos: " + position.x + "x" + position.y) + webWindow.setVisible(!webWindow.visible); + webWindow.setTitle(titles[titleIndex]); + webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100); + titleIndex += 1; + titleIndex %= titles.length; +}, 2 * 1000); -Script.scriptEnding.connect(function(){ +Script.setTimeout(function() { + print("Closing script"); webWindow.close(); - webWindow.deleteLater(); -}); - + Script.stop(); +}, 15 * 1000) diff --git a/interface/icon/interface-beta.icns b/interface/icon/interface-beta.icns index af4f7cf499..1e2a4baeaa 100644 Binary files a/interface/icon/interface-beta.icns and b/interface/icon/interface-beta.icns differ diff --git a/interface/icon/interface-beta.ico b/interface/icon/interface-beta.ico index 4d59dab12e..1ed1ebddb9 100644 Binary files a/interface/icon/interface-beta.ico and b/interface/icon/interface-beta.ico differ diff --git a/interface/icon/interface.icns b/interface/icon/interface.icns index bc2eff31e5..332539af2a 100644 Binary files a/interface/icon/interface.icns and b/interface/icon/interface.icns differ diff --git a/interface/icon/interface.ico b/interface/icon/interface.ico index 4799dc7c0d..e3d852cb41 100644 Binary files a/interface/icon/interface.ico and b/interface/icon/interface.ico differ diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 0058770462..fd4e629568 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -1,7 +1,6 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebEngine 1.1 -import QtWebChannel 1.0 import "windows" as Windows import "controls" as Controls @@ -16,22 +15,11 @@ Windows.Window { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property alias source: webview.url - property alias webChannel: webview.webChannel - // A unique identifier to let the HTML JS find the event bridge - // object (our C++ wrapper) - property string uid; - - // This is for JS/QML communication, which is unused in a WebWindow, - // but not having this here results in spurious warnings about a - // missing signal - signal sendToScript(var message); Controls.WebView { id: webview url: "about:blank" anchors.fill: parent focus: true - onUrlChanged: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";"); - Component.onCompleted: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";"); } } // dialog diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index 5313ca23e9..75aa50aa34 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -37,33 +37,14 @@ Windows.Window { Repeater { model: 4 Tab { - // Force loading of the content even if the tab is not visible - // (required for letting the C++ code access the webview) active: true - enabled: false + enabled: false; + // we need to store the original url here for future identification property string originalUrl: ""; - + onEnabledChanged: toolWindow.updateVisiblity(); Controls.WebView { id: webView; - // we need to store the original url here for future identification - // A unique identifier to let the HTML JS find the event bridge - // object (our C++ wrapper) - property string uid; anchors.fill: parent - enabled: false - - // This is for JS/QML communication, which is unused in a WebWindow, - // but not having this here results in spurious warnings about a - // missing signal - signal sendToScript(var message); - - onUrlChanged: webView.runJavaScript("EventBridgeUid = \"" + uid + "\";"); - onEnabledChanged: toolWindow.updateVisiblity(); - onLoadingChanged: { - if (loadRequest.status == WebEngineView.LoadSucceededStatus) { - webView.runJavaScript("EventBridgeUid = \"" + uid + "\";"); - } - } } } } @@ -132,23 +113,20 @@ Windows.Window { var tab = tabView.getTab(index); tab.title = ""; - tab.enabled = false; tab.originalUrl = ""; - tab.item.url = "about:blank"; - tab.item.enabled = false; + tab.enabled = false; } function addWebTab(properties) { if (!properties.source) { - console.warn("Attempted to open Web Tool Pane without URL"); + console.warn("Attempted to open Web Tool Pane without URL") return; } var existingTabIndex = findIndexForUrl(properties.source); if (existingTabIndex >= 0) { - console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source); - var tab = tabView.getTab(existingTabIndex); - return tab.item; + console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source) + return tabView.getTab(existingTabIndex); } var freeTabIndex = findFreeTab(); @@ -157,22 +135,25 @@ Windows.Window { return; } + var newTab = tabView.getTab(freeTabIndex); + newTab.title = properties.title || "Unknown"; + newTab.originalUrl = properties.source; + newTab.item.url = properties.source; + newTab.active = true; + if (properties.width) { - tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x); + tabView.width = Math.min(Math.max(tabView.width, properties.width), + toolWindow.maxSize.x); } if (properties.height) { - tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y); + tabView.height = Math.min(Math.max(tabView.height, properties.height), + toolWindow.maxSize.y); } - var tab = tabView.getTab(freeTabIndex); - tab.title = properties.title || "Unknown"; - tab.enabled = true; - tab.originalUrl = properties.source; - - var result = tab.item; - result.enabled = true; - result.url = properties.source; - return result; + console.log("Updating visibility based on child tab added"); + newTab.enabledChanged.connect(updateVisiblity) + updateVisiblity(); + return newTab } } diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 1361e6e322..18080cd448 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -59,7 +59,6 @@ WebEngineView { request.openIn(newWindow.webView) } - // This breaks the webchannel used for passing messages. Fixed in Qt 5.6 - // See https://bugreports.qt.io/browse/QTBUG-49521 - //profile: desktop.browserProfile + + profile: desktop.browserProfile } diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 620c35c832..5587096ce3 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -231,6 +231,13 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { _leftEyePosition = _rightEyePosition = getPosition(); _eyePosition = calculateAverageEyePosition(); + + if (!billboard && _owningAvatar) { + auto skeletonModel = static_cast(_owningAvatar)->getSkeletonModel(); + if (skeletonModel) { + skeletonModel->getEyePositions(_leftEyePosition, _rightEyePosition); + } + } } void Head::calculateMouthShapes() { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8f11c635e9..210e6feb5b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -897,7 +897,9 @@ void MyAvatar::updateLookAtTargetAvatar() { // Scale by proportional differences between avatar and human. float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye) * ipdScale; float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye); - gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; + if (avatarEyeSeparation > 0.0f) { + gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; + } } // And now we can finally add that offset to the camera. diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index e222de54f9..70cf7e248f 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -611,7 +611,7 @@ AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& j return loadNode(rootVal.toObject(), jsonUrl); } -void AnimNodeLoader::onRequestDone(const QByteArray& data) { +void AnimNodeLoader::onRequestDone(const QByteArray data) { auto node = load(data, _url); if (node) { emit success(node); diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index 27b94f81bb..d6fdfa7e2c 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -36,7 +36,7 @@ protected: static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl); protected slots: - void onRequestDone(const QByteArray& data); + void onRequestDone(const QByteArray data); void onRequestError(QNetworkReply::NetworkError error); protected: diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4d44a771f7..7e01196dc7 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -565,10 +565,10 @@ void AudioClient::updateReverbOptions() { _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); reverbChanged = true; } - //if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { - // _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); - // reverbChanged = true; - //} + if (_zoneReverbOptions.getWetDryMix() != _receivedAudioStream.getWetLevel()) { + _zoneReverbOptions.setWetDryMix(_receivedAudioStream.getWetLevel()); + reverbChanged = true; + } if (_reverbOptions != &_zoneReverbOptions) { _reverbOptions = &_zoneReverbOptions; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index ff4ed28150..98559a56a4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -113,14 +113,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) { return _originalTexturesMap; } - QString jsonTextures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; + // Legacy: a ,\n-delimited list of filename:"texturepath" + if (*textures.cbegin() != '{') { + textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; + } + QJsonParseError error; - QJsonDocument texturesAsJson = QJsonDocument::fromJson(jsonTextures.toUtf8(), &error); + QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures; + return _originalTexturesMap; } - QJsonObject texturesAsJsonObject = texturesAsJson.object(); - return texturesAsJsonObject.toVariantMap(); + return texturesJson.object().toVariantMap(); } void RenderableModelEntityItem::remapTextures() { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 5118664268..c9c4c8503a 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -55,9 +55,58 @@ gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; + +/* + A PolyVoxEntity has several interdependent parts: + + _voxelData -- compressed QByteArray representation of which voxels have which values + _volData -- datastructure from the PolyVox library which holds which voxels have which values + _mesh -- renderable representation of the voxels + _shape -- used for bullet collisions + + Each one depends on the one before it, except that _voxelData is set from _volData if a script edits the voxels. + + There are booleans to indicate that something has been updated and the dependents now need to be updated. + + _voxelDataDirty + _volDataDirty + _meshDirty + + In RenderablePolyVoxEntityItem::render, these flags are checked and changes are propagated along the chain. + decompressVolumeData() is called to decompress _voxelData into _volData. getMesh() is called to invoke the + polyVox surface extractor to create _mesh (as well as set Simulation _dirtyFlags). Because Simulation::DIRTY_SHAPE + is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on + the surface style. + + When a script changes _volData, compressVolumeDataAndSendEditPacket is called to update _voxelData and to + send a packet to the entity-server. + + decompressVolumeData, getMesh, computeShapeInfoWorker, and compressVolumeDataAndSendEditPacket are too expensive + to run on a thread that has other things to do. These use QtConcurrent::run to spawn a thread. As each thread + finishes, it adjusts the dirty flags so that the next call to render() will kick off the next step. + + polyvoxes are designed to seemlessly fit up against neighbors. If voxels go right up to the edge of polyvox, + the resulting mesh wont be closed -- the library assumes you'll have another polyvox next to it to continue the + mesh. + + If a polyvox entity is "edged", the voxel space is wrapped in an extra layer of zero-valued voxels. This avoids the + previously mentioned gaps along the edges. + + Non-edged polyvox entities can be told about their neighbors in all 6 cardinal directions. On the positive + edges of the polyvox, the values are set from the (negative edge of) relevant neighbor so that their meshes + knit together. This is handled by bonkNeighbors and copyUpperEdgesFromNeighbors. In these functions, variable + names have XP for x-positive, XN x-negative, etc. + + */ + + + + + EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderablePolyVoxEntityItem(entityID) }; entity->setProperties(properties); + std::static_pointer_cast(entity)->initializePolyVox(); return entity; } @@ -68,13 +117,19 @@ RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& ent _xTexture(nullptr), _yTexture(nullptr), _zTexture(nullptr) { - setVoxelVolumeSize(_voxelVolumeSize); - getMeshAsync(); } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { + withWriteLock([&] { + if (_volData) { + delete _volData; + } + }); } +void RenderablePolyVoxEntityItem::initializePolyVox() { + setVoxelVolumeSize(_voxelVolumeSize); +} bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) { switch (surfaceStyle) { @@ -88,59 +143,70 @@ bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) { return false; } - void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - _voxelDataLock.lockForWrite(); - if (_voxelData == voxelData) { - _voxelDataLock.unlock(); - return; - } - - _voxelData = voxelData; - _voxelDataDirty = true; - _voxelDataLock.unlock(); - decompressVolumeData(); + // compressed voxel information from the entity-server + withWriteLock([&] { + if (_voxelData != voxelData) { + _voxelData = voxelData; + _voxelDataDirty = true; + } + }); } - void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - if (_voxelSurfaceStyle == voxelSurfaceStyle) { - return; - } + // this controls whether the polyvox surface extractor does marching-cubes or makes a cubic mesh. It + // also determines if the extra "edged" layer is used. + bool volSizeChanged = false; - // if we are switching to or from "edged" we need to force a resize of _volData. - bool wasEdged = isEdged(_voxelSurfaceStyle); - bool willBeEdged = isEdged(voxelSurfaceStyle); - - if (wasEdged != willBeEdged) { - _volDataLock.lockForWrite(); - _volDataDirty = true; - if (_volData) { - delete _volData; + withWriteLock([&] { + if (_voxelSurfaceStyle == voxelSurfaceStyle) { + return; } - _volData = nullptr; - _voxelSurfaceStyle = voxelSurfaceStyle; - _volDataLock.unlock(); + + // if we are switching to or from "edged" we need to force a resize of _volData. + bool wasEdged = isEdged(_voxelSurfaceStyle); + bool willBeEdged = isEdged(voxelSurfaceStyle); + + if (wasEdged != willBeEdged) { + _volDataDirty = true; + if (_volData) { + delete _volData; + } + _volData = nullptr; + _voxelSurfaceStyle = voxelSurfaceStyle; + _voxelDataDirty = true; + volSizeChanged = true; + } else { + _volDataDirty = true; + _voxelSurfaceStyle = voxelSurfaceStyle; + } + }); + + if (volSizeChanged) { + // setVoxelVolumeSize will re-alloc _volData with the right size setVoxelVolumeSize(_voxelVolumeSize); - decompressVolumeData(); - } else { - _voxelSurfaceStyle = voxelSurfaceStyle; - getMesh(); } } - glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { - glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units - if (isEdged(_voxelSurfaceStyle)) { - return scale / -2.0f; - } - return scale / 2.0f; + glm::vec3 result; + withReadLock([&] { + glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units + if (isEdged(_voxelSurfaceStyle)) { + result = scale / -2.0f; + } + return scale / 2.0f; + }); + return result; } - glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { - glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units + glm::vec3 voxelVolumeSize; + withReadLock([&] { + voxelVolumeSize = _voxelVolumeSize; + }); + + glm::vec3 scale = getDimensions() / voxelVolumeSize; // meters / voxel-units bool success; // TODO -- Does this actually have to happen in world space? glm::vec3 center = getCenterPosition(success); // this handles registrationPoint changes glm::vec3 position = getPosition(success); @@ -168,18 +234,15 @@ glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { return worldToModelMatrix; } - bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { if (_locked) { return false; } - _volDataLock.lockForWrite(); - bool result = setVoxelInternal(x, y, z, toValue); - if (result) { - _volDataDirty = true; - } - _volDataLock.unlock(); + bool result = false; + withWriteLock([&] { + result = setVoxelInternal(x, y, z, toValue); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -187,6 +250,21 @@ bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) return result; } +void RenderablePolyVoxEntityItem::forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize, + std::function thunk) { + // a thread-safe way for code outside this class to iterate over a range of voxels + withReadLock([&] { + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + uint8_t uVoxelValue = getVoxelInternal(x, y, z); + thunk(x, y, z, uVoxelValue); + } + } + } + }); +} + bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { bool result = false; @@ -194,16 +272,15 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { return result; } - _volDataLock.lockForWrite(); - _volDataDirty = true; - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + result |= setVoxelInternal(x, y, z, toValue); + } } } - } - _volDataLock.unlock(); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -224,17 +301,15 @@ bool RenderablePolyVoxEntityItem::setCuboid(const glm::vec3& lowPosition, const int yHigh = std::max(std::min(yLow + (int)roundf(cuboidSize.y), (int)roundf(_voxelVolumeSize.y)), yLow); int zHigh = std::max(std::min(zLow + (int)roundf(cuboidSize.z), (int)roundf(_voxelVolumeSize.z)), zLow); - _volDataLock.lockForWrite(); - _volDataDirty = true; - - for (int x = xLow; x < xHigh; x++) { - for (int y = yLow; y < yHigh; y++) { - for (int z = zLow; z < zHigh; z++) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int x = xLow; x < xHigh; x++) { + for (int y = yLow; y < yHigh; y++) { + for (int z = zLow; z < zHigh; z++) { + result |= setVoxelInternal(x, y, z, toValue); + } } } - } - _volDataLock.unlock(); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -259,28 +334,25 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi } // This three-level for loop iterates over every voxel in the volume - _volDataLock.lockForWrite(); - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - // Store our current position as a vector... - glm::vec3 pos(x + 0.5f, y + 0.5f, z + 0.5f); // consider voxels cenetered on their coordinates - // And compute how far the current position is from the center of the volume - float fDistToCenter = glm::distance(pos, center); - // If the current voxel is less than 'radius' units from the center then we set its value - if (fDistToCenter <= radius) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + // Store our current position as a vector... + glm::vec3 pos(x + 0.5f, y + 0.5f, z + 0.5f); // consider voxels cenetered on their coordinates + // And compute how far the current position is from the center of the volume + float fDistToCenter = glm::distance(pos, center); + // If the current voxel is less than 'radius' units from the center then we set its value + if (fDistToCenter <= radius) { + result |= setVoxelInternal(x, y, z, toValue); + } } } } - } + }); if (result) { - _volDataDirty = true; - _volDataLock.unlock(); compressVolumeDataAndSendEditPacket(); - } else { - _volDataLock.unlock(); } return result; } @@ -294,30 +366,27 @@ bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float r glm::mat4 vtwMatrix = voxelToWorldMatrix(); // This three-level for loop iterates over every voxel in the volume - _volDataLock.lockForWrite(); - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - // Store our current position as a vector... - glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates - // convert to world coordinates - glm::vec3 worldPos = glm::vec3(vtwMatrix * pos); - // compute how far the current position is from the center of the volume - float fDistToCenter = glm::distance(worldPos, centerWorldCoords); - // If the current voxel is less than 'radius' units from the center then we set its value - if (fDistToCenter <= radiusWorldCoords) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + // Store our current position as a vector... + glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates + // convert to world coordinates + glm::vec3 worldPos = glm::vec3(vtwMatrix * pos); + // compute how far the current position is from the center of the volume + float fDistToCenter = glm::distance(worldPos, centerWorldCoords); + // If the current voxel is less than 'radius' units from the center then we set its value + if (fDistToCenter <= radiusWorldCoords) { + result |= setVoxelInternal(x, y, z, toValue); + } } } } - } + }); if (result) { - _volDataDirty = true; - _volDataLock.unlock(); compressVolumeDataAndSendEditPacket(); - } else { - _volDataLock.unlock(); } return result; } @@ -399,7 +468,8 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o float voxelDistance; - bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face, surfaceNormal); + bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), + voxelDistance, face, surfaceNormal); glm::vec4 voxelIntersectionPoint = glm::vec4(glm::vec3(originInVoxel) + glm::vec3(directionInVoxel) * voxelDistance, 1.0); glm::vec4 intersectionPoint = vtwMatrix * voxelIntersectionPoint; @@ -414,13 +484,15 @@ PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originIn PolyVox::Vector3DFloat startPoint(originInVoxel.x, originInVoxel.y, originInVoxel.z); PolyVox::Vector3DFloat endPoint(farInVoxel.x, farInVoxel.y, farInVoxel.z); - _volDataLock.lockForRead(); - RaycastFunctor callback(_volData); - PolyVox::RaycastResult raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); - _volDataLock.unlock(); + PolyVox::RaycastResult raycastResult; + withReadLock([&] { + RaycastFunctor callback(_volData); + raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); + + // result is in voxel-space coordinates. + result = callback._result; + }); - // result is in voxel-space coordinates. - result = callback._result; return raycastResult; } @@ -438,19 +510,22 @@ void RenderablePolyVoxEntityItem::updateRegistrationPoint(const glm::vec3& value } bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { - _meshLock.lockForRead(); - if (_meshDirty) { - _meshLock.unlock(); + // we determine if we are ready to compute the physics shape by actually doing so. + // if _voxelDataDirty or _volDataDirty is set, don't do this yet -- wait for their + // threads to finish before creating the collision shape. + if (_meshDirty && !_voxelDataDirty && !_volDataDirty) { + _meshDirty = false; computeShapeInfoWorker(); return false; } - _meshLock.unlock(); return true; } void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { - QReadLocker(&this->_shapeInfoLock); - info = _shapeInfo; + // the shape was actually computed in isReadyToComputeShape. Just hand it off, here. + withWriteLock([&] { + info = _shapeInfo; + }); } void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { @@ -479,18 +554,29 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::PolyVox); Q_ASSERT(args->_batch); - _volDataLock.lockForRead(); - if (_volDataDirty) { - _volDataLock.unlock(); + bool voxelDataDirty; + bool volDataDirty; + withWriteLock([&] { + voxelDataDirty = _voxelDataDirty; + volDataDirty = _volDataDirty; + if (_voxelDataDirty) { + _voxelDataDirty = false; + } else if (_volDataDirty) { + _volDataDirty = false; + } + }); + if (voxelDataDirty) { + decompressVolumeData(); + } else if (volDataDirty) { getMesh(); - } else { - _volDataLock.unlock(); } - - _meshLock.lockForRead(); - model::MeshPointer mesh = _mesh; - _meshLock.unlock(); + model::MeshPointer mesh; + glm::vec3 voxelVolumeSize; + withReadLock([&] { + mesh = _mesh; + voxelVolumeSize = _voxelVolumeSize; + }); if (!_pipeline) { gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(std::string(polyvox_vert)); @@ -552,7 +638,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { } int voxelVolumeSizeLocation = _pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize"); - batch._glUniform3f(voxelVolumeSizeLocation, _voxelVolumeSize.x, _voxelVolumeSize.y, _voxelVolumeSize.z); + batch._glUniform3f(voxelVolumeSizeLocation, voxelVolumeSize.x, voxelVolumeSize.y, voxelVolumeSize.z); batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)mesh->getNumIndices(), 0); } @@ -636,49 +722,52 @@ glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& local void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - if (_volData && _voxelVolumeSize == voxelVolumeSize) { - return; - } + // This controls how many individual voxels are in the entity. This is unrelated to + // the dimentions of the entity -- it defines the size of the arrays that hold voxel values. + // In addition to setting the number of voxels, this is used in a few places for its + // side-effect of allocating _volData to be the correct size. + withWriteLock([&] { + if (_volData && _voxelVolumeSize == voxelVolumeSize) { + return; + } - _volDataLock.lockForWrite(); - _volDataDirty = true; - _voxelVolumeSize = voxelVolumeSize; + _voxelDataDirty = true; + _voxelVolumeSize = voxelVolumeSize; - if (_volData) { - delete _volData; - } - _onCount = 0; + if (_volData) { + delete _volData; + } + _onCount = 0; - if (isEdged(_voxelSurfaceStyle)) { - // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This - // changes how the surface extractor acts -- mainly it becomes impossible to have holes in the - // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the - // voxel space. - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x + 1, // corners are inclusive - _voxelVolumeSize.y + 1, - _voxelVolumeSize.z + 1); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } else { - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - // these should each have -1 after them, but if we leave layers on the upper-axis faces, - // they act more like I expect. - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x, - _voxelVolumeSize.y, - _voxelVolumeSize.z); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } + if (isEdged(_voxelSurfaceStyle)) { + // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This + // changes how the surface extractor acts -- it becomes impossible to have holes in the + // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the + // voxel space. + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x + 1, // corners are inclusive + _voxelVolumeSize.y + 1, + _voxelVolumeSize.z + 1); + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); + } else { + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + // these should each have -1 after them, but if we leave layers on the upper-axis faces, + // they act more like I expect. + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x, + _voxelVolumeSize.y, + _voxelVolumeSize.z); + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); + } - // having the "outside of voxel-space" value be 255 has helped me notice some problems. - _volData->setBorderValue(255); - _volDataLock.unlock(); - decompressVolumeData(); + // having the "outside of voxel-space" value be 255 has helped me notice some problems. + _volData->setBorderValue(255); + }); } -bool RenderablePolyVoxEntityItem::inUserBounds(const PolyVox::SimpleVolume* vol, - PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z) const { +bool inUserBounds(const PolyVox::SimpleVolume* vol, + PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z) { // x, y, z are in user voxel-coords, not adjusted-for-edge voxel-coords. if (isEdged(surfaceStyle)) { if (x < 0 || y < 0 || z < 0 || @@ -697,8 +786,11 @@ bool RenderablePolyVoxEntityItem::inUserBounds(const PolyVox::SimpleVolume_volDataLock); - return getVoxelInternal(x, y, z); + uint8_t result; + withReadLock([&] { + result = getVoxelInternal(x, y, z); + }); + return result; } @@ -718,7 +810,8 @@ uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) { bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { - // set a voxel without recompressing the voxel data + // set a voxel without recompressing the voxel data. This assumes that the caller has + // write-locked the entity. bool result = false; if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { return result; @@ -732,6 +825,12 @@ bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t _volData->setVoxelAt(x, y, z, toValue); } + if (x == 0 || y == 0 || z == 0) { + _neighborsNeedUpdate = true; + } + + _volDataDirty |= result; + return result; } @@ -760,214 +859,176 @@ bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toV } void RenderablePolyVoxEntityItem::decompressVolumeData() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::decompressVolumeDataAsync); + // take compressed data and expand it into _volData. + QByteArray voxelData; + auto entity = std::static_pointer_cast(getThisPointer()); + + withReadLock([&] { + voxelData = _voxelData; + }); + + QtConcurrent::run([=] { + QDataStream reader(voxelData); + quint16 voxelXSize, voxelYSize, voxelZSize; + reader >> voxelXSize; + reader >> voxelYSize; + reader >> voxelZSize; + + if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || + voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || + voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { + qDebug() << "voxelSize is not reasonable, skipping decompressions." + << voxelXSize << voxelYSize << voxelZSize << getName() << getID(); + entity->setVoxelDataDirty(false); + return; + } + int rawSize = voxelXSize * voxelYSize * voxelZSize; + + QByteArray compressedData; + reader >> compressedData; + + QByteArray uncompressedData = qUncompress(compressedData); + + if (uncompressedData.size() != rawSize) { + qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" + << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size() + << getName() << getID(); + entity->setVoxelDataDirty(false); + return; + } + + entity->setVoxelsFromData(uncompressedData, voxelXSize, voxelYSize, voxelZSize); + }); } -// take compressed data and expand it into _volData. -void RenderablePolyVoxEntityItem::decompressVolumeDataAsync() { - _voxelDataLock.lockForRead(); - QDataStream reader(_voxelData); - quint16 voxelXSize, voxelYSize, voxelZSize; - reader >> voxelXSize; - reader >> voxelYSize; - reader >> voxelZSize; - - if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || - voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || - voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { - qDebug() << "voxelSize is not reasonable, skipping decompressions." - << voxelXSize << voxelYSize << voxelZSize << getName() << getID(); - _voxelDataDirty = false; - _voxelDataLock.unlock(); - _threadRunning.release(); - return; - } - - int rawSize = voxelXSize * voxelYSize * voxelZSize; - - QByteArray compressedData; - reader >> compressedData; - _voxelDataDirty = false; - _voxelDataLock.unlock(); - QByteArray uncompressedData = qUncompress(compressedData); - - if (uncompressedData.size() != rawSize) { - qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" - << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size() - << getName() << getID(); - _threadRunning.release(); - return; - } - - _volDataLock.lockForWrite(); - if (!_volData) { - _volDataLock.unlock(); - _threadRunning.release(); - return; - } - _volDataDirty = true; - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; - setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); +void RenderablePolyVoxEntityItem::setVoxelsFromData(QByteArray uncompressedData, + quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) { + // this accepts the payload from decompressVolumeData + withWriteLock([&] { + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; + setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); + } } } - } - _volDataLock.unlock(); - _threadRunning.release(); + _volDataDirty = true; + }); } void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync); -} + // compress the data in _volData and save the results. The compressed form is used during + // saves to disk and for transmission over the wire to the entity-server -// compress the data in _volData and save the results. The compressed form is used during -// saves to disk and for transmission over the wire -void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync() { - quint16 voxelXSize = _voxelVolumeSize.x; - quint16 voxelYSize = _voxelVolumeSize.y; - quint16 voxelZSize = _voxelVolumeSize.z; - int rawSize = voxelXSize * voxelYSize * voxelZSize; + EntityItemPointer entity = getThisPointer(); - QByteArray uncompressedData = QByteArray(rawSize, '\0'); - - _volDataLock.lockForRead(); - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - uint8_t uVoxelValue = getVoxelInternal(x, y, z); - int uncompressedIndex = - z * voxelYSize * voxelXSize + - y * voxelXSize + - x; - uncompressedData[uncompressedIndex] = uVoxelValue; - } - } - } - _volDataLock.unlock(); - - QByteArray newVoxelData; - QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); - - writer << voxelXSize << voxelYSize << voxelZSize; - - QByteArray compressedData = qCompress(uncompressedData, 9); - writer << compressedData; - - // make sure the compressed data can be sent over the wire-protocol - if (newVoxelData.size() > 1150) { - // HACK -- until we have a way to allow for properties larger than MTU, don't update. - // revert the active voxel-space to the last version that fit. - // XXX - qDebug() << "compressed voxel data is too large" << getName() << getID(); - _threadRunning.release(); - return; - } - - auto now = usecTimestampNow(); - setLastEdited(now); - setLastBroadcast(now); - - _voxelDataLock.lockForWrite(); - _voxelDataDirty = true; - _voxelData = newVoxelData; - _voxelDataLock.unlock(); - - EntityItemProperties properties = getProperties(); - properties.setVoxelDataDirty(); - properties.setLastEdited(now); + quint16 voxelXSize; + quint16 voxelYSize; + quint16 voxelZSize; + withReadLock([&] { + voxelXSize = _voxelVolumeSize.x; + voxelYSize = _voxelVolumeSize.y; + voxelZSize = _voxelVolumeSize.z; + }); EntityTreeElementPointer element = getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; - EntitySimulation* simulation = tree ? tree->getSimulation() : nullptr; - PhysicalEntitySimulation* peSimulation = static_cast(simulation); - EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; - if (packetSender) { - packetSender->queueEditEntityMessage(PacketType::EntityEdit, _id, properties); - } - _threadRunning.release(); + + QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] { + int rawSize = voxelXSize * voxelYSize * voxelZSize; + QByteArray uncompressedData = QByteArray(rawSize, '\0'); + + auto polyVoxEntity = std::static_pointer_cast(entity); + polyVoxEntity->forEachVoxelValue(voxelXSize, voxelYSize, voxelZSize, [&] (int x, int y, int z, uint8_t uVoxelValue) { + int uncompressedIndex = + z * voxelYSize * voxelXSize + + y * voxelXSize + + x; + uncompressedData[uncompressedIndex] = uVoxelValue; + }); + + QByteArray newVoxelData; + QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); + + writer << voxelXSize << voxelYSize << voxelZSize; + + QByteArray compressedData = qCompress(uncompressedData, 9); + writer << compressedData; + + // make sure the compressed data can be sent over the wire-protocol + if (newVoxelData.size() > 1150) { + // HACK -- until we have a way to allow for properties larger than MTU, don't update. + // revert the active voxel-space to the last version that fit. + qDebug() << "compressed voxel data is too large" << entity->getName() << entity->getID(); + return; + } + + auto now = usecTimestampNow(); + entity->setLastEdited(now); + entity->setLastBroadcast(now); + + std::static_pointer_cast(entity)->setVoxelData(newVoxelData); + + tree->withReadLock([&] { + EntityItemProperties properties = entity->getProperties(); + properties.setVoxelDataDirty(); + properties.setLastEdited(now); + + EntitySimulation* simulation = tree ? tree->getSimulation() : nullptr; + PhysicalEntitySimulation* peSimulation = static_cast(simulation); + EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; + if (packetSender) { + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + } + }); + }); } -void RenderablePolyVoxEntityItem::getMesh() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::getMeshAsync); -} +EntityItemPointer lookUpNeighbor(EntityTreePointer tree, EntityItemID neighborID, EntityItemWeakPointer& currentWP) { + EntityItemPointer current = currentWP.lock(); -void RenderablePolyVoxEntityItem::clearOutOfDateNeighbors() { - if (_xNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); - if (currentXNNeighbor && currentXNNeighbor->getID() != _xNNeighborID) { - _xNNeighbor.reset(); - } - } - if (_yNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentYNNeighbor = _yNNeighbor.lock(); - if (currentYNNeighbor && currentYNNeighbor->getID() != _yNNeighborID) { - _yNNeighbor.reset(); - } - } - if (_zNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentZNNeighbor = _zNNeighbor.lock(); - if (currentZNNeighbor && currentZNNeighbor->getID() != _zNNeighborID) { - _zNNeighbor.reset(); - } + if (!current && neighborID == UNKNOWN_ENTITY_ID) { + // no neighbor + return nullptr; } - if (_xPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentXPNeighbor = _xPNeighbor.lock(); - if (currentXPNeighbor && currentXPNeighbor->getID() != _xPNeighborID) { - _xPNeighbor.reset(); - } - } - if (_yPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentYPNeighbor = _yPNeighbor.lock(); - if (currentYPNeighbor && currentYPNeighbor->getID() != _yPNeighborID) { - _yPNeighbor.reset(); - } - } - if (_zPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentZPNeighbor = _zPNeighbor.lock(); - if (currentZPNeighbor && currentZPNeighbor->getID() != _zPNeighborID) { - _zPNeighbor.reset(); - } + if (current && current->getID() == neighborID) { + // same neighbor + return current; } + if (neighborID == UNKNOWN_ENTITY_ID) { + currentWP.reset(); + return nullptr; + } + + current = tree->findEntityByID(neighborID); + if (!current) { + return nullptr; + } + + currentWP = current; + return current; } void RenderablePolyVoxEntityItem::cacheNeighbors() { - clearOutOfDateNeighbors(); + // this attempts to turn neighbor entityIDs into neighbor weak-pointers EntityTreeElementPointer element = getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; if (!tree) { return; } - - if (_xNNeighborID != UNKNOWN_ENTITY_ID && _xNNeighbor.expired()) { - _xNNeighbor = tree->findEntityByID(_xNNeighborID); - } - if (_yNNeighborID != UNKNOWN_ENTITY_ID && _yNNeighbor.expired()) { - _yNNeighbor = tree->findEntityByID(_yNNeighborID); - } - if (_zNNeighborID != UNKNOWN_ENTITY_ID && _zNNeighbor.expired()) { - _zNNeighbor = tree->findEntityByID(_zNNeighborID); - } - - if (_xPNeighborID != UNKNOWN_ENTITY_ID && _xPNeighbor.expired()) { - _xPNeighbor = tree->findEntityByID(_xPNeighborID); - } - if (_yPNeighborID != UNKNOWN_ENTITY_ID && _yPNeighbor.expired()) { - _yPNeighbor = tree->findEntityByID(_yPNeighborID); - } - if (_zPNeighborID != UNKNOWN_ENTITY_ID && _zPNeighbor.expired()) { - _zPNeighbor = tree->findEntityByID(_zPNeighborID); - } - + lookUpNeighbor(tree, _xNNeighborID, _xNNeighbor); + lookUpNeighbor(tree, _yNNeighborID, _yNNeighbor); + lookUpNeighbor(tree, _zNNeighborID, _zNNeighbor); + lookUpNeighbor(tree, _xPNeighborID, _xPNeighbor); + lookUpNeighbor(tree, _yPNeighborID, _yPNeighbor); + lookUpNeighbor(tree, _zPNeighborID, _zPNeighbor); } void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() { + // fill in our upper edges with a copy of our neighbors lower edges so that the meshes knit together if (_voxelSurfaceStyle != PolyVoxEntityItem::SURFACE_MARCHING_CUBES) { return; } @@ -979,347 +1040,360 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() { if (currentXPNeighbor) { auto polyVoxXPNeighbor = std::dynamic_pointer_cast(currentXPNeighbor); if (polyVoxXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int y = 0; y < _volData->getHeight(); y++) { - for (int z = 0; z < _volData->getDepth(); z++) { - uint8_t neighborValue = polyVoxXPNeighbor->getVoxel(0, y, z); - _volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue); + withWriteLock([&] { + for (int y = 0; y < _volData->getHeight(); y++) { + for (int z = 0; z < _volData->getDepth(); z++) { + uint8_t neighborValue = polyVoxXPNeighbor->getVoxel(0, y, z); + _volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue); + } } - } + }); } } if (currentYPNeighbor) { auto polyVoxYPNeighbor = std::dynamic_pointer_cast(currentYPNeighbor); if (polyVoxYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int x = 0; x < _volData->getWidth(); x++) { - for (int z = 0; z < _volData->getDepth(); z++) { - uint8_t neighborValue = polyVoxYPNeighbor->getVoxel(x, 0, z); - _volData->setVoxelAt(x, _volData->getWidth() - 1, z, neighborValue); + withWriteLock([&] { + for (int x = 0; x < _volData->getWidth(); x++) { + for (int z = 0; z < _volData->getDepth(); z++) { + uint8_t neighborValue = polyVoxYPNeighbor->getVoxel(x, 0, z); + _volData->setVoxelAt(x, _volData->getWidth() - 1, z, neighborValue); + } } - } + }); } } if (currentZPNeighbor) { auto polyVoxZPNeighbor = std::dynamic_pointer_cast(currentZPNeighbor); if (polyVoxZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int x = 0; x < _volData->getWidth(); x++) { - for (int y = 0; y < _volData->getHeight(); y++) { - uint8_t neighborValue = polyVoxZPNeighbor->getVoxel(x, y, 0); - _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); + withWriteLock([&] { + for (int x = 0; x < _volData->getWidth(); x++) { + for (int y = 0; y < _volData->getHeight(); y++) { + uint8_t neighborValue = polyVoxZPNeighbor->getVoxel(x, y, 0); + _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); + } } - } + }); } } } -void RenderablePolyVoxEntityItem::getMeshAsync() { - model::MeshPointer mesh(new model::Mesh()); +void RenderablePolyVoxEntityItem::getMesh() { + // use _volData to make a renderable mesh + PolyVoxSurfaceStyle voxelSurfaceStyle; + withReadLock([&] { + voxelSurfaceStyle = _voxelSurfaceStyle; + }); cacheNeighbors(); - - // A mesh object to hold the result of surface extraction - PolyVox::SurfaceMesh polyVoxMesh; - - _volDataLock.lockForRead(); - if (!_volData) { - _volDataLock.unlock(); - return; - } copyUpperEdgesFromNeighbors(); - switch (_voxelSurfaceStyle) { - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; + auto entity = std::static_pointer_cast(getThisPointer()); + + + QtConcurrent::run([entity, voxelSurfaceStyle] { + model::MeshPointer mesh(new model::Mesh()); + + // A mesh object to hold the result of surface extraction + PolyVox::SurfaceMesh polyVoxMesh; + + entity->withReadLock([&] { + PolyVox::SimpleVolume* volData = entity->getVolData(); + switch (voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: { + PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_CUBIC: { + PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + } + }); + + // convert PolyVox mesh to a Sam mesh + const std::vector& vecIndices = polyVoxMesh.getIndices(); + auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), + (gpu::Byte*)vecIndices.data()); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); + mesh->setIndexBuffer(*indexBufferView); + + const std::vector& vecVertices = polyVoxMesh.getVertices(); + auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), + (gpu::Byte*)vecVertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + gpu::Resource::Size vertexBufferSize = 0; + if (vertexBufferPtr->getSize() > sizeof(float) * 3) { + vertexBufferSize = vertexBufferPtr->getSize() - sizeof(float) * 3; } + auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, 0, vertexBufferSize, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); + mesh->setVertexBuffer(*vertexBufferView); + mesh->addAttribute(gpu::Stream::NORMAL, + gpu::BufferView(vertexBufferPtr, + sizeof(float) * 3, + vertexBufferPtr->getSize() - sizeof(float) * 3, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); + entity->setMesh(mesh); + }); +} + +void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) { + // this catches the payload from getMesh + bool neighborsNeedUpdate; + withWriteLock([&] { + _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; + _mesh = mesh; + _meshDirty = true; + _meshInitialized = true; + neighborsNeedUpdate = _neighborsNeedUpdate; + _neighborsNeedUpdate = false; + }); + if (neighborsNeedUpdate) { + bonkNeighbors(); } - - // convert PolyVox mesh to a Sam mesh - const std::vector& vecIndices = polyVoxMesh.getIndices(); - auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), - (gpu::Byte*)vecIndices.data()); - auto indexBufferPtr = gpu::BufferPointer(indexBuffer); - auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); - mesh->setIndexBuffer(*indexBufferView); - - const std::vector& vecVertices = polyVoxMesh.getVertices(); - auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), - (gpu::Byte*)vecVertices.data()); - auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - gpu::Resource::Size vertexBufferSize = 0; - if (vertexBufferPtr->getSize() > sizeof(float) * 3) { - vertexBufferSize = vertexBufferPtr->getSize() - sizeof(float) * 3; - } - auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, 0, vertexBufferSize, sizeof(PolyVox::PositionMaterialNormal), - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); - mesh->setVertexBuffer(*vertexBufferView); - mesh->addAttribute(gpu::Stream::NORMAL, - gpu::BufferView(vertexBufferPtr, - sizeof(float) * 3, - vertexBufferPtr->getSize() - sizeof(float) * 3, - sizeof(PolyVox::PositionMaterialNormal), - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); - - _meshLock.lockForWrite(); - _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; - _mesh = mesh; - _meshDirty = true; - _meshLock.unlock(); - _volDataDirty = false; - _volDataLock.unlock(); - bonkNeighbors(); - _threadRunning.release(); } void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync); -} - - -void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() { - QVector> points; - AABox box; - glm::mat4 vtoM = voxelToLocalMatrix(); - - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || - _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { - // pull each triangle in the mesh into a polyhedron which can be collided with - unsigned int i = 0; - - _meshLock.lockForRead(); - model::MeshPointer mesh = _mesh; - const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); - const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); - _meshLock.unlock(); - - gpu::BufferView::Iterator it = indexBufferView.cbegin(); - while (it != indexBufferView.cend()) { - uint32_t p0Index = *(it++); - uint32_t p1Index = *(it++); - uint32_t p2Index = *(it++); - - const glm::vec3& p0 = vertexBufferView.get(p0Index); - const glm::vec3& p1 = vertexBufferView.get(p1Index); - const glm::vec3& p2 = vertexBufferView.get(p2Index); - - glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face - glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); - glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; - - glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); - glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); - glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); - glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); - - box += p0Model; - box += p1Model; - box += p2Model; - box += p3Model; - - QVector pointsInPart; - pointsInPart << p0Model; - pointsInPart << p1Model; - pointsInPart << p2Model; - pointsInPart << p3Model; - // add next convex hull - QVector newMeshPoints; - points << newMeshPoints; - // add points to the new convex hull - points[i++] << pointsInPart; - } - } else { - unsigned int i = 0; - - _volDataLock.lockForRead(); - if (!_volData) { - _volDataLock.unlock(); - return; - } - - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - if (getVoxelInternal(x, y, z) > 0) { - - if ((x > 0 && getVoxel(x - 1, y, z) > 0) && - (y > 0 && getVoxel(x, y - 1, z) > 0) && - (z > 0 && getVoxel(x, y, z - 1) > 0) && - (x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) > 0) && - (y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) > 0) && - (z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) > 0)) { - // this voxel has neighbors in every cardinal direction, so there's no need - // to include it in the collision hull. - continue; - } - - QVector pointsInPart; - - float offL = -0.5f; - float offH = 0.5f; - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { - offL += 1.0f; - offH += 1.0f; - } - - glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); - glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); - glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); - glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); - glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); - glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); - glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); - glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); - - box += p000; - box += p001; - box += p010; - box += p011; - box += p100; - box += p101; - box += p110; - box += p111; - - pointsInPart << p000; - pointsInPart << p001; - pointsInPart << p010; - pointsInPart << p011; - pointsInPart << p100; - pointsInPart << p101; - pointsInPart << p110; - pointsInPart << p111; - - // add next convex hull - QVector newMeshPoints; - points << newMeshPoints; - // add points to the new convex hull - points[i++] << pointsInPart; - } - } - } - } - _volDataLock.unlock(); + // this creates a collision-shape for the physics engine. The shape comes from + // _volData for cubic extractors and from _mesh for marching-cube extractors + if (!_meshInitialized) { + return; } + EntityItemPointer entity = getThisPointer(); + + PolyVoxSurfaceStyle voxelSurfaceStyle; + glm::vec3 voxelVolumeSize; + model::MeshPointer mesh; + + withReadLock([&] { + voxelSurfaceStyle = _voxelSurfaceStyle; + voxelVolumeSize = _voxelVolumeSize; + mesh = _mesh; + }); + + QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { + auto polyVoxEntity = std::static_pointer_cast(entity); + QVector> points; + AABox box; + glm::mat4 vtoM = std::static_pointer_cast(entity)->voxelToLocalMatrix(); + + if (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || + voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + // pull each triangle in the mesh into a polyhedron which can be collided with + unsigned int i = 0; + + const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); + const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); + + gpu::BufferView::Iterator it = indexBufferView.cbegin(); + while (it != indexBufferView.cend()) { + uint32_t p0Index = *(it++); + uint32_t p1Index = *(it++); + uint32_t p2Index = *(it++); + + const glm::vec3& p0 = vertexBufferView.get(p0Index); + const glm::vec3& p1 = vertexBufferView.get(p1Index); + const glm::vec3& p2 = vertexBufferView.get(p2Index); + + glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face + glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); + glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; + + glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); + glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); + glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); + glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); + + box += p0Model; + box += p1Model; + box += p2Model; + box += p3Model; + + QVector pointsInPart; + pointsInPart << p0Model; + pointsInPart << p1Model; + pointsInPart << p2Model; + pointsInPart << p3Model; + // add next convex hull + QVector newMeshPoints; + points << newMeshPoints; + // add points to the new convex hull + points[i++] << pointsInPart; + } + } else { + unsigned int i = 0; + polyVoxEntity->forEachVoxelValue(voxelVolumeSize.x, voxelVolumeSize.y, voxelVolumeSize.z, + [&](int x, int y, int z, uint8_t value) { + if (value > 0) { + if ((x > 0 && polyVoxEntity->getVoxelInternal(x - 1, y, z) > 0) && + (y > 0 && polyVoxEntity->getVoxelInternal(x, y - 1, z) > 0) && + (z > 0 && polyVoxEntity->getVoxelInternal(x, y, z - 1) > 0) && + (x < voxelVolumeSize.x - 1 && polyVoxEntity->getVoxelInternal(x + 1, y, z) > 0) && + (y < voxelVolumeSize.y - 1 && polyVoxEntity->getVoxelInternal(x, y + 1, z) > 0) && + (z < voxelVolumeSize.z - 1 && polyVoxEntity->getVoxelInternal(x, y, z + 1) > 0)) { + // this voxel has neighbors in every cardinal direction, so there's no need + // to include it in the collision hull. + return; + } + + QVector pointsInPart; + + float offL = -0.5f; + float offH = 0.5f; + if (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { + offL += 1.0f; + offH += 1.0f; + } + + glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); + glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); + glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); + glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); + glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); + glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); + glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); + glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); + + box += p000; + box += p001; + box += p010; + box += p011; + box += p100; + box += p101; + box += p110; + box += p111; + + pointsInPart << p000; + pointsInPart << p001; + pointsInPart << p010; + pointsInPart << p011; + pointsInPart << p100; + pointsInPart << p101; + pointsInPart << p110; + pointsInPart << p111; + + // add next convex hull + QVector newMeshPoints; + points << newMeshPoints; + // add points to the new convex hull + points[i++] << pointsInPart; + } + }); + } + polyVoxEntity->setCollisionPoints(points, box); + }); +} + +void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector> points, AABox box) { + // this catches the payload from computeShapeInfoWorker if (points.isEmpty()) { - _shapeInfoLock.lockForWrite(); EntityItem::computeShapeInfo(_shapeInfo); - _shapeInfoLock.unlock(); - _threadRunning.release(); return; } glm::vec3 collisionModelDimensions = box.getDimensions(); // include the registrationPoint in the shape key, because the offset is already // included in the points and the shapeManager wont know that the shape has changed. - QString shapeKey = QString(_voxelData.toBase64()) + "," + - QString::number(_registrationPoint.x) + "," + - QString::number(_registrationPoint.y) + "," + - QString::number(_registrationPoint.z); - _shapeInfoLock.lockForWrite(); - _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey); - _shapeInfo.setConvexHulls(points); - // adjustShapeInfoByRegistration(_shapeInfo); - _shapeInfoLock.unlock(); - - _meshLock.lockForWrite(); - _meshDirty = false; - _meshLock.unlock(); - _threadRunning.release(); - return; + withWriteLock([&] { + QString shapeKey = QString(_voxelData.toBase64()) + "," + + QString::number(_registrationPoint.x) + "," + + QString::number(_registrationPoint.y) + "," + + QString::number(_registrationPoint.z); + _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey); + _shapeInfo.setConvexHulls(points); + _meshDirty = false; + }); } - void RenderablePolyVoxEntityItem::setXNNeighborID(const EntityItemID& xNNeighborID) { + if (xNNeighborID == _id) { // TODO loops are still possible + return; + } + if (xNNeighborID != _xNNeighborID) { PolyVoxEntityItem::setXNNeighborID(xNNeighborID); cacheNeighbors(); - EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); - if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxXNNeighbor = std::dynamic_pointer_cast(currentXNNeighbor); - polyVoxXNNeighbor->setXPNeighborID(_id); - polyVoxXNNeighbor->rebakeMesh(); - } } } void RenderablePolyVoxEntityItem::setYNNeighborID(const EntityItemID& yNNeighborID) { + if (yNNeighborID == _id) { // TODO loops are still possible + return; + } + if (yNNeighborID != _yNNeighborID) { PolyVoxEntityItem::setYNNeighborID(yNNeighborID); cacheNeighbors(); - EntityItemPointer currentYNNeighbor = _yNNeighbor.lock(); - if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxYNNeighbor = std::dynamic_pointer_cast(currentYNNeighbor); - polyVoxYNNeighbor->setYPNeighborID(_id); - polyVoxYNNeighbor->rebakeMesh(); - } } } void RenderablePolyVoxEntityItem::setZNNeighborID(const EntityItemID& zNNeighborID) { + if (zNNeighborID == _id) { // TODO loops are still possible + return; + } + if (zNNeighborID != _zNNeighborID) { PolyVoxEntityItem::setZNNeighborID(zNNeighborID); cacheNeighbors(); - EntityItemPointer currentZNNeighbor = _yNNeighbor.lock(); - if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxZNNeighbor = std::dynamic_pointer_cast(currentZNNeighbor); - polyVoxZNNeighbor->setZPNeighborID(_id); - polyVoxZNNeighbor->rebakeMesh(); - } } } - void RenderablePolyVoxEntityItem::setXPNeighborID(const EntityItemID& xPNeighborID) { + if (xPNeighborID == _id) { // TODO loops are still possible + return; + } if (xPNeighborID != _xPNeighborID) { PolyVoxEntityItem::setXPNeighborID(xPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } void RenderablePolyVoxEntityItem::setYPNeighborID(const EntityItemID& yPNeighborID) { + if (yPNeighborID == _id) { // TODO loops are still possible + return; + } if (yPNeighborID != _yPNeighborID) { PolyVoxEntityItem::setYPNeighborID(yPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } void RenderablePolyVoxEntityItem::setZPNeighborID(const EntityItemID& zPNeighborID) { + if (zPNeighborID == _id) { // TODO loops are still possible + return; + } if (zPNeighborID != _zPNeighborID) { PolyVoxEntityItem::setZPNeighborID(zPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } -void RenderablePolyVoxEntityItem::rebakeMesh() { - QReadLocker(&this->_volDataLock); - _volDataDirty = true; -} - void RenderablePolyVoxEntityItem::bonkNeighbors() { - clearOutOfDateNeighbors(); + // flag neighbors to the negative of this entity as needing to rebake their meshes. cacheNeighbors(); EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); @@ -1328,14 +1402,14 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxXNNeighbor = std::dynamic_pointer_cast(currentXNNeighbor); - polyVoxXNNeighbor->rebakeMesh(); + polyVoxXNNeighbor->setVolDataDirty(); } if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxYNNeighbor = std::dynamic_pointer_cast(currentYNNeighbor); - polyVoxYNNeighbor->rebakeMesh(); + polyVoxYNNeighbor->setVolDataDirty(); } if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxZNNeighbor = std::dynamic_pointer_cast(currentZNNeighbor); - polyVoxZNNeighbor->rebakeMesh(); + polyVoxZNNeighbor->setVolDataDirty(); } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index b40507f36a..e5afb94afa 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -48,6 +48,8 @@ public: virtual ~RenderablePolyVoxEntityItem(); + void initializePolyVox(); + virtual void somethingChangedNotification() { // This gets called from EnityItem::readEntityDataFromBuffer every time a packet describing // this entity comes from the entity-server. It gets called even if nothing has actually changed @@ -114,17 +116,28 @@ public: virtual void setYPNeighborID(const EntityItemID& yPNeighborID); virtual void setZPNeighborID(const EntityItemID& zPNeighborID); - virtual void rebakeMesh(); - virtual void updateRegistrationPoint(const glm::vec3& value); + void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize); + void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize, + std::function thunk); + + void setMesh(model::MeshPointer mesh); + void setCollisionPoints(const QVector> points, AABox box); + PolyVox::SimpleVolume* getVolData() { return _volData; } + + uint8_t getVoxelInternal(int x, int y, int z); + bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + + void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); } + private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. model::MeshPointer _mesh; - bool _meshDirty; // does collision-shape need to be recomputed? - mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive}; + bool _meshDirty { true }; // does collision-shape need to be recomputed? + bool _meshInitialized { false }; NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; @@ -135,44 +148,35 @@ private: static gpu::PipelinePointer _pipeline; ShapeInfo _shapeInfo; - mutable QReadWriteLock _shapeInfoLock; PolyVox::SimpleVolume* _volData = nullptr; - mutable QReadWriteLock _volDataLock{QReadWriteLock::Recursive}; // lock for _volData bool _volDataDirty = false; // does getMesh need to be called? int _onCount; // how many non-zero voxels are in _volData - bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z) const; - uint8_t getVoxelInternal(int x, int y, int z); - bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + bool _neighborsNeedUpdate { false }; + bool updateOnCount(int x, int y, int z, uint8_t toValue); PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; // these are run off the main thread void decompressVolumeData(); - void decompressVolumeDataAsync(); void compressVolumeDataAndSendEditPacket(); - void compressVolumeDataAndSendEditPacketAsync(); - void getMesh(); - void getMeshAsync(); + virtual void getMesh(); // recompute mesh void computeShapeInfoWorker(); - void computeShapeInfoWorkerAsync(); - - QSemaphore _threadRunning{1}; // these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID - EntityItemWeakPointer _xNNeighbor; // neighor found by going along negative X axis + EntityItemWeakPointer _xNNeighbor; // neighbor found by going along negative X axis EntityItemWeakPointer _yNNeighbor; EntityItemWeakPointer _zNNeighbor; - EntityItemWeakPointer _xPNeighbor; // neighor found by going along positive X axis + EntityItemWeakPointer _xPNeighbor; // neighbor found by going along positive X axis EntityItemWeakPointer _yPNeighbor; EntityItemWeakPointer _zPNeighbor; - void clearOutOfDateNeighbors(); void cacheNeighbors(); void copyUpperEdgesFromNeighbors(); void bonkNeighbors(); }; +bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z); #endif // hifi_RenderablePolyVoxEntityItem_h diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 014ab33094..14bfc5ac7a 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -101,12 +101,15 @@ void EntitySimulation::expireMortalEntities(const quint64& now) { prepareEntityForDelete(entity); } else { if (expiry < _nextExpiry) { - // remeber the smallest _nextExpiry so we know when to start the next search + // remember the smallest _nextExpiry so we know when to start the next search _nextExpiry = expiry; } ++itemItr; } } + if (_mortalEntities.size() < 1) { + _nextExpiry = -1; + } } } diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 9b85938a78..44bf940dae 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -64,44 +64,47 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID) : } void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - QWriteLocker(&this->_voxelDataLock); + withWriteLock([&] { + assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x); + assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); + assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); - assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x); - assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); - assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); + _voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z)); + if (_voxelVolumeSize.x < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1"; + _voxelVolumeSize.x = 1; + } + if (_voxelVolumeSize.x > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to max"; + _voxelVolumeSize.x = MAX_VOXEL_DIMENSION; + } - _voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z)); - if (_voxelVolumeSize.x < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1"; - _voxelVolumeSize.x = 1; - } - if (_voxelVolumeSize.x > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to max"; - _voxelVolumeSize.x = MAX_VOXEL_DIMENSION; - } + if (_voxelVolumeSize.y < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to 1"; + _voxelVolumeSize.y = 1; + } + if (_voxelVolumeSize.y > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to max"; + _voxelVolumeSize.y = MAX_VOXEL_DIMENSION; + } - if (_voxelVolumeSize.y < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to 1"; - _voxelVolumeSize.y = 1; - } - if (_voxelVolumeSize.y > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to max"; - _voxelVolumeSize.y = MAX_VOXEL_DIMENSION; - } - - if (_voxelVolumeSize.z < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to 1"; - _voxelVolumeSize.z = 1; - } - if (_voxelVolumeSize.z > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max"; - _voxelVolumeSize.z = MAX_VOXEL_DIMENSION; - } + if (_voxelVolumeSize.z < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to 1"; + _voxelVolumeSize.z = 1; + } + if (_voxelVolumeSize.z > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max"; + _voxelVolumeSize.z = MAX_VOXEL_DIMENSION; + } + }); } -const glm::vec3& PolyVoxEntityItem::getVoxelVolumeSize() const { - QWriteLocker locker(&this->_voxelDataLock); - return _voxelVolumeSize; +glm::vec3 PolyVoxEntityItem::getVoxelVolumeSize() const { + glm::vec3 voxelVolumeSize; + withReadLock([&] { + voxelVolumeSize = _voxelVolumeSize; + }); + return voxelVolumeSize; } @@ -226,12 +229,16 @@ void PolyVoxEntityItem::debugDump() const { } void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - QWriteLocker(&this->_voxelDataLock); - _voxelData = voxelData; - _voxelDataDirty = true; + withWriteLock([&] { + _voxelData = voxelData; + _voxelDataDirty = true; + }); } const QByteArray PolyVoxEntityItem::getVoxelData() const { - QReadLocker(&this->_voxelDataLock); - return _voxelData; + QByteArray voxelDataCopy; + withReadLock([&] { + voxelDataCopy = _voxelData; + }); + return voxelDataCopy; } diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 13e541d298..7441b34c9c 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -52,7 +52,7 @@ class PolyVoxEntityItem : public EntityItem { virtual void debugDump() const; virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); - virtual const glm::vec3& getVoxelVolumeSize() const; + virtual glm::vec3 getVoxelVolumeSize() const; virtual void setVoxelData(QByteArray voxelData); virtual const QByteArray getVoxelData() const; @@ -128,12 +128,14 @@ class PolyVoxEntityItem : public EntityItem { virtual void rebakeMesh() {}; + void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); } + virtual void getMesh() {}; // recompute mesh + protected: glm::vec3 _voxelVolumeSize; // this is always 3 bytes - mutable QReadWriteLock _voxelDataLock; QByteArray _voxelData; - bool _voxelDataDirty; + bool _voxelDataDirty; // _voxelData has changed, things that depend on it should be updated PolyVoxSurfaceStyle _voxelSurfaceStyle; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index b0b769d5e9..e5094a5224 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -293,7 +293,7 @@ void NetworkGeometry::requestModel(const QUrl& url) { connect(_resource, &Resource::failed, this, &NetworkGeometry::modelRequestError); } -void NetworkGeometry::mappingRequestDone(const QByteArray& data) { +void NetworkGeometry::mappingRequestDone(const QByteArray data) { assert(_state == RequestMappingState); // parse the mapping file @@ -325,7 +325,7 @@ void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) { emit onFailure(*this, MappingRequestError); } -void NetworkGeometry::modelRequestDone(const QByteArray& data) { +void NetworkGeometry::modelRequestDone(const QByteArray data) { assert(_state == RequestModelState); _state = ParsingModelState; diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 1c76a0b878..550b16d2ba 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -113,10 +113,10 @@ public slots: void textureLoaded(const QWeakPointer& networkTexture); protected slots: - void mappingRequestDone(const QByteArray& data); + void mappingRequestDone(const QByteArray data); void mappingRequestError(QNetworkReply::NetworkError error); - void modelRequestDone(const QByteArray& data); + void modelRequestDone(const QByteArray data); void modelRequestError(QNetworkReply::NetworkError error); void modelParseSuccess(FBXGeometry* geometry); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index af30132b70..e97b077f24 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -81,6 +81,8 @@ AccountManager::AccountManager() : qRegisterMetaType("QHttpMultiPart*"); + qRegisterMetaType(); + connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); } @@ -215,12 +217,13 @@ void AccountManager::sendRequest(const QString& path, if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "sendRequest", Q_ARG(const QString&, path), - Q_ARG(AccountManagerAuth::Type, AccountManagerAuth::Required), + Q_ARG(AccountManagerAuth::Type, authType), Q_ARG(QNetworkAccessManager::Operation, operation), Q_ARG(const JSONCallbackParameters&, callbackParams), Q_ARG(const QByteArray&, dataByteArray), Q_ARG(QHttpMultiPart*, dataMultiPart), Q_ARG(QVariantMap, propertyMap)); + return; } QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index fd3dd75d15..7f4d86393b 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -198,7 +198,7 @@ public: signals: /// Fired when the resource has been downloaded. /// This can be used instead of downloadFinished to access data before it is processed. - void loaded(const QByteArray& request); + void loaded(const QByteArray request); /// Fired when the resource has finished loading. void finished(bool success); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 2e74a5166c..e0369195ae 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -58,8 +58,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall // if no callbacks specified, call our owns if (params.isEmpty()) { - params.jsonCallbackReceiver = this; - params.jsonCallbackMethod = "requestFinished"; params.errorCallbackReceiver = this; params.errorCallbackMethod = "requestError"; } @@ -70,10 +68,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall params, NULL, multipart); } -void UserActivityLogger::requestFinished(QNetworkReply& requestReply) { - // qCDebug(networking) << object; -} - void UserActivityLogger::requestError(QNetworkReply& errorReply) { qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString(); } diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 8eda086521..0fdc4fdb9a 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -39,7 +39,6 @@ public slots: void wentTo(QString destinationType, QString destinationName); private slots: - void requestFinished(QNetworkReply& requestReply); void requestError(QNetworkReply& errorReply); private: diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 2ffa42cb82..8933c984d5 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -27,6 +28,7 @@ #include "ControlPacket.h" #include "Packet.h" #include "PacketList.h" +#include "../UserActivityLogger.h" #include "Socket.h" using namespace udt; @@ -328,7 +330,39 @@ void SendQueue::run() { nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta); // sleep as long as we need until next packet send, if we can - const auto timeToSleep = duration_cast(nextPacketTimestamp - p_high_resolution_clock::now()); + auto now = p_high_resolution_clock::now(); + auto timeToSleep = duration_cast(nextPacketTimestamp - now); + + // we're seeing SendQueues sleep for a long period of time here, + // which can lock the NodeList if it's attempting to clear connections + // for now we guard this by capping the time this thread and sleep for + + const microseconds MAX_SEND_QUEUE_SLEEP_USECS { 2000000 }; + if (timeToSleep > MAX_SEND_QUEUE_SLEEP_USECS) { + qWarning() << "udt::SendQueue wanted to sleep for" << timeToSleep.count() << "microseconds"; + qWarning() << "Capping sleep to" << MAX_SEND_QUEUE_SLEEP_USECS.count(); + qWarning() << "PSP:" << _packetSendPeriod << "NPD:" << nextPacketDelta + << "NPT:" << nextPacketTimestamp.time_since_epoch().count() + << "NOW:" << now.time_since_epoch().count(); + + // alright, we're in a weird state + // we want to know why this is happening so we can implement a better fix than this guard + // send some details up to the API (if the user allows us) that indicate how we could such a large timeToSleep + static const QString SEND_QUEUE_LONG_SLEEP_ACTION = "sendqueue-sleep"; + + // setup a json object with the details we want + QJsonObject longSleepObject; + longSleepObject["timeToSleep"] = qint64(timeToSleep.count()); + longSleepObject["packetSendPeriod"] = _packetSendPeriod.load(); + longSleepObject["nextPacketDelta"] = nextPacketDelta; + longSleepObject["nextPacketTimestamp"] = qint64(nextPacketTimestamp.time_since_epoch().count()); + longSleepObject["then"] = qint64(now.time_since_epoch().count()); + + // hopefully send this event using the user activity logger + UserActivityLogger::getInstance().logAction(SEND_QUEUE_LONG_SLEEP_ACTION, longSleepObject); + + timeToSleep = MAX_SEND_QUEUE_SLEEP_USECS; + } std::this_thread::sleep_for(timeToSleep); } diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 497ed2031f..2d219915c8 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -57,16 +57,18 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { } void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { - EntitySimulation::removeEntityInternal(entity); - QMutexLocker lock(&_mutex); - _entitiesToAddToPhysics.remove(entity); + if (entity->isSimulated()) { + EntitySimulation::removeEntityInternal(entity); + QMutexLocker lock(&_mutex); + _entitiesToAddToPhysics.remove(entity); - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState) { - _outgoingChanges.remove(motionState); - _entitiesToRemoveFromPhysics.insert(entity); - } else { - _entitiesToDelete.insert(entity); + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + _outgoingChanges.remove(motionState); + _entitiesToRemoveFromPhysics.insert(entity); + } else { + _entitiesToDelete.insert(entity); + } } } @@ -175,7 +177,7 @@ void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionState _entitiesToRelease.insert(entity); } - if (entity->isSimulated() && entity->isDead()) { + if (entity->isDead()) { _entitiesToDelete.insert(entity); } } @@ -190,7 +192,7 @@ void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() { entity->setPhysicsInfo(nullptr); delete motionState; - if (entity->isSimulated() && entity->isDead()) { + if (entity->isDead()) { _entitiesToDelete.insert(entity); } } diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 484f049944..56805e8f83 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -114,6 +114,7 @@ void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const Render void CullSpatialSelection::configure(const Config& config) { _justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum); _freezeFrustum = config.freezeFrustum; + _skipCulling = config.skipCulling; } void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, @@ -191,60 +192,112 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re // visibility cull if partially selected ( octree cell contianing it was partial) // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item) - // inside & fit items: easy, just filter - { - PerformanceTimer perfTimer("insideFitItems"); - for (auto id : inSelection.insideItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - outItems.emplace_back(itemBound); - } - } - } - - // inside & subcell items: filter & distance cull - { - PerformanceTimer perfTimer("insideSmallItems"); - for (auto id : inSelection.insideSubcellItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.solidAngleTest(itemBound.bound)) { + if (_skipCulling) { + // inside & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideFitItems"); + for (auto id : inSelection.insideItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); } } } - } - // partial & fit items: filter & frustum cull - { - PerformanceTimer perfTimer("partialFitItems"); - for (auto id : inSelection.partialItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.frustumTest(itemBound.bound)) { + // inside & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideSmallItems"); + for (auto id : inSelection.insideSubcellItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); } } } - } - // partial & subcell items:: filter & frutum cull & solidangle cull - { - PerformanceTimer perfTimer("partialSmallItems"); - for (auto id : inSelection.partialSubcellItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.frustumTest(itemBound.bound)) { + // partial & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialFitItems"); + for (auto id : inSelection.partialItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // partial & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialSmallItems"); + for (auto id : inSelection.partialSubcellItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + } else { + + // inside & fit items: easy, just filter + { + PerformanceTimer perfTimer("insideFitItems"); + for (auto id : inSelection.insideItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // inside & subcell items: filter & distance cull + { + PerformanceTimer perfTimer("insideSmallItems"); + for (auto id : inSelection.insideSubcellItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); } } } } + + // partial & fit items: filter & frustum cull + { + PerformanceTimer perfTimer("partialFitItems"); + for (auto id : inSelection.partialItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + if (test.frustumTest(itemBound.bound)) { + outItems.emplace_back(itemBound); + } + } + } + } + + // partial & subcell items:: filter & frutum cull & solidangle cull + { + PerformanceTimer perfTimer("partialSmallItems"); + for (auto id : inSelection.partialSubcellItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + if (test.frustumTest(itemBound.bound)) { + if (test.solidAngleTest(itemBound.bound)) { + outItems.emplace_back(itemBound); + } + } + } + } + } } details._rendered += (int)outItems.size(); diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index a6a32e4561..e84f018e91 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -70,14 +70,16 @@ namespace render { Q_OBJECT Q_PROPERTY(int numItems READ getNumItems) Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum) + Q_PROPERTY(bool skipCulling MEMBER skipCulling WRITE setSkipCulling) public: int numItems{ 0 }; int getNumItems() { return numItems; } bool freezeFrustum{ false }; + bool skipCulling{ false }; public slots: void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); } - + void setSkipCulling(bool enabled) { skipCulling = enabled; emit dirty(); } signals: void dirty(); }; @@ -85,6 +87,7 @@ namespace render { class CullSpatialSelection { bool _freezeFrustum{ false }; // initialized by Config bool _justFrozeFrustum{ false }; + bool _skipCulling{ false }; ViewFrustum _frozenFrutstum; public: using Config = CullSpatialSelectionConfig; diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 43fd5a64df..0228f77f4f 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -14,8 +14,6 @@ #include -#include - #include #include @@ -37,29 +35,8 @@ QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngi } QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { - _uid = QUuid::createUuid().toString(); - asQuickItem()->setProperty("uid", _uid); - auto webchannelVar = qmlWindow->property("webChannel"); - _webchannel = qvariant_cast(webchannelVar); - Q_ASSERT(_webchannel); - _webchannel->registerObject(_uid, this); } -void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage)); - } else { - emit scriptEventReceived(scriptMessage); - } -} - -void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage)); - } else { - emit webEventReceived(webMessage); - } -} QString QmlWebWindowClass::getURL() const { QVariant result = DependencyManager::get()->returnFromUiThread([&]()->QVariant { diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 35322ef0ea..27c0e6996d 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -11,13 +11,10 @@ #include "QmlWindowClass.h" -class QWebChannel; - // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping class QmlWebWindowClass : public QmlWindowClass { Q_OBJECT Q_PROPERTY(QString url READ getURL CONSTANT) - Q_PROPERTY(QString uid READ getUid CONSTANT) public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); @@ -26,18 +23,9 @@ public: public slots: QString getURL() const; void setURL(const QString& url); - const QString& getUid() const { return _uid; } - void emitScriptEvent(const QVariant& scriptMessage); - void emitWebEvent(const QVariant& webMessage); signals: void urlChanged(); - void scriptEventReceived(const QVariant& message); - void webEventReceived(const QVariant& message); - -private: - QString _uid; - QWebChannel* _webchannel { nullptr }; }; #endif diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 37d461acd0..b7fe330a4e 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -26,6 +26,10 @@ #include "OffscreenUi.h" +QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr }; +static QWebChannel webChannel; +static const uint16_t WEB_CHANNEL_PORT = 51016; +static std::atomic nextWindowId; static const char* const SOURCE_PROPERTY = "source"; static const char* const TITLE_PROPERTY = "title"; static const char* const WIDTH_PROPERTY = "width"; @@ -33,6 +37,54 @@ static const char* const HEIGHT_PROPERTY = "height"; static const char* const VISIBILE_PROPERTY = "visible"; static const char* const TOOLWINDOW_PROPERTY = "toolWindow"; +void QmlScriptEventBridge::emitWebEvent(const QString& data) { + QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data)); +} + +void QmlScriptEventBridge::emitScriptEvent(const QString& data) { + QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection, + Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data)); +} + +class QmlWebTransport : public QWebChannelAbstractTransport { + Q_OBJECT +public: + QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) { + // Translate from the websocket layer to the webchannel layer + connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error); + if (error.error || !document.isObject()) { + qWarning() << "Unable to parse incoming JSON message" << message; + return; + } + emit messageReceived(document.object(), this); + }); + } + + virtual void sendMessage(const QJsonObject &message) override { + // Translate from the webchannel layer to the websocket layer + _webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact)); + } + +private: + QWebSocket* const _webSocket; +}; + + +void QmlWindowClass::setupServer() { + if (!_webChannelServer) { + _webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode); + if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) { + qFatal("Failed to open web socket server."); + } + + QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] { + webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection())); + }); + } +} + QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, QScriptContext* context, QScriptEngine* engine, std::function builder) @@ -116,8 +168,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, } offscreenUi->returnFromUiThread([&] { + setupServer(); retVal = builder(newTab); retVal->_toolWindow = true; + registerObject(url.toLower(), retVal); return QVariant(); }); } else { @@ -125,8 +179,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection, Q_ARG(const QString&, qmlSource), Q_ARG(std::function, [&](QQmlContext* context, QObject* object) { + setupServer(); retVal = builder(object); context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); + registerObject(url.toLower(), retVal); if (!title.isEmpty()) { retVal->setTitle(title); } @@ -153,7 +209,10 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* }); } -QmlWindowClass::QmlWindowClass(QObject* qmlWindow) : _qmlWindow(qmlWindow) { +QmlWindowClass::QmlWindowClass(QObject* qmlWindow) + : _windowId(++nextWindowId), _qmlWindow(qmlWindow) +{ + qDebug() << "Created window with ID " << _windowId; Q_ASSERT(_qmlWindow); Q_ASSERT(dynamic_cast(_qmlWindow.data())); // Forward messages received from QML on to the script @@ -169,6 +228,14 @@ QmlWindowClass::~QmlWindowClass() { close(); } +void QmlWindowClass::registerObject(const QString& name, QObject* object) { + webChannel.registerObject(name, object); +} + +void QmlWindowClass::deregisterObject(QObject* object) { + webChannel.deregisterObject(object); +} + QQuickItem* QmlWindowClass::asQuickItem() const { if (_toolWindow) { return DependencyManager::get()->getToolWindow(); diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index eda6ce674c..fb7dbf1253 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -13,16 +13,38 @@ #include #include #include +#include #include class QScriptEngine; class QScriptContext; +class QmlWindowClass; +class QWebSocketServer; +class QWebSocket; +class QmlScriptEventBridge : public QObject { + Q_OBJECT +public: + QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {} + +public slots : + void emitWebEvent(const QString& data); + void emitScriptEvent(const QString& data); + +signals: + void webEventReceived(const QString& data); + void scriptEventReceived(int windowId, const QString& data); + +private: + const QmlWindowClass* _webWindow { nullptr }; + QWebSocket *_socket { nullptr }; +}; // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping class QmlWindowClass : public QObject { Q_OBJECT Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) + Q_PROPERTY(int windowId READ getWindowId CONSTANT) Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged) Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) @@ -47,7 +69,8 @@ public slots: Q_INVOKABLE void raise(); Q_INVOKABLE void close(); - Q_INVOKABLE QObject* getEventBridge() { return this; }; + Q_INVOKABLE int getWindowId() const { return _windowId; }; + Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; }; // Scripts can use this to send a message to the QML object void sendToQml(const QVariant& message); @@ -69,12 +92,18 @@ protected: static QScriptValue internalConstructor(const QString& qmlSource, QScriptContext* context, QScriptEngine* engine, std::function function); + static void setupServer(); + static void registerObject(const QString& name, QObject* object); + static void deregisterObject(QObject* object); + static QWebSocketServer* _webChannelServer; QQuickItem* asQuickItem() const; + QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) }; // FIXME needs to be initialized in the ctor once we have support // for tool window panes in QML bool _toolWindow { false }; + const int _windowId; QPointer _qmlWindow; QString _source; }; diff --git a/server-console/resources/console-beta.icns b/server-console/resources/console-beta.icns index a0c2af881b..70a00698bd 100644 Binary files a/server-console/resources/console-beta.icns and b/server-console/resources/console-beta.icns differ diff --git a/server-console/resources/console-beta.ico b/server-console/resources/console-beta.ico index 90962c4871..57df32f878 100644 Binary files a/server-console/resources/console-beta.ico and b/server-console/resources/console-beta.ico differ diff --git a/server-console/resources/console-beta.png b/server-console/resources/console-beta.png index 4fa8e1ce80..c2df15832d 100644 Binary files a/server-console/resources/console-beta.png and b/server-console/resources/console-beta.png differ diff --git a/server-console/resources/console.icns b/server-console/resources/console.icns index 45dd334a3e..8810f804c3 100644 Binary files a/server-console/resources/console.icns and b/server-console/resources/console.icns differ diff --git a/server-console/resources/console.ico b/server-console/resources/console.ico index 872a4a7a68..c5a4bb0381 100644 Binary files a/server-console/resources/console.ico and b/server-console/resources/console.ico differ diff --git a/server-console/resources/console.png b/server-console/resources/console.png index a8260f9687..9fa1b843ec 100644 Binary files a/server-console/resources/console.png and b/server-console/resources/console.png differ diff --git a/server-console/src/log.js b/server-console/src/log.js index e45848e5a5..3634eaeaa7 100644 --- a/server-console/src/log.js +++ b/server-console/src/log.js @@ -44,6 +44,14 @@ ready = function() { var domainServer = remote.getGlobal('domainServer'); var acMonitor = remote.getGlobal('acMonitor'); + var pendingLines = { + 'ds': new Array(), + 'ac': new Array() + }; + + var UPDATE_INTERVAL = 16; // Update log at ~60 fps + var interval = setInterval(flushPendingLines, UPDATE_INTERVAL); + var logWatchers = { 'ds': { }, @@ -83,7 +91,7 @@ ready = function() { var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 }); logTail.on('line', function(msg) { - appendLogMessage(msg, stream); + pendingLines[stream].push(msg); }); logTail.on('error', function(error) { @@ -107,6 +115,7 @@ ready = function() { } window.onbeforeunload = function(e) { + clearInterval(interval); domainServer.removeListener('logs-updated', updateLogFiles); acMonitor.removeListener('logs-updated', updateLogFiles); }; @@ -164,14 +173,23 @@ ready = function() { return !filter || message.toLowerCase().indexOf(filter) >= 0; } - function appendLogMessage(msg, name) { + function appendLogMessages(name) { + var array = pendingLines[name]; + if (array.length === 0) { + return; + } + if (array.length > maxLogLines) { + array = array.slice(-maxLogLines); + } + + console.log(name, array.length); + var id = name == "ds" ? "domain-server" : "assignment-client"; var $pidLog = $('#' + id); - var size = ++tabStates[id].size; + var size = tabStates[id].size + array.length; if (size > maxLogLines) { - $pidLog.find('div.log-line:first').remove(); - removed = true; + $pidLog.find('div.log-line:lt(' + (size - maxLogLines) + ')').remove(); } var wasAtBottom = false; @@ -179,17 +197,25 @@ ready = function() { wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height()); } - var $logLine = $('
').text(msg); - if (!shouldDisplayLogMessage(msg)) { - $logLine.hide(); + for (line in array) { + var $logLine = $('
').text(array[line]); + if (!shouldDisplayLogMessage(array[line])) { + $logLine.hide(); + } + + $pidLog.append($logLine); } - $pidLog.append($logLine); + delete pendingLines[name]; + pendingLines[name] = new Array(); if (wasAtBottom) { $pidLog.scrollTop($pidLog[0].scrollHeight); } - + } + function flushPendingLines() { + appendLogMessages('ds'); + appendLogMessages('ac'); } // handle filtering of table rows on input change