557 lines
21 KiB
C++
557 lines
21 KiB
C++
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
|
|
|
#if USE_EDITOR
|
|
|
|
#include "Editor.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 "FlaxEngine.Gen.h"
|
|
|
|
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()
|
|
{
|
|
const auto versionFilePath = Globals::ProjectCacheFolder / TEXT("version");
|
|
|
|
// Load version cache file
|
|
int32 lastMajor = FLAXENGINE_VERSION_MAJOR;
|
|
int32 lastMinor = FLAXENGINE_VERSION_MINOR;
|
|
int32 lastBuild = FLAXENGINE_VERSION_BUILD;
|
|
{
|
|
auto file = FileReadStream::Open(versionFilePath);
|
|
if (file)
|
|
{
|
|
file->ReadInt32(&lastMajor);
|
|
file->ReadInt32(&lastMinor);
|
|
file->ReadInt32(&lastBuild);
|
|
|
|
// Invalidate results if data has issues
|
|
if (file->HasError() || lastMajor < 0 || lastMinor < 0 || lastMajor > 100 || lastMinor > 1000)
|
|
{
|
|
lastMajor = FLAXENGINE_VERSION_MAJOR;
|
|
lastMinor = FLAXENGINE_VERSION_MINOR;
|
|
lastBuild = FLAXENGINE_VERSION_BUILD;
|
|
LOG(Warning, "Invalid version cache data");
|
|
}
|
|
else
|
|
{
|
|
LOG(Info, "Last project open version: {0}.{1}.{2}", lastMajor, lastMinor, lastBuild);
|
|
LastProjectOpenedEngineBuild = lastBuild;
|
|
}
|
|
|
|
Delete(file);
|
|
}
|
|
else
|
|
{
|
|
LOG(Warning, "Missing version cache 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, TEXT("*"), DirectorySearchOption::AllDirectories);
|
|
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, true);
|
|
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::Unicode);
|
|
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::Unicode);
|
|
}
|
|
|
|
// 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(\"{0}\");\n"
|
|
" }}\n"
|
|
"}}\n"
|
|
), codeName), Encoding::Unicode);
|
|
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(\"{0}\");\n"
|
|
"{1}"
|
|
" }}\n"
|
|
"}}\n"
|
|
), codeName, editorTargetGameEditorModule), Encoding::Unicode);
|
|
|
|
// 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 (lastMajor == FLAXENGINE_VERSION_MAJOR && lastMinor == FLAXENGINE_VERSION_MINOR)
|
|
{
|
|
// Do nothing
|
|
IsOldProjectOpened = false;
|
|
}
|
|
// Check if last version was older
|
|
else if (lastMajor < FLAXENGINE_VERSION_MAJOR || (lastMajor == FLAXENGINE_VERSION_MAJOR && lastMinor < FLAXENGINE_VERSION_MINOR))
|
|
{
|
|
LOG(Warning, "The project was opened with the older editor version last time");
|
|
const auto result = MessageBox::Show(TEXT("The project was opened with the older editor version last time. 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;
|
|
}
|
|
}
|
|
// Check if last version was newer
|
|
else if (lastMajor > FLAXENGINE_VERSION_MAJOR || (lastMajor == FLAXENGINE_VERSION_MAJOR && lastMinor > FLAXENGINE_VERSION_MINOR))
|
|
{
|
|
LOG(Warning, "The project was opened with the newer editor version last time");
|
|
const auto result = MessageBox::Show(TEXT("The project was opened with the newer editor version last time. Loading it may fail and corrupt existing data. Do you want to perform a backup before or cancel 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;
|
|
}
|
|
}
|
|
|
|
// Upgrade old 0.7 projects
|
|
// [Deprecated: 01.11.2020, expires 01.11.2021]
|
|
if (lastMajor == 0 && lastMinor == 7 && lastBuild <= 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)
|
|
{
|
|
file->WriteInt32(FLAXENGINE_VERSION_MAJOR);
|
|
file->WriteInt32(FLAXENGINE_VERSION_MINOR);
|
|
file->WriteInt32(FLAXENGINE_VERSION_BUILD);
|
|
|
|
Delete(file);
|
|
}
|
|
else
|
|
{
|
|
LOG(Warning, "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, true);
|
|
}
|
|
|
|
int32 Editor::LoadProduct()
|
|
{
|
|
// Flax Editor product
|
|
Globals::ProductName = TEXT("Flax Editor");
|
|
Globals::CompanyName = TEXT("Flax");
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Missing project case
|
|
if (projectPath.IsEmpty())
|
|
{
|
|
#if PLATFORM_HAS_HEADLESS_MODE
|
|
if (CommandLine::Options.Headless)
|
|
{
|
|
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 opoen 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;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
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()
|
|
{
|
|
return Managed->GetMainWindow();
|
|
}
|
|
|
|
bool Editor::Init()
|
|
{
|
|
// Scripts project files generation from command line
|
|
if (CommandLine::Options.GenProjectFiles)
|
|
{
|
|
const bool failed = ScriptsBuilder::GenerateProject();
|
|
exit(failed ? 1 : 0);
|
|
return true;
|
|
}
|
|
|
|
// 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 (EditorImpl::Splash == nullptr)
|
|
EditorImpl::Splash = New<SplashScreen>();
|
|
EditorImpl::Splash->SetTitle(Project->Name);
|
|
EditorImpl::Splash->Show();
|
|
|
|
// Initialize managed editor
|
|
Managed->Init();
|
|
|
|
return false;
|
|
}
|
|
|
|
void Editor::BeforeRun()
|
|
{
|
|
// If during last lightmaps baking engine crashed we could try to restore the progress
|
|
if (ShadowsOfMordor::Builder::Instance()->RestoreState())
|
|
Managed->GetClass()->GetMethod("Internal_StartLightingBake")->Invoke(Managed->GetOrCreateManagedInstance(), nullptr, nullptr);
|
|
}
|
|
|
|
void Editor::BeforeExit()
|
|
{
|
|
CloseSplashScreen();
|
|
|
|
Managed->Exit();
|
|
SAFE_DELETE(Managed);
|
|
Project = nullptr;
|
|
ProjectInfo::ProjectsCache.ClearDelete();
|
|
}
|
|
|
|
void EditorImpl::OnUpdate()
|
|
{
|
|
// 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);
|
|
}
|
|
if (!hasFocus)
|
|
{
|
|
// Sleep for a bit to not eat up all CPU time
|
|
Platform::Sleep(5);
|
|
}
|
|
HasFocus = hasFocus;
|
|
}
|
|
|
|
#endif
|