GameLiftPlugin/Source/GameLiftPlugin/Private/SMenu/Anywhere/SAnywhereRegisterComputeMenu.cpp (441 lines of code) (raw):

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #include "SAnywhereRegisterComputeMenu.h" #include <Widgets/Layout/SWidgetSwitcher.h> #include <Async/Async.h> #include "SWidgets/SInputTextBox.h" #include "SWidgets/SIPAddressTextBox.h" #include "SWidgets/STextStatus.h" #include "SWidgets/SOnlineHyperlink.h" #include "SWidgets/SNamedRow.h" #include "SWidgets/SSectionsWithHeaders.h" #include "SWidgets/SSelectionComboBox.h" #include "SWidgets/SBootstrapStatus.h" #include "SMenu/SGameLiftSettingsAwsAccountMenu.h" #include "IGameLiftCoreModule.h" #include "GameLiftPlugin.h" #include "GameLiftPluginStyle.h" #include <Developer/Settings/Public/ISettingsModule.h> #include "Settings/UGameLiftAnywhereStatus.h" #include "Settings/UGameLiftSettings.h" #include "Types/EBootstrapMessageState.h" #include "Utils/StringPaths.h" #include "Utils/Misc.h" #define LOCTEXT_NAMESPACE "SAnywhereRegisterComputeMenu" void SAnywhereRegisterComputeMenu::Construct(const FArguments& InArgs) { OnComputeChangedDelegate = InArgs._OnComputeChangedDelegate; // Make sure compute registration settings are still valid based on bootstrap status ValidateComputeSettings(); SSectionStep::Construct( SSectionStep::FArguments() .HeaderTitle(Menu::DeployAnywhere::kRegisterComputeHeader) .HeaderDescription(Menu::DeployAnywhere::kRegisterWorkstationDescription) .BodyContent() [ SAssignNew(ComputeWidgetSwitcher, SWidgetSwitcher) + SWidgetSwitcher::Slot() [ CreateAddNewComputeUI() ] + SWidgetSwitcher::Slot() [ CreateShowExistingComputeUI() ] ]); // Set up compute data from UGameLiftAnywhereStatus. UGameLiftAnywhereStatus* AnywhereStatus = GetMutableDefault<UGameLiftAnywhereStatus>(); if (AnywhereStatus->IsFleetDeployed && AnywhereStatus->IsComputeRegistered) { AddNewCompute(AnywhereStatus->ComputeName, AnywhereStatus->ComputeIPAddress); } else { // If IP address was not set up before, we set it to the local host address by default. ComputeIPTextInput->SetAddress(Utils::GetLocalHostIPv4Address()); ChangeComputeSelectionUIState(EComputeUIState::CreateNewCompute); } // Listen to profile changes SGameLiftSettingsAwsAccountMenu::OnProfileSelectionChangedMultiDelegate.AddSP(this, &SAnywhereRegisterComputeMenu::OnBootstrapStatusChanged); // Initialize section UI based on state if (!HaveExistingCompute() && !AnywhereStatus->IsFleetDeployed) { ResetAndCollapseSection(); } } void SAnywhereRegisterComputeMenu::OnComputeRegistered() { // Called when a user registers a new compute OnComputeChangedDelegate.ExecuteIfBound(); } void SAnywhereRegisterComputeMenu::OnBootstrapStatusChanged(const SGameLiftSettingsAwsAccountMenu* /* Sender */) { UGameLiftSettings* Settings = GetMutableDefault<UGameLiftSettings>(); if (EBootstrapMessageStateFromInt(Settings->BootstrapStatus) == EBootstrapMessageState::NoBootstrappedMessage) { InvalidateCompute(); } } void SAnywhereRegisterComputeMenu::StartSection() { InvalidateCompute(); } void SAnywhereRegisterComputeMenu::InvalidateCompute() { UGameLiftAnywhereStatus* AnywhereStatus = GetMutableDefault<UGameLiftAnywhereStatus>(); ComputeNameTextInput->SetText(GetDefaultComputeName()); ComputeIPTextInput->SetAddress(Utils::GetLocalHostIPv4Address()); SelectedCompute.ComputeName = FString(); SelectedCompute.RegisteredIPAddress = FString(); SelectedCompute.LinkedFleetId = AnywhereStatus->FleetId; AnywhereStatus->ComputeName = FString(); AnywhereStatus->ComputeIPAddress = FString(); AnywhereStatus->IsComputeRegistered = false; AnywhereStatus->SaveConfig(); ChangeComputeSelectionUIState(EComputeUIState::CreateNewCompute); } TSharedRef<SWidget> SAnywhereRegisterComputeMenu::CreateAddNewComputeUI() { TSharedRef<SVerticalBox> VerticalBox = SNew(SVerticalBox) // Compute name text input + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) .Padding(SPadding::Right2x + SPadding::Top) [ SNew(SNamedRow) .NameText(Menu::DeployAnywhere::kComputeNameTitle) .RowWidget( SAssignNew(ComputeNameTextInput, SEditableTextBox) .HintText(Menu::DeployAnywhere::kComputeNameTextHint) .IsEnabled_Raw(this, &SAnywhereRegisterComputeMenu::CanEditComputeSettings) ) ] + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) .Padding(SPadding::Top2x + SPadding::Right2x) [ SNew(SNamedRow) .NameText(Menu::DeployAnywhere::kComputeIPTitle) .NameTooltipText(Menu::DeployAnywhere::kComputeIPTextTooltip) .RowWidget( SAssignNew(ComputeIPTextInput, SIPAddressTextBox) .HintText(Menu::DeployAnywhere::kComputeIPTextHint) .IsEnabled_Raw(this, &SAnywhereRegisterComputeMenu::CanEditComputeSettings) ) ] + SVerticalBox::Slot() .AutoHeight() .Padding(SPadding::Top3x + SPadding::Right2x) [ SNew(SHorizontalBox) // Button to cancel adding new compute + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(SPadding::Right2x) [ SAssignNew(CancelButton, SButton) .Text(Menu::DeployAnywhere::kCancelButtonText) .OnClicked_Raw(this, &SAnywhereRegisterComputeMenu::OnCancelComputeButtonClicked) .ButtonStyle(FGameLiftPluginStyle::Get(), Style::Button::kNormalButtonStyleName) .TextStyle(FGameLiftPluginStyle::Get(), Style::Text::kButtonNormal) ] // Button to register new compute + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(SPadding::Right2x) [ SNew(SButton) .Text(Menu::DeployAnywhere::kRegisterFirstComputeButtonText) .OnClicked_Raw(this, &SAnywhereRegisterComputeMenu::OnRegisterFirstComputeButtonClicked) .IsEnabled_Raw(this, &SAnywhereRegisterComputeMenu::CanRegisterCompute) .ButtonStyle(FGameLiftPluginStyle::Get(), Style::Button::kSuccessButtonStyleName) .TextStyle(FGameLiftPluginStyle::Get(), Style::Text::kButtonLight) ] // Loading indicator + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(STextStatus) .IconState(STextStatus::EIconState::Loading) .Visibility_Lambda([&] { return IsLoading ? EVisibility::Visible : EVisibility::Hidden; }) ] ] + SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Center) .Padding(SPadding::Top2x + SPadding::Right2x) [ SAssignNew(RegisterComputeErrorRow, SNamedRow) .SecondaryColumnLeftPadding(true) .RowWidget( SAssignNew(RegisterComputeErrorTextBlock, STextBlock) .TextStyle(FGameLiftPluginStyle::Get(), Style::Text::kNote) .AutoWrapText(true) ) .Visibility(EVisibility::Collapsed) ]; RegisterComputeErrorTextBlock->SetColorAndOpacity(FGameLiftPluginStyle::Get().GetColor(Style::Color::kError)); return VerticalBox; } TSharedRef<SWidget> SAnywhereRegisterComputeMenu::CreateShowExistingComputeUI() { return SNew(SVerticalBox) // Compute name + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) .Padding(SPadding::Right2x + SPadding::Top) [ SNew(SNamedRow) .NameText(Menu::DeployAnywhere::kComputeNameTitle) .SecondaryColumnLeftPadding(true) .RowWidget( SAssignNew(ComputeNameTextView, SEditableText) .Style(FGameLiftPluginStyle::Get(), Style::EditableText::kFieldMedium) .IsReadOnly(true) ) ] // Compute IP + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) .Padding(SPadding::Top2x + SPadding::Right2x) [ SNew(SNamedRow) .NameText(Menu::DeployAnywhere::kComputeIPTitle) .NameTooltipText(Menu::DeployAnywhere::kComputeIPTextTooltip) .SecondaryColumnLeftPadding(true) .RowWidget( SAssignNew(ComputeIPTextView, SEditableText) .Style(FGameLiftPluginStyle::Get(), Style::EditableText::kFieldMedium) .IsReadOnly(true) ) ] // Compute status + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Fill) .Padding(SPadding::Top2x + SPadding::Right2x) [ CreateComputeStatusRow() ] + SVerticalBox::Slot() .AutoHeight() .Padding(SPadding::Top3x + SPadding::Right2x) [ SNew(SHorizontalBox) // Button to register a new compute + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .Padding(SPadding::Right) [ SAssignNew(RegisterComputeButton, SButton) .Text(Menu::DeployAnywhere::kRegisterNewComputeButtonText) .OnClicked_Raw(this, &SAnywhereRegisterComputeMenu::OnRegisterNewComputeButtonClicked) .ButtonStyle(FGameLiftPluginStyle::Get(), Style::Button::kNormalButtonStyleName) .TextStyle(FGameLiftPluginStyle::Get(), Style::Text::kButtonNormal) ] ]; } TSharedRef<SWidget> SAnywhereRegisterComputeMenu::CreateComputeStatusRow() { TSharedRef<STextStatus> TextStatus = SNew(STextStatus) .StatusText(Menu::DeployAnywhere::kComputeStatusRegisteredText) .StatusTextStyle(Style::Text::kFieldBold) .IconState(STextStatus::EIconState::Success); TextStatus->SetStatusTextColor(FGameLiftPluginStyle::Get().GetColor(Style::Color::kSuccess)); return SAssignNew(ComputeStatusRow, SNamedRow) .NameText(Menu::DeployAnywhere::kStatusTitle) .SecondaryColumnLeftPadding(true) .RowWidget( SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) [ TextStatus ] ); } FReply SAnywhereRegisterComputeMenu::OnRegisterFirstComputeButtonClicked() { FString ComputeName = ComputeNameTextInput->GetText().ToString().TrimStartAndEnd(); FString ComputeIPAddress = ComputeIPTextInput->GetAddress(); // Validate input if (ComputeName.IsEmpty() || ComputeIPAddress.IsEmpty()) { return FReply::Handled(); } IsLoading = true; SetProgressBarState(SProgressBar::EProgressBarUIState::InProgress); Async(EAsyncExecution::Thread, [this, ComputeName = MoveTemp(ComputeName), ComputeIPAddress = MoveTemp(ComputeIPAddress)]() { TTuple<bool, FString> RegisterResult = RegisterCompute(ComputeName, ComputeIPAddress); IsLoading = false; Async(EAsyncExecution::TaskGraphMainThread, [this, ComputeNameCopy = ComputeName, ComputeIPAddressCopy = ComputeIPAddress, RegisterResult = MoveTemp(RegisterResult)] { if (RegisterResult.Get<0>()) { AddNewCompute(ComputeNameCopy, ComputeIPAddressCopy); OnComputeRegistered(); UpdateErrorMessage(FText::GetEmpty()); SetProgressBarState(SProgressBar::EProgressBarUIState::ProgressComplete); StartNextSection(); } else { UpdateErrorMessage(FText::FromString(RegisterResult.Get<1>())); SetProgressBarState(SProgressBar::EProgressBarUIState::ProgressError); } }); }); return FReply::Handled(); } FReply SAnywhereRegisterComputeMenu::OnRegisterNewComputeButtonClicked() { ChangeComputeSelectionUIState(EComputeUIState::CreateNewCompute); ResetAndCollapseNextSections(); return FReply::Handled(); } FReply SAnywhereRegisterComputeMenu::OnCancelComputeButtonClicked() { ChangeComputeSelectionUIState(EComputeUIState::ShowExistingCompute); return FReply::Handled(); } TTuple<bool, FString> SAnywhereRegisterComputeMenu::RegisterCompute(const FString& InComputeName, const FString& InIPAddress) { UGameLiftAnywhereStatus* AnywhereStatus = GetMutableDefault<UGameLiftAnywhereStatus>(); IGameLiftAnywhereHandler& Handler = IGameLiftCoreModule::Get().GetGameLiftAnywhereHandler(); // First, make sure that we have a custom location created for the current AWS region. // Needed for existing fleets that were created outside of the plugin. TTuple<FString, FString> CreateLocationResult = CreateCustomLocation(); const FString LocationName = CreateLocationResult.Get<0>(); if (LocationName.IsEmpty()) { // Failed to create custom location. return MakeTuple(false, CreateLocationResult.Get<1>()); } // Next, make sure the custom location has been added to the fleet. // Needed for existing fleets that were created outside of the plugin. GameLiftAnywhereAddFleetLocationResult AddFleetLocationResult = Handler.AddFleetLocation(AnywhereStatus->FleetId, LocationName); if (!AddFleetLocationResult.bIsSuccessful) { return MakeTuple(false, AddFleetLocationResult.ErrorMessage); } // Register the compute GameLiftAnywhereRegisterComputeResult RegisterComputeResult = Handler.RegisterCompute( InComputeName, InIPAddress, AnywhereStatus->FleetId, AnywhereStatus->CustomLocation); if (RegisterComputeResult.bIsSuccessful) { AnywhereStatus->ComputeName = InComputeName; AnywhereStatus->ComputeIPAddress = InIPAddress; AnywhereStatus->IsComputeRegistered = true; AnywhereStatus->SaveConfig(); return MakeTuple(true, TEXT("")); } else { return MakeTuple(false, RegisterComputeResult.ErrorMessage); } } TTuple<FString, FString> SAnywhereRegisterComputeMenu::CreateCustomLocation() { UGameLiftSettings* Settings = GetMutableDefault<UGameLiftSettings>(); FString AwsRegion = Settings->AwsRegion.ToString(); IGameLiftAnywhereHandler& Handler = IGameLiftCoreModule::Get().GetGameLiftAnywhereHandler(); GameLiftAnywhereCreateLocationResult CreateLocationResult = Handler.CreateCustomLocation(AwsRegion); UGameLiftAnywhereStatus* AnywhereStatus = GetMutableDefault<UGameLiftAnywhereStatus>(); if (CreateLocationResult.bIsSuccessful) { AnywhereStatus->CustomLocation = CreateLocationResult.LocationName; AnywhereStatus->DeployedRegion = AwsRegion; AnywhereStatus->SaveConfig(); return MakeTuple(CreateLocationResult.LocationName, TEXT("")); } else { return MakeTuple(TEXT(""), CreateLocationResult.ErrorMessage); } } void SAnywhereRegisterComputeMenu::AddNewCompute(const FString& InComputeName, const FString& InIPAddress) { // Set selected compute SelectedCompute.ComputeName = InComputeName; SelectedCompute.RegisteredIPAddress = InIPAddress; UGameLiftAnywhereStatus* AnywhereStatus = GetMutableDefault<UGameLiftAnywhereStatus>(); SelectedCompute.LinkedFleetId = AnywhereStatus->FleetId; ChangeComputeSelectionUIState(EComputeUIState::ShowExistingCompute); } FText SAnywhereRegisterComputeMenu::GetDefaultComputeName() const { return Menu::DeployAnywhere::kComputeNameDefault; } void SAnywhereRegisterComputeMenu::ValidateComputeSettings() { UGameLiftSettings* Settings = GetMutableDefault<UGameLiftSettings>(); if (EBootstrapMessageStateFromInt(Settings->BootstrapStatus) == EBootstrapMessageState::NoBootstrappedMessage) { UGameLiftAnywhereStatus* AnywhereStatus = GetMutableDefault<UGameLiftAnywhereStatus>(); AnywhereStatus->ComputeName = FString(); AnywhereStatus->ComputeIPAddress = FString(); AnywhereStatus->IsComputeRegistered = false; AnywhereStatus->SaveConfig(); } } bool SAnywhereRegisterComputeMenu::CanEditComputeSettings() const { UGameLiftSettings* Settings = GetMutableDefault<UGameLiftSettings>(); UGameLiftAnywhereStatus* AnywhereStatus = GetMutableDefault<UGameLiftAnywhereStatus>(); return EBootstrapMessageStateFromInt(Settings->BootstrapStatus) == EBootstrapMessageState::ActiveMessage && AnywhereStatus->IsFleetDeployed && !IsLoading; } bool SAnywhereRegisterComputeMenu::CanRegisterCompute() const { UGameLiftSettings* Settings = GetMutableDefault<UGameLiftSettings>(); UGameLiftAnywhereStatus* AnywhereStatus = GetMutableDefault<UGameLiftAnywhereStatus>(); return CanEditComputeSettings() && !ComputeNameTextInput->GetText().IsEmpty() && ComputeIPTextInput->IsAddressValid(); } void SAnywhereRegisterComputeMenu::ChangeComputeSelectionUIState(EComputeUIState InState) { ComputeWidgetSwitcher->SetActiveWidgetIndex(ComputeUIStateToInt(InState)); UpdateContentForCurrentUIState(); } void SAnywhereRegisterComputeMenu::UpdateContentForCurrentUIState() { ComputeNameTextInput->SetText(GetDefaultComputeName()); if (ComputeWidgetSwitcher->GetActiveWidgetIndex() == ComputeUIStateToInt(EComputeUIState::CreateNewCompute)) { // Update UI for registering a new compute CancelButton->SetVisibility(HaveExistingCompute() ? EVisibility::Visible : EVisibility::Collapsed); SetProgressBarState(SProgressBar::EProgressBarUIState::NotStart); } else { // Update UI for registering an existing compute if (HaveExistingCompute()) { // Show ID ComputeNameTextView->SetText(FText::FromString(SelectedCompute.ComputeName)); FString FormattedAddress = SelectedCompute.RegisteredIPAddress; SIPAddressTextBox::FormatAddressForDisplay(FormattedAddress); ComputeIPTextView->SetText(FText::FromString(FormattedAddress)); // Show status ComputeStatusRow->SetVisibility(EVisibility::Visible); SetProgressBarState(SProgressBar::EProgressBarUIState::ProgressComplete); StartNextSection(); } else { // Hide status ComputeStatusRow->SetVisibility(EVisibility::Collapsed); SetProgressBarState(SProgressBar::EProgressBarUIState::NotStart); } } // Update button style const FButtonStyle& CurrentButtonStyle = FGameLiftPluginStyle::Get().GetWidgetStyle<FButtonStyle>( HaveExistingCompute() ? Style::Button::kNormalButtonStyleName : Style::Button::kSuccessButtonStyleName); RegisterComputeButton->SetButtonStyle(&CurrentButtonStyle); } bool SAnywhereRegisterComputeMenu::HaveExistingCompute() const { return !SelectedCompute.ComputeName.IsEmpty(); } void SAnywhereRegisterComputeMenu::UpdateErrorMessage(const FText& ErrorMessage) { if (ErrorMessage.IsEmpty()) { RegisterComputeErrorRow->SetVisibility(EVisibility::Collapsed); RegisterComputeErrorTextBlock->SetText(FText::GetEmpty()); } else { RegisterComputeErrorRow->SetVisibility(EVisibility::Visible); RegisterComputeErrorTextBlock->SetText(ErrorMessage); } } #undef LOCTEXT_NAMESPACE