Răsfoiți Sursa

Added a popup message when the scene file or scene manifest is changed (#12020)

* Added a popup message when the scene file or scene manifest is changed outside of the scene settings tool, plus refresh the tool when this happens to load latest.

Signed-off-by: AMZN-stankowi <[email protected]>

* Pull request feedback

Signed-off-by: AMZN-stankowi <[email protected]>

* Fixed unused variable warning

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>
AMZN-stankowi 3 ani în urmă
părinte
comite
80fa9cc1b9

+ 59 - 1
Code/Editor/Plugins/EditorAssetImporter/AssetImporterWindow.cpp

@@ -43,10 +43,12 @@ class CXTPDockingPaneLayout; // Needed for settings.h
 #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
 #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
 #include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
 #include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
 #include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
+#include <SceneAPI/SceneCore/Events/SceneSerializationBus.h>
 #include <SceneAPI/SceneCore/Utilities/Reporting.h>
 #include <SceneAPI/SceneCore/Utilities/Reporting.h>
 #include <SceneAPI/SceneData/Rules/ScriptProcessorRule.h>
 #include <SceneAPI/SceneData/Rules/ScriptProcessorRule.h>
 #include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/AsyncOperationProcessingHandler.h>
 #include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/AsyncOperationProcessingHandler.h>
 #include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ExportJobProcessingHandler.h>
 #include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ExportJobProcessingHandler.h>
+#include <SceneAPI/SceneUI/SceneWidgets/ManifestWidget.h>
 #include <SceneAPI/SceneUI/SceneWidgets/SceneGraphInspectWidget.h>
 #include <SceneAPI/SceneUI/SceneWidgets/SceneGraphInspectWidget.h>
 
 
 const char* AssetImporterWindow::s_documentationWebAddress = "https://www.o3de.org/docs/user-guide/assets/scene-settings/";
 const char* AssetImporterWindow::s_documentationWebAddress = "https://www.o3de.org/docs/user-guide/assets/scene-settings/";
@@ -222,12 +224,17 @@ void AssetImporterWindow::Init()
             "No importable file types were detected. This likely means an internal error has taken place which has broken the "
             "No importable file types were detected. This likely means an internal error has taken place which has broken the "
             "registration of valid import types (e.g. FBX). This type of issue requires engineering support.");
             "registration of valid import types (e.g. FBX). This type of issue requires engineering support.");
     }
     }
+    
+    QObject::connect(&m_qtFileWatcher, &QFileSystemWatcher::fileChanged, this, &AssetImporterWindow::FileChanged);
 }
 }
 
 
 void AssetImporterWindow::OpenFileInternal(const AZStd::string& filePath)
 void AssetImporterWindow::OpenFileInternal(const AZStd::string& filePath)
 {
 {
     using namespace AZ::SceneAPI::SceneUI;
     using namespace AZ::SceneAPI::SceneUI;
 
 
+    // Clear all previously watched files
+    m_qtFileWatcher.removePaths(m_qtFileWatcher.files());
+
     auto asyncLoadHandler = AZStd::make_shared<AZ::SceneAPI::SceneUI::AsyncOperationProcessingHandler>(
     auto asyncLoadHandler = AZStd::make_shared<AZ::SceneAPI::SceneUI::AsyncOperationProcessingHandler>(
         s_browseTag,
         s_browseTag,
         [this, filePath]()
         [this, filePath]()
@@ -289,6 +296,8 @@ void AssetImporterWindow::SceneSettingsCardProcessingCompleted()
     {
     {
         return;
         return;
     }
     }
+    
+    m_isSaving = false;
     m_overlay->PopLayer(m_sceneSettingsCardOverlay);
     m_overlay->PopLayer(m_sceneSettingsCardOverlay);
     m_sceneSettingsCardOverlay = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
     m_sceneSettingsCardOverlay = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
 }
 }
@@ -316,7 +325,7 @@ bool AssetImporterWindow::IsAllowedToChangeSourceFile()
     {
     {
         return true;
         return true;
     }
     }
-
+    m_isSaving = true;
     AZStd::shared_ptr<AZ::ActionOutput> output = AZStd::make_shared<AZ::ActionOutput>();
     AZStd::shared_ptr<AZ::ActionOutput> output = AZStd::make_shared<AZ::ActionOutput>();
     m_assetImporterDocument->SaveScene(
     m_assetImporterDocument->SaveScene(
         output,
         output,
@@ -371,6 +380,7 @@ void AssetImporterWindow::UpdateClicked()
     }
     }
 
 
     AZStd::shared_ptr<AZ::ActionOutput> output = AZStd::make_shared<AZ::ActionOutput>();
     AZStd::shared_ptr<AZ::ActionOutput> output = AZStd::make_shared<AZ::ActionOutput>();
+    m_isSaving = true;
     m_assetImporterDocument->SaveScene(output,
     m_assetImporterDocument->SaveScene(output,
         [output, this, isSourceControlActive, card](bool wasSuccessful)
         [output, this, isSourceControlActive, card](bool wasSuccessful)
         {
         {
@@ -651,6 +661,54 @@ void AssetImporterWindow::HandleAssetLoadingCompleted()
     //  show the main area where all the actual work takes place
     //  show the main area where all the actual work takes place
     ui->m_initialBrowseContainer->hide();
     ui->m_initialBrowseContainer->hide();
     m_rootDisplay->show();
     m_rootDisplay->show();
+
+   
+    m_qtFileWatcher.addPath(m_fullSourcePath.c_str());
+    m_qtFileWatcher.addPath(m_assetImporterDocument->GetScene()->GetManifestFilename().c_str());
+}
+
+void AssetImporterWindow::FileChanged(QString path)
+{
+    if (m_isSaving)
+    {
+        return;
+    }
+
+    QString promptMessage([this]()
+    {
+        if(m_rootDisplay->HasUnsavedChanges()) 
+        {
+            return tr("The file %1 has been changed outside of the scene settings tool. This tool will be reloaded and any unsaved changes will be lost. \n\n"
+                        "To prevent this from occuring in the future, do not modify the scene file or scene manifest outside of this tool while this tool has unsaved work.");
+        }
+        return  tr("The file %1 has been changed outside of the scene settings tool. This tool will be reloaded.");
+    }());
+
+    // The scene system holds weak pointers to any previously loaded scenes,
+    // and will return a previously cached scene on a requested load.
+    // In this case, it's known the scene file is different than what's in memory, so make sure to flush
+    // any cached scene info, so it is freshly reloaded from disk.
+    m_assetImporterDocument->ClearScene();
+    m_rootDisplay->GetManifestWidget()->ResetScene();
+
+    // Verify nothing is left holding a shared pointer to the scene.
+    namespace SceneEvents = AZ::SceneAPI::Events;
+    bool foundSharedScene = true; // If the ebus fails, default to true to assume there's something sharing the scene still.
+    SceneEvents::SceneSerializationBus::BroadcastResult(foundSharedScene, &SceneEvents::SceneSerializationBus::Events::IsSceneCached, m_fullSourcePath);
+
+    // The scene is still cached, somewhere. Warn the user.
+    if (foundSharedScene)
+    {
+        promptMessage += tr("\n\nThis scene file is still cached and will not reload correctly. The Editor should be shut down and re-launched to properly load the modified external data.");
+    }
+
+    QMessageBox::question(
+        this,
+        tr("External Change"),
+        promptMessage.arg(path),
+        QMessageBox::Ok);
+
+    OpenFileInternal(m_fullSourcePath);
 }
 }
 
 
 #include <moc_AssetImporterWindow.cpp>
 #include <moc_AssetImporterWindow.cpp>

+ 9 - 2
Code/Editor/Plugins/EditorAssetImporter/AssetImporterWindow.h

@@ -15,12 +15,13 @@
 /////////////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////////////
 
 
 #if !defined(Q_MOC_RUN)
 #if !defined(Q_MOC_RUN)
+#include <AzCore/Math/Guid.h>
 #include <AzCore/PlatformIncl.h>
 #include <AzCore/PlatformIncl.h>
-#include <QMainWindow>
 #include <AzCore/std/smart_ptr/shared_ptr.h>
 #include <AzCore/std/smart_ptr/shared_ptr.h>
-#include <AzCore/Math/Guid.h>
 #include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
 #include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
 #include <SceneAPI/SceneUI/CommonWidgets/SceneSettingsCard.h>
 #include <SceneAPI/SceneUI/CommonWidgets/SceneSettingsCard.h>
+#include <QFileSystemWatcher>
+#include <QMainWindow>
 #endif
 #endif
 
 
 
 
@@ -124,6 +125,8 @@ private slots:
     void OverlayLayerRemoved();
     void OverlayLayerRemoved();
     void UpdateSceneDisplay(const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene> scene = {}) const;
     void UpdateSceneDisplay(const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene> scene = {}) const;
 
 
+    void FileChanged(QString path);
+
 private:
 private:
     static const AZ::Uuid s_browseTag;
     static const AZ::Uuid s_browseTag;
     static const char* s_documentationWebAddress;
     static const char* s_documentationWebAddress;
@@ -134,11 +137,15 @@ private:
     int m_openSceneSettingsCards = 0;
     int m_openSceneSettingsCards = 0;
     int m_sceneSettingsCardOverlay = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
     int m_sceneSettingsCardOverlay = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
 
 
+    // Monitor the scene file, and scene settings file in case they are changed outside the scene settings tool.
+    QFileSystemWatcher m_qtFileWatcher;
+
     AZ::SerializeContext* m_serializeContext;
     AZ::SerializeContext* m_serializeContext;
     AZStd::string m_fullSourcePath;
     AZStd::string m_fullSourcePath;
 
 
     QScopedPointer<ImporterRootDisplay> m_rootDisplay;
     QScopedPointer<ImporterRootDisplay> m_rootDisplay;
     bool m_isClosed;
     bool m_isClosed;
+    bool m_isSaving = false;
 
 
     AZStd::string m_scriptProcessorRuleFilename;
     AZStd::string m_scriptProcessorRuleFilename;
 };
 };

+ 31 - 10
Code/Editor/Plugins/EditorAssetImporter/SceneSerializationHandler.cpp

@@ -38,7 +38,7 @@ namespace AZ
     }
     }
 
 
     AZStd::shared_ptr<SceneAPI::Containers::Scene> SceneSerializationHandler::LoadScene(
     AZStd::shared_ptr<SceneAPI::Containers::Scene> SceneSerializationHandler::LoadScene(
-        const AZStd::string& filePath, Uuid sceneSourceGuid)
+        const AZStd::string& sceneFilePath, Uuid sceneSourceGuid)
     {
     {
         AZ_PROFILE_FUNCTION(Editor);
         AZ_PROFILE_FUNCTION(Editor);
         namespace Utilities = AZ::SceneAPI::Utilities;
         namespace Utilities = AZ::SceneAPI::Utilities;
@@ -46,19 +46,13 @@ namespace AZ
 
 
         CleanSceneMap();
         CleanSceneMap();
 
 
-        AZ_TraceContext("File", filePath);
-        if (!IsValidExtension(filePath))
+        AZ_TraceContext("File", sceneFilePath.c_str());
+        if (!IsValidExtension(sceneFilePath))
         {
         {
             return nullptr;
             return nullptr;
         }
         }
 
 
-        AZ::IO::Path enginePath;
-        if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
-        {
-            settingsRegistry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
-        }
-
-        AZ::IO::Path cleanPath = (enginePath / filePath).LexicallyNormal();
+        AZ::IO::Path cleanPath(BuildCleanPathFromFilePath(sceneFilePath));
 
 
         auto sceneIt = m_scenes.find(cleanPath.Native());
         auto sceneIt = m_scenes.find(cleanPath.Native());
         if (sceneIt != m_scenes.end())
         if (sceneIt != m_scenes.end())
@@ -104,6 +98,33 @@ namespace AZ
         
         
         return scene;
         return scene;
     }
     }
+    
+    bool SceneSerializationHandler::IsSceneCached(const AZStd::string& sceneFilePath)
+    {
+        if (!IsValidExtension(sceneFilePath))
+        {
+            return false;
+        }
+        AZ::IO::Path cleanPath(BuildCleanPathFromFilePath(sceneFilePath));
+        
+        CleanSceneMap();
+        // There's a small window where all shared pointers might be released after cleaning the map
+        // and before checking the list here, so this won't be 100% accurate, but it will still catch
+        // cases where the scene is in use somewhere.
+        return m_scenes.find(cleanPath.Native()) != m_scenes.end();
+
+    }
+
+    AZ::IO::Path SceneSerializationHandler::BuildCleanPathFromFilePath(const AZStd::string& filePath) const
+    {
+        AZ::IO::Path enginePath;
+        if (auto* settingsRegistry = AZ::SettingsRegistry::Get())
+        {
+            settingsRegistry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
+        }
+
+        return((enginePath / filePath).LexicallyNormal());
+    }
 
 
     bool SceneSerializationHandler::IsValidExtension(const AZStd::string& filePath) const
     bool SceneSerializationHandler::IsValidExtension(const AZStd::string& filePath) const
     {
     {

+ 2 - 0
Code/Editor/Plugins/EditorAssetImporter/SceneSerializationHandler.h

@@ -27,10 +27,12 @@ namespace AZ
 
 
         AZStd::shared_ptr<SceneAPI::Containers::Scene> LoadScene(
         AZStd::shared_ptr<SceneAPI::Containers::Scene> LoadScene(
             const AZStd::string& sceneFilePath, Uuid sceneSourceGuid) override;
             const AZStd::string& sceneFilePath, Uuid sceneSourceGuid) override;
+        bool IsSceneCached(const AZStd::string& sceneFilePath) override;
 
 
     private:
     private:
         bool IsValidExtension(const AZStd::string& filePath) const;
         bool IsValidExtension(const AZStd::string& filePath) const;
         void CleanSceneMap();
         void CleanSceneMap();
+        AZ::IO::Path BuildCleanPathFromFilePath(const AZStd::string& filePath) const;
 
 
         AZStd::unordered_map<AZStd::string, AZStd::weak_ptr<SceneAPI::Containers::Scene>> m_scenes;
         AZStd::unordered_map<AZStd::string, AZStd::weak_ptr<SceneAPI::Containers::Scene>> m_scenes;
     };
     };

+ 5 - 0
Code/Tools/SceneAPI/SceneCore/Events/SceneSerializationBus.h

@@ -44,6 +44,11 @@ namespace AZ
                 //! @return The loaded scene or null if the file couldn't be fully resolved or an error 
                 //! @return The loaded scene or null if the file couldn't be fully resolved or an error 
                 //! occurred during loading.
                 //! occurred during loading.
                 virtual AZStd::shared_ptr<Containers::Scene> LoadScene(const AZStd::string& sceneFilePath, Uuid sceneSourceGuid) = 0;
                 virtual AZStd::shared_ptr<Containers::Scene> LoadScene(const AZStd::string& sceneFilePath, Uuid sceneSourceGuid) = 0;
+
+                //! The scene system caches loaded scenes. This checks if the given scene is valid and in the cache or not.
+                //! @param sceneFilePath The absolute or relative path to the scene file in the source folder.
+                //! @return True if the given scene is actively cached, false if not.
+                virtual bool IsSceneCached(const AZStd::string& /*sceneFilePath*/) { return false; }
             };
             };
 
 
             using SceneSerializationBus = AZ::EBus<SceneSerialization>;
             using SceneSerializationBus = AZ::EBus<SceneSerialization>;

+ 7 - 0
Code/Tools/SceneAPI/SceneUI/SceneWidgets/ManifestWidget.cpp

@@ -38,6 +38,13 @@ namespace AZ
             {
             {
             }
             }
 
 
+            void ManifestWidget::ResetScene()
+            {
+                ui->m_tabs->clear();
+                m_pages.clear();
+                m_scene.reset();
+            }
+
             void ManifestWidget::BuildFromScene(const AZStd::shared_ptr<Containers::Scene>& scene)
             void ManifestWidget::BuildFromScene(const AZStd::shared_ptr<Containers::Scene>& scene)
             {
             {
                 AZ_PROFILE_FUNCTION(Editor);
                 AZ_PROFILE_FUNCTION(Editor);

+ 2 - 0
Code/Tools/SceneAPI/SceneUI/SceneWidgets/ManifestWidget.h

@@ -57,6 +57,8 @@ namespace AZ
                 AZStd::shared_ptr<Containers::Scene> GetScene();
                 AZStd::shared_ptr<Containers::Scene> GetScene();
                 AZStd::shared_ptr<const Containers::Scene> GetScene() const;
                 AZStd::shared_ptr<const Containers::Scene> GetScene() const;
 
 
+                void ResetScene();
+
                 //! Finds this ManifestWidget if the given widget is it's child, otherwise returns null.
                 //! Finds this ManifestWidget if the given widget is it's child, otherwise returns null.
                 static ManifestWidget* FindRoot(QWidget* child);
                 static ManifestWidget* FindRoot(QWidget* child);
                 //! Finds this ManifestWidget if the given widget is it's child, otherwise returns null.
                 //! Finds this ManifestWidget if the given widget is it's child, otherwise returns null.