From 9a77517cb49c5c7a2ff28138ca99fd1809ade534 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 7 Sep 2022 11:52:11 +0200 Subject: [PATCH] Add `Cook&Run` and `Run cooked game` utilities to `Game` menu list for multilayer games testing --- Source/Editor/Cooker/CookingData.h | 10 +++ Source/Editor/Cooker/GameCooker.cpp | 67 ++++++++++++++++++- Source/Editor/Cooker/GameCooker.h | 5 ++ .../Platform/Windows/WindowsPlatformTools.cpp | 14 +++- .../Platform/Windows/WindowsPlatformTools.h | 1 + Source/Editor/Cooker/PlatformTools.h | 11 +++ Source/Editor/Modules/UIModule.cs | 3 + Source/Editor/Windows/GameCookerWindow.cs | 50 ++++++++++++-- 8 files changed, 154 insertions(+), 7 deletions(-) diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h index 5f91638ad..1f62a7738 100644 --- a/Source/Editor/Cooker/CookingData.h +++ b/Source/Editor/Cooker/CookingData.h @@ -26,6 +26,16 @@ API_ENUM(Attributes="Flags") enum class BuildOptions /// Shows the output directory folder on building end. /// ShowOutput = 1 << 0, + + /// + /// Starts the cooked game build on building end. + /// + AutoRun = 1 << 1, + + /// + /// Skips cooking logic and uses already cooked data (eg. to only use AutoRun or ShowOutput feature). + /// + NoCook = 1 << 2, }; DECLARE_ENUM_OPERATORS(BuildOptions); diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 01d09bfbf..cdd51ed55 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -430,6 +430,52 @@ void GameCooker::Cancel(bool waitForEnd) } } +void GameCooker::GetCurrentPlatform(PlatformType& platform, BuildPlatform& buildPlatform, BuildConfiguration& buildConfiguration) +{ + platform = PLATFORM_TYPE; +#if BUILD_DEBUG + buildConfiguration = BuildConfiguration::Debug; +#elif BUILD_DEVELOPMENT + buildConfiguration = BuildConfiguration::Development; +#elif BUILD_RELEASE + buildConfiguration = BuildConfiguration::Release; +#endif + switch (PLATFORM_TYPE) + { + case PlatformType::Windows: + buildPlatform = PLATFORM_64BITS ? BuildPlatform::Windows64 : BuildPlatform::Windows32; + break; + case PlatformType::XboxOne: + buildPlatform = BuildPlatform::XboxOne; + break; + case PlatformType::UWP: + buildPlatform = BuildPlatform::UWPx64; + break; + case PlatformType::Linux: + buildPlatform = BuildPlatform::LinuxX64; + break; + case PlatformType::PS4: + buildPlatform = BuildPlatform::PS4; + break; + case PlatformType::XboxScarlett: + buildPlatform = BuildPlatform::XboxScarlett; + break; + case PlatformType::Android: + buildPlatform = BuildPlatform::AndroidARM64; + break; + case PlatformType::Switch: + buildPlatform = BuildPlatform::Switch; + break; + case PlatformType::PS5: + buildPlatform = BuildPlatform::PS5; + break; + case PlatformType::Mac: + buildPlatform = BuildPlatform::MacOSx64; + break; + default: ; + } +} + void GameCookerImpl::CallEvent(GameCooker::EventType type) { if (Internal_OnEvent == nullptr) @@ -492,7 +538,6 @@ bool GameCookerImpl::Build() // Late init feature if (Steps.IsEmpty()) { - // Create steps Steps.Add(New()); Steps.Add(New()); Steps.Add(New()); @@ -517,6 +562,8 @@ bool GameCookerImpl::Build() { if (GameCooker::IsCancelRequested()) break; + if (data.Options & BuildOptions::NoCook) + continue; auto step = Steps[stepIndex]; data.NextStep(); @@ -544,6 +591,24 @@ bool GameCookerImpl::Build() { FileSystem::ShowFileExplorer(data.OriginalOutputPath); } + + if (data.Options & BuildOptions::AutoRun) + { + String executableFile, commandLineFormat, workingDir; + data.Tools->OnRun(data, executableFile, commandLineFormat, workingDir); + if (executableFile.HasChars()) + { + const String gameArgs; // TODO: pass custom game run args from Editor? eg. starting map? or client info? + const String commandLine = commandLineFormat.HasChars() ? String::Format(*commandLineFormat, gameArgs) : gameArgs; + if (workingDir.IsEmpty()) + workingDir = data.NativeCodeOutputPath; + Platform::StartProcess(executableFile, commandLine, workingDir); + } + else + { + LOG(Warning, "Missing executable to run or platform doesn't support build&run."); + } + } } IsRunning = false; CancelFlag = 0; diff --git a/Source/Editor/Cooker/GameCooker.h b/Source/Editor/Cooker/GameCooker.h index 981151c63..6ff563e26 100644 --- a/Source/Editor/Cooker/GameCooker.h +++ b/Source/Editor/Cooker/GameCooker.h @@ -90,6 +90,11 @@ public: /// If set to true wait for the stopped building end. API_FUNCTION() static void Cancel(bool waitForEnd = false); + /// + /// Gets the current Editor build info (platform, configuration, etc). + /// + API_FUNCTION() static void GetCurrentPlatform(API_PARAM(Out) PlatformType& platform, API_PARAM(Out) BuildPlatform& buildPlatform, API_PARAM(Out) BuildConfiguration& buildConfiguration); + /// /// Building event type. /// diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index 57183df5b..457c068c9 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -36,11 +36,10 @@ ArchitectureType WindowsPlatformTools::GetArchitecture() const bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) { const auto platformSettings = WindowsPlatformSettings::Get(); - const auto& outputPath = data.NativeCodeOutputPath; // Apply executable icon Array files; - FileSystem::DirectoryGetFiles(files, outputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly); + FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly); if (files.HasItems()) { TextureData iconData; @@ -57,4 +56,15 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) return false; } +void WindowsPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) +{ + // Pick the first executable file + Array files; + FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly); + if (files.HasItems()) + { + executableFile = files[0]; + } +} + #endif diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h index a6ae40fd4..404a9f9d6 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h @@ -30,6 +30,7 @@ public: PlatformType GetPlatform() const override; ArchitectureType GetArchitecture() const override; bool OnDeployBinaries(CookingData& data) override; + void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override; }; #endif diff --git a/Source/Editor/Cooker/PlatformTools.h b/Source/Editor/Cooker/PlatformTools.h index ac07c88af..58888964f 100644 --- a/Source/Editor/Cooker/PlatformTools.h +++ b/Source/Editor/Cooker/PlatformTools.h @@ -203,4 +203,15 @@ public: { return false; } + + /// + /// Called to run the cooked game build on device. + /// + /// The cooking data. + /// The game executable file path to run (or tool path to run if build should run on remote device). Empty if not supported. + /// The command line for executable file. Use `{0}` to insert custom command line for passing to the cooked game. + /// Overriden custom working directory to use. Leave empty if use cooked data output folder. + virtual void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) + { + } }; diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 7c71c5628..829ad5e00 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -485,6 +485,9 @@ namespace FlaxEditor.Modules cm.VisibleChanged += OnMenuGameShowHide; _menuGamePlay = cm.AddButton("Play", "F5", Editor.Simulation.RequestStartPlay); _menuGamePause = cm.AddButton("Pause", "F6", Editor.Simulation.RequestPausePlay); + cm.AddSeparator(); + cm.AddButton("Cook&Run", Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip("Runs Game Cooker to build the game for this platform and runs the game after."); + cm.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked).LinkTooltip("Runs the game build from the last cooking output. Use Cook&Play or Game Cooker first."); // Tools MenuTools = MainMenu.AddButton("Tools"); diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 58b7ac5cc..e2fb99464 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.Windows public readonly GameCookerWindow GameCookerWin; public readonly PlatformSelector Selector; - private readonly Dictionary PerPlatformOptions = new Dictionary + internal readonly Dictionary PerPlatformOptions = new Dictionary { { PlatformType.Windows, new Windows() }, { PlatformType.XboxOne, new XboxOne() }, @@ -67,7 +67,7 @@ namespace FlaxEditor.Windows } [HideInEditor] - abstract class Platform + internal abstract class Platform { [HideInEditor] public bool IsSupported; @@ -490,10 +490,12 @@ namespace FlaxEditor.Windows { public string PresetName; public BuildTarget Target; + public BuildOptions Options; } private PresetsColumn _presets; private TargetsColumn _targets; + private BuildTabProxy _buildTabProxy; private int _selectedPresetIndex = -1; private int _selectedTargetIndex = -1; private CustomEditorPresenter _targetSettings; @@ -617,6 +619,44 @@ namespace FlaxEditor.Windows }); } + /// + /// Builds the target for this platform and runs it on this device. + /// + public void BuildAndRun() + { + Editor.Log("Building and running"); + GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration); + _buildingQueue.Enqueue(new QueueItem + { + Target = new BuildTarget + { + Output = _buildTabProxy.PerPlatformOptions[platform].Output, + Platform = buildPlatform, + Mode = buildConfiguration, + }, + Options = BuildOptions.AutoRun, + }); + } + + /// + /// Runs the cooked game for this platform on this device. + /// + public void RunCooked() + { + Editor.Log("Running cooked build"); + GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration); + _buildingQueue.Enqueue(new QueueItem + { + Target = new BuildTarget + { + Output = _buildTabProxy.PerPlatformOptions[platform].Output, + Platform = buildPlatform, + Mode = buildConfiguration, + }, + Options = BuildOptions.AutoRun | BuildOptions.NoCook, + }); + } + private void BuildTarget() { if (_data == null || _data.Length <= _selectedPresetIndex || _selectedPresetIndex == -1) @@ -829,7 +869,8 @@ namespace FlaxEditor.Windows var settings = new CustomEditorPresenter(null); settings.Panel.Parent = panel; - settings.Select(new BuildTabProxy(this, platformSelector)); + _buildTabProxy = new BuildTabProxy(this, platformSelector); + settings.Select(_buildTabProxy); } private void OnPlatformSelectorSizeChanged(Control platformSelector) @@ -894,7 +935,7 @@ namespace FlaxEditor.Windows _preBuildAction = target.PreBuildAction; _postBuildAction = target.PostBuildAction; - GameCooker.Build(target.Platform, target.Mode, target.Output, BuildOptions.None, target.CustomDefines, item.PresetName, target.Name); + GameCooker.Build(target.Platform, target.Mode, target.Output, item.Options, target.CustomDefines, item.PresetName, target.Name); } else if (_exitOnBuildEnd) { @@ -908,6 +949,7 @@ namespace FlaxEditor.Windows public override void OnDestroy() { GameCooker.Event -= OnGameCookerEvent; + _buildTabProxy = null; base.OnDestroy(); }