From 1bc8484a4b6b06b6f659222d8c8b9abd50d6aefc Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 28 Feb 2018 11:50:52 -0800 Subject: [PATCH 01/12] determine OpenVr headset namme --- .../src/plugins/InputConfiguration.cpp | 5 +++-- libraries/plugins/src/plugins/InputPlugin.h | 5 +++-- plugins/openvr/src/ViveControllerManager.cpp | 22 +++++++++++++++++++ plugins/openvr/src/ViveControllerManager.h | 4 ++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/libraries/plugins/src/plugins/InputConfiguration.cpp b/libraries/plugins/src/plugins/InputConfiguration.cpp index 9234ac6585..976a9c9463 100644 --- a/libraries/plugins/src/plugins/InputConfiguration.cpp +++ b/libraries/plugins/src/plugins/InputConfiguration.cpp @@ -54,7 +54,8 @@ QStringList InputConfiguration::activeInputPlugins() { if (plugin->configurable()) { QString pluginName = plugin->getName(); if (pluginName == QString("OpenVR")) { - activePlugins << QString("Vive"); + QString headsetName = plugin->getDeviceName(); + activePlugins << headsetName; } else { activePlugins << pluginName; } @@ -74,7 +75,7 @@ QString InputConfiguration::configurationLayout(QString pluginName) { QString sourcePath; for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { - if (plugin->getName() == pluginName) { + if (plugin->getName() == pluginName || plugin->getDeviceName() == pluginName) { return plugin->configurationLayout(); } } diff --git a/libraries/plugins/src/plugins/InputPlugin.h b/libraries/plugins/src/plugins/InputPlugin.h index 5d02964c97..23fbb6cac6 100644 --- a/libraries/plugins/src/plugins/InputPlugin.h +++ b/libraries/plugins/src/plugins/InputPlugin.h @@ -26,10 +26,11 @@ public: // If an input plugin is only a single device, it will only return it's primary name. virtual QStringList getSubdeviceNames() { return { getName() }; }; virtual void setConfigurationSettings(const QJsonObject configurationSettings) { } - virtual QJsonObject configurationSettings() { return QJsonObject(); } + virtual QJsonObject configurationSettings() { return QJsonObject(); } virtual QString configurationLayout() { return QString(); } + virtual QString getDeviceName() { return QString(); } virtual void calibrate() {} - virtual bool uncalibrate() { return false; } + virtual bool uncalibrate() { return false; } virtual bool configurable() { return false; } virtual bool isHandController() const { return false; } virtual bool isHeadController() const { return false; } diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index c58e97cc06..1f63769b22 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -11,6 +11,7 @@ #include "ViveControllerManager.h" #include +#include #include #include @@ -333,12 +334,33 @@ ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : _configStringMap[Config::FeetHipsChestAndShoulders] = QString("FeetHipsChestAndShoulders"); } +std::string ViveControllerManager::InputDevice::getTrackingSystemName() { + std::string trackingSystemName = ""; + if (_system) { + uint32_t HmdTrackingIndex = 0; + uint32_t bufferLength = _system->GetStringTrackedDeviceProperty(HmdTrackingIndex, vr::Prop_TrackingSystemName_String, NULL, 0, NULL); + if (bufferLength > 0) { + char* stringBuffer = new char[bufferLength]; + _system->GetStringTrackedDeviceProperty(HmdTrackingIndex, vr::Prop_ManufacturerName_String, stringBuffer, bufferLength, NULL); + trackingSystemName = stringBuffer; + delete[] stringBuffer; + } + } + return trackingSystemName; +} + void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _poseStateMap.clear(); _buttonPressedMap.clear(); _validTrackedObjects.clear(); _trackedControllers = 0; + if (_headsetName == "") { + _headsetName = getTrackingSystemName(); + if (_headsetName == "HTC") { + _headsetName += " Vive"; + } + } // While the keyboard is open, we defer strictly to the keyboard values if (isOpenVrKeyboardShown()) { _axisStateMap.clear(); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 0dd13d95cb..d66db45ee9 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -52,6 +52,8 @@ public: bool activate() override; void deactivate() override; + QString getDeviceName() { return QString::fromStdString(_inputDevice->_headsetName); } + void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; @@ -76,6 +78,7 @@ private: void calibrateOrUncalibrate(const controller::InputCalibrationData& inputCalibration); void calibrate(const controller::InputCalibrationData& inputCalibration); void uncalibrate(); + std::string getTrackingSystemName(); void sendUserActivityData(QString activity); void configureCalibrationSettings(const QJsonObject configurationSettings); QJsonObject configurationSettings(); @@ -161,6 +164,7 @@ private: HandConfig _handConfig { HandConfig::HandController }; FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; + std::string _headsetName {""}; std::vector _validTrackedObjects; std::map _pucksPostOffset; From 1bae22fed016de2a1a7ff5f454e29ddfb569fb6f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 28 Feb 2018 16:32:53 -0800 Subject: [PATCH 02/12] get actual name for OpenVrDispayPlugin --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 10 +++++++++- plugins/openvr/src/OpenVrDisplayPlugin.h | 2 +- plugins/openvr/src/OpenVrHelpers.cpp | 16 ++++++++++++++++ plugins/openvr/src/OpenVrHelpers.h | 2 ++ plugins/openvr/src/ViveControllerManager.cpp | 17 +---------------- plugins/openvr/src/ViveControllerManager.h | 1 - 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 2949e72c74..615b176527 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -410,6 +410,15 @@ void OpenVrDisplayPlugin::init() { emit deviceConnected(getName()); } +const QString OpenVrDisplayPlugin::getName() const { + std::string headsetName = getOpenVrDeviceName(); + if (headsetName == "HTC") { + headsetName += " Vive"; + } + + return QString::fromStdString(headsetName); +} + bool OpenVrDisplayPlugin::internalActivate() { if (!_system) { _system = acquireOpenVrSystem(); @@ -444,7 +453,6 @@ bool OpenVrDisplayPlugin::internalActivate() { _openVrDisplayActive = true; _container->setIsOptionChecked(StandingHMDSensorMode, true); - _system->GetRecommendedRenderTargetSize(&_renderTargetSize.x, &_renderTargetSize.y); // Recommended render target size is per-eye, so double the X size for // left + right eyes diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index f681852cd5..e6fc6367d5 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -36,7 +36,7 @@ class OpenVrDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: bool isSupported() const override; - const QString getName() const override { return NAME; } + const QString getName() const override; glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index c8a0cb5f8b..9a57413f95 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -66,6 +66,22 @@ bool oculusViaOpenVR() { return enableDebugOpenVR && isOculusPresent() && vr::VR_IsHmdPresent(); } +std::string getOpenVrDeviceName() { + auto system = acquireOpenVrSystem(); + std::string trackingSystemName = ""; + if (system) { + uint32_t HmdTrackingIndex = 0; + uint32_t bufferLength = system->GetStringTrackedDeviceProperty(HmdTrackingIndex, vr::Prop_TrackingSystemName_String, NULL, 0, NULL); + if (bufferLength > 0) { + char* stringBuffer = new char[bufferLength]; + system->GetStringTrackedDeviceProperty(HmdTrackingIndex, vr::Prop_ManufacturerName_String, stringBuffer, bufferLength, NULL); + trackingSystemName = stringBuffer; + delete[] stringBuffer; + } + } + return trackingSystemName; +} + bool openVrSupported() { static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index c54f2326c2..833e5ba65d 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -14,6 +14,7 @@ #include #include +#include bool oculusViaOpenVR(); // is the user using Oculus via OpenVR bool openVrSupported(); @@ -26,6 +27,7 @@ void enableOpenVrKeyboard(PluginContainer* container); void disableOpenVrKeyboard(); bool isOpenVrKeyboardShown(); QString getVrSettingString(const char* section, const char* setting); +std::string getOpenVrDeviceName(); template diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 1f63769b22..caa9fc3d70 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -334,21 +334,6 @@ ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : _configStringMap[Config::FeetHipsChestAndShoulders] = QString("FeetHipsChestAndShoulders"); } -std::string ViveControllerManager::InputDevice::getTrackingSystemName() { - std::string trackingSystemName = ""; - if (_system) { - uint32_t HmdTrackingIndex = 0; - uint32_t bufferLength = _system->GetStringTrackedDeviceProperty(HmdTrackingIndex, vr::Prop_TrackingSystemName_String, NULL, 0, NULL); - if (bufferLength > 0) { - char* stringBuffer = new char[bufferLength]; - _system->GetStringTrackedDeviceProperty(HmdTrackingIndex, vr::Prop_ManufacturerName_String, stringBuffer, bufferLength, NULL); - trackingSystemName = stringBuffer; - delete[] stringBuffer; - } - } - return trackingSystemName; -} - void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _poseStateMap.clear(); _buttonPressedMap.clear(); @@ -356,7 +341,7 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle _trackedControllers = 0; if (_headsetName == "") { - _headsetName = getTrackingSystemName(); + _headsetName = getOpenVrDeviceName(); if (_headsetName == "HTC") { _headsetName += " Vive"; } diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index d66db45ee9..f3631ece9d 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -78,7 +78,6 @@ private: void calibrateOrUncalibrate(const controller::InputCalibrationData& inputCalibration); void calibrate(const controller::InputCalibrationData& inputCalibration); void uncalibrate(); - std::string getTrackingSystemName(); void sendUserActivityData(QString activity); void configureCalibrationSettings(const QJsonObject configurationSettings); QJsonObject configurationSettings(); From 95e14446dd698c771301350eb365cbffa0a68eff Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 1 Mar 2018 09:03:52 -0800 Subject: [PATCH 03/12] update qml file to show the current OpenVr headset --- .../qml/hifi/tablet/ControllerSettings.qml | 1 + .../qml/hifi/tablet/OpenVrConfiguration.qml | 20 +++++++++---------- .../src/plugins/InputConfiguration.cpp | 3 ++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 4814eaf01c..78cc2c5a00 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -17,6 +17,7 @@ StackView { id: stack initialItem: inputConfiguration property alias messageVisible: imageMessageBox.visible + property alias selectedPlugin: box.currentText Rectangle { id: inputConfiguration anchors.fill: parent diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 0978f23013..6f79136a49 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -36,7 +36,7 @@ Rectangle { readonly property bool hmdHead: headBox.checked readonly property bool headPuck: headPuckBox.checked readonly property bool handController: handBox.checked - + readonly property bool handPuck: handPuckBox.checked readonly property bool hmdDesktop: hmdInDesktop.checked @@ -107,7 +107,7 @@ Rectangle { RalewayBold { size: 12 - text: "Vive HMD" + text: stack.selectedPlugin + " HMD" color: hifi.colors.lightGrayText } @@ -145,7 +145,7 @@ Rectangle { anchors.topMargin: 5 anchors.left: openVrConfiguration.left anchors.leftMargin: leftMargin + 10 - + onClicked: { if (checked) { headBox.checked = false; @@ -778,12 +778,12 @@ Rectangle { RalewayBold { id: advanceSettings - + text: "Advanced Settings" size: 12 - + color: hifi.colors.white - + anchors.top: advanceSeperator.bottom anchors.topMargin: 10 anchors.left: parent.left @@ -801,7 +801,7 @@ Rectangle { anchors.topMargin: 5 anchors.left: openVrConfiguration.left anchors.leftMargin: leftMargin + 10 - + onClicked: { if (!checked & hmdInDesktop.checked) { headBox.checked = true; @@ -815,9 +815,9 @@ Rectangle { RalewayBold { id: viveDesktopText size: 10 - text: "Use Vive devices in desktop mode" + text: "Use " + stack.selectedPlugin + " devices in desktop mode" color: hifi.colors.white - + anchors { left: viveInDesktop.right leftMargin: 5 @@ -825,7 +825,7 @@ Rectangle { } } - + NumberAnimation { id: numberAnimation target: openVrConfiguration diff --git a/libraries/plugins/src/plugins/InputConfiguration.cpp b/libraries/plugins/src/plugins/InputConfiguration.cpp index 976a9c9463..daa8c6d08c 100644 --- a/libraries/plugins/src/plugins/InputConfiguration.cpp +++ b/libraries/plugins/src/plugins/InputConfiguration.cpp @@ -32,7 +32,8 @@ QStringList InputConfiguration::inputPlugins() { for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { QString pluginName = plugin->getName(); if (pluginName == QString("OpenVR")) { - inputPlugins << QString("Vive"); + QString headsetName = plugin->getDeviceName(); + inputPlugins << headsetName; } else { inputPlugins << pluginName; } From aa985fa6826d6518c4390484d702d760fa310445 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 16 Apr 2018 15:30:28 -0700 Subject: [PATCH 04/12] add-more-hmd-data --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 1 - plugins/openvr/src/OpenVrDisplayPlugin.h | 1 - 2 files changed, 2 deletions(-) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 615b176527..714cb91b3f 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -36,7 +36,6 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins) -const char* OpenVrDisplayPlugin::NAME { "OpenVR (Vive)" }; const char* StandingHMDSensorMode { "Standing HMD Sensor Mode" }; // this probably shouldn't be hardcoded here const char* OpenVrThreadedSubmit { "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index e6fc6367d5..15a434341d 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -78,7 +78,6 @@ private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; std::atomic _keyboardSupressionCount{ 0 }; - static const char* NAME; vr::HmdMatrix34_t _lastGoodHMDPose; mat4 _sensorResetMat; From e36dbab838424dba86170b94388aa8ae35be9686 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 6 Apr 2018 16:52:49 -0700 Subject: [PATCH 05/12] remove unused data member --- libraries/entities-renderer/src/RenderableZoneEntityItem.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index c5824abef0..2f8fd47b79 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -69,7 +69,6 @@ private: graphics::SkyboxPointer editSkybox() { return editBackground()->getSkybox(); } graphics::HazePointer editHaze() { _needHazeUpdate = true; return _haze; } - bool _needsInitialSimulation{ true }; glm::vec3 _lastPosition; glm::vec3 _lastDimensions; glm::quat _lastRotation; From 75508385cf888e23683e99f3fc82de167600379c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 6 Apr 2018 16:53:08 -0700 Subject: [PATCH 06/12] more correct needsToCallUpdate() implementations --- libraries/entities/src/MaterialEntityItem.h | 2 +- libraries/entities/src/ModelEntityItem.cpp | 67 +++++++++------------ libraries/entities/src/PolyLineEntityItem.h | 2 - 3 files changed, 29 insertions(+), 42 deletions(-) diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index 30743850dd..92289cdf73 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -26,7 +26,7 @@ public: ALLOW_INSTANTIATION // This class can be instantiated void update(const quint64& now) override; - bool needsToCallUpdate() const override { return true; } + bool needsToCallUpdate() const override { return _retryApply; } // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index be62664ff9..6552de87d1 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -194,53 +194,43 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit // added update function back for property fix void ModelEntityItem::update(const quint64& now) { + auto currentAnimationProperties = this->getAnimationProperties(); + if (_previousAnimationProperties != currentAnimationProperties) { + withWriteLock([&] { + // if we hit start animation or change the first or last frame then restart the animation + if ((currentAnimationProperties.getFirstFrame() != _previousAnimationProperties.getFirstFrame()) || + (currentAnimationProperties.getLastFrame() != _previousAnimationProperties.getLastFrame()) || + (currentAnimationProperties.getRunning() && !_previousAnimationProperties.getRunning())) { - { - auto currentAnimationProperties = this->getAnimationProperties(); - - if (_previousAnimationProperties != currentAnimationProperties) { - withWriteLock([&] { - // if we hit start animation or change the first or last frame then restart the animation - if ((currentAnimationProperties.getFirstFrame() != _previousAnimationProperties.getFirstFrame()) || - (currentAnimationProperties.getLastFrame() != _previousAnimationProperties.getLastFrame()) || - (currentAnimationProperties.getRunning() && !_previousAnimationProperties.getRunning())) { - - // when we start interface and the property is are set then the current frame is initialized to -1 - if (_currentFrame < 0) { - // don't reset _lastAnimated here because we need the timestamp from the ModelEntityItem constructor for when the properties were set - _currentFrame = currentAnimationProperties.getCurrentFrame(); - setAnimationCurrentFrame(_currentFrame); - } else { - _lastAnimated = usecTimestampNow(); - _currentFrame = currentAnimationProperties.getFirstFrame(); - setAnimationCurrentFrame(currentAnimationProperties.getFirstFrame()); - } - } else if (!currentAnimationProperties.getRunning() && _previousAnimationProperties.getRunning()) { - _currentFrame = currentAnimationProperties.getFirstFrame(); - setAnimationCurrentFrame(_currentFrame); - } else if (currentAnimationProperties.getCurrentFrame() != _previousAnimationProperties.getCurrentFrame()) { - // don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated + // when we start interface and the property is are set then the current frame is initialized to -1 + if (_currentFrame < 0) { + // don't reset _lastAnimated here because we need the timestamp from the ModelEntityItem constructor for when the properties were set _currentFrame = currentAnimationProperties.getCurrentFrame(); + setAnimationCurrentFrame(_currentFrame); + } else { + _lastAnimated = usecTimestampNow(); + _currentFrame = currentAnimationProperties.getFirstFrame(); + setAnimationCurrentFrame(currentAnimationProperties.getFirstFrame()); } - - }); - _previousAnimationProperties = this->getAnimationProperties(); - - } - - if (isAnimatingSomething()) { - if (!(getAnimationFirstFrame() < 0) && !(getAnimationFirstFrame() > getAnimationLastFrame())) { - updateFrameCount(); + } else if (!currentAnimationProperties.getRunning() && _previousAnimationProperties.getRunning()) { + _currentFrame = currentAnimationProperties.getFirstFrame(); + setAnimationCurrentFrame(_currentFrame); + } else if (currentAnimationProperties.getCurrentFrame() != _previousAnimationProperties.getCurrentFrame()) { + // don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated + _currentFrame = currentAnimationProperties.getCurrentFrame(); } - } - } + }); + _previousAnimationProperties = this->getAnimationProperties(); + } + if (!(getAnimationFirstFrame() < 0) && !(getAnimationFirstFrame() > getAnimationLastFrame())) { + updateFrameCount(); + } EntityItem::update(now); } bool ModelEntityItem::needsToCallUpdate() const { - - return true; + return isAnimatingSomething(); } void ModelEntityItem::updateFrameCount() { @@ -713,7 +703,6 @@ float ModelEntityItem::getAnimationFPS() const { }); } - bool ModelEntityItem::isAnimatingSomething() const { return resultWithReadLock([&] { return !_animationProperties.getURL().isEmpty() && diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 871c451c50..c76419af02 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -77,8 +77,6 @@ class PolyLineEntityItem : public EntityItem { QString getTextures() const; void setTextures(const QString& textures); - virtual bool needsToCallUpdate() const override { return true; } - virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } bool pointsChanged() const { return _pointsChanged; } From b48e9495a5484d09703a53175b02aa66a3661345 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 6 Apr 2018 17:32:57 -0700 Subject: [PATCH 07/12] rollback change to MaterialEntityItem, we'll fix later --- libraries/entities/src/MaterialEntityItem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index 92289cdf73..30743850dd 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -26,7 +26,7 @@ public: ALLOW_INSTANTIATION // This class can be instantiated void update(const quint64& now) override; - bool needsToCallUpdate() const override { return _retryApply; } + bool needsToCallUpdate() const override { return true; } // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; From 8e51d6609201da6055723fe11e25ba6bc8c6d841 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 19 Apr 2018 11:46:47 -0700 Subject: [PATCH 08/12] fix bug: animation should reset on start running --- .../entities/src/AnimationPropertyGroup.cpp | 19 +- .../entities/src/AnimationPropertyGroup.h | 4 + libraries/entities/src/EntityItem.h | 2 +- libraries/entities/src/ModelEntityItem.cpp | 205 ++++++++---------- libraries/entities/src/ModelEntityItem.h | 7 +- 5 files changed, 117 insertions(+), 120 deletions(-) diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index 82af60ed1a..43c6b7a6a5 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -216,7 +216,6 @@ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData, bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes) { - int bytesRead = 0; bool overwriteLocalData = true; bool somethingChanged = false; @@ -360,3 +359,21 @@ int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold); return bytesRead; } + +float AnimationPropertyGroup::getNumFrames() const { + return _lastFrame - _firstFrame + 1.0f; +} + +float AnimationPropertyGroup::computeLoopedFrame(float frame) const { + float numFrames = getNumFrames(); + if (numFrames > 1.0f) { + frame = getFirstFrame() + fmodf(frame - getFirstFrame(), numFrames); + } else { + frame = getFirstFrame(); + } + return frame; +} + +bool AnimationPropertyGroup::isValidAndRunning() const { + return getRunning() && (getFPS() > 0.0f) && (getNumFrames() > 1.0f) && !(getURL().isEmpty()); +} diff --git a/libraries/entities/src/AnimationPropertyGroup.h b/libraries/entities/src/AnimationPropertyGroup.h index 54d4ced92f..bebfe2c194 100644 --- a/libraries/entities/src/AnimationPropertyGroup.h +++ b/libraries/entities/src/AnimationPropertyGroup.h @@ -77,6 +77,10 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + float getNumFrames() const; + float computeLoopedFrame(float frame) const; + bool isValidAndRunning() const; + DEFINE_PROPERTY_REF(PROP_ANIMATION_URL, URL, url, QString, ""); DEFINE_PROPERTY(PROP_ANIMATION_FPS, FPS, fps, float, 30.0f); DEFINE_PROPERTY(PROP_ANIMATION_FRAME_INDEX, CurrentFrame, currentFrame, float, 0.0f); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0557bbe5ad..a88250a133 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -597,7 +597,7 @@ protected: // // DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about. - uint32_t _flags { 0 }; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation + std::atomic_uint _flags { 0 }; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation // these backpointers are only ever set/cleared by friends: EntityTreeElementPointer _element; // set by EntityTreeElement diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 6552de87d1..d8eb8b1e7a 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -82,11 +82,9 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslations, setJointTranslations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints); - bool somethingChangedInAnimations = _animationProperties.setProperties(properties); - - if (somethingChangedInAnimations) { - _flags |= Simulation::DIRTY_UPDATEABLE; - } + AnimationPropertyGroup animationProperties = getAnimationProperties(); + animationProperties.setProperties(properties); + bool somethingChangedInAnimations = applyNewAnimationProperties(animationProperties); somethingChanged = somethingChanged || somethingChangedInAnimations; if (somethingChanged) { @@ -118,24 +116,21 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); - int bytesFromAnimation; - withWriteLock([&] { - // Note: since we've associated our _animationProperties with our _animationLoop, the readEntitySubclassDataFromBuffer() - // will automatically read into the animation loop - bytesFromAnimation = _animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, animationPropertiesChanged); - }); + // grab a local copy of _animationProperties to avoid multiple locks + AnimationPropertyGroup animationProperties = getAnimationProperties(); + int bytesFromAnimation = animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, animationPropertiesChanged); + if (animationPropertiesChanged) { + applyNewAnimationProperties(animationProperties); + _flags |= Simulation::DIRTY_UPDATEABLE; + somethingChanged = true; + } bytesRead += bytesFromAnimation; dataAt += bytesFromAnimation; READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); - if (animationPropertiesChanged) { - _flags |= Simulation::DIRTY_UPDATEABLE; - somethingChanged = true; - } - READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, QVector, setJointRotationsSet); READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, QVector, setJointRotations); READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector, setJointTranslationsSet); @@ -194,88 +189,38 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit // added update function back for property fix void ModelEntityItem::update(const quint64& now) { - auto currentAnimationProperties = this->getAnimationProperties(); - if (_previousAnimationProperties != currentAnimationProperties) { - withWriteLock([&] { - // if we hit start animation or change the first or last frame then restart the animation - if ((currentAnimationProperties.getFirstFrame() != _previousAnimationProperties.getFirstFrame()) || - (currentAnimationProperties.getLastFrame() != _previousAnimationProperties.getLastFrame()) || - (currentAnimationProperties.getRunning() && !_previousAnimationProperties.getRunning())) { + assert(_lastAnimated > 0); - // when we start interface and the property is are set then the current frame is initialized to -1 - if (_currentFrame < 0) { - // don't reset _lastAnimated here because we need the timestamp from the ModelEntityItem constructor for when the properties were set - _currentFrame = currentAnimationProperties.getCurrentFrame(); - setAnimationCurrentFrame(_currentFrame); - } else { - _lastAnimated = usecTimestampNow(); - _currentFrame = currentAnimationProperties.getFirstFrame(); - setAnimationCurrentFrame(currentAnimationProperties.getFirstFrame()); - } - } else if (!currentAnimationProperties.getRunning() && _previousAnimationProperties.getRunning()) { - _currentFrame = currentAnimationProperties.getFirstFrame(); - setAnimationCurrentFrame(_currentFrame); - } else if (currentAnimationProperties.getCurrentFrame() != _previousAnimationProperties.getCurrentFrame()) { - // don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated - _currentFrame = currentAnimationProperties.getCurrentFrame(); - } - - }); - _previousAnimationProperties = this->getAnimationProperties(); - } - if (!(getAnimationFirstFrame() < 0) && !(getAnimationFirstFrame() > getAnimationLastFrame())) { - updateFrameCount(); - } - EntityItem::update(now); -} - -bool ModelEntityItem::needsToCallUpdate() const { - return isAnimatingSomething(); -} - -void ModelEntityItem::updateFrameCount() { - - if (_currentFrame < 0.0f) { - return; - } - - if (!_lastAnimated) { - _lastAnimated = usecTimestampNow(); - return; - } - - auto now = usecTimestampNow(); - - // update the interval since the last animation. + // increment timestamp before checking "hold" auto interval = now - _lastAnimated; _lastAnimated = now; - // if fps is negative then increment timestamp and return. - if (getAnimationFPS() < 0.0f) { + // grab a local copy of _animationProperties to avoid multiple locks + auto animationProperties = getAnimationProperties(); + + // bail on "hold" + if (animationProperties.getHold()) { return; } - int updatedFrameCount = getAnimationLastFrame() - getAnimationFirstFrame() + 1; - - if (!getAnimationHold() && getAnimationIsPlaying()) { - float deltaTime = (float)interval / (float)USECS_PER_SECOND; - _currentFrame += (deltaTime * getAnimationFPS()); - if (_currentFrame > getAnimationLastFrame() + 1) { - if (getAnimationLoop() && getAnimationFirstFrame() != getAnimationLastFrame()) { - _currentFrame = getAnimationFirstFrame() + (int)(_currentFrame - getAnimationFirstFrame()) % updatedFrameCount; - } else { - _currentFrame = getAnimationLastFrame(); - } - } else if (_currentFrame < getAnimationFirstFrame()) { - if (getAnimationFirstFrame() < 0) { - _currentFrame = 0; - } else { - _currentFrame = getAnimationFirstFrame(); - } + // increment animation frame + _currentFrame += (animationProperties.getFPS() * ((float)interval) / (float)USECS_PER_SECOND); + if (_currentFrame > animationProperties.getLastFrame() + 1.0f) { + if (animationProperties.getLoop()) { + _currentFrame = animationProperties.computeLoopedFrame(_currentFrame); + } else { + _currentFrame = animationProperties.getLastFrame(); + } + } else if (_currentFrame < animationProperties.getFirstFrame()) { + if (animationProperties.getFirstFrame() < 0.0f) { + _currentFrame = 0.0f; + } else { + _currentFrame = animationProperties.getFirstFrame(); } - // qCDebug(entities) << "in update frame " << _currentFrame; - setAnimationCurrentFrame(_currentFrame); } + setAnimationCurrentFrame(_currentFrame); + + EntityItem::update(now); } void ModelEntityItem::debugDump() const { @@ -351,10 +296,11 @@ void ModelEntityItem::setAnimationURL(const QString& url) { } void ModelEntityItem::setAnimationSettings(const QString& value) { + // NOTE: this method only called for old bitstream format + // the animations setting is a JSON string that may contain various animation settings. // if it includes fps, currentFrame, or running, those values will be parsed out and // will over ride the regular animation settings - QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); QJsonObject settingsAsJsonObject = settingsAsJson.object(); QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); @@ -363,54 +309,48 @@ void ModelEntityItem::setAnimationSettings(const QString& value) { setAnimationFPS(fps); } + // grab a local copy of _animationProperties to avoid multiple locks + auto animationProperties = getAnimationProperties(); + // old settings used frameIndex if (settingsMap.contains("frameIndex")) { float currentFrame = settingsMap["frameIndex"].toFloat(); -#ifdef WANT_DEBUG - if (!getAnimationURL().isEmpty()) { - qCDebug(entities) << "ModelEntityItem::setAnimationSettings() calling setAnimationFrameIndex()..."; - qCDebug(entities) << " model URL:" << getModelURL(); - qCDebug(entities) << " animation URL:" << getAnimationURL(); - qCDebug(entities) << " settings:" << value; - qCDebug(entities) << " settingsMap[frameIndex]:" << settingsMap["frameIndex"]; - qCDebug(entities" currentFrame: %20.5f", currentFrame); - } -#endif - - setAnimationCurrentFrame(currentFrame); + animationProperties.setCurrentFrame(currentFrame); } if (settingsMap.contains("running")) { bool running = settingsMap["running"].toBool(); - if (running != getAnimationIsPlaying()) { - setAnimationIsPlaying(running); + if (running != animationProperties.getRunning()) { + animationProperties.setRunning(running); } } if (settingsMap.contains("firstFrame")) { float firstFrame = settingsMap["firstFrame"].toFloat(); - setAnimationFirstFrame(firstFrame); + animationProperties.setFirstFrame(firstFrame); } if (settingsMap.contains("lastFrame")) { float lastFrame = settingsMap["lastFrame"].toFloat(); - setAnimationLastFrame(lastFrame); + animationProperties.setLastFrame(lastFrame); } if (settingsMap.contains("loop")) { bool loop = settingsMap["loop"].toBool(); - setAnimationLoop(loop); + animationProperties.setLoop(loop); } if (settingsMap.contains("hold")) { bool hold = settingsMap["hold"].toBool(); - setAnimationHold(hold); + animationProperties.setHold(hold); } if (settingsMap.contains("allowTranslation")) { bool allowTranslation = settingsMap["allowTranslation"].toBool(); - setAnimationAllowTranslation(allowTranslation); + animationProperties.setAllowTranslation(allowTranslation); } + applyNewAnimationProperties(animationProperties); + // TODO? set the dirty flag inside applyNewAnimationProperties() _flags |= Simulation::DIRTY_UPDATEABLE; } @@ -705,8 +645,45 @@ float ModelEntityItem::getAnimationFPS() const { bool ModelEntityItem::isAnimatingSomething() const { return resultWithReadLock([&] { - return !_animationProperties.getURL().isEmpty() && - _animationProperties.getRunning() && - (_animationProperties.getFPS() != 0.0f); - }); + return _animationProperties.isValidAndRunning(); + }); +} + +bool ModelEntityItem::applyNewAnimationProperties(AnimationPropertyGroup newProperties) { + // call applyNewAnimationProperties() whenever trying to update _animationProperties + // because there is some reset logic we need to do whenever the animation "config" properties change + + // grab a local copy of _animationProperties to avoid multiple locks + AnimationPropertyGroup oldProperties = getAnimationProperties(); + + // if we hit start animation or change the first or last frame then restart the animation + if ((newProperties.getFirstFrame() != oldProperties.getFirstFrame()) || + (newProperties.getLastFrame() != oldProperties.getLastFrame()) || + (newProperties.getRunning() && !oldProperties.getRunning())) { + + // when we start interface and the property is are set then the current frame is initialized to -1 + if (_currentFrame < 0.0f) { + // don't reset _lastAnimated here because we need the timestamp from the ModelEntityItem constructor for when the properties were set + _currentFrame = newProperties.getCurrentFrame(); + newProperties.setCurrentFrame(_currentFrame); + } else { + _lastAnimated = usecTimestampNow(); + _currentFrame = newProperties.getFirstFrame(); + newProperties.setCurrentFrame(newProperties.getFirstFrame()); + } + } else if (!newProperties.getRunning() && oldProperties.getRunning()) { + _currentFrame = newProperties.getFirstFrame(); + newProperties.setCurrentFrame(_currentFrame); + } else if (newProperties.getCurrentFrame() != oldProperties.getCurrentFrame()) { + // don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated + _currentFrame = newProperties.getCurrentFrame(); + } + + // finally apply the changes with a lock + withWriteLock([&] { + _animationProperties = newProperties; + }); + + // return 'true' if something changed + return newProperties != oldProperties; } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 327606ae2f..91b44359f7 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -46,10 +46,10 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - // update() and needstocallupdate() added back for the entity property fix + + bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); virtual void update(const quint64& now) override; - virtual bool needsToCallUpdate() const override; - void updateFrameCount(); + bool needsToCallUpdate() const override { return isAnimatingSomething(); } virtual void debugDump() const override; @@ -172,7 +172,6 @@ protected: private: uint64_t _lastAnimated{ 0 }; - AnimationPropertyGroup _previousAnimationProperties; float _currentFrame{ -1.0f }; }; From 748af5a65f219139caa97b519023c8ce0be5c825 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 19 Apr 2018 12:30:16 -0700 Subject: [PATCH 09/12] worry about multi-threads when updating animation props --- libraries/entities/src/ModelEntityItem.cpp | 149 +++++++++++---------- libraries/entities/src/ModelEntityItem.h | 2 +- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index d8eb8b1e7a..f759de915e 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -82,10 +82,12 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslations, setJointTranslations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints); - AnimationPropertyGroup animationProperties = getAnimationProperties(); - animationProperties.setProperties(properties); - bool somethingChangedInAnimations = applyNewAnimationProperties(animationProperties); - somethingChanged = somethingChanged || somethingChangedInAnimations; + withWriteLock([&] { + AnimationPropertyGroup animationProperties = _animationProperties; + animationProperties.setProperties(properties); + bool somethingChangedInAnimations = applyNewAnimationProperties(animationProperties); + somethingChanged = somethingChanged || somethingChangedInAnimations; + }); if (somethingChanged) { bool wantDebug = false; @@ -117,14 +119,17 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); // grab a local copy of _animationProperties to avoid multiple locks - AnimationPropertyGroup animationProperties = getAnimationProperties(); - int bytesFromAnimation = animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, animationPropertiesChanged); - if (animationPropertiesChanged) { - applyNewAnimationProperties(animationProperties); - _flags |= Simulation::DIRTY_UPDATEABLE; - somethingChanged = true; - } + int bytesFromAnimation; + withReadLock([&] { + AnimationPropertyGroup animationProperties = _animationProperties; + bytesFromAnimation = animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, animationPropertiesChanged); + if (animationPropertiesChanged) { + applyNewAnimationProperties(animationProperties); + _flags |= Simulation::DIRTY_UPDATEABLE; + somethingChanged = true; + } + }); bytesRead += bytesFromAnimation; dataAt += bytesFromAnimation; @@ -298,60 +303,61 @@ void ModelEntityItem::setAnimationURL(const QString& url) { void ModelEntityItem::setAnimationSettings(const QString& value) { // NOTE: this method only called for old bitstream format - // the animations setting is a JSON string that may contain various animation settings. - // if it includes fps, currentFrame, or running, those values will be parsed out and - // will over ride the regular animation settings - QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); - QJsonObject settingsAsJsonObject = settingsAsJson.object(); - QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); - if (settingsMap.contains("fps")) { - float fps = settingsMap["fps"].toFloat(); - setAnimationFPS(fps); - } + withWriteLock([&] { + auto animationProperties = _animationProperties; - // grab a local copy of _animationProperties to avoid multiple locks - auto animationProperties = getAnimationProperties(); - - // old settings used frameIndex - if (settingsMap.contains("frameIndex")) { - float currentFrame = settingsMap["frameIndex"].toFloat(); - animationProperties.setCurrentFrame(currentFrame); - } - - if (settingsMap.contains("running")) { - bool running = settingsMap["running"].toBool(); - if (running != animationProperties.getRunning()) { - animationProperties.setRunning(running); + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, currentFrame, or running, those values will be parsed out and + // will over ride the regular animation settings + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + animationProperties.setFPS(fps); } - } - if (settingsMap.contains("firstFrame")) { - float firstFrame = settingsMap["firstFrame"].toFloat(); - animationProperties.setFirstFrame(firstFrame); - } + // old settings used frameIndex + if (settingsMap.contains("frameIndex")) { + float currentFrame = settingsMap["frameIndex"].toFloat(); + animationProperties.setCurrentFrame(currentFrame); + } - if (settingsMap.contains("lastFrame")) { - float lastFrame = settingsMap["lastFrame"].toFloat(); - animationProperties.setLastFrame(lastFrame); - } + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + if (running != animationProperties.getRunning()) { + animationProperties.setRunning(running); + } + } - if (settingsMap.contains("loop")) { - bool loop = settingsMap["loop"].toBool(); - animationProperties.setLoop(loop); - } + if (settingsMap.contains("firstFrame")) { + float firstFrame = settingsMap["firstFrame"].toFloat(); + animationProperties.setFirstFrame(firstFrame); + } - if (settingsMap.contains("hold")) { - bool hold = settingsMap["hold"].toBool(); - animationProperties.setHold(hold); - } + if (settingsMap.contains("lastFrame")) { + float lastFrame = settingsMap["lastFrame"].toFloat(); + animationProperties.setLastFrame(lastFrame); + } - if (settingsMap.contains("allowTranslation")) { - bool allowTranslation = settingsMap["allowTranslation"].toBool(); - animationProperties.setAllowTranslation(allowTranslation); - } - applyNewAnimationProperties(animationProperties); - // TODO? set the dirty flag inside applyNewAnimationProperties() - _flags |= Simulation::DIRTY_UPDATEABLE; + if (settingsMap.contains("loop")) { + bool loop = settingsMap["loop"].toBool(); + animationProperties.setLoop(loop); + } + + if (settingsMap.contains("hold")) { + bool hold = settingsMap["hold"].toBool(); + animationProperties.setHold(hold); + } + + if (settingsMap.contains("allowTranslation")) { + bool allowTranslation = settingsMap["allowTranslation"].toBool(); + animationProperties.setAllowTranslation(allowTranslation); + } + applyNewAnimationProperties(animationProperties); + // TODO? set the dirty flag inside applyNewAnimationProperties() + _flags |= Simulation::DIRTY_UPDATEABLE; + }); } void ModelEntityItem::setAnimationIsPlaying(bool value) { @@ -652,14 +658,12 @@ bool ModelEntityItem::isAnimatingSomething() const { bool ModelEntityItem::applyNewAnimationProperties(AnimationPropertyGroup newProperties) { // call applyNewAnimationProperties() whenever trying to update _animationProperties // because there is some reset logic we need to do whenever the animation "config" properties change - - // grab a local copy of _animationProperties to avoid multiple locks - AnimationPropertyGroup oldProperties = getAnimationProperties(); + // NOTE: this private method is always called inside withWriteLock() // if we hit start animation or change the first or last frame then restart the animation - if ((newProperties.getFirstFrame() != oldProperties.getFirstFrame()) || - (newProperties.getLastFrame() != oldProperties.getLastFrame()) || - (newProperties.getRunning() && !oldProperties.getRunning())) { + if ((newProperties.getFirstFrame() != _animationProperties.getFirstFrame()) || + (newProperties.getLastFrame() != _animationProperties.getLastFrame()) || + (newProperties.getRunning() && !_animationProperties.getRunning())) { // when we start interface and the property is are set then the current frame is initialized to -1 if (_currentFrame < 0.0f) { @@ -671,19 +675,16 @@ bool ModelEntityItem::applyNewAnimationProperties(AnimationPropertyGroup newProp _currentFrame = newProperties.getFirstFrame(); newProperties.setCurrentFrame(newProperties.getFirstFrame()); } - } else if (!newProperties.getRunning() && oldProperties.getRunning()) { + } else if (!newProperties.getRunning() && _animationProperties.getRunning()) { _currentFrame = newProperties.getFirstFrame(); newProperties.setCurrentFrame(_currentFrame); - } else if (newProperties.getCurrentFrame() != oldProperties.getCurrentFrame()) { + } else if (newProperties.getCurrentFrame() != _animationProperties.getCurrentFrame()) { // don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated _currentFrame = newProperties.getCurrentFrame(); } - // finally apply the changes with a lock - withWriteLock([&] { - _animationProperties = newProperties; - }); - - // return 'true' if something changed - return newProperties != oldProperties; + // finally apply the changes + bool somethingChanged = newProperties != _animationProperties; + _animationProperties = newProperties; + return somethingChanged; } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 91b44359f7..ad6cdf4040 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -47,7 +47,6 @@ public: bool& somethingChanged) override; - bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); virtual void update(const quint64& now) override; bool needsToCallUpdate() const override { return isAnimatingSomething(); } @@ -132,6 +131,7 @@ public: private: void setAnimationSettings(const QString& value); // only called for old bitstream format + bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); ShapeType computeTrueShapeType() const; protected: From 577f9a03e6586264ce714778ec76d0984e1c3d67 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 23 Apr 2018 12:07:49 -0700 Subject: [PATCH 10/12] fix bug: animation reset on 'hold' --- libraries/entities/src/ModelEntityItem.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index f759de915e..0f59bc673d 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -126,7 +126,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, propertyFlags, overwriteLocalData, animationPropertiesChanged); if (animationPropertiesChanged) { applyNewAnimationProperties(animationProperties); - _flags |= Simulation::DIRTY_UPDATEABLE; somethingChanged = true; } }); @@ -355,8 +354,6 @@ void ModelEntityItem::setAnimationSettings(const QString& value) { animationProperties.setAllowTranslation(allowTranslation); } applyNewAnimationProperties(animationProperties); - // TODO? set the dirty flag inside applyNewAnimationProperties() - _flags |= Simulation::DIRTY_UPDATEABLE; }); } @@ -685,6 +682,9 @@ bool ModelEntityItem::applyNewAnimationProperties(AnimationPropertyGroup newProp // finally apply the changes bool somethingChanged = newProperties != _animationProperties; - _animationProperties = newProperties; + if (somethingChanged) { + _animationProperties = newProperties; + _flags |= Simulation::DIRTY_UPDATEABLE; + } return somethingChanged; } From 861200db27c0bbbd0ae41ab21808156b62258884 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 23 Apr 2018 12:29:41 -0700 Subject: [PATCH 11/12] Attempt to shutdown web surfaces more consistently --- interface/src/Application.cpp | 13 ++++- interface/src/Application.h | 7 ++- interface/src/ui/overlays/Web3DOverlay.cpp | 12 ++-- .../src/RenderableWebEntityItem.cpp | 39 +++++++------ libraries/qml/src/qml/OffscreenSurface.cpp | 7 +-- .../qml/src/qml/impl/RenderEventHandler.cpp | 11 ++-- libraries/qml/src/qml/impl/SharedObject.cpp | 56 +++++++++++++------ .../src/AbstractViewStateInterface.h | 19 +++++-- 8 files changed, 99 insertions(+), 65 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a171de87bc..6d4c82d4bf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4741,7 +4741,7 @@ void Application::updateLOD(float deltaTime) const { } } -void Application::pushPostUpdateLambda(void* key, std::function func) { +void Application::pushPostUpdateLambda(void* key, const std::function& func) { std::unique_lock guard(_postUpdateLambdasLock); _postUpdateLambdas[key] = func; } @@ -7351,7 +7351,7 @@ void Application::windowMinimizedChanged(bool minimized) { } } -void Application::postLambdaEvent(std::function f) { +void Application::postLambdaEvent(const std::function& f) { if (this->thread() == QThread::currentThread()) { f(); } else { @@ -7359,6 +7359,15 @@ void Application::postLambdaEvent(std::function f) { } } +void Application::sendLambdaEvent(const std::function& f) { + if (this->thread() == QThread::currentThread()) { + f(); + } else { + LambdaEvent event(f); + QCoreApplication::sendEvent(this, &event); + } +} + void Application::initPlugins(const QStringList& arguments) { QCommandLineOption display("display", "Preferred displays", "displays"); QCommandLineOption disableDisplays("disable-displays", "Displays to disable", "displays"); diff --git a/interface/src/Application.h b/interface/src/Application.h index 769658b0d6..74b0e5a110 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -136,7 +136,8 @@ public: Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runningMarkerExisted); ~Application(); - void postLambdaEvent(std::function f) override; + void postLambdaEvent(const std::function& f) override; + void sendLambdaEvent(const std::function& f) override; QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); @@ -240,7 +241,7 @@ public: qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } - bool isAboutToQuit() const override { return _aboutToQuit; } + bool isAboutToQuit() const { return _aboutToQuit; } bool isPhysicsEnabled() const { return _physicsEnabled; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display @@ -264,7 +265,7 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } - virtual void pushPostUpdateLambda(void* key, std::function func) override; + virtual void pushPostUpdateLambda(void* key, const std::function& func) override; void updateMyAvatarLookAtPosition(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 10050c94d0..d574e08a94 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -65,14 +65,10 @@ const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { - AbstractViewStateInterface::instance()->postLambdaEvent([surface] { - if (AbstractViewStateInterface::instance()->isAboutToQuit()) { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete surface; - } else { - surface->deleteLater(); - } + AbstractViewStateInterface::instance()->sendLambdaEvent([surface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete surface; }); }; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f647082d73..2b87cbae58 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -25,7 +25,7 @@ #include #include "EntitiesRendererLogging.h" - +#include using namespace render; using namespace render::entities; @@ -45,6 +45,7 @@ static int DEFAULT_MAX_FPS = 10; static int YOUTUBE_MAX_FPS = 30; static QTouchDevice _touchDevice; +static const char* URL_PROPERTY = "url"; WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) { if (urlString.isEmpty()) { @@ -52,7 +53,7 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& } const QUrl url(urlString); - if (url.scheme() == "http" || url.scheme() == "https" || + if (url.scheme() == URL_SCHEME_HTTP || url.scheme() == URL_SCHEME_HTTPS || urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { return ContentType::HtmlContent; } @@ -164,6 +165,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (urlChanged) { if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { destroyWebSurface(); + // If we destroyed the surface, the URL change will be implicitly handled by the re-creation + urlChanged = false; } withWriteLock([&] { @@ -185,8 +188,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene return; } - if (urlChanged) { - _webSurface->getRootItem()->setProperty("url", _lastSourceUrl); + if (urlChanged && _contentType == ContentType::HtmlContent) { + _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); } if (_contextPosition != entity->getWorldPosition()) { @@ -254,6 +257,14 @@ bool WebEntityRenderer::hasWebSurface() { return (bool)_webSurface && _webSurface->getRootItem(); } +static const auto WebSurfaceDeleter = [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + }); +}; + bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; @@ -261,20 +272,9 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { } ++_currentWebCount; - auto deleter = [](OffscreenQmlSurface* webSurface) { - AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { - if (AbstractViewStateInterface::instance()->isAboutToQuit()) { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete webSurface; - } else { - webSurface->deleteLater(); - } - }); - }; // FIXME use the surface cache instead of explicit creation - _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), WebSurfaceDeleter); // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) _webSurface->setMaxFps(DEFAULT_MAX_FPS); @@ -302,7 +302,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { _webSurface->setMaxFps(DEFAULT_MAX_FPS); } _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty("url", _lastSourceUrl); + item->setProperty(URL_PROPERTY, _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { @@ -327,6 +327,11 @@ void WebEntityRenderer::destroyWebSurface() { if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); + // Explicitly set the web URL to an empty string, in an effort to get a + // faster shutdown of any chromium processes interacting with audio + if (rootItem && _contentType == ContentType::HtmlContent) { + rootItem->setProperty(URL_PROPERTY, ""); + } if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 2da1c41340..53a8998977 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -66,14 +66,11 @@ OffscreenSurface::OffscreenSurface() } OffscreenSurface::~OffscreenSurface() { - disconnect(qApp); - _sharedObject->destroy(); + delete _sharedObject; + const_cast(_sharedObject) = nullptr; } bool OffscreenSurface::fetchTexture(TextureAndFence& textureAndFence) { - if (!_sharedObject) { - return false; - } hifi::qml::impl::TextureAndFence typedTextureAndFence; bool result = _sharedObject->fetchTexture(typedTextureAndFence); textureAndFence = typedTextureAndFence; diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 6b66ce9314..945a469611 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -49,8 +49,8 @@ RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThre qFatal("Unable to create new offscreen GL context"); } - moveToThread(targetThread); _canvas.moveToThreadWithContext(targetThread); + moveToThread(targetThread); } void RenderEventHandler::onInitalize() { @@ -160,11 +160,8 @@ void RenderEventHandler::onQuit() { } _shared->shutdownRendering(_canvas, _currentSize); - // Release the reference to the shared object. This will allow it to - // be destroyed (should happen on it's own thread). - _shared->deleteLater(); - - deleteLater(); - + _canvas.doneCurrent(); + _canvas.moveToThreadWithContext(qApp->thread()); + moveToThread(qApp->thread()); QThread::currentThread()->quit(); } diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index b2057117f6..9253c41b39 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -72,26 +72,35 @@ SharedObject::SharedObject() { QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } + SharedObject::~SharedObject() { - if (_quickWindow) { - _quickWindow->destroy(); - _quickWindow = nullptr; + // After destroy returns, the rendering thread should be gone + destroy(); + + // _renderTimer is created with `this` as the parent, so need no explicit destruction + + // Destroy the event hand + if (_renderObject) { + delete _renderObject; + _renderObject = nullptr; } if (_renderControl) { - _renderControl->deleteLater(); + delete _renderControl; _renderControl = nullptr; } - if (_renderThread) { - _renderThread->quit(); - _renderThread->deleteLater(); + if (_quickWindow) { + _quickWindow->destroy(); + delete _quickWindow; + _quickWindow = nullptr; } - if (_rootItem) { - _rootItem->deleteLater(); - _rootItem = nullptr; - } + // _rootItem is parented to the quickWindow, so needs no explicit destruction + //if (_rootItem) { + // delete _rootItem; + // _rootItem = nullptr; + //} releaseEngine(_qmlContext->engine()); } @@ -119,6 +128,10 @@ void SharedObject::create(OffscreenSurface* surface) { } void SharedObject::setRootItem(QQuickItem* rootItem) { + if (_quit) { + return; + } + _rootItem = rootItem; _rootItem->setSize(_quickWindow->size()); @@ -127,7 +140,6 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { _renderThread->setObjectName(objectName()); _renderThread->start(); - // Create event handler for the render thread _renderObject = new RenderEventHandler(this, _renderThread); QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Initialize)); @@ -137,35 +149,43 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { } void SharedObject::destroy() { + // `destroy` is idempotent, it can be called multiple times without issues if (_quit) { return; } if (!_rootItem) { - deleteLater(); return; } - _paused = true; if (_renderTimer) { + _renderTimer->stop(); QObject::disconnect(_renderTimer); - _renderTimer->deleteLater(); } - QObject::disconnect(_renderControl); + if (_renderControl) { + QObject::disconnect(_renderControl); + } + QObject::disconnect(qApp); { QMutexLocker lock(&_mutex); _quit = true; - QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + if (_renderObject) { + QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + } } // Block until the rendering thread has stopped // FIXME this is undesirable because this is blocking the main thread, // but I haven't found a reliable way to do this only at application // shutdown - _renderThread->wait(); + if (_renderThread) { + _renderThread->wait(); + delete _renderThread; + _renderThread = nullptr; + } } diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 96e9f4d222..54fdc903ca 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -37,15 +37,25 @@ public: virtual glm::vec3 getAvatarPosition() const = 0; - virtual bool isAboutToQuit() const = 0; - virtual void postLambdaEvent(std::function f) = 0; + // Unfortunately, having this here is a bad idea. Lots of objects connect to + // the aboutToQuit signal, and it's impossible to know the order in which + // the receivers will be called, so this might return false negatives + //virtual bool isAboutToQuit() const = 0; + + // Queue code to execute on the main thread. + // If called from the main thread, the lambda will execute synchronously + virtual void postLambdaEvent(const std::function& f) = 0; + // Synchronously execute code on the main thread. This function will + // not return until the code is executed, regardles of which thread it + // is called from + virtual void sendLambdaEvent(const std::function& f) = 0; virtual qreal getDevicePixelRatio() = 0; virtual render::ScenePointer getMain3DScene() = 0; virtual render::EnginePointer getRenderEngine() = 0; - virtual void pushPostUpdateLambda(void* key, std::function func) = 0; + virtual void pushPostUpdateLambda(void* key, const std::function& func) = 0; virtual bool isHMDMode() const = 0; @@ -54,5 +64,4 @@ public: static void setInstance(AbstractViewStateInterface* instance); }; - -#endif // hifi_AbstractViewStateInterface_h +#endif // hifi_AbstractViewStateInterface_h From ecaf162afb1c0e7a5abda6c4bde1e034ea28e061 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 23 Apr 2018 20:41:36 -0700 Subject: [PATCH 12/12] Fixing PR build --- libraries/qml/src/qml/OffscreenSurface.cpp | 1 - tests/render-perf/src/main.cpp | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 53a8998977..688f3fdb5f 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -67,7 +67,6 @@ OffscreenSurface::OffscreenSurface() OffscreenSurface::~OffscreenSurface() { delete _sharedObject; - const_cast(_sharedObject) = nullptr; } bool OffscreenSurface::fetchTexture(TextureAndFence& textureAndFence) { diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 93672cc5a2..9249b3d957 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -453,8 +453,8 @@ protected: return vec3(); } - bool isAboutToQuit() const override { return false; } - void postLambdaEvent(std::function f) override {} + void postLambdaEvent(const std::function& f) override {} + void sendLambdaEvent(const std::function& f) override {} qreal getDevicePixelRatio() override { return 1.0f; @@ -469,7 +469,7 @@ protected: } std::map> _postUpdateLambdas; - void pushPostUpdateLambda(void* key, std::function func) override { + void pushPostUpdateLambda(void* key, const std::function& func) override { _postUpdateLambdas[key] = func; }