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/src/Application.cpp b/interface/src/Application.cpp index b9effa444b..381816e81f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -240,25 +240,26 @@ class DeadlockWatchdogThread : public QThread { public: static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1; static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1; - static const unsigned long MAX_HEARTBEAT_AGE_USECS = 15 * USECS_PER_SECOND; - + static const unsigned long HEARTBEAT_REPORT_INTERVAL_USECS = 5 * USECS_PER_SECOND; + static const unsigned long MAX_HEARTBEAT_AGE_USECS = 30 * USECS_PER_SECOND; + static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large + static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples + // Set the heartbeat on launch DeadlockWatchdogThread() { setObjectName("Deadlock Watchdog"); - QTimer* heartbeatTimer = new QTimer(); // Give the heartbeat an initial value - updateHeartbeat(); - connect(heartbeatTimer, &QTimer::timeout, [this] { - updateHeartbeat(); - }); - heartbeatTimer->start(HEARTBEAT_UPDATE_INTERVAL_SECS * MSECS_PER_SECOND); + _heartbeat = usecTimestampNow(); connect(qApp, &QCoreApplication::aboutToQuit, [this] { _quit = true; }); } void updateHeartbeat() { - _heartbeat = usecTimestampNow(); + auto now = usecTimestampNow(); + auto elapsed = now - _heartbeat; + _movingAverage.addSample(elapsed); + _heartbeat = now; } void deadlockDetectionCrash() { @@ -269,10 +270,52 @@ public: void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); -#ifdef NDEBUG auto now = usecTimestampNow(); - auto lastHeartbeatAge = now - _heartbeat; + + // in the unlikely event that now is less than _heartbeat, don't rollover and confuse ourselves + auto lastHeartbeatAge = (now > _heartbeat) ? now - _heartbeat : 0; + auto sinceLastReport = (now > _lastReport) ? now - _lastReport : 0; + auto elapsedMovingAverage = _movingAverage.getAverage(); + + if (elapsedMovingAverage > _maxElapsedAverage) { + qDebug() << "DEADLOCK WATCHDOG NEW maxElapsedAverage:" + << "lastHeartbeatAge:" << lastHeartbeatAge + << "elapsedMovingAverage:" << elapsedMovingAverage + << "maxElapsed:" << _maxElapsed + << "PREVIOUS maxElapsedAverage:" << _maxElapsedAverage + << "NEW maxElapsedAverage:" << elapsedMovingAverage + << "samples:" << _movingAverage.getSamples(); + _maxElapsedAverage = elapsedMovingAverage; + } + if (lastHeartbeatAge > _maxElapsed) { + qDebug() << "DEADLOCK WATCHDOG NEW maxElapsed:" + << "lastHeartbeatAge:" << lastHeartbeatAge + << "elapsedMovingAverage:" << elapsedMovingAverage + << "PREVIOUS maxElapsed:" << _maxElapsed + << "NEW maxElapsed:" << lastHeartbeatAge + << "maxElapsedAverage:" << _maxElapsedAverage + << "samples:" << _movingAverage.getSamples(); + _maxElapsed = lastHeartbeatAge; + } + if ((sinceLastReport > HEARTBEAT_REPORT_INTERVAL_USECS) || (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT)) { + qDebug() << "DEADLOCK WATCHDOG STATUS -- lastHeartbeatAge:" << lastHeartbeatAge + << "elapsedMovingAverage:" << elapsedMovingAverage + << "maxElapsed:" << _maxElapsed + << "maxElapsedAverage:" << _maxElapsedAverage + << "samples:" << _movingAverage.getSamples(); + _lastReport = now; + } + +#ifdef NDEBUG if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) { + qDebug() << "DEADLOCK DETECTED -- " + << "lastHeartbeatAge:" << lastHeartbeatAge + << "[ _heartbeat:" << _heartbeat + << "now:" << now << " ]" + << "elapsedMovingAverage:" << elapsedMovingAverage + << "maxElapsed:" << _maxElapsed + << "maxElapsedAverage:" << _maxElapsedAverage + << "samples:" << _movingAverage.getSamples(); deadlockDetectionCrash(); } #endif @@ -280,10 +323,19 @@ public: } static std::atomic _heartbeat; + static std::atomic _lastReport; + static std::atomic _maxElapsed; + static std::atomic _maxElapsedAverage; + static ThreadSafeMovingAverage _movingAverage; + bool _quit { false }; }; std::atomic DeadlockWatchdogThread::_heartbeat; +std::atomic DeadlockWatchdogThread::_lastReport; +std::atomic DeadlockWatchdogThread::_maxElapsed; +std::atomic DeadlockWatchdogThread::_maxElapsedAverage; +ThreadSafeMovingAverage DeadlockWatchdogThread::_movingAverage; #ifdef Q_OS_WIN class MyNativeEventFilter : public QAbstractNativeEventFilter { @@ -1381,6 +1433,8 @@ void Application::initializeUi() { void Application::paintGL() { + updateHeartbeat(); + // Some plugins process message events, potentially leading to // re-entering a paint event. don't allow further processing if this // happens @@ -2502,6 +2556,8 @@ static uint32_t _renderedFrameIndex { INVALID_FRAME }; void Application::idle(uint64_t now) { + updateHeartbeat(); + if (_aboutToQuit || _inPaint) { return; // bail early, nothing to do here. } 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/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 98559a56a4..312e1fcea5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -354,7 +354,9 @@ void RenderableModelEntityItem::updateModelBounds() { bool movingOrAnimating = isMovingRelativeToParent() || isAnimatingSomething(); if ((movingOrAnimating || _needsInitialSimulation || + _needsJointSimulation || _model->getTranslation() != getPosition() || + _model->getScaleToFitDimensions() != getDimensions() || _model->getRotation() != getRotation() || _model->getRegistrationPoint() != getRegistrationPoint()) && _model->isActive() && _dimensionsInitialized) { @@ -370,6 +372,7 @@ void RenderableModelEntityItem::updateModelBounds() { } _needsInitialSimulation = false; + _needsJointSimulation = false; } } @@ -770,6 +773,7 @@ bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index, _absoluteJointRotationsInObjectFrameSet[index] = true; _absoluteJointRotationsInObjectFrameDirty[index] = true; result = true; + _needsJointSimulation = true; } }); return result; @@ -785,11 +789,33 @@ bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int ind _absoluteJointTranslationsInObjectFrameSet[index] = true; _absoluteJointTranslationsInObjectFrameDirty[index] = true; result = true; + _needsJointSimulation = true; } }); return result; } +void RenderableModelEntityItem::setJointRotations(const QVector& rotations) { + ModelEntityItem::setJointRotations(rotations); + _needsJointSimulation = true; +} + +void RenderableModelEntityItem::setJointRotationsSet(const QVector& rotationsSet) { + ModelEntityItem::setJointRotationsSet(rotationsSet); + _needsJointSimulation = true; +} + +void RenderableModelEntityItem::setJointTranslations(const QVector& translations) { + ModelEntityItem::setJointTranslations(translations); + _needsJointSimulation = true; +} + +void RenderableModelEntityItem::setJointTranslationsSet(const QVector& translationsSet) { + ModelEntityItem::setJointTranslationsSet(translationsSet); + _needsJointSimulation = true; +} + + void RenderableModelEntityItem::locationChanged() { EntityItem::locationChanged(); if (_model && _model->isActive()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 069b7385b5..ac82be83d8 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -69,6 +69,11 @@ public: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override; virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override; + virtual void setJointRotations(const QVector& rotations) override; + virtual void setJointRotationsSet(const QVector& rotationsSet) override; + virtual void setJointTranslations(const QVector& translations) override; + virtual void setJointTranslationsSet(const QVector& translationsSet) override; + virtual void loader() override; virtual void locationChanged() override; @@ -97,6 +102,8 @@ private: bool _showCollisionHull = false; bool getAnimationFrame(); + + bool _needsJointSimulation { false }; }; #endif // hifi_RenderableModelEntityItem_h 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/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 686fb1f72d..bce27f1cca 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -120,10 +120,10 @@ public: virtual glm::vec3 getJointPosition(int jointIndex) const { return glm::vec3(); } virtual glm::quat getJointRotation(int jointIndex) const { return glm::quat(); } - void setJointRotations(const QVector& rotations); - void setJointRotationsSet(const QVector& rotationsSet); - void setJointTranslations(const QVector& translations); - void setJointTranslationsSet(const QVector& translationsSet); + virtual void setJointRotations(const QVector& rotations); + virtual void setJointRotationsSet(const QVector& rotationsSet); + virtual void setJointTranslations(const QVector& translations); + virtual void setJointTranslationsSet(const QVector& translationsSet); QVector getJointRotations() const; QVector getJointRotationsSet() const; QVector getJointTranslations() const; 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.cpp b/libraries/networking/src/ResourceCache.cpp index 3de94ed839..9738233c85 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -423,12 +423,12 @@ void Resource::handleReplyFinished() { auto result = _request->getResult(); if (result == ResourceRequest::Success) { - _data = _request->getData(); auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString()); qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo); - emit loaded(_data); - downloadFinished(_data); + auto data = _request->getData(); + emit loaded(data); + downloadFinished(data); } else { switch (result) { case ResourceRequest::Result::Timeout: { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 77878794b5..7f4d86393b 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -194,12 +194,11 @@ public: Q_INVOKABLE void allReferencesCleared(); const QUrl& getURL() const { return _url; } - const QByteArray& getData() const { return _data; } 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); @@ -235,7 +234,6 @@ protected: QHash, float> _loadPriorities; QWeakPointer _self; QPointer _cache; - QByteArray _data; private slots: void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal); 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/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index 53754ae241..296911ae3e 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -14,6 +14,7 @@ #ifndef hifi_SimpleMovingAverage_h #define hifi_SimpleMovingAverage_h +#include #include class SimpleMovingAverage { @@ -64,4 +65,45 @@ public: } }; +template class ThreadSafeMovingAverage { +public: + void clear() { + std::unique_lock lock(_lock); + _samples = 0; + } + + bool isAverageValid() const { + std::unique_lock lock(_lock); + return (_samples > 0); + } + + void addSample(T sample) { + std::unique_lock lock(_lock); + if (_samples > 0) { + _average = (sample * WEIGHTING) + (_average * ONE_MINUS_WEIGHTING); + } else { + _average = sample; + } + _samples++; + } + + T getAverage() const { + std::unique_lock lock(_lock); + return _average; + } + + size_t getSamples() const { + std::unique_lock lock(_lock); + return _samples; + } + +private: + const float WEIGHTING = 1.0f / (float)MAX_NUM_SAMPLES; + const float ONE_MINUS_WEIGHTING = 1.0f - WEIGHTING; + size_t _samples { 0 }; + T _average; + mutable std::mutex _lock; +}; + + #endif // hifi_SimpleMovingAverage_h 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