Files
o3de/Code/Sandbox/Editor/CryEdit.h
T
Nicholas Van Sickle f39460e617 Fix rare re-entrancy issue with CCryEditApp::IdleProcessing (#1134)
This issue manifested in a crash in rare circumstances when the Editor lost and gained focus while a modal dialog was active. After investigation, it was discovered that native event processing can lead to IdleProcessing being called again from the main thread while idle processing is still happening. As this is unintentional and generally undesirable, we now guard against this within the IdleProcessing method.
2021-06-03 20:08:51 -07:00

535 lines
19 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#ifndef CRYINCLUDE_EDITOR_CRYEDIT_H
#define CRYINCLUDE_EDITOR_CRYEDIT_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Outcome/Outcome.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include "WipFeatureManager.h"
#include "CryEditDoc.h"
#include "ViewPane.h"
#include <QSettings>
#endif
class CCryDocManager;
class CQuickAccessBar;
class CCryEditDoc;
class CEditCommandLineInfo;
class CMainFrame;
class CConsoleDialog;
struct mg_connection;
struct mg_request_info;
struct mg_context;
struct IEventLoopHook;
class QAction;
class MainWindow;
class QSharedMemory;
class SANDBOX_API RecentFileList
{
public:
RecentFileList();
void Remove(int index);
void Add(const QString& filename);
int GetSize();
void GetDisplayName(QString& name, int index, const QString& curDir);
QString& operator[](int index);
void ReadList();
void WriteList();
static const int Max = 12;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
QStringList m_arrNames;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
QSettings m_settings;
};
#define PROJECT_CONFIGURATOR_GEM_PAGE "Gems Settings"
/**
* Bus for controlling the application's idle processing (may include things like entity updates, ticks, viewport rendering, etc.).
* This is sometimes necessary in special event-processing loops to prevent long (or infinite) processing time because Idle Processing
* can perpetually generate more events.
*/
class EditorIdleProcessing : public AZ::EBusTraits
{
public:
using Bus = AZ::EBus<EditorIdleProcessing>;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
/// Disable the Editor's idle processing. EnableIdleProcessing() must be called exactly once when special processing is complete.
virtual void DisableIdleProcessing() {}
/// Re-enables Idle Processing. Must be called exactly one time for every call to DisableIdleProcessing().
virtual void EnableIdleProcessing() {}
};
using EditorIdleProcessingBus = AZ::EBus<EditorIdleProcessing>;
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
class SANDBOX_API CCryEditApp
: public QObject
, protected AzFramework::AssetSystemInfoBus::Handler
, protected EditorIdleProcessingBus::Handler
{
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
friend MainWindow;
Q_OBJECT
public:
enum ECreateLevelResult
{
ECLR_OK = 0,
ECLR_ALREADY_EXISTS,
ECLR_DIR_CREATION_FAILED,
ECLR_MAX_PATH_EXCEEDED
};
CCryEditApp();
~CCryEditApp();
static CCryEditApp* instance();
bool GetRootEnginePath(QDir& rootEnginePath) const;
bool CreateLevel(bool& wasCreateLevelOperationCancelled);
void LoadFile(QString fileName);
void ForceNextIdleProcessing() { m_bForceProcessIdle = true; }
void KeepEditorActive(bool bActive) { m_bKeepEditorActive = bActive; };
bool IsInTestMode() const { return m_bTestMode; };
bool IsInPreviewMode() const { return m_bPreviewMode; };
bool IsInExportMode() const { return m_bExportMode; };
bool IsExportingLegacyData() const { return m_bIsExportingLegacyData; }
bool IsInConsoleMode() const { return m_bConsoleMode; };
bool IsInAutotestMode() const { return m_bAutotestMode; };
bool IsInLevelLoadTestMode() const { return m_bLevelLoadTestMode; }
bool IsInRegularEditorMode();
bool IsExiting() const { return m_bExiting; }
void EnableAccelerator(bool bEnable);
void SaveAutoBackup();
void SaveAutoRemind();
void ExportToGame(bool bNoMsgBox = true);
//! \param sTitleStr overwrites the default title - "Sandbox Editor 3 (tm)"
void SetEditorWindowTitle(QString sTitleStr = QString(), QString sPreTitleStr = QString(), QString sPostTitleStr = QString());
RecentFileList* GetRecentFileList();
virtual void AddToRecentFileList(const QString& lpszPathName);
ECreateLevelResult CreateLevel(const QString& levelName, QString& fullyQualifiedLevelName);
static void InitDirectory();
BOOL FirstInstance(bool bForceNewInstance = false);
void InitFromCommandLine(CEditCommandLineInfo& cmdInfo);
BOOL CheckIfAlreadyRunning();
//! @return successful outcome if initialization succeeded. or failed outcome with error message.
AZ::Outcome<void, AZStd::string> InitGameSystem(HWND hwndForInputSystem);
void CreateSplashScreen();
void InitPlugins();
bool InitGame();
BOOL InitConsole();
int IdleProcessing(bool bBackground);
bool IsWindowInForeground();
void RunInitPythonScript(CEditCommandLineInfo& cmdInfo);
void RegisterEventLoopHook(IEventLoopHook* pHook);
void UnregisterEventLoopHook(IEventLoopHook* pHook);
void DisableIdleProcessing() override;
void EnableIdleProcessing() override;
// Print to stdout even if there out has been redirected
void PrintAlways(const AZStd::string& output);
//! Launches a detached process
//! \param process The path to the process to start
//! \param args Space separated list of arguments to pass to the process on start.
void StartProcessDetached(const char* process, const char* args);
//! Launches the Lua Editor/Debugger
//! \param files A space separated list of aliased paths
void OpenLUAEditor(const char* files);
QString GetRootEnginePath() const;
void RedirectStdoutToNull();
// Overrides
// ClassWizard generated virtual function overrides
public:
virtual BOOL InitInstance();
virtual int ExitInstance(int exitCode = 0);
virtual BOOL OnIdle(LONG lCount);
virtual CCryEditDoc* OpenDocumentFile(LPCTSTR lpszFileName);
CCryDocManager* GetDocManager() { return m_pDocManager; }
void RegisterActionHandlers();
// Implementation
void OnCreateLevel();
void OnOpenLevel();
void OnCreateSlice();
void OnOpenSlice();
void OnAppAbout();
void OnAppShowWelcomeScreen();
void OnUpdateShowWelcomeScreen(QAction* action);
void OnDocumentationTutorials();
void OnDocumentationGlossary();
void OnDocumentationO3DE();
void OnDocumentationGamelift();
void OnDocumentationReleaseNotes();
void OnDocumentationGameDevBlog();
void OnDocumentationGettingStartedGuide();
void OnDocumentationTwitchChannel();
void OnDocumentationForums();
void OnDocumentationAWSSupport();
void OnDocumentationFeedback();
void OnCommercePublish();
void OnCommerceMerch();
void SaveTagLocations();
void OnExportSelectedObjects();
void OnEditHold();
void OnEditFetch();
void OnFileExportToGameNoSurfaceTexture();
void OnViewSwitchToGame();
void OnViewDeploy();
void DeleteSelectedEntities(bool includeDescendants);
void OnMoveObject();
void OnRenameObj();
void OnEditmodeMove();
void OnEditmodeRotate();
void OnEditmodeScale();
void OnUpdateEditmodeMove(QAction* action);
void OnUpdateEditmodeRotate(QAction* action);
void OnUpdateEditmodeScale(QAction* action);
void OnUndo();
void OnOpenAssetImporter();
void OnUpdateSelected(QAction* action);
void OnEditLevelData();
void OnFileEditLogFile();
void OnFileResaveSlices();
void OnFileEditEditorini();
void OnPreferences();
void OnOpenProjectManagerSettings();
void OnOpenProjectManagerNew();
void OnOpenProjectManager();
void OnRedo();
void OnUpdateRedo(QAction* action);
void OnUpdateUndo(QAction* action);
void OnSwitchPhysics();
void OnSwitchPhysicsUpdate(QAction* action);
void OnSyncPlayer();
void OnSyncPlayerUpdate(QAction* action);
void OnResourcesReduceworkingset();
void OnDummyCommand() {};
void OnShowHelpers();
void OnFileSave();
void OnUpdateDocumentReady(QAction* action);
void OnUpdateFileOpen(QAction* action);
void OnUpdateNonGameMode(QAction* action);
void OnUpdateNewLevel(QAction* action);
void OnUpdatePlayGame(QAction* action);
protected:
// ------- AzFramework::AssetSystemInfoBus::Handler ------
void OnError(AzFramework::AssetSystem::AssetSystemErrors error) override;
// -------------------------------------------
private:
void InitLevel(const CEditCommandLineInfo& cmdInfo);
bool ConnectToAssetProcessor() const;
void CompileCriticalAssets() const;
CMainFrame* GetMainFrame() const;
void WriteConfig();
void TagLocation(int index);
void GotoTagLocation(int index);
void LoadTagLocations();
bool UserExportToGame(bool bNoMsgBox = true);
static void ShowSplashScreen(CCryEditApp* app);
static void CloseSplashScreen();
static void OutputStartupMessage(QString str);
bool ShowEnableDisableGemDialog(const QString& title, const QString& message);
QString ShowWelcomeDialog();
bool FixDanglingSharedMemory(const QString& sharedMemName) const;
//! Displays level load errors after a certain number of idle frames have been processed.
//! Due to the asyncrhonous nature of loading assets any errors that are reported by components
//! can happen after the level is loaded. This method will wait for a few idle updates and then
//! display the load errors to ensure all errors are displayed properly.
void DisplayLevelLoadErrors();
class CEditorImpl* m_pEditor = nullptr;
static CCryEditApp* s_currentInstance;
//! True if editor is in test mode.
//! Test mode is a special mode enabled when Editor ran with /test command line.
//! In this mode editor starts up, but exit immediately after all initialization.
bool m_bTestMode = false;
//! In this mode editor will load specified cry file, export t, and then close.
bool m_bExportMode = false;
QString m_exportFile;
//! This flag is set to true every time any of the "Export" commands is being executed.
//! Once exporting is finished the flag is set back to false.
//! UI events like "New Level" or "Open Level", should not be allowed while m_bIsExportingLegacyData==true.
//! Otherwise it could trigger crashes trying to export while exporting.
bool m_bIsExportingLegacyData = false;
//! If application exiting.
bool m_bExiting = false;
//! True if editor is in preview mode.
//! In this mode only very limited functionality is available and only for fast preview of models.
bool m_bPreviewMode = false;
// Only console window is created.
bool m_bConsoleMode = false;
// Skip showing the WelcomeScreenDialog
bool m_bSkipWelcomeScreenDialog = false;
// Level load test mode
bool m_bLevelLoadTestMode = false;
//! Current file in preview mode.
char m_sPreviewFile[_MAX_PATH];
//! True if "/runpythontest" was passed as a flag.
bool m_bRunPythonTestScript = false;
//! True if "/runpython" was passed as a flag.
bool m_bRunPythonScript = false;
//! File to run on startup
QString m_execFile;
//! Command to run on startup
QString m_execLineCmd;
//! Autotest mode: Special mode meant for automated testing, things like blocking dialogs or error report windows won't appear
bool m_bAutotestMode = false;
CConsoleDialog* m_pConsoleDialog = nullptr;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
Vec3 m_tagLocations[12];
Ang3 m_tagAngles[12];
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
float m_fastRotateAngle = 45.0f;
float m_moveSpeedStep = 0.1f;
ULONG_PTR m_gdiplusToken;
QSharedMemory* m_mutexApplication = nullptr;
//! was the editor active in the previous frame ... needed to detect if the game lost focus and
//! dispatch proper SystemEvent (needed to release input keys)
bool m_bPrevActive = false;
// If this flag is set, the next OnIdle() will update, even if the app is in the background, and then
// this flag will be reset.
bool m_bForceProcessIdle = false;
// This is set while IdleProcessing is running to prevent re-entrancy
bool m_idleProcessingRunning = false;
// Keep the editor alive, even if no focus is set
bool m_bKeepEditorActive = false;
// Currently creating a new level
bool m_creatingNewLevel = false;
bool m_openingLevel = false;
bool m_savingLevel = false;
// Flag indicating if the errors for the currently loaded level have been displayed
bool m_levelErrorsHaveBeenDisplayed = false;
// Number of idle frames that have passed before displaying level errors
int m_numBeforeDisplayErrorFrames = 0;
QString m_lastOpenLevelPath;
CQuickAccessBar* m_pQuickAccessBar = nullptr;
IEventLoopHook* m_pEventLoopHook = nullptr;
QString m_rootEnginePath;
int m_disableIdleProcessingCounter = 0; //!< Counts requests to disable idle processing. When non-zero, idle processing will be disabled.
CCryDocManager* m_pDocManager = nullptr;
// Disable warning for dll export since this member won't be used outside this class
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZ::IO::FileDescriptorRedirector m_stdoutRedirection = AZ::IO::FileDescriptorRedirector(1); // < 1 for STDOUT
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
private:
static inline constexpr const char* DefaultLevelTemplateName = "Prefabs/Default_Level.prefab";
struct PythonOutputHandler;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::shared_ptr<PythonOutputHandler> m_pythonOutputHandler;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
friend struct PythonTestOutputHandler;
void OpenProjectManager(const AZStd::string& screen);
void OnWireframe();
void OnUpdateWireframe(QAction* action);
void OnViewConfigureLayout();
// Tag Locations.
void OnTagLocation1();
void OnTagLocation2();
void OnTagLocation3();
void OnTagLocation4();
void OnTagLocation5();
void OnTagLocation6();
void OnTagLocation7();
void OnTagLocation8();
void OnTagLocation9();
void OnTagLocation10();
void OnTagLocation11();
void OnTagLocation12();
void OnGotoLocation1();
void OnGotoLocation2();
void OnGotoLocation3();
void OnGotoLocation4();
void OnGotoLocation5();
void OnGotoLocation6();
void OnGotoLocation7();
void OnGotoLocation8();
void OnGotoLocation9();
void OnGotoLocation10();
void OnGotoLocation11();
void OnGotoLocation12();
void OnToolsLogMemoryUsage();
void OnCustomizeKeyboard();
void OnToolsConfiguretools();
void OnToolsScriptHelp();
void OnViewCycle2dviewport();
void OnDisplayGotoPosition();
void OnChangemovespeedIncrease();
void OnChangemovespeedDecrease();
void OnChangemovespeedChangestep();
void OnFileSavelevelresources();
void OnClearRegistryData();
void OnValidatelevel();
void OnToolsPreferences();
void OnGraphicsSettings();
void OnSwitchToDefaultCamera();
void OnUpdateSwitchToDefaultCamera(QAction* action);
void OnSwitchToSequenceCamera();
void OnUpdateSwitchToSequenceCamera(QAction* action);
void OnSwitchToSelectedcamera();
void OnUpdateSwitchToSelectedCamera(QAction* action);
void OnSwitchcameraNext();
void OnOpenProceduralMaterialEditor();
void OnOpenAssetBrowserView();
void OnOpenTrackView();
void OnOpenAudioControlsEditor();
void OnOpenUICanvasEditor();
void OnChangeGameSpec(UINT nID);
void SetGameSpecCheck(ESystemConfigSpec spec, ESystemConfigPlatform platform, int &nCheck, bool &enable);
void OnUpdateGameSpec(QAction* action);
void OnOpenQuickAccessBar();
public:
void ExportLevel(bool bExportToGame, bool bExportTexture, bool bAutoExport);
static bool Command_ExportToEngine();
void OnFileExportOcclusionMesh();
};
//////////////////////////////////////////////////////////////////////////
class CCrySingleDocTemplate
: public QObject
{
private:
explicit CCrySingleDocTemplate(const QMetaObject* pDocClass)
: QObject()
, m_documentClass(pDocClass)
{
}
public:
enum Confidence
{
noAttempt,
maybeAttemptForeign,
maybeAttemptNative,
yesAttemptForeign,
yesAttemptNative,
yesAlreadyOpen
};
template<typename DOCUMENT>
static CCrySingleDocTemplate* create()
{
return new CCrySingleDocTemplate(&DOCUMENT::staticMetaObject);
}
~CCrySingleDocTemplate() {};
// avoid creating another CMainFrame
// close other type docs before opening any things
virtual CCryEditDoc* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bAddToMRU, BOOL bMakeVisible);
virtual CCryEditDoc* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible = TRUE);
virtual Confidence MatchDocType(LPCTSTR lpszPathName, CCryEditDoc*& rpDocMatch);
private:
const QMetaObject* m_documentClass = nullptr;
};
class CDocTemplate;
class CCryDocManager
{
CCrySingleDocTemplate* m_pDefTemplate = nullptr;
public:
CCryDocManager();
CCrySingleDocTemplate* SetDefaultTemplate(CCrySingleDocTemplate* pNew);
// Copied from MFC to get rid of the silly ugly unoverridable doc-type pick dialog
virtual void OnFileNew();
virtual BOOL DoPromptFileName(QString& fileName, UINT nIDSTitle,
DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);
virtual CCryEditDoc* OpenDocumentFile(LPCTSTR lpszFileName, BOOL bAddToMRU);
QVector<CCrySingleDocTemplate*> m_templateList;
};
#include <AzCore/Component/Component.h>
namespace AzToolsFramework
{
//! A component to reflect scriptable commands for the Editor
class CryEditPythonHandler final
: public AZ::Component
{
public:
AZ_COMPONENT(CryEditPythonHandler, "{D4B19973-54D9-44BD-9E70-6069462A0CDC}")
virtual ~CryEditPythonHandler() = default;
SANDBOX_API static void Reflect(AZ::ReflectContext* context);
// AZ::Component ...
void Activate() override {}
void Deactivate() override {}
class CryEditHandler
{
public:
AZ_RTTI(CryEditHandler, "{6C1FD05A-2F39-4094-80D4-CA526676F13E}")
virtual ~CryEditHandler() = default;
};
class CryEditCheckoutHandler
{
public:
AZ_RTTI(CryEditCheckoutHandler, "{C65EF439-6754-4ACD-AEA2-196F2DBA0AF3}")
virtual ~CryEditCheckoutHandler() = default;
};
};
} // namespace AzToolsFramework
extern "C" AZ_DLL_EXPORT void InitializeDynamicModule(void* env);
extern "C" AZ_DLL_EXPORT void UninitializeDynamicModule();
#endif // CRYINCLUDE_EDITOR_CRYEDIT_H