// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "LiveCodingModule.h" #include "Modules/ModuleManager.h" #include "Features/IModularFeatures.h" #include "HAL/IConsoleManager.h" #include "Misc/CoreDelegates.h" #include "LiveCodingLog.h" #include "External/LC_EntryPoint.h" #include "Misc/App.h" #include "Misc/Paths.h" #include "Misc/ConfigCacheIni.h" #include "LiveCodingSettings.h" #include "ISettingsModule.h" #include "Windows/WindowsHWrapper.h" IMPLEMENT_MODULE(FLiveCodingModule, LiveCoding) #define LOCTEXT_NAMESPACE "LiveCodingModule" bool GIsCompileActive = false; FString GLiveCodingConsolePath; FString GLiveCodingConsoleArguments; FLiveCodingModule::FLiveCodingModule() : bEnabledLastTick(false) , bStarted(false) { } void FLiveCodingModule::StartupModule() { Settings = GetMutableDefault(); IConsoleManager& ConsoleManager = IConsoleManager::Get(); EnableCommand = ConsoleManager.RegisterConsoleCommand( TEXT("LiveCoding"), TEXT("Enables live coding support"), FConsoleCommandDelegate::CreateRaw(this, &FLiveCodingModule::EnableForSession, true), ECVF_Cheat ); ConsolePathVariable = ConsoleManager.RegisterConsoleVariable( TEXT("LiveCoding.ConsolePath"), FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/Win64/LiveCodingConsole.exe")), TEXT("Path to the live coding console application"), ECVF_Cheat ); EndFrameDelegateHandle = FCoreDelegates::OnEndFrame.AddRaw(this, &FLiveCodingModule::Tick); ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); if (SettingsModule != nullptr) { SettingsModule->RegisterSettings("Editor", "General", "Live Coding", LOCTEXT("LiveCodingSettingsName", "Live Coding"), LOCTEXT("LiveCodintSettingsDescription", "Settings for recompiling C++ code while the engine is running."), GetMutableDefault() ); } extern void Startup(Windows::HINSTANCE hInstance); Startup(hInstance); if (Settings->bEnabled) { if(Settings->Startup == ELiveCodingStartupMode::Automatic) { StartLiveCoding(); ShowConsole(); } else if(Settings->Startup == ELiveCodingStartupMode::AutomaticButHidden) { GLiveCodingConsoleArguments = L"-Hidden"; StartLiveCoding(); } } if(FParse::Param(FCommandLine::Get(), TEXT("LiveCoding"))) { StartLiveCoding(); } bEnabledLastTick = Settings->bEnabled; } void FLiveCodingModule::ShutdownModule() { extern void Shutdown(); Shutdown(); FCoreDelegates::OnEndFrame.Remove(EndFrameDelegateHandle); IConsoleManager& ConsoleManager = IConsoleManager::Get(); ConsoleManager.UnregisterConsoleObject(ConsolePathVariable); ConsoleManager.UnregisterConsoleObject(EnableCommand); } void FLiveCodingModule::EnableByDefault(bool bEnable) { Settings->bEnabled = bEnable; EnableForSession(bEnable); } bool FLiveCodingModule::IsEnabledByDefault() const { return Settings->bEnabled; } void FLiveCodingModule::EnableForSession(bool bEnable) { if (bEnable) { if(!bStarted) { StartLiveCoding(); ShowConsole(); } } else { if(bStarted) { UE_LOG(LogLiveCoding, Display, TEXT("Console will be hidden but remain running in the background. Restart to disable completely.")); LppSetActive(false); LppSetVisible(false); } } } bool FLiveCodingModule::IsEnabledForSession() const { return bStarted; } void FLiveCodingModule::ShowConsole() { if (bStarted) { LppSetVisible(true); LppSetActive(true); LppShowConsole(); } } void FLiveCodingModule::Compile() { if(!GIsCompileActive) { EnableForSession(true); if(bStarted) { LppTriggerRecompile(); GIsCompileActive = true; } } } bool FLiveCodingModule::IsCompiling() const { return GIsCompileActive; } void FLiveCodingModule::Tick() { if (Settings->bEnabled != bEnabledLastTick && Settings->Startup != ELiveCodingStartupMode::Manual) { EnableForSession(Settings->bEnabled); bEnabledLastTick = Settings->bEnabled; } } bool FLiveCodingModule::StartLiveCoding() { if(!bStarted) { // Setup the console path GLiveCodingConsolePath = ConsolePathVariable->GetString(); if (!FPaths::FileExists(GLiveCodingConsolePath)) { UE_LOG(LogLiveCoding, Error, TEXT("Unable to start live coding session. Missing executable '%s'. Use the LiveCoding.ConsolePath console variable to modify."), *GLiveCodingConsolePath); return false; } UE_LOG(LogLiveCoding, Display, TEXT("Starting LiveCoding")); // Enable external build system LppUseExternalBuildSystem(); // Enable the server FString ProcessGroup = FString::Printf(TEXT("UE4_%s_0x%08x"), FApp::GetProjectName(), GetTypeHash(FPaths::ProjectDir())); LppRegisterProcessGroup(TCHAR_TO_ANSI(*ProcessGroup)); // Build the command line FString Arguments; Arguments += FString::Printf(TEXT("%s"), FPlatformMisc::GetUBTPlatform()); Arguments += FString::Printf(TEXT(" %s"), EBuildConfigurations::ToString(FApp::GetBuildConfiguration())); Arguments += FString::Printf(TEXT(" -TargetType=%s"), FPlatformMisc::GetUBTTarget()); if(FPaths::IsProjectFilePathSet()) { Arguments += FString::Printf(TEXT(" -Project=\"%s\""), *FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath())); } LppSetBuildArguments(*Arguments); // Configure all the current modules UpdateModules(); // Register a delegate to listen for new modules loaded from this point onwards ModulesChangedDelegateHandle = FModuleManager::Get().OnModulesChanged().AddRaw(this, &FLiveCodingModule::OnModulesChanged); // Mark it as started bStarted = true; } return true; } void FLiveCodingModule::UpdateModules() { #if IS_MONOLITHIC wchar_t FullFilePath[WINDOWS_MAX_PATH]; verify(GetModuleFileName(hInstance, FullFilePath, ARRAY_COUNT(FullFilePath))); EnableModule(FullFilePath); #else TArray ModuleStatuses; FModuleManager::Get().QueryModules(ModuleStatuses); for (const FModuleStatus& ModuleStatus : ModuleStatuses) { if (ModuleStatus.bIsLoaded) { FString FullFilePath = FPaths::ConvertRelativePathToFull(ModuleStatus.FilePath); ConfigureModule(FName(*ModuleStatus.Name), ModuleStatus.bIsGameModule, FullFilePath); } } #endif } void FLiveCodingModule::EnableModule(const FString& FullFilePath) { if (!EnabledModules.Contains(FullFilePath)) { LppEnableModule(*FullFilePath); EnabledModules.Add(FullFilePath); } } void FLiveCodingModule::DisableModule(const FString& FullFilePath) { if(EnabledModules.Contains(FullFilePath)) { LppDisableModule(*FullFilePath); EnabledModules.Remove(FullFilePath); } } void FLiveCodingModule::OnModulesChanged(FName ModuleName, EModuleChangeReason Reason) { #if !IS_MONOLITHIC if (Reason == EModuleChangeReason::ModuleLoaded) { FModuleStatus Status; if (FModuleManager::Get().QueryModule(ModuleName, Status)) { FString FullFilePath = FPaths::ConvertRelativePathToFull(Status.FilePath); ConfigureModule(ModuleName, Status.bIsGameModule, FullFilePath); } } #endif } void FLiveCodingModule::ConfigureModule(const FName& Name, bool bIsProjectModule, const FString& FullFilePath) { #if !IS_MONOLITHIC if (ShouldEnableModule(Name, bIsProjectModule, FullFilePath)) { EnableModule(FullFilePath); } else { DisableModule(FullFilePath); } #endif } bool FLiveCodingModule::ShouldEnableModule(const FName& Name, bool bIsProjectModule, const FString& FullFilePath) const { if (Settings->ExcludeSpecificModules.Contains(Name)) { return false; } if (Settings->IncludeSpecificModules.Contains(Name)) { return true; } if (bIsProjectModule) { if (Settings->bIncludeProjectModules == Settings->bIncludeProjectPluginModules) { return Settings->bIncludeProjectModules; } FString FullProjectPluginsDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectPluginsDir()); if(FullFilePath.StartsWith(FullProjectPluginsDir)) { return Settings->bIncludeProjectPluginModules; } else { return Settings->bIncludeProjectModules; } } else { if (FApp::IsEngineInstalled()) { return false; } if (Settings->bIncludeEngineModules == Settings->bIncludeEnginePluginModules) { return Settings->bIncludeEngineModules; } FString FullEnginePluginsDir = FPaths::ConvertRelativePathToFull(FPaths::EnginePluginsDir()); if(FullFilePath.StartsWith(FullEnginePluginsDir)) { return Settings->bIncludeEnginePluginModules; } else { return Settings->bIncludeEngineModules; } } } #undef LOCTEXT_NAMESPACE