// Copyright Epic Games, Inc. All Rights Reserved. #include "Settings/LevelEditorPlayNetworkEmulationSettings.h" #include "IDetailChildrenBuilder.h" #include "IDetailPropertyRow.h" #include "DetailWidgetRow.h" #include "DetailLayoutBuilder.h" #include "Engine/NetDriver.h" #include "Engine/NetworkSettings.h" #include "Framework/MultiBox/MultiBoxBuilder.h" namespace NetworkEmulationSettingsHelper { const FString& GetCustomProfileName() { static const FString CustomProfileName = TEXT("Custom"); return CustomProfileName; } void ConvertNetDriverSettingsToLevelEditorSettings(const FPacketSimulationSettings& NetDriverSettings, FNetworkEmulationPacketSettings& OutgoingTrafficPieSettings, FNetworkEmulationPacketSettings& IncomingTrafficPieSettings) { if (NetDriverSettings.PktLag > 0) { OutgoingTrafficPieSettings.MinLatency = FMath::Max(NetDriverSettings.PktLag - NetDriverSettings.PktLagVariance, 0); OutgoingTrafficPieSettings.MaxLatency = FMath::Max(OutgoingTrafficPieSettings.MinLatency, NetDriverSettings.PktLag + NetDriverSettings.PktLagVariance); ; } else if (NetDriverSettings.PktLagMin > 0 || NetDriverSettings.PktLagMax > 0) { OutgoingTrafficPieSettings.MinLatency = NetDriverSettings.PktLagMin; OutgoingTrafficPieSettings.MaxLatency = NetDriverSettings.PktLagMax; } OutgoingTrafficPieSettings.PacketLossPercentage = NetDriverSettings.PktLoss; IncomingTrafficPieSettings.MinLatency = NetDriverSettings.PktIncomingLagMin; IncomingTrafficPieSettings.MaxLatency = NetDriverSettings.PktIncomingLagMax; IncomingTrafficPieSettings.PacketLossPercentage = NetDriverSettings.PktIncomingLoss; } FNetworkEmulationPacketSettings* GetPacketSettingsFromHandle(const TSharedPtr& PropertyHandle) { void* ValueData(nullptr); FPropertyAccess::Result Result = PropertyHandle->GetValueData(ValueData); if (Result != FPropertyAccess::Success) { return nullptr; } return (FNetworkEmulationPacketSettings*)ValueData; } } // namespace NetworkEmulationSettingsHelper //------------------------------------------------------------------------------------------------- // FLevelEditorPlayNetworkEmulationSettingsDetail TSharedRef FLevelEditorPlayNetworkEmulationSettingsDetail::MakeInstance() { return MakeShareable(new FLevelEditorPlayNetworkEmulationSettingsDetail()); } void FLevelEditorPlayNetworkEmulationSettingsDetail::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { IsNetworkEmulationEnabledHandle = StructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLevelEditorPlayNetworkEmulationSettings, bIsNetworkEmulationEnabled)); HeaderRow.NameContent() [IsNetworkEmulationEnabledHandle->CreatePropertyNameWidget() ] .ValueContent() [IsNetworkEmulationEnabledHandle->CreatePropertyValueWidget()]; } void FLevelEditorPlayNetworkEmulationSettingsDetail::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { // Grab the widget handles NetworkEmulationSettingsHandle = StructPropertyHandle; CurrentProfileHandle = NetworkEmulationSettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLevelEditorPlayNetworkEmulationSettings, CurrentProfile)); OutPacketsHandle = NetworkEmulationSettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLevelEditorPlayNetworkEmulationSettings, OutPackets)); InPacketsHandle = NetworkEmulationSettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLevelEditorPlayNetworkEmulationSettings, InPackets)); StructBuilder.AddProperty(NetworkEmulationSettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FLevelEditorPlayNetworkEmulationSettings, EmulationTarget)).ToSharedRef()) .IsEnabled(TAttribute(this, &FLevelEditorPlayNetworkEmulationSettingsDetail::HandleAreSettingsEnabled)); StructBuilder.AddCustomRow(NSLOCTEXT("FLevelEditorPlayNetworkEmulationSettings", "NetworkEmulationProfileName", "Network Emulation Profile")) .NameContent() [SNew(STextBlock) .Text(NSLOCTEXT("FLevelEditorPlayNetworkEmulationSettings", "NetworkEmulationProfileName", "Network Emulation Profile")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [SNew(SComboButton) .OnGetMenuContent(this, &FLevelEditorPlayNetworkEmulationSettingsDetail::OnGetProfilesMenuContent) .ButtonContent() [SNew(STextBlock) .Text(this, &FLevelEditorPlayNetworkEmulationSettingsDetail::GetSelectedNetworkEmulationProfile) .Font(IDetailLayoutBuilder::GetDetailFont())]] .IsEnabled(TAttribute(this, &FLevelEditorPlayNetworkEmulationSettingsDetail::HandleAreSettingsEnabled)); StructBuilder.AddProperty(InPacketsHandle.ToSharedRef()) .IsEnabled(TAttribute(this, &FLevelEditorPlayNetworkEmulationSettingsDetail::HandleAreSettingsEnabled)); StructBuilder.AddProperty(OutPacketsHandle.ToSharedRef()) .IsEnabled(TAttribute(this, &FLevelEditorPlayNetworkEmulationSettingsDetail::HandleAreSettingsEnabled)); } bool FLevelEditorPlayNetworkEmulationSettingsDetail::HandleAreSettingsEnabled() const { if (IsNetworkEmulationEnabledHandle.IsValid()) { bool bIsEnabled(false); IsNetworkEmulationEnabledHandle->GetValue(bIsEnabled); return bIsEnabled; } return false; } TSharedRef FLevelEditorPlayNetworkEmulationSettingsDetail::OnGetProfilesMenuContent() const { FMenuBuilder MenuBuilder(true, nullptr); if (const UNetworkSettings* NetworkSettings = GetDefault()) { // Add preconfigured profiles for (int32 Index = 0; Index < NetworkSettings->NetworkEmulationProfiles.Num(); Index++) { const FNetworkEmulationProfileDescription& ProfileDescription = NetworkSettings->NetworkEmulationProfiles[Index]; MenuBuilder.AddMenuEntry( FText::FromString(ProfileDescription.ProfileName), FText::FromString(ProfileDescription.ToolTip), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FLevelEditorPlayNetworkEmulationSettingsDetail::OnEmulationProfileChanged, Index), FCanExecuteAction())); } // Add the custom entry MenuBuilder.AddMenuEntry( NSLOCTEXT("FLevelEditorPlayNetworkEmulationSettings", "NetworkEmulationCustomProfile", "Custom"), NSLOCTEXT("FLevelEditorPlayNetworkEmulationSettings", "NetworkEmulationCustomProfileToolTip", "Customizable profile"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FLevelEditorPlayNetworkEmulationSettingsDetail::OnEmulationProfileChanged, -1), FCanExecuteAction())); } return MenuBuilder.MakeWidget(); } void FLevelEditorPlayNetworkEmulationSettingsDetail::OnEmulationProfileChanged(int32 Index) const { FString Selection; if (const UNetworkSettings* NetworkSettings = GetDefault()) { if (NetworkSettings->NetworkEmulationProfiles.IsValidIndex(Index)) { Selection = NetworkSettings->NetworkEmulationProfiles[Index].ProfileName; } else { Selection = NetworkEmulationSettingsHelper::GetCustomProfileName(); } } // Get the current settings FNetworkEmulationPacketSettings* OutSettings = NetworkEmulationSettingsHelper::GetPacketSettingsFromHandle(OutPacketsHandle); FNetworkEmulationPacketSettings* InSettings = NetworkEmulationSettingsHelper::GetPacketSettingsFromHandle(InPacketsHandle); // Reload the settings when switching to an official profile if (Selection != NetworkEmulationSettingsHelper::GetCustomProfileName()) { FPacketSimulationSettings NetDriverSettings; NetDriverSettings.LoadEmulationProfile(*Selection); FNetworkEmulationPacketSettings OutgoingTrafficPIESettings; FNetworkEmulationPacketSettings IncomingTrafficPIESettings; NetworkEmulationSettingsHelper::ConvertNetDriverSettingsToLevelEditorSettings(NetDriverSettings, OutgoingTrafficPIESettings, IncomingTrafficPIESettings); *OutSettings = OutgoingTrafficPIESettings; *InSettings = IncomingTrafficPIESettings; } CurrentProfileHandle->SetValue(Selection); } FText FLevelEditorPlayNetworkEmulationSettingsDetail::GetSelectedNetworkEmulationProfile() const { FString SelectedProfile; CurrentProfileHandle->GetValue(SelectedProfile); return FText::FromString(SelectedProfile); } //------------------------------------------------------------------------------------------------- // FLevelEditorPlayNetworkEmulationSettings bool FLevelEditorPlayNetworkEmulationSettings::IsCustomProfile() const { return CurrentProfile == NetworkEmulationSettingsHelper::GetCustomProfileName(); } bool FLevelEditorPlayNetworkEmulationSettings::IsEmulationEnabledForTarget(NetworkEmulationTarget CurrentTarget) const { // Settings are applied to everyone if (EmulationTarget == NetworkEmulationTarget::Any) { return true; } // Otherwise check if target matches the wanted setting return EmulationTarget == CurrentTarget; } void FLevelEditorPlayNetworkEmulationSettings::ConvertToNetDriverSettings(FPacketSimulationSettings& OutNetDriverSettings) const { OutNetDriverSettings.ResetSettings(); bool bProfileFound(false); if (!IsCustomProfile()) { // Load hardcoded profiles from the config since they might have advanced settings not found in PIE settings bProfileFound = OutNetDriverSettings.LoadEmulationProfile(*CurrentProfile); } if (IsCustomProfile() || !bProfileFound) { // For custom set the settings manually from the PIE variables OutNetDriverSettings.PktLagMin = OutPackets.MinLatency; OutNetDriverSettings.PktLagMax = OutPackets.MaxLatency; OutNetDriverSettings.PktLoss = OutPackets.PacketLossPercentage; OutNetDriverSettings.PktIncomingLagMin = InPackets.MinLatency; OutNetDriverSettings.PktIncomingLagMax = InPackets.MaxLatency; OutNetDriverSettings.PktIncomingLoss = InPackets.PacketLossPercentage; } } FString FLevelEditorPlayNetworkEmulationSettings::BuildPacketSettingsForCmdLine() const { // Empty string when disabled if (!bIsNetworkEmulationEnabled) { return FString(); } FString CmdLine; if (IsCustomProfile()) { // Set each setting manually for user-edited profiles CmdLine += FString::Printf(TEXT(" -PktLagMin=%d"), OutPackets.MinLatency); CmdLine += FString::Printf(TEXT(" -PktLagMax=%d"), OutPackets.MaxLatency); CmdLine += FString::Printf(TEXT(" -PktLoss=%d"), OutPackets.PacketLossPercentage); CmdLine += FString::Printf(TEXT(" -PktIncomingLagMin=%d"), InPackets.MinLatency); CmdLine += FString::Printf(TEXT(" -PktIncomingLagMax=%d"), InPackets.MaxLatency); CmdLine += FString::Printf(TEXT(" -PktIncomingLoss=%d"), InPackets.PacketLossPercentage); } else { CmdLine += FString::Printf(TEXT(" -PktEmulationProfile=%s"), *CurrentProfile); } return CmdLine; } FString FLevelEditorPlayNetworkEmulationSettings::BuildPacketSettingsForURL() const { // Empty string when disabled if (!bIsNetworkEmulationEnabled) { return FString(); } FString CmdLine; if (IsCustomProfile()) { // Set each setting manually for user-edited profiles CmdLine += FString::Printf(TEXT("?PktLagMin=%d"), OutPackets.MinLatency); CmdLine += FString::Printf(TEXT("?PktLagMax=%d"), OutPackets.MaxLatency); CmdLine += FString::Printf(TEXT("?PktLoss=%d"), OutPackets.PacketLossPercentage); CmdLine += FString::Printf(TEXT("?PktIncomingLagMin=%d"), InPackets.MinLatency); CmdLine += FString::Printf(TEXT("?PktIncomingLagMax=%d"), InPackets.MaxLatency); CmdLine += FString::Printf(TEXT("?PktIncomingLoss=%d"), InPackets.PacketLossPercentage); } else { CmdLine += FString::Printf(TEXT("?PktEmulationProfile=%s"), *CurrentProfile); } return CmdLine; } void FLevelEditorPlayNetworkEmulationSettings::OnPostInitProperties() { if (CurrentProfile.IsEmpty()) { CurrentProfile = NetworkEmulationSettingsHelper::GetCustomProfileName(); } else if (!IsCustomProfile()) { // For official profiles reload the settings from the config in case some values got changed FPacketSimulationSettings NetDriverSettings; bool bProfileFound = NetDriverSettings.LoadEmulationProfile(*CurrentProfile); if (bProfileFound) { NetworkEmulationSettingsHelper::ConvertNetDriverSettingsToLevelEditorSettings(NetDriverSettings, OutPackets, InPackets); } else { CurrentProfile = NetworkEmulationSettingsHelper::GetCustomProfileName(); } } } void FLevelEditorPlayNetworkEmulationSettings::OnPostEditChange(struct FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.Property) { if (GET_MEMBER_NAME_CHECKED(FLevelEditorPlayNetworkEmulationSettings, CurrentProfile) == PropertyChangedEvent.Property->GetFName() || GET_MEMBER_NAME_CHECKED(FLevelEditorPlayNetworkEmulationSettings, bIsNetworkEmulationEnabled) == PropertyChangedEvent.Property->GetFName() || GET_MEMBER_NAME_CHECKED(FLevelEditorPlayNetworkEmulationSettings, EmulationTarget) == PropertyChangedEvent.Property->GetFName()) { // Don't consider the settings dirty when these properties change return; } // Set profile to Custom when the user dirties any other property CurrentProfile = NetworkEmulationSettingsHelper::GetCustomProfileName(); } }