From 31ae80b060d0b353c75ef24ca55b33b1cecdf408 Mon Sep 17 00:00:00 2001 From: "matthieu.begoghina" <12561988+Voulz@users.noreply.github.com> Date: Tue, 23 Jul 2024 00:20:31 +1000 Subject: [PATCH] Added a Packaging Notification allowing Cancelling + Minor fixes --- Source/GFPakExporter/GFPakExporter.Build.cs | 1 + .../GFPakExporter/Private/AuroraBuildTask.cpp | 365 ++++++++++++++++++ .../Private/GFPakExporterSubsystem.cpp | 112 +----- .../Private/Slate/SAuroraExportWizard.cpp | 11 +- Source/GFPakExporter/Public/AuroraBuildTask.h | 43 +++ .../Public/AuroraExporterConfig.h | 3 + .../Public/GFPakExporterSubsystem.h | 16 +- .../Private/GFPakLoaderEditorModule.cpp | 2 +- 8 files changed, 447 insertions(+), 106 deletions(-) create mode 100644 Source/GFPakExporter/Private/AuroraBuildTask.cpp create mode 100644 Source/GFPakExporter/Public/AuroraBuildTask.h diff --git a/Source/GFPakExporter/GFPakExporter.Build.cs b/Source/GFPakExporter/GFPakExporter.Build.cs index 74b5840..0aaebaa 100644 --- a/Source/GFPakExporter/GFPakExporter.Build.cs +++ b/Source/GFPakExporter/GFPakExporter.Build.cs @@ -37,6 +37,7 @@ public GFPakExporter(ReadOnlyTargetRules Target) : base(Target) "AssetRegistry", "ContentBrowser", "ToolWidgets", + "OutputLog", } ); } diff --git a/Source/GFPakExporter/Private/AuroraBuildTask.cpp b/Source/GFPakExporter/Private/AuroraBuildTask.cpp new file mode 100644 index 0000000..aa1f890 --- /dev/null +++ b/Source/GFPakExporter/Private/AuroraBuildTask.cpp @@ -0,0 +1,365 @@ +// Copyright GeoTech BV +#include "AuroraBuildTask.h" + +#include "GFPakExporterLog.h" +#include "ITargetDeviceServicesModule.h" +#include "OutputLogModule.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Widgets/Notifications/SNotificationList.h" + +#define LOCTEXT_NAMESPACE "FGFPakLoaderEditorModule" + +class FMainFrameActionsNotificationTask // As per UATHelperModule.cpp +{ +public: + + FMainFrameActionsNotificationTask(TWeakPtr InNotificationItemPtr, SNotificationItem::ECompletionState InCompletionState, const FText& InText, const FText& InLinkText = FText(), bool InExpireAndFadeout = true) + : CompletionState(InCompletionState) + , NotificationItemPtr(InNotificationItemPtr) + , Text(InText) + , LinkText(InLinkText) + , bExpireAndFadeout(InExpireAndFadeout) + + { } + + static void HandleHyperlinkNavigate() + { + FMessageLog("PackagingResults").Open(EMessageSeverity::Error, true); + } + + static void HandleDismissButtonClicked() + { + TSharedPtr NotificationItem = ExpireNotificationItemPtr.Pin(); + if (NotificationItem.IsValid()) + { + + NotificationItem->SetExpireDuration(0.0f); + NotificationItem->SetFadeOutDuration(0.0f); + NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); + NotificationItem->ExpireAndFadeout(); + ExpireNotificationItemPtr.Reset(); + } + } + + void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) + { + if ( NotificationItemPtr.IsValid() ) + { + if (CompletionState == SNotificationItem::CS_Fail) + { + GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue")); + } + else + { + GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileSuccess_Cue.CompileSuccess_Cue")); + } + + TSharedPtr NotificationItem = NotificationItemPtr.Pin(); + NotificationItem->SetText(Text); + + if (!LinkText.IsEmpty()) + { + FText VLinkText(LinkText); + const TAttribute Message = TAttribute::Create(TAttribute::FGetter::CreateLambda([VLinkText]() + { + return VLinkText; + })); + + NotificationItem->SetHyperlink(FSimpleDelegate::CreateStatic(&HandleHyperlinkNavigate), Message); + + } + + ExpireNotificationItemPtr = NotificationItem; + if (bExpireAndFadeout) + { + NotificationItem->SetExpireDuration(6.0f); + NotificationItem->SetFadeOutDuration(0.5f); + NotificationItem->SetCompletionState(CompletionState); + NotificationItem->ExpireAndFadeout(); + } + else + { + // Handling the notification expiration in callback + NotificationItem->SetCompletionState(CompletionState); + } + + // // Since the task was probably fairly long, we should try and grab the users attention if they have the option enabled. + // const UEditorPerProjectUserSettings* SettingsPtr = GetDefault(); + // if (SettingsPtr->bGetAttentionOnUATCompletion) + // { + // IMainFrameModule* MainFrame = FModuleManager::LoadModulePtr("MainFrame"); + // if (MainFrame != nullptr) + // { + // TSharedPtr ParentWindow = MainFrame->GetParentWindow(); + // if (ParentWindow != nullptr) + // { + // ParentWindow->DrawAttention(FWindowDrawAttentionParameters(EWindowDrawAttentionRequestType::UntilActivated)); + // } + // } + // } + } + } + + static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; } + ENamedThreads::Type GetDesiredThread() { return ENamedThreads::GameThread; } + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(FMainFrameActionsNotificationTask, STATGROUP_TaskGraphTasks); + } + +private: + + static TWeakPtr ExpireNotificationItemPtr; + + SNotificationItem::ECompletionState CompletionState; + TWeakPtr NotificationItemPtr; + FText Text; + FText LinkText; + bool bExpireAndFadeout; +}; + +TWeakPtr FMainFrameActionsNotificationTask::ExpireNotificationItemPtr; + + + +bool FAuroraBuildTask::Launch(const ILauncherPtr& Launcher) +{ + UE_LOG(LogGFPakExporter, Verbose, TEXT("FAuroraBuildTask::Launch")); + + if (Status != ELauncherTaskStatus::Pending) + { + UE_LOG(LogGFPakExporter, Error, TEXT("Aurora Build Task was already launched")); + return false; + } + + if (!ensure(Profile)) + { + UE_LOG(LogGFPakExporter, Error, TEXT("Launcher Profile is null")); + return false; + } + + if (!Profile->IsValidForLaunch()) + { + UE_LOG(LogGFPakExporter, Error, TEXT("Launcher profile '%s' for is not valid for launch."), + *Profile->GetName()); + for (int32 i = 0; i < (int32)ELauncherProfileValidationErrors::Count; ++i) + { + ELauncherProfileValidationErrors::Type Error = (ELauncherProfileValidationErrors::Type)i; + if (Profile->HasValidationError(Error)) + { + UE_LOG(LogGFPakExporter, Error, TEXT("ValidationError: %s"), *LexToStringLocalized(Error)); + } + } + return false; + } + + if (!Launcher) + { + UE_LOG(LogGFPakExporter, Error, TEXT("The given Launcher is null")); + return false; + } + + ITargetDeviceServicesModule& DeviceServiceModule = FModuleManager::LoadModuleChecked(TEXT("TargetDeviceServices")); + TSharedRef DeviceProxyManager = DeviceServiceModule.GetDeviceProxyManager(); + + Status = ELauncherTaskStatus::Busy; + LauncherWorker = Launcher->Launch(DeviceProxyManager, Profile.ToSharedRef()); + // Not ideal but not able to set callbacks before + // This will allow us to pipe the launcher messages into the command window. + LauncherWorker->OnOutputReceived().AddSP(this, &FAuroraBuildTask::MessageReceived); + // Allows us to exit this command once the launcher worker has completed or is canceled + LauncherWorker->OnStageStarted().AddSP(this, &FAuroraBuildTask::HandleStageStarted); + LauncherWorker->OnStageCompleted().AddSP(this, &FAuroraBuildTask::HandleStageCompleted); + LauncherWorker->OnCompleted().AddSP(this, &FAuroraBuildTask::LaunchCompleted); + LauncherWorker->OnCanceled().AddSP(this, &FAuroraBuildTask::LaunchCanceled); + + TArray TaskList; + int NbTasks = LauncherWorker->GetTasks(TaskList); + UE_LOG(LogGFPakExporter, Display, TEXT("There are '%i' tasks to be completed."), NbTasks); + + GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileStart_Cue.CompileStart_Cue")); + + CreateNotification(); + + return true; +} + +void FAuroraBuildTask::MessageReceived(const FString& InMessage) +{ + GLog->Logf(ELogVerbosity::Log, TEXT("%s"), *InMessage); +} + +void FAuroraBuildTask::HandleStageStarted(const FString& InStage) +{ + UE_LOG(LogGFPakExporter, Warning, TEXT("Starting stage %s."), *InStage); + if (ensure(LauncherWorker)) + { + TArray TaskList; + int NbTasks = LauncherWorker->GetTasks(TaskList); + int CurrentTask = 1 + TaskList.IndexOfByPredicate([](const ILauncherTaskPtr& Task) + { + return Task->GetStatus() != ELauncherTaskStatus::Completed; + }); + + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("TaskStatusNb"), FText::Format(LOCTEXT("Notification_TaskStatusNb", "[{0}/{1}] "), FText::AsNumber(CurrentTask), FText::AsNumber(NbTasks))); + Arguments.Add(TEXT("Stage"), FText::FromString(InStage)); + + FScopeLock Lock(&NotificationTextMutex); + NotificationText = FText::Format(LOCTEXT("Notification_DisplayText", "{TaskStatusNb}{Stage}"), Arguments); + } +} + +void FAuroraBuildTask::HandleStageCompleted(const FString& InStage, double StageTime) +{ + UE_LOG(LogGFPakExporter, Warning, TEXT("Completed Stage %s."), *InStage); +} + +void FAuroraBuildTask::LaunchCompleted(bool Outcome, double ExecutionTime, int32 ReturnCode) +{ + UE_LOG(LogGFPakExporter, Log, TEXT("Profile launch command %s."), Outcome ? TEXT("is SUCCESSFUL") : TEXT("has FAILED")); + + if (LauncherWorker) + { + if ( ReturnCode == 0 ) + { + // if (!ResultLocation.IsEmpty()) + // { + // if (TSharedPtr SharedNotificationItemPtr = NotificationItemPtr.Pin()) + // { + // SharedNotificationItemPtr->SetHyperlink(FSimpleDelegate::CreateStatic(&FUATHelperModule::HandleUatResultHyperlinkNavigate, ResultLocation), LOCTEXT("ShowOutputLocation", "Show in Explorer")); + // } + // + // } + + TGraphTask::CreateTask().ConstructAndDispatchWhenReady( + NotificationItemPtr, + SNotificationItem::CS_Success, + FText::Format(LOCTEXT("UatProcessSucceededNotification", "'{0}' complete!"), FText::FromString(Settings.Config.DLCName)) + ); + Status = ELauncherTaskStatus::Completed; + } + else + { + TGraphTask::CreateTask().ConstructAndDispatchWhenReady( + NotificationItemPtr, + SNotificationItem::CS_Fail, + FText::Format(LOCTEXT("PackagerFailedNotification", "'{0}' failed!"), FText::FromString(Settings.Config.DLCName)), + FText(), //FPackagingErrorHandler::GetHasAssetErrors() ? LOCTEXT("ShowResultsLogHyperlink", "Show Results Log") : FText(), + false); + Status = ELauncherTaskStatus::Failed; + } + // LauncherWorker = nullptr; + } +} + +void FAuroraBuildTask::LaunchCanceled(double ExecutionTime) +{ + UE_LOG(LogGFPakExporter, Log, TEXT("Profile launch command was canceled.")); + + if (LauncherWorker) + { + TGraphTask::CreateTask().ConstructAndDispatchWhenReady( + NotificationItemPtr, + SNotificationItem::CS_Fail, + FText::Format(LOCTEXT("UatProcessFailedNotification", "'{0}' canceled!"), FText::FromString(Settings.Config.DLCName)) + ); + Status = ELauncherTaskStatus::Pending; + // LauncherWorker = nullptr; + } +} + +bool FAuroraBuildTask::CreateNotification() +{ + FText DLCName = FText::FromString(Settings.Config.DLCName); + { + FScopeLock Lock(&NotificationTextMutex); + NotificationText = FText::GetEmpty(); + } + + // As per FUATHelperModule::CreateUatTask + FFormatNamedArguments Arguments; + FText PlatformDisplayName = Profile->GetCookedPlatforms().IsEmpty() ? FText::GetEmpty() : FText::FromString(Profile->GetCookedPlatforms()[0]); + Arguments.Add(TEXT("Platform"), FText::FromString(Profile->GetCookedPlatforms()[0])); + Arguments.Add(TEXT("DLCName"), DLCName); + FText NotificationFormat = (PlatformDisplayName.IsEmpty()) ? LOCTEXT("Notification_Title_NoPlatform", "Aurora Packaging...") : LOCTEXT("Notification_Title", "Aurora Packaging for {Platform}:\n{DLCName}"); + FNotificationInfo Info( FText::Format( NotificationFormat, Arguments) ); + + Info.Image = FAppStyle::GetBrush(TEXT("MainFrame.CookContent")); + Info.bFireAndForget = false; + Info.FadeOutDuration = 0.0f; + Info.ExpireDuration = 0.0f; + Info.SubText = TAttribute::CreateSP(this, &FAuroraBuildTask::HandleNotificationGetText), + Info.Hyperlink = FSimpleDelegate::CreateSP(this, &FAuroraBuildTask::HandleNotificationHyperlinkNavigateShowOutput); + Info.HyperlinkText = LOCTEXT("ShowOutputLogHyperlink", "Show Output Log"); + Info.ButtonDetails.Add( + FNotificationButtonInfo( + LOCTEXT("UatTaskCancel", "Cancel"), + LOCTEXT("UatTaskCancelToolTip", "Cancels execution of this task."), + FSimpleDelegate::CreateSP(this, &FAuroraBuildTask::HandleNotificationCancelButtonClicked), + SNotificationItem::CS_Pending + ) + ); + Info.ButtonDetails.Add( + FNotificationButtonInfo( + LOCTEXT("UatTaskDismiss", "Dismiss"), + FText(), + FSimpleDelegate::CreateSP(this, &FAuroraBuildTask::HandleNotificationDismissButtonClicked), + SNotificationItem::CS_Fail + ) + ); + + TSharedPtr NotificationItem = FSlateNotificationManager::Get().AddNotification(Info); + TSharedPtr OldNotification = NotificationItemPtr.Pin(); + if(OldNotification.IsValid()) + { + OldNotification->Fadeout(); + } + if (ensure(NotificationItem.IsValid())) + { + NotificationItem->SetCompletionState(SNotificationItem::CS_Pending); + NotificationItemPtr = NotificationItem; + return true; + } + else + { + UE_LOG(LogGFPakExporter, Error, TEXT("Unable to create a Notification")) + NotificationItemPtr = nullptr; + return false; + } +} + +FText FAuroraBuildTask::HandleNotificationGetText() const +{ + FScopeLock Lock(&NotificationTextMutex); + return NotificationText; +} + +void FAuroraBuildTask::HandleNotificationCancelButtonClicked() +{ + if (LauncherWorker.IsValid() ) + { + LauncherWorker->Cancel(); + } +} + +void FAuroraBuildTask::HandleNotificationDismissButtonClicked() +{ + TSharedPtr NotificationItem = NotificationItemPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->SetExpireDuration(0.0f); + NotificationItem->SetFadeOutDuration(0.0f); + NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); + NotificationItem->ExpireAndFadeout(); + NotificationItemPtr.Reset(); + } +} + +void FAuroraBuildTask::HandleNotificationHyperlinkNavigateShowOutput() +{ + FOutputLogModule& OutputLogModule = FOutputLogModule::Get(); + OutputLogModule.FocusOutputLog(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/GFPakExporter/Private/GFPakExporterSubsystem.cpp b/Source/GFPakExporter/Private/GFPakExporterSubsystem.cpp index 39e9fc9..e65bc1c 100644 --- a/Source/GFPakExporter/Private/GFPakExporterSubsystem.cpp +++ b/Source/GFPakExporter/Private/GFPakExporterSubsystem.cpp @@ -3,6 +3,7 @@ #include "GFPakExporterSubsystem.h" +#include "AuroraBuildTask.h" #include "AuroraExporterConfig.h" #include "GFPakExporter.h" #include "GFPakExporterLog.h" @@ -11,6 +12,7 @@ #include "ITargetDeviceServicesModule.h" #include "Slate/SAuroraExportWizard.h" +#define LOCTEXT_NAMESPACE "UGFPakExporterSubsystem" void UGFPakExporterSubsystem::PromptForExport(const FAuroraExporterSettings& InSettings) { @@ -22,13 +24,13 @@ void UGFPakExporterSubsystem::PromptForExport(const FAuroraExporterSettings& InS if (Settings) { ILauncherProfilePtr Profile = Subsystem->CreateLauncherProfileFromSettings(Settings.GetValue()); - Subsystem->LaunchProfile(Profile); + Subsystem->LaunchProfile(Profile, Settings.GetValue()); } } })); } -ILauncherProfilePtr UGFPakExporterSubsystem::CreateLauncherProfileFromSettings(const FAuroraExporterSettings& InSettings) +ILauncherProfilePtr UGFPakExporterSubsystem::CreateLauncherProfileFromSettings(const FAuroraExporterSettings& InSettings) const { UE_LOG(LogGFPakExporter, Display, TEXT("UGFPakExporterSubsystem::CreateLauncherProfileFromConfig")); @@ -46,14 +48,8 @@ ILauncherProfilePtr UGFPakExporterSubsystem::CreateLauncherProfileFromSettings(c } UE_LOG(LogGFPakExporter, Display, TEXT("Saved the Exporter Config to '%s'"), *ConfigFilename); - ILauncherServicesModule* LauncherServicesModule = FModuleManager::GetModulePtr(TEXT("LauncherServices")); - if (!LauncherServicesModule) - { - UE_LOG(LogGFPakExporter, Error, TEXT("Unable to get the 'LauncherServices' module")); - return nullptr; - } - - TSharedRef LauncherProfileManager = LauncherServicesModule->GetProfileManager(); + ILauncherServicesModule& LauncherServicesModule = FModuleManager::LoadModuleChecked(TEXT("LauncherServices")); + TSharedRef LauncherProfileManager = LauncherServicesModule.GetProfileManager(); TArray Platforms = GetTargetPlatformManager()->GetTargetPlatforms(); ITargetDeviceServicesModule& DeviceServiceModule = FModuleManager::LoadModuleChecked(TEXT("TargetDeviceServices")); TSharedRef DeviceProxyManager = DeviceServiceModule.GetDeviceProxyManager(); @@ -62,6 +58,7 @@ ILauncherProfilePtr UGFPakExporterSubsystem::CreateLauncherProfileFromSettings(c Profile->SetDefaults(); Profile->SetProjectSpecified(true); + Profile->SetBuildUAT(InSettings.bBuildUAT); // Build Profile->SetBuildConfiguration(InSettings.GetBuildConfiguration()); // Cook @@ -95,101 +92,30 @@ ILauncherProfilePtr UGFPakExporterSubsystem::CreateLauncherProfileFromSettings(c return Profile; } -ILauncherWorkerPtr UGFPakExporterSubsystem::LaunchProfile(const ILauncherProfilePtr& InProfile) +TSharedPtr UGFPakExporterSubsystem::LaunchProfile(const ILauncherProfilePtr& InProfile, const FAuroraExporterSettings& InSettings) { - UE_LOG(LogGFPakExporter, Verbose, TEXT("UGFPakExporterSubsystem::LaunchProfile")); - - if (!InProfile) + if (IsExporting()) { - UE_LOG(LogGFPakExporter, Error, TEXT("Launcher profile is null")); - return nullptr; - } - - if (!InProfile->IsValidForLaunch()) - { - UE_LOG(LogGFPakExporter, Error, TEXT("Launcher profile '%s' for is not valid for launch."), - *InProfile->GetName()); - for (int32 i = 0; i < (int32)ELauncherProfileValidationErrors::Count; ++i) - { - ELauncherProfileValidationErrors::Type Error = (ELauncherProfileValidationErrors::Type)i; - if (InProfile->HasValidationError(Error)) - { - UE_LOG(LogGFPakExporter, Error, TEXT("ValidationError: %s"), *LexToStringLocalized(Error)); - } - } + UE_LOG(LogGFPakExporter, Error, TEXT("A Build Task is already processing, unable to start a new one.")); return nullptr; } - if (LauncherWorker) + if (!Launcher) { - UE_LOG(LogGFPakExporter, Error, TEXT("A Profile is already processing. Unable to start a new one.")); - return nullptr; + ILauncherServicesModule& LauncherServicesModule = FModuleManager::LoadModuleChecked(TEXT("LauncherServices")); + Launcher = LauncherServicesModule.CreateLauncher(); } - - ILauncherServicesModule* LauncherServicesModule = FModuleManager::GetModulePtr(TEXT("LauncherServices")); - if (!LauncherServicesModule) + AuroraBuildTask = MakeShared(InProfile, InSettings); + if (!AuroraBuildTask->Launch(Launcher)) { - UE_LOG(LogGFPakExporter, Error, TEXT("Unable to get the 'LauncherServices' module")); return nullptr; } - - // We are now ready, let's Launch - if (!Launcher) - { - Launcher = LauncherServicesModule->CreateLauncher(); - } - - ITargetDeviceServicesModule& DeviceServiceModule = FModuleManager::LoadModuleChecked(TEXT("TargetDeviceServices")); - TSharedRef DeviceProxyManager = DeviceServiceModule.GetDeviceProxyManager(); - - LauncherWorker = Launcher->Launch(DeviceProxyManager, InProfile.ToSharedRef()); - - // This will allow us to pipe the launcher messages into the command window. - LauncherWorker->OnOutputReceived().AddUObject(this, &UGFPakExporterSubsystem::MessageReceived, LauncherWorker); - // Allows us to exit this command once the launcher worker has completed or is canceled - LauncherWorker->OnStageStarted().AddUObject(this, &UGFPakExporterSubsystem::HandleStageStarted, LauncherWorker); - LauncherWorker->OnStageCompleted().AddUObject(this, &UGFPakExporterSubsystem::HandleStageCompleted, LauncherWorker); - LauncherWorker->OnCompleted().AddUObject(this, &UGFPakExporterSubsystem::LaunchCompleted, LauncherWorker); - LauncherWorker->OnCanceled().AddUObject(this, &UGFPakExporterSubsystem::LaunchCanceled, LauncherWorker); - - TArray TaskList; - int32 NumOfTasks = LauncherWorker->GetTasks(TaskList); - UE_LOG(LogGFPakExporter, Display, TEXT("There are '%i' tasks to be completed."), NumOfTasks); - - return LauncherWorker; -} - -void UGFPakExporterSubsystem::MessageReceived(const FString& InMessage, ILauncherWorkerPtr Worker) -{ - GLog->Logf(ELogVerbosity::Log, TEXT("%s"), *InMessage); -} - -void UGFPakExporterSubsystem::HandleStageStarted(const FString& InStage, ILauncherWorkerPtr Worker) -{ - UE_LOG(LogGFPakExporter, Warning, TEXT("Starting stage %s."), *InStage); + return AuroraBuildTask; } -void UGFPakExporterSubsystem::HandleStageCompleted(const FString& InStage, double StageTime, ILauncherWorkerPtr Worker) +bool UGFPakExporterSubsystem::IsExporting() const { - UE_LOG(LogGFPakExporter, Warning, TEXT("Completed Stage %s."), *InStage); + return AuroraBuildTask && AuroraBuildTask->GetStatus() == ELauncherTaskStatus::Busy; } -void UGFPakExporterSubsystem::LaunchCompleted(bool Outcome, double ExecutionTime, int32 ReturnCode, ILauncherWorkerPtr Worker) -{ - UE_LOG(LogGFPakExporter, Log, TEXT("Profile launch command %s."), Outcome ? TEXT("is SUCCESSFUL") : TEXT("has FAILED")); - - if (LauncherWorker == Worker) - { - LauncherWorker = nullptr; - } -} - -void UGFPakExporterSubsystem::LaunchCanceled(double ExecutionTime, ILauncherWorkerPtr Worker) -{ - UE_LOG(LogGFPakExporter, Log, TEXT("Profile launch command was canceled.")); - - if (LauncherWorker == Worker) - { - LauncherWorker = nullptr; - } -} +#undef LOCTEXT_NAMESPACE diff --git a/Source/GFPakExporter/Private/Slate/SAuroraExportWizard.cpp b/Source/GFPakExporter/Private/Slate/SAuroraExportWizard.cpp index 06fdf2f..44f912e 100644 --- a/Source/GFPakExporter/Private/Slate/SAuroraExportWizard.cpp +++ b/Source/GFPakExporter/Private/Slate/SAuroraExportWizard.cpp @@ -2,6 +2,7 @@ #include "SAuroraExportWizard.h" +#include "GFPakExporterSubsystem.h" #include "IStructureDetailsView.h" #include "SPrimaryButton.h" #include "Interfaces/IMainFrameModule.h" @@ -112,11 +113,15 @@ void SAuroraExportWizard::Construct(const FArguments& InArgs, const FAuroraExpor .OnClicked(this, &SAuroraExportWizard::HandleExportButtonClicked) .IsEnabled(TAttribute::CreateSPLambda(this, [Settings = Settings]() { - if (Settings) + if (GEditor && Settings) { - if (auto ExporterSettings = Settings->Cast()) + UGFPakExporterSubsystem* Subsystem = GEditor->GetEditorSubsystem(); + if (Subsystem && !Subsystem->IsExporting()) { - return !ExporterSettings->Config.DLCName.IsEmpty(); + if (FAuroraExporterSettings* ExporterSettings = Settings->Cast()) + { + return !ExporterSettings->Config.DLCName.IsEmpty(); + } } } return false; diff --git a/Source/GFPakExporter/Public/AuroraBuildTask.h b/Source/GFPakExporter/Public/AuroraBuildTask.h new file mode 100644 index 0000000..8a78998 --- /dev/null +++ b/Source/GFPakExporter/Public/AuroraBuildTask.h @@ -0,0 +1,43 @@ +// Copyright GeoTech BV + +#pragma once + +#include "CoreMinimal.h" +#include "AuroraExporterConfig.h" +#include "ILauncher.h" +#include "ILauncherWorker.h" + + + +struct FAuroraBuildTask : public TSharedFromThis +{ +public: + FAuroraBuildTask(const ILauncherProfilePtr& InProfile, const FAuroraExporterSettings& InSettings) + : Profile(InProfile), Settings(InSettings) {} + + bool Launch(const ILauncherPtr& Launcher); + ELauncherTaskStatus::Type GetStatus() const { return Status; }; +private: + ILauncherProfilePtr Profile{}; + FAuroraExporterSettings Settings; + ELauncherTaskStatus::Type Status = ELauncherTaskStatus::Pending; + ILauncherWorkerPtr LauncherWorker{}; + + void MessageReceived(const FString& InMessage); + void HandleStageStarted(const FString& InStage); + void HandleStageCompleted(const FString& InStage, double StageTime); + void LaunchCompleted(bool Outcome, double ExecutionTime, int32 ReturnCode); + void LaunchCanceled(double ExecutionTime); + +private: // Notification + bool CreateNotification(); + + TWeakPtr NotificationItemPtr; + mutable FCriticalSection NotificationTextMutex; + FText NotificationText; + + FText HandleNotificationGetText() const; + void HandleNotificationCancelButtonClicked(); + void HandleNotificationDismissButtonClicked(); + void HandleNotificationHyperlinkNavigateShowOutput(); +}; diff --git a/Source/GFPakExporter/Public/AuroraExporterConfig.h b/Source/GFPakExporter/Public/AuroraExporterConfig.h index fcbe094..57bec54 100644 --- a/Source/GFPakExporter/Public/AuroraExporterConfig.h +++ b/Source/GFPakExporter/Public/AuroraExporterConfig.h @@ -125,6 +125,9 @@ struct GFPAKEXPORTER_API FAuroraExporterSettings UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Settings) bool bCookUnversioned = false; + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Settings) + bool bBuildUAT = false; + /** Location where the Config Json will be saved. Leave blank to save in the default location in the Intermediate folder */ UPROPERTY(BlueprintReadWrite, EditAnywhere, AdvancedDisplay, Category=Settings, meta = (FilePathFilter = "json")) FFilePath ConfigFilePath; diff --git a/Source/GFPakExporter/Public/GFPakExporterSubsystem.h b/Source/GFPakExporter/Public/GFPakExporterSubsystem.h index eff4cae..ee8a438 100644 --- a/Source/GFPakExporter/Public/GFPakExporterSubsystem.h +++ b/Source/GFPakExporter/Public/GFPakExporterSubsystem.h @@ -9,6 +9,7 @@ #include "ILauncherProfile.h" #include "GFPakExporterSubsystem.generated.h" +struct FAuroraBuildTask; /** * */ @@ -27,15 +28,12 @@ class GFPAKEXPORTER_API UGFPakExporterSubsystem : public UEditorSubsystem } void PromptForExport(const FAuroraExporterSettings& InSettings); - ILauncherProfilePtr CreateLauncherProfileFromSettings(const FAuroraExporterSettings& InSettings); - ILauncherWorkerPtr LaunchProfile(const ILauncherProfilePtr& InProfile); -private: - void MessageReceived(const FString& InMessage, ILauncherWorkerPtr Worker); - void HandleStageStarted(const FString& InStage, ILauncherWorkerPtr Worker); - void HandleStageCompleted(const FString& InStage, double StageTime, ILauncherWorkerPtr Worker); - void LaunchCompleted(bool Outcome, double ExecutionTime, int32 ReturnCode, ILauncherWorkerPtr Worker); - void LaunchCanceled(double ExecutionTime, ILauncherWorkerPtr Worker); + ILauncherProfilePtr CreateLauncherProfileFromSettings(const FAuroraExporterSettings& InSettings) const; + TSharedPtr LaunchProfile(const ILauncherProfilePtr& InProfile, const FAuroraExporterSettings& InSettings); + bool IsExporting() const; + +private: ILauncherPtr Launcher{}; - ILauncherWorkerPtr LauncherWorker{}; + TSharedPtr AuroraBuildTask{}; }; diff --git a/Source/GFPakLoaderEditor/Private/GFPakLoaderEditorModule.cpp b/Source/GFPakLoaderEditor/Private/GFPakLoaderEditorModule.cpp index e53b907..b779b87 100644 --- a/Source/GFPakLoaderEditor/Private/GFPakLoaderEditorModule.cpp +++ b/Source/GFPakLoaderEditor/Private/GFPakLoaderEditorModule.cpp @@ -11,7 +11,7 @@ DEFINE_LOG_CATEGORY(LogGFPakLoaderEditor); #define LOCTEXT_NAMESPACE "FGFPakLoaderEditorModule" -const FString FGFPakLoaderEditorModule::GFPakLoaderVirtualPathPrefix{TEXT("/PakPlugins")}; +const FString FGFPakLoaderEditorModule::GFPakLoaderVirtualPathPrefix{TEXT("/DLC Paks")}; void FGFPakLoaderEditorModule::StartupModule() {