Files
FlaxEngine/Source/Editor/Editor.cpp

740 lines
30 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#if USE_EDITOR
#include "Editor.h"
#include "ProjectInfo.h"
#include "Engine/Core/Log.h"
#include "Scripting/ScriptsBuilder.h"
#include "Windows/SplashScreen.h"
#include "Managed/ManagedEditor.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Serialization/FileReadStream.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/MessageBox.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Engine/Engine.h"
#include "Engine/ShadowsOfMordor/Builder.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "FlaxEngine.Gen.h"
#if PLATFORM_LINUX
#include "Engine/Tools/TextureTool/TextureTool.h"
#endif
namespace EditorImpl
{
bool IsOldProjectXmlFormat = false;
bool HasFocus = false;
SplashScreen* Splash = nullptr;
void OnUpdate();
}
ManagedEditor* Editor::Managed = nullptr;
ProjectInfo* Editor::Project = nullptr;
bool Editor::IsPlayMode = false;
bool Editor::IsOldProjectOpened = true;
int32 Editor::LastProjectOpenedEngineBuild = 0;
void Editor::CloseSplashScreen()
{
SAFE_DELETE(EditorImpl::Splash);
}
bool Editor::CheckProjectUpgrade()
{
PROFILE_MEM(Editor);
const auto versionFilePath = Globals::ProjectCacheFolder / TEXT("version");
// Load version cache file
struct VersionCache
{
// When changing this ensure that Flax Launcher properly reads the version
int32 Major = FLAXENGINE_VERSION_MAJOR;
int32 Minor = FLAXENGINE_VERSION_MINOR;
int32 Build = FLAXENGINE_VERSION_BUILD;
int32 RealSize = sizeof(Real); // Rebuild when changing between Large Worlds
};
VersionCache lastVersion;
if (FileSystem::FileExists(versionFilePath))
{
auto file = FileReadStream::Open(versionFilePath);
if (file)
{
file->ReadBytes(&lastVersion, sizeof(lastVersion));
// Invalidate results if data has issues
if (file->HasError() || lastVersion.Major < 0 || lastVersion.Minor < 0 || lastVersion.Major > 100 || lastVersion.Minor > 1000)
{
lastVersion = VersionCache();
LOG(Warning, "Invalid version cache data");
}
else
{
LOG(Info, "Last project open version: {0}.{1}.{2}", lastVersion.Major, lastVersion.Minor, lastVersion.Build);
LastProjectOpenedEngineBuild = lastVersion.Build;
}
Delete(file);
}
}
// Check if project is in the old, deprecated layout
if (EditorImpl::IsOldProjectXmlFormat)
{
// [Deprecated: 16.04.2020, expires 16.04.2021]
LOG(Warning, "The project is in an old format and will be upgraded.");
const auto result = MessageBox::Show(TEXT("The Flax project is in an old format and will be upgraded. Loading it may modify existing data so older editor version won't open it. Do you want to perform a backup before or cancel operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question);
if (result == DialogResult::Yes)
{
if (BackupProject())
{
LOG(Warning, "Backup failed");
return true;
}
}
else if (result == DialogResult::No)
{
// Don't backup, just load
}
else
{
// Cancel
return true;
}
const String& root = Globals::ProjectFolder;
const String& name = Project->Name;
String codeName;
for (int32 i = 0; i < name.Length(); i++)
{
Char c = name[i];
if (StringUtils::IsAlnum(c) && c != ' ' && c != '.')
codeName += c;
}
const String& sourceFolder = Globals::ProjectSourceFolder;
const String gameModuleFolder = sourceFolder / codeName;
const String gameEditorModuleFolder = sourceFolder / codeName + TEXT("Editor");
const String tempSourceSetup = Globals::ProjectCacheFolder / TEXT("UpgradeSource");
const String tempSourceSetupGame = tempSourceSetup / codeName;
const String tempSourceSetupGameEditor = tempSourceSetup / codeName + TEXT("Editor");
// Remove old project files
FileSystem::DeleteFile(root / name + TEXT(".sln"));
FileSystem::DeleteFile(root / name + TEXT(".csproj"));
FileSystem::DeleteFile(root / name + TEXT(".csproj.user"));
FileSystem::DeleteFile(root / name + TEXT(".Editor.csproj"));
FileSystem::DeleteFile(root / name + TEXT(".Editor.csproj.user"));
// Remove old cache files
FileSystem::DeleteDirectory(root / TEXT("Cache/Assemblies"));
FileSystem::DeleteDirectory(root / TEXT("Cache/bin"));
FileSystem::DeleteDirectory(root / TEXT("Cache/obj"));
FileSystem::DeleteDirectory(root / TEXT("Cache/Shaders"));
// Move C# files to new locations
FileSystem::DeleteDirectory(tempSourceSetup);
FileSystem::CreateDirectory(tempSourceSetup);
Array<String> files;
FileSystem::DirectoryGetFiles(files, sourceFolder);
bool useEditorModule = false;
for (auto& file : files)
{
String tempSourceFile;
if (file.Contains(TEXT("/Editor/")))
{
useEditorModule = true;
tempSourceFile = tempSourceSetupGameEditor / StringUtils::GetFileName(file);
}
else
{
tempSourceFile = tempSourceSetupGame / FileSystem::ConvertAbsolutePathToRelative(sourceFolder, file);
}
FileSystem::CreateDirectory(StringUtils::GetDirectoryName(tempSourceFile));
FileSystem::CopyFile(tempSourceFile, file);
}
FileSystem::DeleteDirectory(sourceFolder);
FileSystem::CopyDirectory(sourceFolder, tempSourceSetup);
FileSystem::DeleteDirectory(tempSourceSetup);
// Generate module files
File::WriteAllText(gameModuleFolder / String::Format(TEXT("{0}.Build.cs"), codeName), String::Format(TEXT(
"using Flax.Build;\n"
"using Flax.Build.NativeCpp;\n"
"\n"
"public class {0} : GameModule\n"
"{{\n"
" /// <inheritdoc />\n"
" public override void Setup(BuildOptions options)\n"
" {{\n"
" base.Setup(options);\n"
"\n"
" // Ignore compilation warnings due to missing code documentation comments\n"
" options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true;\n"
"\n"
" // Here you can modify the build options for your game module\n"
" // To reference another module use: options.PublicDependencies.Add(\"Audio\");\n"
" // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n"
" // To learn more see scripting documentation.\n"
" BuildNativeCode = false;\n"
" }}\n"
"}}\n"
), codeName), Encoding::UTF8);
if (useEditorModule)
{
File::WriteAllText(gameEditorModuleFolder / String::Format(TEXT("{0}Editor.Build.cs"), codeName), String::Format(TEXT(
"using Flax.Build;\n"
"using Flax.Build.NativeCpp;\n"
"\n"
"public class {0}Editor : GameEditorModule\n"
"{{\n"
" /// <inheritdoc />\n"
" public override void Setup(BuildOptions options)\n"
" {{\n"
" base.Setup(options);\n"
"\n"
" // Reference game source module to access game code types\n"
" options.PublicDependencies.Add(\"{0}\");\n"
"\n"
" // Ignore compilation warnings due to missing code documentation comments\n"
" options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true;\n"
"\n"
" // Here you can modify the build options for your game editor module\n"
" // To reference another module use: options.PublicDependencies.Add(\"Audio\");\n"
" // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n"
" // To learn more see scripting documentation.\n"
" BuildNativeCode = false;\n"
" }}\n"
"}}\n"
), codeName), Encoding::UTF8);
}
// Generate target files
File::WriteAllText(sourceFolder / String::Format(TEXT("{0}Target.Build.cs"), codeName), String::Format(TEXT(
"using Flax.Build;\n"
"\n"
"public class {0}Target : GameProjectTarget\n"
"{{\n"
" /// <inheritdoc />\n"
" public override void Init()\n"
" {{\n"
" base.Init();\n"
"\n"
" // Reference the modules for game\n"
" Modules.Add(nameof({0}));\n"
" }}\n"
"}}\n"
), codeName), Encoding::UTF8);
const String editorTargetGameEditorModule = useEditorModule ? String::Format(TEXT(" Modules.Add(\"{0}Editor\");\n"), codeName) : String::Empty;
File::WriteAllText(sourceFolder / String::Format(TEXT("{0}EditorTarget.Build.cs"), codeName), String::Format(TEXT(
"using Flax.Build;\n"
"\n"
"public class {0}EditorTarget : GameProjectEditorTarget\n"
"{{\n"
" /// <inheritdoc />\n"
" public override void Init()\n"
" {{\n"
" base.Init();\n"
"\n"
" // Reference the modules for editor\n"
" Modules.Add(nameof({0}));\n"
"{1}"
" }}\n"
"}}\n"
), codeName, editorTargetGameEditorModule), Encoding::UTF8);
// Generate new project file
Project->ProjectPath = root / String::Format(TEXT("{0}.flaxproj"), codeName);
Project->GameTarget = codeName + TEXT("Target");
Project->EditorTarget = codeName + TEXT("EditorTarget");
Project->SaveProject();
// Remove old project file
FileSystem::DeleteFile(root / TEXT("Project.xml"));
LOG(Warning, "Project layout upgraded!");
}
// Check if last version was the same
else if (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor == FLAXENGINE_VERSION_MINOR)
{
// Do nothing
IsOldProjectOpened = false;
}
// Check if last version was older
else if (lastVersion.Major < FLAXENGINE_VERSION_MAJOR || (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor < FLAXENGINE_VERSION_MINOR))
{
LOG(Warning, "The project was last opened with an older editor version");
const auto result = MessageBox::Show(TEXT("The project was last opened with an older editor version.\nLoading it may modify existing data, which can result in older editor versions being unable to open it.\n\nDo you want to perform a backup before or cancel the operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question);
if (result == DialogResult::Yes)
{
if (BackupProject())
{
LOG(Warning, "Backup failed");
return true;
}
}
else if (result == DialogResult::No)
{
// Don't backup, just load
}
else
{
// Cancel
return true;
}
}
// Check if last version was newer
else if (lastVersion.Major > FLAXENGINE_VERSION_MAJOR || (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor > FLAXENGINE_VERSION_MINOR))
{
LOG(Warning, "The project was last opened with a newer editor version");
const auto result = MessageBox::Show(TEXT("The project was last opened with a newer editor version.\nLoading it may fail and corrupt existing data.\n\nDo you want to perform a backup before loading or cancel the operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Warning);
if (result == DialogResult::Yes)
{
if (BackupProject())
{
LOG(Warning, "Backup failed");
return true;
}
}
else if (result == DialogResult::No)
{
// Don't backup, just load
}
else
{
// Cancel
return true;
}
}
// When changing between major/minor version clear some caches to prevent possible issues
if (lastVersion.Major != FLAXENGINE_VERSION_MAJOR || lastVersion.Minor != FLAXENGINE_VERSION_MINOR || lastVersion.RealSize != sizeof(Real))
{
LOG(Info, "Cleaning cache files from different engine version");
FileSystem::DeleteDirectory(Globals::ProjectFolder / TEXT("Cache/Cooker"));
FileSystem::DeleteDirectory(Globals::ProjectFolder / TEXT("Cache/Intermediate"));
}
// Upgrade old 0.7 projects
// [Deprecated: 01.11.2020, expires 01.11.2021]
if (lastVersion.Major == 0 && lastVersion.Minor == 7 && lastVersion.Build <= 6197)
{
Array<String> files;
FileSystem::DirectoryGetFiles(files, Globals::ProjectSourceFolder, TEXT("*.Gen.cs"));
for (auto& file : files)
FileSystem::DeleteFile(file);
}
// Update version the cache file
{
auto file = FileWriteStream::Open(versionFilePath);
if (file)
{
lastVersion = VersionCache();
file->WriteBytes(&lastVersion, sizeof(lastVersion));
Delete(file);
}
else
{
LOG(Error, "Failed to create version cache file");
}
}
return false;
}
bool Editor::BackupProject()
{
// Create backup directory
auto dstPath = Globals::ProjectFolder + TEXT(" - Backup");
{
int32 count = 0;
while (count < 1000 && FileSystem::DirectoryExists(dstPath))
{
dstPath = Globals::ProjectFolder + TEXT(" - Backup") + StringUtils::ToString(count++);
}
}
LOG(Info, "Backup project to \"{0}\"", dstPath);
// Copy everything
return FileSystem::CopyDirectory(dstPath, Globals::ProjectFolder);
}
int32 Editor::LoadProduct()
{
PROFILE_MEM(Editor);
// Flax Editor product
Globals::ProductName = TEXT("Flax Editor");
Globals::CompanyName = TEXT("Flax");
#if FLAX_TESTS
// Flax Tests use auto-generated temporary project
CommandLine::Options.Project = Globals::TemporaryFolder / TEXT("Project");
CommandLine::Options.NewProject = true;
#endif
// Gather project directory from the command line
String projectPath = CommandLine::Options.Project.TrimTrailing();
const int32 startIndex = projectPath.StartsWith('\"') || projectPath.StartsWith('\'') ? 1 : 0;
const int32 length = projectPath.Length() - (projectPath.EndsWith('\"') || projectPath.EndsWith('\'') ? 1 : 0) - startIndex;
if (length > 0)
{
projectPath = projectPath.Substring(startIndex, length - startIndex);
StringUtils::PathRemoveRelativeParts(projectPath);
if (FileSystem::IsRelative(projectPath))
{
projectPath = Platform::GetWorkingDirectory() / projectPath;
StringUtils::PathRemoveRelativeParts(projectPath);
}
if (projectPath.EndsWith(TEXT(".flaxproj")))
{
projectPath = StringUtils::GetDirectoryName(projectPath);
}
}
else
{
projectPath.Clear();
}
// Create new project option
if (CommandLine::Options.NewProject.IsTrue())
{
Array<String> projectFiles;
FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly);
if (projectFiles.Count() > 1)
{
Platform::Fatal(TEXT("Too many project files."));
return -2;
}
else if (projectFiles.Count() == 1)
{
LOG(Info, "Skip creating new project because it already exists");
CommandLine::Options.NewProject.Reset();
}
else
{
Array<String> files;
FileSystem::DirectoryGetFiles(files, projectPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
if (files.Count() > 0)
{
Platform::Fatal(String::Format(TEXT("Target project folder '{0}' is not empty."), projectPath));
return -1;
}
}
}
if (CommandLine::Options.NewProject.IsTrue())
{
if (projectPath.IsEmpty())
projectPath = Platform::GetWorkingDirectory();
else if (!FileSystem::DirectoryExists(projectPath))
FileSystem::CreateDirectory(projectPath);
FileSystem::NormalizePath(projectPath);
String folderName = StringUtils::GetFileName(projectPath);
String tmpName;
for (int32 i = 0; i < folderName.Length(); i++)
{
Char c = folderName[i];
if (StringUtils::IsAlnum(c) && c != ' ' && c != '.')
tmpName += c;
}
// Create project file
ProjectInfo newProject;
newProject.Name = MoveTemp(tmpName);
newProject.ProjectPath = projectPath / newProject.Name + TEXT(".flaxproj");
newProject.ProjectFolderPath = projectPath;
newProject.Version = Version(1, 0);
newProject.Company = TEXT("My Company");
newProject.MinEngineVersion = FLAXENGINE_VERSION;
newProject.GameTarget = TEXT("GameTarget");
newProject.EditorTarget = TEXT("GameEditorTarget");
auto& flaxRef = newProject.References.AddOne();
flaxRef.Name = TEXT("$(EnginePath)/Flax.flaxproj");
flaxRef.Project = nullptr;
if (newProject.SaveProject())
return 10;
// Generate source files
if (FileSystem::CreateDirectory(projectPath / TEXT("Content")))
return 11;
if (FileSystem::CreateDirectory(projectPath / TEXT("Source/Game")))
return 11;
bool failed = File::WriteAllText(projectPath / TEXT("Source/GameTarget.Build.cs"),TEXT(
"using Flax.Build;\n"
"\n"
"public class GameTarget : GameProjectTarget\n"
"{\n"
" /// <inheritdoc />\n"
" public override void Init()\n"
" {\n"
" base.Init();\n"
"\n"
" // Reference the modules for game\n"
" Modules.Add(nameof(Game));\n"
" }\n"
"}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/GameEditorTarget.Build.cs"),TEXT(
"using Flax.Build;\n"
"\n"
"public class GameEditorTarget : GameProjectEditorTarget\n"
"{\n"
" /// <inheritdoc />\n"
" public override void Init()\n"
" {\n"
" base.Init();\n"
"\n"
" // Reference the modules for editor\n"
" Modules.Add(nameof(Game));\n"
" }\n"
"}\n"), Encoding::UTF8);
failed |= File::WriteAllText(projectPath / TEXT("Source/Game/Game.Build.cs"),TEXT(
"using Flax.Build;\n"
"using Flax.Build.NativeCpp;\n"
"\n"
"public class Game : GameModule\n"
"{\n"
" /// <inheritdoc />\n"
" public override void Init()\n"
" {\n"
" base.Init();\n"
"\n"
" // C#-only scripting\n"
" BuildNativeCode = false;\n"
" }\n"
"\n"
" /// <inheritdoc />\n"
" public override void Setup(BuildOptions options)\n"
" {\n"
" base.Setup(options);\n"
"\n"
" options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true;\n"
"\n"
" // Here you can modify the build options for your game module\n"
" // To reference another module use: options.PublicDependencies.Add(\"Audio\");\n"
" // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n"
" // To learn more see scripting documentation.\n"
" }\n"
"}\n"), Encoding::UTF8);
if (failed)
return 12;
}
// Missing project case
if (projectPath.IsEmpty())
{
#if PLATFORM_HAS_HEADLESS_MODE
if (CommandLine::Options.Headless.IsTrue())
{
Platform::Fatal(TEXT("Missing project path."));
return -1;
}
#endif
// Ask user to pick a project to open
Array<String> files;
if (FileSystem::ShowOpenFileDialog(
nullptr,
StringView::Empty,
TEXT("Project files (*.flaxproj)\0*.flaxproj\0All files (*.*)\0*.*\0"),
false,
TEXT("Select project to open in Editor"),
files) || files.Count() != 1)
{
return -1;
}
if (!FileSystem::FileExists(files[0]))
{
Platform::Fatal(TEXT("Cannot open selected project file because it doesn't exist."));
return -1;
}
projectPath = StringUtils::GetDirectoryName(files[0]);
StringUtils::PathRemoveRelativeParts(projectPath);
}
// Check folder with project exists
if (!FileSystem::DirectoryExists(projectPath))
{
Platform::Fatal(String::Format(TEXT("Project folder '{0}' is missing"), projectPath));
return -1;
}
Globals::ProjectFolder = projectPath;
ASSERT(!FileSystem::IsRelative(Globals::ProjectFolder));
// Check if opening old project (the one with Project.xml file)
// [Deprecated: 16.04.2020, expires 16.04.2021]
const String projectXmlPath = projectPath / TEXT("Project.xml");
Project = New<ProjectInfo>();
ProjectInfo::ProjectsCache.Add(Project);
if (FileSystem::FileExists(projectXmlPath))
{
// Load project
const bool loadResult = Project->LoadOldProject(projectXmlPath);
if (loadResult)
{
Platform::Fatal(TEXT("Cannot load project."));
return -2;
}
EditorImpl::IsOldProjectXmlFormat = true;
}
else
{
// Load project
Array<String> projectFiles;
FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly);
if (projectFiles.Count() == 0)
{
Platform::Fatal(TEXT("Missing project file (*.flaxproj)."));
return -2;
}
if (projectFiles.Count() > 1)
{
Platform::Fatal(TEXT("Too many project files."));
return -2;
}
const bool loadResult = Project->LoadProject(projectFiles[0]);
if (loadResult)
{
Platform::Fatal(TEXT("Cannot load project."));
return -2;
}
}
HashSet<ProjectInfo*> projects;
Project->GetAllProjects(projects);
// Validate project min supported version (older engine may try to load newer project)
// Special check if project specifies only build number, then major/minor fields are set to 0
const auto engineVersion = FLAXENGINE_VERSION;
for (const auto& e : projects)
{
const auto project = e.Item;
if (project->MinEngineVersion > engineVersion ||
(project->MinEngineVersion.Major() == 0 && project->MinEngineVersion.Minor() == 0 && project->MinEngineVersion.Build() > engineVersion.Build())
)
{
Platform::Fatal(String::Format(TEXT("Cannot open project \"{0}\".\nIt requires version {1} but editor has version {2}.\nPlease update the editor."), project->Name, project->MinEngineVersion.ToString(), engineVersion.ToString()));
return -2;
}
}
return 0;
}
Window* Editor::CreateMainWindow()
{
PROFILE_MEM(Editor);
Window* window = Managed->GetMainWindow();
#if PLATFORM_LINUX
// Set window icon
const String iconPath = Globals::BinariesFolder / TEXT("Logo.png");
if (FileSystem::FileExists(iconPath))
{
TextureData icon;
if (TextureTool::ImportTexture(iconPath, icon))
{
LOG(Warning, "Failed to load icon file.");
}
else
{
window->SetIcon(icon);
}
}
else
{
LOG(Warning, "Missing icon file.");
}
#endif
return window;
}
bool Editor::Init()
{
// Scripts project files generation from command line
if (CommandLine::Options.GenProjectFiles.IsTrue())
{
const String customArgs = TEXT("-verbose -log -logfile=\"Cache/Intermediate/ProjectFileLog.txt\"");
const bool failed = ScriptsBuilder::GenerateProject(customArgs);
exit(failed ? 1 : 0);
return true;
}
PROFILE_CPU();
PROFILE_MEM(Editor);
// If during last lightmaps baking engine crashed we could try to restore the progress
ShadowsOfMordor::Builder::Instance()->CheckIfRestoreState();
Engine::Update.Bind(&EditorImpl::OnUpdate);
Managed = New<ManagedEditor>();
// Show splash screen
if (!CommandLine::Options.Headless.IsTrue())
{
PROFILE_CPU_NAMED("Splash");
if (EditorImpl::Splash == nullptr)
EditorImpl::Splash = New<SplashScreen>();
EditorImpl::Splash->SetTitle(Project->Name);
EditorImpl::Splash->Show();
}
// Initialize managed editor
Managed->Init();
// Start play if requested by cmd line
if (CommandLine::Options.Play.HasValue())
{
Managed->RequestStartPlayOnEditMode();
}
return false;
}
void Editor::BeforeRun()
{
PROFILE_MEM(Editor);
Managed->BeforeRun();
}
void Editor::BeforeExit()
{
PROFILE_MEM(Editor);
CloseSplashScreen();
Managed->Exit();
SAFE_DELETE(Managed);
Project = nullptr;
ProjectInfo::ProjectsCache.ClearDelete();
}
void EditorImpl::OnUpdate()
{
PROFILE_MEM(Editor);
// Update c# editor
Editor::Managed->Update();
// If editor thread doesn't have the focus, don't suck up too much CPU time
const auto hasFocus = Engine::HasFocus;
if (HasFocus && !hasFocus)
{
// Drop our priority to speed up whatever is in the foreground
Platform::SetThreadPriority(ThreadPriority::BelowNormal);
}
else if (hasFocus && !HasFocus)
{
// Boost our priority back to normal
Platform::SetThreadPriority(ThreadPriority::Normal);
}
HasFocus = hasFocus;
}
#endif