You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1695 lines
57 KiB
C++
1695 lines
57 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UnrealEd.h"
|
|
#include "EditorSupportDelegates.h"
|
|
#include "ISourceControlModule.h"
|
|
#include "Kismet2/DebuggerCommands.h"
|
|
#include "Toolkits/AssetEditorCommonCommands.h"
|
|
#include "SoundCueGraphEditorCommands.h"
|
|
#include "RichCurveEditorCommands.h"
|
|
#include "FileHelpers.h"
|
|
#include "EditorBuildUtils.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "MessageLog.h"
|
|
#include "Developer/MessageLog/Public/MessageLogModule.h"
|
|
#include "Kismet2/KismetDebugUtilities.h"
|
|
#include "SourceControlWindows.h"
|
|
#include "FbxLibs.h"
|
|
#include "CompilerResultsLog.h"
|
|
#include "AssetEditorManager.h"
|
|
#include "AssetRegistryModule.h"
|
|
#include "EngineAnalytics.h"
|
|
#include "IAnalyticsProvider.h"
|
|
#include "ISettingsEditorModule.h"
|
|
#include "LevelEditor.h"
|
|
#include "Toolkits/AssetEditorManager.h"
|
|
#include "UObjectToken.h"
|
|
#include "BusyCursor.h"
|
|
#include "ComponentAssetBroker.h"
|
|
#include "PackageTools.h"
|
|
#include "GameProjectGenerationModule.h"
|
|
#include "MaterialEditorActions.h"
|
|
#include "EngineBuildSettings.h"
|
|
#include "SlateBasics.h"
|
|
#include "DesktopPlatformModule.h"
|
|
#include "ShaderCompiler.h"
|
|
#include "NavigationBuildingNotification.h"
|
|
#include "HotReloadInterface.h"
|
|
#include "PerformanceMonitor.h"
|
|
#include "Engine/WorldComposition.h"
|
|
#include "GameMapsSettings.h"
|
|
#include "GeneralProjectSettings.h"
|
|
#include "Lightmass/LightmappedSurfaceCollection.h"
|
|
#include "IProjectManager.h"
|
|
#include "FeaturePackContentSource.h"
|
|
#include "TemplateProjectDefs.h"
|
|
#include "GameProjectUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "UnrealEd"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogUnrealEdMisc, Log, All);
|
|
|
|
namespace
|
|
{
|
|
static const FName LevelEditorName("LevelEditor");
|
|
static const FName AssetRegistryName("AssetRegistry");
|
|
}
|
|
|
|
/**
|
|
* Manages the stats needed by the analytics heartbeat
|
|
* This is very similar to FStatUnitData, however it's not tied to a single viewport,
|
|
* nor does it rely on the stats being active to be updated
|
|
*/
|
|
class FPerformanceAnalyticsStats
|
|
{
|
|
public:
|
|
FPerformanceAnalyticsStats()
|
|
: AverageFrameTime(SampleSize)
|
|
, AverageGameThreadTime(SampleSize)
|
|
, AverageRenderThreadTime(SampleSize)
|
|
, AverageGPUFrameTime(SampleSize)
|
|
{
|
|
}
|
|
|
|
/** Get the average number of milliseconds in total over the frames that have been sampled */
|
|
float GetAverageFrameTime() const
|
|
{
|
|
return AverageFrameTime.GetAverage();
|
|
}
|
|
|
|
/** Get the average number of milliseconds the gamethread was used over the frames that have been sampled */
|
|
float GetAverageGameThreadTime() const
|
|
{
|
|
return AverageGameThreadTime.GetAverage();
|
|
}
|
|
|
|
/** Get the average number of milliseconds the renderthread was used over the frames that have been sampled */
|
|
float GetAverageRenderThreadTime() const
|
|
{
|
|
return AverageRenderThreadTime.GetAverage();
|
|
}
|
|
|
|
/** Get the average number of milliseconds the GPU was busy over the frames that have been sampled */
|
|
float GetAverageGPUFrameTime() const
|
|
{
|
|
return AverageGPUFrameTime.GetAverage();
|
|
}
|
|
|
|
/** Have we taken enough samples to get a reliable average? */
|
|
bool IsReliable() const
|
|
{
|
|
return AverageFrameTime.IsReliable();
|
|
}
|
|
|
|
/** Update the samples based on what happened last frame */
|
|
void Update()
|
|
{
|
|
const double CurrentTime = FApp::GetCurrentTime();
|
|
const double DeltaTime = CurrentTime - FApp::GetLastTime();
|
|
|
|
// Number of milliseconds in total last frame
|
|
const double RawFrameTime = DeltaTime * 1000.0;
|
|
AverageFrameTime.Tick(CurrentTime, static_cast<float>(RawFrameTime));
|
|
|
|
// Number of milliseconds the gamethread was used last frame
|
|
const double RawGameThreadTime = FPlatformTime::ToMilliseconds(GGameThreadTime);
|
|
AverageGameThreadTime.Tick(CurrentTime, static_cast<float>(RawGameThreadTime));
|
|
|
|
// Number of milliseconds the renderthread was used last frame
|
|
const double RawRenderThreadTime = FPlatformTime::ToMilliseconds(GRenderThreadTime);
|
|
AverageRenderThreadTime.Tick(CurrentTime, static_cast<float>(RawRenderThreadTime));
|
|
|
|
// Number of milliseconds the GPU was busy last frame
|
|
const uint32 GPUCycles = RHIGetGPUFrameCycles();
|
|
const double RawGPUFrameTime = FPlatformTime::ToMilliseconds(GPUCycles);
|
|
AverageGPUFrameTime.Tick(CurrentTime, static_cast<float>(RawGPUFrameTime));
|
|
}
|
|
|
|
private:
|
|
/** Samples for the total frame time */
|
|
FMovingAverage AverageFrameTime;
|
|
|
|
/** Samples for the gamethread time */
|
|
FMovingAverage AverageGameThreadTime;
|
|
|
|
/** Samples for the renderthread time */
|
|
FMovingAverage AverageRenderThreadTime;
|
|
|
|
/** Samples for the GPU busy time */
|
|
FMovingAverage AverageGPUFrameTime;
|
|
|
|
/** Number of samples to average over */
|
|
static const int32 SampleSize = 10;
|
|
};
|
|
|
|
namespace PerformanceSurveyDefs
|
|
{
|
|
const static int32 NumFrameRateSamples = 10;
|
|
const static FTimespan FrameRateSampleInterval(0, 0, 1); // 1 second intervals
|
|
}
|
|
|
|
FUnrealEdMisc::FUnrealEdMisc() :
|
|
AutosaveState( EAutosaveState::Inactive ),
|
|
bCancelBuild( false ),
|
|
bInitialized( false ),
|
|
bSaveLayoutOnClose( true ),
|
|
bDeletePreferences( false ),
|
|
bIsAssetAnalyticsPending( false ),
|
|
PerformanceAnalyticsStats(new FPerformanceAnalyticsStats()),
|
|
NavigationBuildingNotificationHandler(NULL)
|
|
{
|
|
}
|
|
|
|
FUnrealEdMisc::~FUnrealEdMisc()
|
|
{
|
|
}
|
|
|
|
FUnrealEdMisc& FUnrealEdMisc::Get()
|
|
{
|
|
static FUnrealEdMisc UnrealEdMisc;
|
|
return UnrealEdMisc;
|
|
}
|
|
|
|
void FUnrealEdMisc::OnInit()
|
|
{
|
|
if ( bInitialized )
|
|
{
|
|
return;
|
|
}
|
|
bInitialized = true;
|
|
|
|
FScopedSlowTask SlowTask(100);
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
// Register all callback notifications
|
|
FEditorDelegates::SelectedProps.AddRaw(this, &FUnrealEdMisc::CB_SelectedProps);
|
|
FEditorDelegates::DisplayLoadErrors.AddRaw(this, &FUnrealEdMisc::CB_DisplayLoadErrors);
|
|
FEditorDelegates::MapChange.AddRaw(this, &FUnrealEdMisc::CB_MapChange);
|
|
FEditorDelegates::RefreshEditor.AddRaw(this, &FUnrealEdMisc::CB_RefreshEditor);
|
|
FEditorDelegates::PreSaveWorld.AddRaw(this, &FUnrealEdMisc::PreSaveWorld);
|
|
FEditorSupportDelegates::RedrawAllViewports.AddRaw(this, &FUnrealEdMisc::CB_RedrawAllViewports);
|
|
GEngine->OnLevelActorAdded().AddRaw( this, &FUnrealEdMisc::CB_LevelActorsAdded );
|
|
|
|
FCoreUObjectDelegates::OnObjectSaved.AddRaw(this, &FUnrealEdMisc::OnObjectSaved);
|
|
FEditorDelegates::PreSaveWorld.AddRaw(this, &FUnrealEdMisc::OnWorldSaved);
|
|
|
|
#if USE_UNIT_TESTS
|
|
FAutomationTestFramework::GetInstance().PreTestingEvent.AddRaw(this, &FUnrealEdMisc::CB_PreAutomationTesting);
|
|
FAutomationTestFramework::GetInstance().PostTestingEvent.AddRaw(this, &FUnrealEdMisc::CB_PostAutomationTesting);
|
|
#endif // USE_UNIT_TESTS
|
|
|
|
/** Delegate that gets called when a script exception occurs */
|
|
FBlueprintCoreDelegates::OnScriptException.AddStatic(&FKismetDebugUtilities::OnScriptException);
|
|
|
|
FEditorDelegates::ChangeEditorMode.AddRaw(this, &FUnrealEdMisc::OnEditorChangeMode);
|
|
FCoreDelegates::PreModal.AddRaw(this, &FUnrealEdMisc::OnEditorPreModal);
|
|
FCoreDelegates::PostModal.AddRaw(this, &FUnrealEdMisc::OnEditorPostModal);
|
|
|
|
// Register the play world commands
|
|
FPlayWorldCommands::Register();
|
|
FPlayWorldCommands::BindGlobalPlayWorldCommands();
|
|
|
|
|
|
// Register common asset editor commands
|
|
FAssetEditorCommonCommands::Register();
|
|
|
|
// Register Graph based SoundCue editor commands
|
|
FSoundCueGraphEditorCommands::Register();
|
|
|
|
// Register Material Editor commands
|
|
FMaterialEditorCommands::Register();
|
|
|
|
// Register navigation commands for all viewports
|
|
FViewportNavigationCommands::Register();
|
|
|
|
// Register curve editor commands.
|
|
FRichCurveEditorCommands::Register();
|
|
|
|
FEditorModeRegistry::Initialize();
|
|
GLevelEditorModeTools().ActivateDefaultMode();
|
|
|
|
// Are we in immersive mode?
|
|
const TCHAR* ParsedCmdLine = FCommandLine::Get();
|
|
const bool bIsImmersive = FParse::Param( ParsedCmdLine, TEXT( "immersive" ) );
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
ISourceControlModule::Get().GetProvider().Init();
|
|
|
|
// Init the editor tools.
|
|
GTexAlignTools.Init();
|
|
|
|
EKeys::SetConsoleForGamepadLabels(GetDefault<UEditorExperimentalSettings>()->ConsoleForGamepadLabels);
|
|
|
|
// =================== CORE EDITOR INIT FINISHED ===================
|
|
|
|
// Offer to restore the auto-save packages before the startup map gets loaded (in case we want to restore the startup map)
|
|
const bool bHasPackagesToRestore = GUnrealEd->GetPackageAutoSaver().HasPackagesToRestore();
|
|
if(bHasPackagesToRestore)
|
|
{
|
|
// Hide the splash screen while we show the restore UI
|
|
FPlatformSplash::Hide();
|
|
GUnrealEd->GetPackageAutoSaver().OfferToRestorePackages();
|
|
FPlatformSplash::Show();
|
|
}
|
|
|
|
// Check for automated build/submit option
|
|
const bool bDoAutomatedMapBuild = FParse::Param( ParsedCmdLine, TEXT("AutomatedMapBuild") );
|
|
|
|
// Load startup map (conditionally)
|
|
SlowTask.EnterProgressFrame(60);
|
|
{
|
|
bool bMapLoaded = false;
|
|
|
|
// Insert any feature packs if required. We need to do this before we try and load a map since any pack may contain a map
|
|
FFeaturePackContentSource::ImportPendingPacks();
|
|
|
|
FString ParsedMapName;
|
|
if ( FParse::Token(ParsedCmdLine, ParsedMapName, false) &&
|
|
// If it's not a parameter
|
|
ParsedMapName.StartsWith( TEXT("-") ) == false )
|
|
{
|
|
FString InitialMapName;
|
|
|
|
// If the specified package exists
|
|
if ( FPackageName::SearchForPackageOnDisk(ParsedMapName, NULL, &InitialMapName) &&
|
|
// and it's a valid map file
|
|
FPaths::GetExtension(InitialMapName, /*bIncludeDot=*/true).ToLower() == FPackageName::GetMapPackageExtension().ToLower() )
|
|
{
|
|
// Never show loading progress when loading a map at startup. Loading status will instead
|
|
// be reflected in the splash screen status
|
|
const bool bShowProgress = false;
|
|
const bool bLoadAsTemplate = false;
|
|
|
|
// Load the map
|
|
FEditorFileUtils::LoadMap(InitialMapName, bLoadAsTemplate, bShowProgress);
|
|
bMapLoaded = true;
|
|
}
|
|
}
|
|
|
|
if( !bDoAutomatedMapBuild )
|
|
{
|
|
if (!bMapLoaded && GEditor)
|
|
{
|
|
const FString& StartupMap = GetDefault<UGameMapsSettings>()->EditorStartupMap;
|
|
|
|
if ((StartupMap.Len() > 0) && (GetDefault<UEditorLoadingSavingSettings>()->LoadLevelAtStartup != ELoadLevelAtStartup::None))
|
|
{
|
|
FEditorFileUtils::LoadDefaultMapAtStartup();
|
|
BeginPerformanceSurvey();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Process global shader results before we try to render anything
|
|
// CreateDefaultMainFrame below will access global shaders
|
|
if (GShaderCompilingManager)
|
|
{
|
|
GShaderCompilingManager->ProcessAsyncResults(false, true);
|
|
}
|
|
|
|
|
|
// =================== MAP LOADING FINISHED ===================
|
|
|
|
|
|
// Don't show map check if we're starting up in immersive mode
|
|
if( !bIsImmersive )
|
|
{
|
|
FMessageLog("MapCheck").Open( EMessageSeverity::Warning );
|
|
}
|
|
|
|
if ( bDoAutomatedMapBuild )
|
|
{
|
|
// If the user is doing an automated build, configure the settings for the build appropriately
|
|
FEditorBuildUtils::FEditorAutomatedBuildSettings AutomatedBuildSettings;
|
|
|
|
// Assume the user doesn't want to add files not in source control, they can specify that they
|
|
// want to via commandline option
|
|
AutomatedBuildSettings.bAutoAddNewFiles = false;
|
|
AutomatedBuildSettings.bCheckInPackages = false;
|
|
|
|
// Shut down the editor upon completion of the automated build
|
|
AutomatedBuildSettings.bShutdownEditorOnCompletion = true;
|
|
|
|
// Assume that save, SCC, and new map errors all result in failure and don't submit anything if any of those occur. If the user
|
|
// wants, they can explicitly ignore each warning type via commandline option
|
|
AutomatedBuildSettings.BuildErrorBehavior = FEditorBuildUtils::ABB_ProceedOnError;
|
|
AutomatedBuildSettings.FailedToSaveBehavior = FEditorBuildUtils::ABB_FailOnError;
|
|
AutomatedBuildSettings.NewMapBehavior = FEditorBuildUtils::ABB_FailOnError;
|
|
AutomatedBuildSettings.UnableToCheckoutFilesBehavior = FEditorBuildUtils::ABB_FailOnError;
|
|
|
|
// Attempt to parse the changelist description from the commandline
|
|
FString ParsedString;
|
|
if ( FParse::Value( ParsedCmdLine, TEXT("CLDesc="), ParsedString ) )
|
|
{
|
|
AutomatedBuildSettings.ChangeDescription = ParsedString;
|
|
}
|
|
|
|
// See if the user has specified any additional commandline options and set the build setting appropriately if so
|
|
bool ParsedBool;
|
|
if ( FParse::Bool( ParsedCmdLine, TEXT("IgnoreBuildErrors="), ParsedBool ) )
|
|
{
|
|
AutomatedBuildSettings.BuildErrorBehavior = ParsedBool ? FEditorBuildUtils::ABB_ProceedOnError : FEditorBuildUtils::ABB_FailOnError;
|
|
}
|
|
if ( FParse::Bool( ParsedCmdLine, TEXT("UseSCC="), ParsedBool ) )
|
|
{
|
|
AutomatedBuildSettings.bUseSCC = ParsedBool;
|
|
}
|
|
if ( FParse::Bool( ParsedCmdLine, TEXT("IgnoreSCCErrors="), ParsedBool ) )
|
|
{
|
|
AutomatedBuildSettings.UnableToCheckoutFilesBehavior = ParsedBool ? FEditorBuildUtils::ABB_ProceedOnError : FEditorBuildUtils::ABB_FailOnError;
|
|
}
|
|
if ( FParse::Bool( ParsedCmdLine, TEXT("IgnoreMapSaveErrors="), ParsedBool ) )
|
|
{
|
|
AutomatedBuildSettings.FailedToSaveBehavior = ParsedBool ? FEditorBuildUtils::ABB_ProceedOnError : FEditorBuildUtils::ABB_FailOnError;
|
|
}
|
|
if ( FParse::Bool( ParsedCmdLine, TEXT("AddFilesNotInDepot="), ParsedBool ) )
|
|
{
|
|
AutomatedBuildSettings.bAutoAddNewFiles = ParsedBool;
|
|
}
|
|
|
|
// Kick off the automated build
|
|
FText ErrorText;
|
|
FEditorBuildUtils::EditorAutomatedBuildAndSubmit( AutomatedBuildSettings, ErrorText );
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
LoadFBxLibraries();
|
|
|
|
// Register message log UIs
|
|
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
|
|
{
|
|
FMessageLogInitializationOptions InitOptions;
|
|
InitOptions.bShowPages = true;
|
|
MessageLogModule.RegisterLogListing("EditorErrors", LOCTEXT("EditorErrors", "Editor Errors"), InitOptions);
|
|
}
|
|
|
|
{
|
|
FMessageLogInitializationOptions InitOptions;
|
|
InitOptions.bDiscardDuplicates = true;
|
|
MessageLogModule.RegisterLogListing("LoadErrors", LOCTEXT("LoadErrors", "Load Errors"), InitOptions);
|
|
}
|
|
|
|
{
|
|
FMessageLogInitializationOptions InitOptions;
|
|
InitOptions.bShowPages = true;
|
|
MessageLogModule.RegisterLogListing("LightingResults", LOCTEXT("LightingResults", "Lighting Results"), InitOptions);
|
|
}
|
|
|
|
{
|
|
FMessageLogInitializationOptions InitOptions;
|
|
InitOptions.bShowPages = true;
|
|
MessageLogModule.RegisterLogListing("PackagingResults", LOCTEXT("PackagingResults", "Packaging Results"), InitOptions);
|
|
}
|
|
|
|
{
|
|
FMessageLogInitializationOptions InitOptions;
|
|
InitOptions.bShowFilters = true;
|
|
MessageLogModule.RegisterLogListing("MapCheck", LOCTEXT("MapCheck", "Map Check"), InitOptions);
|
|
}
|
|
|
|
{
|
|
FMessageLogInitializationOptions InitOptions;
|
|
InitOptions.bShowFilters = true;
|
|
MessageLogModule.RegisterLogListing("SlateStyleLog", LOCTEXT("SlateStyleLog", "Slate Style Log"), InitOptions );
|
|
}
|
|
FCompilerResultsLog::Register();
|
|
{
|
|
FMessageLogInitializationOptions InitOptions;
|
|
InitOptions.bShowPages = true;
|
|
InitOptions.bShowFilters = true;
|
|
MessageLogModule.RegisterLogListing("PIE", LOCTEXT("PlayInEditor", "Play In Editor"), InitOptions);
|
|
}
|
|
|
|
// install message log delegates
|
|
FMessageLog::OnMessageSelectionChanged().BindRaw(this, &FUnrealEdMisc::OnMessageSelectionChanged);
|
|
FUObjectToken::DefaultOnMessageTokenActivated().BindRaw(this, &FUnrealEdMisc::OnMessageTokenActivated);
|
|
FUObjectToken::DefaultOnGetObjectDisplayName().BindRaw(this, &FUnrealEdMisc::OnGetDisplayName);
|
|
FURLToken::OnGenerateURL().BindRaw(this, &FUnrealEdMisc::GenerateURL);
|
|
FAssetNameToken::OnGotoAsset().BindRaw(this, &FUnrealEdMisc::OnGotoAsset);
|
|
|
|
// Register to receive notification of new key bindings
|
|
OnUserDefinedChordChangedDelegateHandle = FInputBindingManager::Get().RegisterUserDefinedChordChanged(FOnUserDefinedChordChanged::FDelegate::CreateRaw( this, &FUnrealEdMisc::OnUserDefinedChordChanged ));
|
|
|
|
SlowTask.EnterProgressFrame(10);
|
|
|
|
// Send Project Analytics
|
|
InitEngineAnalytics();
|
|
|
|
// Setup a timer for a heartbeat event to track if users are actually using the editor or it is idle.
|
|
float Seconds = (float)FTimespan::FromMinutes(5.0).GetTotalSeconds();
|
|
FTimerDelegate Delegate;
|
|
Delegate.BindRaw( this, &FUnrealEdMisc::EditorAnalyticsHeartbeat );
|
|
|
|
GEditor->GetTimerManager()->SetTimer( EditorAnalyticsHeartbeatTimerHandle, Delegate, Seconds, true );
|
|
|
|
// Give the settings editor a way to restart the editor when it needs to
|
|
ISettingsEditorModule& SettingsEditorModule = FModuleManager::GetModuleChecked<ISettingsEditorModule>("SettingsEditor");
|
|
SettingsEditorModule.SetRestartApplicationCallback(FSimpleDelegate::CreateRaw(this, &FUnrealEdMisc::RestartEditor, false));
|
|
|
|
// add handler to notify about navmesh building process
|
|
NavigationBuildingNotificationHandler = MakeShareable(new FNavigationBuildingNotificationImpl());
|
|
|
|
// Handles "Enable World Composition" option in WorldSettings
|
|
UWorldComposition::EnableWorldCompositionEvent.BindRaw(this, &FUnrealEdMisc::EnableWorldComposition);
|
|
}
|
|
|
|
void FUnrealEdMisc::InitEngineAnalytics()
|
|
{
|
|
if ( FEngineAnalytics::IsAvailable() )
|
|
{
|
|
IAnalyticsProvider& EngineAnalytics = FEngineAnalytics::GetProvider();
|
|
|
|
// Send analytics about sample projects
|
|
if( FPaths::IsProjectFilePathSet() )
|
|
{
|
|
const FString& LoadedProjectFilePath = FPaths::GetProjectFilePath();
|
|
FProjectStatus ProjectStatus;
|
|
|
|
if (IProjectManager::Get().QueryStatusForProject(LoadedProjectFilePath, ProjectStatus))
|
|
{
|
|
if ( ProjectStatus.bSignedSampleProject )
|
|
{
|
|
EngineAnalytics.RecordEvent(TEXT( "Rocket.Usage.SampleProjectLoaded" ), TEXT("FileName"), FPaths::GetCleanFilename(LoadedProjectFilePath));
|
|
}
|
|
}
|
|
|
|
// Gather Project Code/Module Stats
|
|
TArray< FAnalyticsEventAttribute > ProjectAttributes;
|
|
ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "Name" ), *GetDefault<UGeneralProjectSettings>()->ProjectName ));
|
|
ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "Id" ), *GetDefault<UGeneralProjectSettings>()->ProjectID.ToString() ));
|
|
|
|
FGameProjectGenerationModule& GameProjectModule = FModuleManager::LoadModuleChecked<FGameProjectGenerationModule>(TEXT("GameProjectGeneration"));
|
|
|
|
int32 SourceFileCount = 0;
|
|
int64 SourceFileDirectorySize = 0;
|
|
GameProjectModule.Get().GetProjectSourceDirectoryInfo(SourceFileCount, SourceFileDirectorySize);
|
|
|
|
ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "SourceFileCount" ), SourceFileCount ));
|
|
ProjectAttributes.Add(FAnalyticsEventAttribute(FString("SourceFileDirectorySize"), SourceFileDirectorySize));
|
|
ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "ModuleCount" ), FModuleManager::Get().GetModuleCount() ));
|
|
|
|
// UObject class count
|
|
int32 UObjectClasses = 0;
|
|
int32 UBlueprintClasses = 0;
|
|
for( TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt )
|
|
{
|
|
if( !ClassIt->ClassGeneratedBy )
|
|
{
|
|
UObjectClasses++;
|
|
}
|
|
else
|
|
{
|
|
UBlueprintClasses++;
|
|
}
|
|
}
|
|
ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "ObjectClasses" ), UObjectClasses ));
|
|
ProjectAttributes.Add( FAnalyticsEventAttribute( FString( "BlueprintClasses" ), UBlueprintClasses ));
|
|
// Send project analytics
|
|
EngineAnalytics.RecordEvent( FString( "Editor.Usage.Project" ), ProjectAttributes );
|
|
// Trigger pending asset survey
|
|
bIsAssetAnalyticsPending = true;
|
|
}
|
|
|
|
// Record known modules' compilation methods
|
|
IHotReloadInterface* HotReload = IHotReloadInterface::GetPtr();
|
|
if(HotReload != nullptr)
|
|
{
|
|
TArray<FModuleStatus> Modules;
|
|
FModuleManager::Get().QueryModules(Modules);
|
|
for (auto& Module : Modules)
|
|
{
|
|
// Record only game modules as these are the only ones that should be hot-reloaded
|
|
if (Module.bIsGameModule)
|
|
{
|
|
TArray< FAnalyticsEventAttribute > ModuleAttributes;
|
|
ModuleAttributes.Add(FAnalyticsEventAttribute(FString("ModuleName"), Module.Name));
|
|
ModuleAttributes.Add(FAnalyticsEventAttribute(FString("CompilationMethod"), HotReload->GetModuleCompileMethod(*Module.Name)));
|
|
EngineAnalytics.RecordEvent(FString("Editor.Usage.Modules"), ModuleAttributes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::EditorAnalyticsHeartbeat()
|
|
{
|
|
// Don't attempt to send the heartbeat if analytics isn't available
|
|
if(!FEngineAnalytics::IsAvailable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
static double LastHeartbeatTime = FPlatformTime::Seconds();
|
|
|
|
double LastInteractionTime = FSlateApplication::Get().GetLastUserInteractionTime();
|
|
|
|
// Did the user interact since the last heartbeat
|
|
bool bIdle = LastInteractionTime < LastHeartbeatTime;
|
|
|
|
extern ENGINE_API float GAverageFPS;
|
|
|
|
TArray< FAnalyticsEventAttribute > Attributes;
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("Idle"), bIdle));
|
|
if(PerformanceAnalyticsStats->IsReliable())
|
|
{
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("AverageFPS"), GAverageFPS));
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("AverageFrameTime"), PerformanceAnalyticsStats->GetAverageFrameTime()));
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("AverageGameThreadTime"), PerformanceAnalyticsStats->GetAverageGameThreadTime()));
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("AverageRenderThreadTime"), PerformanceAnalyticsStats->GetAverageRenderThreadTime()));
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("AverageGPUFrameTime"), PerformanceAnalyticsStats->GetAverageGPUFrameTime()));
|
|
}
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.Heartbeat"), Attributes);
|
|
|
|
LastHeartbeatTime = FPlatformTime::Seconds();
|
|
}
|
|
|
|
void FUnrealEdMisc::TickAssetAnalytics()
|
|
{
|
|
if( bIsAssetAnalyticsPending )
|
|
{
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryName);
|
|
|
|
if( !AssetRegistryModule.Get().IsLoadingAssets())
|
|
{
|
|
// kill the pending flag
|
|
bIsAssetAnalyticsPending = false;
|
|
// Gather Asset stats
|
|
TArray<FAssetData> AssetData;
|
|
AssetRegistryModule.Get().GetAllAssets(AssetData);
|
|
|
|
TArray< FAnalyticsEventAttribute > AssetAttributes;
|
|
int32 NumMapFiles = 0;
|
|
TArray< FName > PackageNames;
|
|
TArray< FName > ClassInstances;
|
|
TArray< int32 > ClassInstanceCount;
|
|
|
|
for( auto AssetIter = AssetData.CreateConstIterator(); AssetIter; ++AssetIter )
|
|
{
|
|
PackageNames.AddUnique( AssetIter->PackageName );
|
|
if( AssetIter->AssetClass == UWorld::StaticClass()->GetFName() )
|
|
{
|
|
NumMapFiles++;
|
|
}
|
|
|
|
if( AssetIter->AssetClass != NAME_None )
|
|
{
|
|
int32 ClassIndex = ClassInstances.AddUnique( AssetIter->AssetClass );
|
|
if( ClassInstanceCount.Num() < ClassInstances.Num() )
|
|
{
|
|
ClassInstanceCount.Add( 1 );
|
|
}
|
|
else
|
|
{
|
|
ClassInstanceCount[ ClassIndex ] += 1;
|
|
}
|
|
}
|
|
}
|
|
const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
|
|
AssetAttributes.Add( FAnalyticsEventAttribute( FString( "ProjectId" ), *ProjectSettings.ProjectID.ToString() ));
|
|
AssetAttributes.Add( FAnalyticsEventAttribute( FString( "AssetPackageCount" ), PackageNames.Num() ));
|
|
AssetAttributes.Add( FAnalyticsEventAttribute( FString( "Maps" ), NumMapFiles ));
|
|
// Send project analytics
|
|
FEngineAnalytics::GetProvider().RecordEvent( FString( "Editor.Usage.AssetCounts" ), AssetAttributes );
|
|
|
|
TArray< FAnalyticsEventAttribute > AssetInstances;
|
|
AssetInstances.Add( FAnalyticsEventAttribute( FString( "ProjectId" ), *ProjectSettings.ProjectID.ToString() ));
|
|
for( auto ClassIter = ClassInstances.CreateIterator(); ClassIter; ++ClassIter )
|
|
{
|
|
if( ClassInstanceCount[ ClassIter.GetIndex() ] > 0 )
|
|
{
|
|
AssetInstances.Add( FAnalyticsEventAttribute( ClassIter->ToString(), ClassInstanceCount[ ClassIter.GetIndex() ] ));
|
|
}
|
|
}
|
|
// Send class instance analytics
|
|
FEngineAnalytics::GetProvider().RecordEvent( FString( "Editor.Usage.AssetClasses" ), AssetInstances );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FUnrealEdMisc::EnableWorldComposition(UWorld* InWorld, bool bEnable)
|
|
{
|
|
if (InWorld == nullptr || InWorld->WorldType != EWorldType::Editor)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!bEnable)
|
|
{
|
|
if (InWorld->WorldComposition != nullptr)
|
|
{
|
|
InWorld->FlushLevelStreaming();
|
|
InWorld->WorldComposition->MarkPendingKill();
|
|
InWorld->WorldComposition = nullptr;
|
|
UWorldComposition::WorldCompositionChangedEvent.Broadcast(InWorld);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (InWorld->WorldComposition == nullptr)
|
|
{
|
|
FString RootPackageName = InWorld->GetOutermost()->GetName();
|
|
|
|
// Map should be saved to disk
|
|
if (!FPackageName::DoesPackageExist(RootPackageName))
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("EnableWorldCompositionNotSaved_Message", "Please save your level to disk before enabling World Composition"));
|
|
return false;
|
|
}
|
|
|
|
// All existing sub-levels on this map should be removed
|
|
int32 NumExistingSublevels = InWorld->StreamingLevels.Num();
|
|
if (NumExistingSublevels > 0)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("EnableWorldCompositionExistingSublevels_Message", "World Composition cannot be enabled because there are already sub-levels manually added to the persistent level. World Composition uses auto-discovery so you must first remove any manually added sub-levels from the Levels window"));
|
|
return false;
|
|
}
|
|
|
|
auto WorldCompostion = NewObject<UWorldComposition>(InWorld);
|
|
// All map files found in the same and folder and all sub-folders will be added ass sub-levels to this map
|
|
// Make sure user understands this
|
|
int32 NumFoundSublevels = WorldCompostion->GetTilesList().Num();
|
|
if (NumFoundSublevels)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("NumSubLevels"), NumFoundSublevels);
|
|
Arguments.Add(TEXT("FolderLocation"), FText::FromString(FPackageName::GetLongPackagePath(RootPackageName)));
|
|
const FText Message = FText::Format(LOCTEXT("EnableWorldCompositionPrompt_Message", "World Composition auto-discovers sub-levels by scanning the folder the level is saved in, and all sub-folders. {NumSubLevels} level files were found in {FolderLocation} and will be added as sub-levels. Do you want to continue?"), Arguments);
|
|
|
|
auto AppResult = FMessageDialog::Open(EAppMsgType::OkCancel, Message);
|
|
if (AppResult != EAppReturnType::Ok)
|
|
{
|
|
WorldCompostion->MarkPendingKill();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//
|
|
InWorld->WorldComposition = WorldCompostion;
|
|
UWorldComposition::WorldCompositionChangedEvent.Broadcast(InWorld);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/** Build and return the path to the current project (used for relaunching the editor.) */
|
|
FString CreateProjectPath()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
// If we are running in 64 bit, launch the 64 bit process
|
|
const TCHAR* PlatformConfig = FPlatformMisc::GetUBTPlatform();
|
|
// Executable filename does not depend on the selected project. Simply create full path to the current executable.
|
|
FString ExeFileName = FPaths::EngineDir() / TEXT("Binaries") / PlatformConfig / FString( FPlatformProcess::ExecutableName() ) + TEXT(".exe");
|
|
return ExeFileName;
|
|
#elif PLATFORM_MAC
|
|
@autoreleasepool
|
|
{
|
|
return UTF8_TO_TCHAR([[[NSBundle mainBundle] executablePath] fileSystemRepresentation]);
|
|
}
|
|
#elif PLATFORM_LINUX
|
|
const TCHAR* PlatformConfig = FPlatformMisc::GetUBTPlatform();
|
|
FString ExeFileName = FPaths::EngineDir() / TEXT("Binaries") / PlatformConfig / FString(FPlatformProcess::ExecutableName());
|
|
return ExeFileName;
|
|
#else
|
|
#error "Unknown platform"
|
|
#endif
|
|
}
|
|
|
|
void FUnrealEdMisc::OnExit()
|
|
{
|
|
if ( !bInitialized )
|
|
{
|
|
return;
|
|
}
|
|
bInitialized = false;
|
|
|
|
if (bIsSurveyingPerformance)
|
|
{
|
|
CancelPerformanceSurvey();
|
|
}
|
|
|
|
if (NavigationBuildingNotificationHandler.IsValid())
|
|
{
|
|
NavigationBuildingNotificationHandler = NULL;
|
|
}
|
|
|
|
// Report session maximum window and tab counts to engine analytics, if available
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
TArray<FAnalyticsEventAttribute> TabsAttribs;
|
|
TabsAttribs.Add(FAnalyticsEventAttribute(FString("MaxTabs"), FGlobalTabmanager::Get()->GetMaximumTabCount()));
|
|
TabsAttribs.Add(FAnalyticsEventAttribute(FString("MaxTopLevelWindows"), FGlobalTabmanager::Get()->GetMaximumWindowCount()));
|
|
|
|
const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
|
|
TabsAttribs.Add(FAnalyticsEventAttribute(FString("ProjectId"), ProjectSettings.ProjectID.ToString()));
|
|
|
|
FEngineAnalytics::GetProvider().RecordEvent(FString("Editor.Usage.WindowCounts"), TabsAttribs);
|
|
|
|
// Report asset updates (to reflect forward progress made by the user)
|
|
TArray<FAnalyticsEventAttribute> AssetUpdateCountAttribs;
|
|
for (auto& UpdatedAssetPair : NumUpdatesByAssetName)
|
|
{
|
|
AssetUpdateCountAttribs.Add(FAnalyticsEventAttribute(UpdatedAssetPair.Key.ToString(), UpdatedAssetPair.Value));
|
|
}
|
|
FEngineAnalytics::GetProvider().RecordEvent(FString("Editor.Usage.AssetsSaved"), AssetUpdateCountAttribs);
|
|
|
|
FSlateApplication::Get().GetPlatformApplication()->SendAnalytics(&FEngineAnalytics::GetProvider());
|
|
FEditorViewportStats::SendUsageData();
|
|
}
|
|
|
|
FInputBindingManager::Get().UnregisterUserDefinedChordChanged(OnUserDefinedChordChangedDelegateHandle);
|
|
FMessageLog::OnMessageSelectionChanged().Unbind();
|
|
FUObjectToken::DefaultOnMessageTokenActivated().Unbind();
|
|
FUObjectToken::DefaultOnGetObjectDisplayName().Unbind();
|
|
FURLToken::OnGenerateURL().Unbind();
|
|
FAssetNameToken::OnGotoAsset().Unbind();
|
|
|
|
// Unregister message log UIs
|
|
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
|
|
MessageLogModule.UnregisterLogListing("EditorErrors");
|
|
MessageLogModule.UnregisterLogListing("LoadErrors");
|
|
MessageLogModule.UnregisterLogListing("LightingResults");
|
|
MessageLogModule.UnregisterLogListing("PackagingResults");
|
|
MessageLogModule.UnregisterLogListing("MapCheck");
|
|
FCompilerResultsLog::Unregister();
|
|
MessageLogModule.UnregisterLogListing("PIE");
|
|
|
|
// Unregister all events
|
|
FEditorDelegates::SelectedProps.RemoveAll(this);
|
|
FEditorDelegates::DisplayLoadErrors.RemoveAll(this);
|
|
FEditorDelegates::MapChange.RemoveAll(this);
|
|
FEditorDelegates::RefreshEditor.RemoveAll(this);
|
|
FEditorDelegates::PreSaveWorld.RemoveAll(this);
|
|
FEditorSupportDelegates::RedrawAllViewports.RemoveAll(this);
|
|
GEngine->OnLevelActorAdded().RemoveAll(this);
|
|
|
|
#if USE_UNIT_TESTS
|
|
FAutomationTestFramework::GetInstance().PreTestingEvent.RemoveAll(this);
|
|
FAutomationTestFramework::GetInstance().PostTestingEvent.RemoveAll(this);
|
|
#endif // USE_UNIT_TESTS
|
|
|
|
// FCoreDelegates::OnBreakpointTriggered.RemoveAll(this);
|
|
// FCoreDelegates::OnScriptFatalError.RemoveAll(this);
|
|
|
|
FEditorDelegates::ChangeEditorMode.RemoveAll(this);
|
|
FCoreDelegates::PreModal.RemoveAll(this);
|
|
FCoreDelegates::PostModal.RemoveAll(this);
|
|
|
|
FComponentAssetBrokerage::PRIVATE_ShutdownBrokerage();
|
|
|
|
ISourceControlModule::Get().GetProvider().Close();
|
|
|
|
UWorldComposition::EnableWorldCompositionEvent.Unbind();
|
|
|
|
UnloadFBxLibraries();
|
|
|
|
const TMap<FString, FString>& IniRestoreFiles = GetConfigRestoreFilenames();
|
|
|
|
for (auto Iter = IniRestoreFiles.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
// Key = Config Filename, Value = Backup Filename
|
|
if (FPaths::FileExists(Iter.Value()))
|
|
{
|
|
IFileManager::Get().Copy(*Iter.Key(), *Iter.Value());
|
|
}
|
|
}
|
|
|
|
|
|
// The new process needs to be spawned as late as possible so two editor processes aren't running concurrently for very long.
|
|
// It definitely needs to happen after the preferences file is restored from an import on the line above
|
|
const FString& PendingProjName = FUnrealEdMisc::Get().GetPendingProjectName();
|
|
if( PendingProjName.Len() > 0 )
|
|
{
|
|
// If there is a pending project switch, spawn that process now and use the same command line parameters that were used for this editor instance.
|
|
FString Cmd = PendingProjName + FCommandLine::Get();
|
|
|
|
FString ExeFilename = CreateProjectPath();
|
|
FProcHandle Handle = FPlatformProcess::CreateProc( *ExeFilename, *Cmd, true, false, false, NULL, 0, NULL, NULL );
|
|
if( !Handle.IsValid() )
|
|
{
|
|
// We were not able to spawn the new project exe.
|
|
// Its likely that the exe doesn't exist.
|
|
// Skip shutting down the editor if this happens
|
|
UE_LOG(LogUnrealEdMisc, Warning, TEXT("Could not restart the editor") );
|
|
|
|
// Clear the pending project to ensure the editor can still be shut down normally
|
|
FUnrealEdMisc::Get().ClearPendingProjectName();
|
|
|
|
return;
|
|
}
|
|
FPlatformProcess::CloseProc(Handle);
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::ShutdownAfterError()
|
|
{
|
|
ISourceControlModule::Get().GetProvider().Close();
|
|
}
|
|
|
|
void FUnrealEdMisc::CB_SelectedProps()
|
|
{
|
|
// Display the actor properties dialog if any actors are selected at all
|
|
if ( GUnrealEd->GetSelectedActorCount() > 0 )
|
|
{
|
|
GUnrealEd->ShowActorProperties();
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::CB_DisplayLoadErrors()
|
|
{
|
|
if( !GIsDemoMode )
|
|
{
|
|
// Don't display load errors when starting up in immersive mode
|
|
// @todo immersive: Really only matters on first level load and PIE startup, maybe only disallow at startup?
|
|
const bool bIsImmersive = FParse::Param( FCommandLine::Get(), TEXT( "immersive" ) );
|
|
if( !bIsImmersive && !GIsAutomationTesting)
|
|
{
|
|
FMessageLog("LoadErrors").Open();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::CB_RefreshEditor()
|
|
{
|
|
FEditorDelegates::RefreshAllBrowsers.Broadcast();
|
|
}
|
|
|
|
void FUnrealEdMisc::PreSaveWorld(uint32 SaveFlags, UWorld* World)
|
|
{
|
|
const bool bAutosaveOrPIE = (SaveFlags & SAVE_FromAutosave) != 0;
|
|
if (bAutosaveOrPIE || World == NULL || World != GEditor->GetEditorWorldContext().World() || !FEngineAnalytics::IsAvailable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 NumAdditiveBrushes = 0;
|
|
int32 NumSubtractiveBrushes = 0;
|
|
for (TActorIterator<ABrush> BrushIt(World); BrushIt; ++BrushIt)
|
|
{
|
|
ABrush* Brush = *BrushIt;
|
|
if (Brush != NULL)
|
|
{
|
|
if (Brush->BrushType == EBrushType::Brush_Add) NumAdditiveBrushes++;
|
|
else if (Brush->BrushType == EBrushType::Brush_Subtract) NumSubtractiveBrushes++;
|
|
}
|
|
}
|
|
|
|
TArray< FAnalyticsEventAttribute > BrushAttributes;
|
|
BrushAttributes.Add( FAnalyticsEventAttribute( FString( "Additive" ), NumAdditiveBrushes ));
|
|
BrushAttributes.Add( FAnalyticsEventAttribute( FString( "Subtractive" ), NumSubtractiveBrushes ));
|
|
const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
|
|
BrushAttributes.Add( FAnalyticsEventAttribute( FString( "ProjectId" ), ProjectSettings.ProjectID.ToString()) );
|
|
|
|
FEngineAnalytics::GetProvider().RecordEvent( FString( "Editor.Usage.Brushes" ), BrushAttributes );
|
|
}
|
|
|
|
void FUnrealEdMisc::CB_MapChange( uint32 InFlags )
|
|
{
|
|
UWorld* World = GWorld;
|
|
|
|
// Make sure the world package is never marked dirty here
|
|
const bool bOldDirtyState = World->GetCurrentLevel()->GetOutermost()->IsDirty();
|
|
|
|
|
|
// Clear property coloration settings.
|
|
const FString EmptyString(TEXT(""));
|
|
GEditor->SetPropertyColorationTarget( World, EmptyString, NULL, NULL, NULL );
|
|
|
|
if (InFlags != MapChangeEventFlags::NewMap)
|
|
{
|
|
// Rebuild the collision hash if this map change was rebuilt
|
|
// Minor things like brush subtraction will set it to "0".
|
|
if (InFlags != MapChangeEventFlags::Default)
|
|
{
|
|
World->ClearWorldComponents();
|
|
|
|
// Note: CleanupWorld is being abused here to detach components and some other stuff
|
|
// CleanupWorld should only be called before destroying the world
|
|
// So bCleanupResources is being passed as false
|
|
World->CleanupWorld(true, false);
|
|
}
|
|
|
|
GEditor->EditorUpdateComponents();
|
|
}
|
|
|
|
GLevelEditorModeTools().MapChangeNotify();
|
|
|
|
/*if ((InFlags&MapChangeEventFlags::MapRebuild) != 0)
|
|
{
|
|
GUnrealEd->UpdateFloatingPropertyWindows();
|
|
}*/
|
|
|
|
CB_RefreshEditor();
|
|
|
|
// Only reset the auto save timer if we've created or loaded a new map
|
|
if( InFlags & MapChangeEventFlags::NewMap )
|
|
{
|
|
GUnrealEd->GetPackageAutoSaver().ResetAutoSaveTimer();
|
|
}
|
|
|
|
if (!bOldDirtyState)
|
|
{
|
|
World->GetCurrentLevel()->GetOutermost()->SetDirtyFlag( bOldDirtyState );
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::CB_RedrawAllViewports()
|
|
{
|
|
GUnrealEd->RedrawAllViewports();
|
|
}
|
|
|
|
void FUnrealEdMisc::CB_LevelActorsAdded(AActor* InActor)
|
|
{
|
|
if (!GIsEditorLoadingPackage &&
|
|
FEngineAnalytics::IsAvailable() &&
|
|
InActor &&
|
|
InActor->GetWorld() == GUnrealEd->GetEditorWorldContext().World() &&
|
|
InActor->IsA(APawn::StaticClass()))
|
|
{
|
|
const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
|
|
FEngineAnalytics::GetProvider().RecordEvent(FString("Editor.Usage.PawnPlacement"), FString( "ProjectId" ), ProjectSettings.ProjectID.ToString());
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::CB_PreAutomationTesting()
|
|
{
|
|
// Shut down SCC if it's enabled, as unit tests shouldn't be allowed to make any modifications to source control
|
|
if ( ISourceControlModule::Get().IsEnabled() )
|
|
{
|
|
ISourceControlModule::Get().GetProvider().Close();
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::CB_PostAutomationTesting()
|
|
{
|
|
// Re-enable source control
|
|
ISourceControlModule::Get().GetProvider().Init();
|
|
}
|
|
|
|
void FUnrealEdMisc::OnEditorChangeMode(FEditorModeID NewEditorMode)
|
|
{
|
|
GLevelEditorModeTools().ActivateMode( NewEditorMode, true );
|
|
}
|
|
|
|
void FUnrealEdMisc::OnEditorPreModal()
|
|
{
|
|
if( FSlateApplication::IsInitialized() )
|
|
{
|
|
FSlateApplication::Get().ExternalModalStart();
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::OnEditorPostModal()
|
|
{
|
|
if( FSlateApplication::IsInitialized() )
|
|
{
|
|
FSlateApplication::Get().ExternalModalStop();
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::OnDeferCommand( const FString& DeferredCommand )
|
|
{
|
|
GUnrealEd->DeferredCommands.Add( DeferredCommand );
|
|
}
|
|
|
|
|
|
void FUnrealEdMisc::OnMessageTokenActivated(const TSharedRef<IMessageToken>& Token)
|
|
{
|
|
if(Token->GetType() != EMessageToken::Object )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const TSharedRef<FUObjectToken> UObjectToken = StaticCastSharedRef<FUObjectToken>(Token);
|
|
|
|
if(UObjectToken->GetObject().IsValid())
|
|
{
|
|
UObject* Object = const_cast<UObject*>(UObjectToken->GetObject().Get());
|
|
|
|
ULightmappedSurfaceCollection* SurfaceCollection = Cast<ULightmappedSurfaceCollection>(Object);
|
|
if (SurfaceCollection)
|
|
{
|
|
// Deselect all selected object...
|
|
GEditor->SelectNone( true, true );
|
|
|
|
// Select the surfaces in this mapping
|
|
TArray<AActor*> SelectedActors;
|
|
for (int32 SurfaceIdx = 0; SurfaceIdx < SurfaceCollection->Surfaces.Num(); SurfaceIdx++)
|
|
{
|
|
int32 SurfaceIndex = SurfaceCollection->Surfaces[SurfaceIdx];
|
|
FBspSurf& Surf = SurfaceCollection->SourceModel->Surfs[SurfaceIndex];
|
|
SurfaceCollection->SourceModel->ModifySurf(SurfaceIndex, 0);
|
|
Surf.PolyFlags |= PF_Selected;
|
|
if (Surf.Actor)
|
|
{
|
|
SelectedActors.AddUnique(Surf.Actor);
|
|
}
|
|
}
|
|
|
|
// Add the brushes to the selected actors list...
|
|
if (SelectedActors.Num() > 0)
|
|
{
|
|
GEditor->MoveViewportCamerasToActor(SelectedActors, false);
|
|
}
|
|
|
|
GEditor->NoteSelectionChange();
|
|
}
|
|
else
|
|
{
|
|
AActor* Actor = Cast<AActor>(Object);
|
|
UPrimitiveComponent* Component = Cast<UPrimitiveComponent>(Object);
|
|
|
|
if (Component)
|
|
{
|
|
check( !Actor);
|
|
if( Component->GetOwner())
|
|
{
|
|
Actor = Component->GetOwner();
|
|
}
|
|
}
|
|
|
|
if (Actor)
|
|
{
|
|
// Select the actor
|
|
GEditor->SelectNone(false, true);
|
|
GEditor->SelectActor(Actor, /*InSelected=*/true, /*bNotify=*/false, /*bSelectEvenIfHidden=*/true);
|
|
GEditor->NoteSelectionChange();
|
|
GEditor->MoveViewportCamerasToActor(*Actor, false);
|
|
|
|
// Update the property windows and create one if necessary
|
|
GUnrealEd->ShowActorProperties();
|
|
GUnrealEd->UpdateFloatingPropertyWindows();
|
|
}
|
|
else
|
|
{
|
|
TArray<UObject*> ObjectArray;
|
|
ObjectArray.Add(Object);
|
|
GEditor->SyncBrowserToObjects(ObjectArray);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FText FUnrealEdMisc::OnGetDisplayName(UObject* InObject, bool bFullPath)
|
|
{
|
|
FText Name = LOCTEXT("DisplayNone", "<None>");
|
|
|
|
if(InObject != NULL)
|
|
{
|
|
// Is this an object held by an actor?
|
|
AActor* Actor = NULL;
|
|
UActorComponent* Component = Cast<UActorComponent>(InObject);
|
|
|
|
if (Component != NULL)
|
|
{
|
|
Actor = Cast<AActor>(Component->GetOuter());
|
|
}
|
|
|
|
if (Actor != NULL)
|
|
{
|
|
Name = FText::FromString( bFullPath ? Actor->GetPathName() : Actor->GetName() );
|
|
}
|
|
else if (InObject != NULL)
|
|
{
|
|
Name = FText::FromString( bFullPath ? InObject->GetPathName() : InObject->GetName() );
|
|
}
|
|
}
|
|
|
|
return Name;
|
|
}
|
|
|
|
void FUnrealEdMisc::OnMessageSelectionChanged(TArray< TSharedRef<FTokenizedMessage> >& Selection)
|
|
{
|
|
// Clear existing selections
|
|
GEditor->SelectNone(false, true);
|
|
|
|
bool bActorsSelected = false;
|
|
TArray<UObject*> ObjectArray;
|
|
|
|
const int32 NumSelected = Selection.Num();
|
|
if (NumSelected > 0)
|
|
{
|
|
const FScopedBusyCursor BusyCursor;
|
|
for( int32 LineIndex = 0; LineIndex < NumSelected; ++LineIndex )
|
|
{
|
|
TSharedPtr<FTokenizedMessage> Line = Selection[ LineIndex ];
|
|
|
|
// Find objects reference by this message
|
|
const TArray< TSharedRef<IMessageToken> >& MessageTokens = Line->GetMessageTokens();
|
|
for(auto TokenIt = MessageTokens.CreateConstIterator(); TokenIt; ++TokenIt)
|
|
{
|
|
const TSharedRef<IMessageToken> Token = *TokenIt;
|
|
if( Token->GetType() == EMessageToken::Object )
|
|
{
|
|
const TSharedRef<FUObjectToken> UObjectToken = StaticCastSharedRef<FUObjectToken>(Token);
|
|
if( UObjectToken->GetObject().IsValid() )
|
|
{
|
|
// Check referenced object type
|
|
UObject* Object = UObjectToken->GetObject().Get();
|
|
UPrimitiveComponent* Component = Cast<UPrimitiveComponent>(Object);
|
|
AActor* Actor = Cast<AActor>(Object);
|
|
if( Component != NULL )
|
|
{
|
|
check( !Actor);
|
|
if( Component->GetOwner() )
|
|
{
|
|
Actor = Component->GetOwner();
|
|
}
|
|
}
|
|
|
|
if( Actor != NULL )
|
|
{
|
|
// Actor found, move to it if it's first and only in the list
|
|
if( !bActorsSelected )
|
|
{
|
|
GEditor->SelectNone(false, true);
|
|
bActorsSelected = true;
|
|
if( Selection.Num() == 1)
|
|
{
|
|
GEditor->MoveViewportCamerasToActor(*Actor, false);
|
|
}
|
|
}
|
|
|
|
GEditor->SelectActor(Actor, /*InSelected=*/true, /*bNotify=*/false, /*bSelectEvenIfHidden=*/true);
|
|
}
|
|
else
|
|
{
|
|
// Add object to list of objects to sync content browser to
|
|
ObjectArray.Add(Object);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bActorsSelected )
|
|
{
|
|
GEditor->NoteSelectionChange();
|
|
|
|
// Update the property windows and create one if necessary
|
|
GUnrealEd->ShowActorProperties();
|
|
GUnrealEd->UpdateFloatingPropertyWindows();
|
|
}
|
|
|
|
if (ObjectArray.Num() > 0)
|
|
{
|
|
GEditor->SyncBrowserToObjects(ObjectArray);
|
|
}
|
|
}
|
|
|
|
// Now, special handle the BSP mappings...
|
|
if (NumSelected > 0)
|
|
{
|
|
const FScopedBusyCursor BusyCursor;
|
|
TArray<ULightmappedSurfaceCollection*> SelectedSurfaceCollections;
|
|
|
|
for( int32 LineIndex = 0; LineIndex < NumSelected; ++LineIndex )
|
|
{
|
|
TSharedPtr<FTokenizedMessage> Line = Selection[ LineIndex ];
|
|
|
|
// Find objects reference by this message
|
|
const TArray< TSharedRef<IMessageToken> >& MessageTokens = Line->GetMessageTokens();
|
|
for(auto TokenIt = MessageTokens.CreateConstIterator(); TokenIt; ++TokenIt)
|
|
{
|
|
const TSharedRef<IMessageToken> Token = *TokenIt;
|
|
if( Token->GetType() == EMessageToken::Object )
|
|
{
|
|
const TSharedRef<FUObjectToken> UObjectToken = StaticCastSharedRef<FUObjectToken>(Token);
|
|
if( UObjectToken->GetObject().IsValid() )
|
|
{
|
|
// Check referenced object type
|
|
UObject* Object = UObjectToken->GetObject().Get();
|
|
if (Object != NULL)
|
|
{
|
|
ULightmappedSurfaceCollection* SelectedSurfaceCollection = Cast<ULightmappedSurfaceCollection>(Object);
|
|
if (SelectedSurfaceCollection)
|
|
{
|
|
SelectedSurfaceCollections.Add(SelectedSurfaceCollection);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If any surface collections are selected, select them in the editor
|
|
if (SelectedSurfaceCollections.Num() > 0)
|
|
{
|
|
TArray<AActor*> SelectedActors;
|
|
for (int32 CollectionIdx = 0; CollectionIdx < SelectedSurfaceCollections.Num(); CollectionIdx++)
|
|
{
|
|
ULightmappedSurfaceCollection* SurfaceCollection = SelectedSurfaceCollections[CollectionIdx];
|
|
if (SurfaceCollection != NULL)
|
|
{
|
|
// Select the surfaces in this mapping
|
|
for (int32 SurfaceIdx = 0; SurfaceIdx < SurfaceCollection->Surfaces.Num(); SurfaceIdx++)
|
|
{
|
|
int32 SurfaceIndex = SurfaceCollection->Surfaces[SurfaceIdx];
|
|
FBspSurf& Surf = SurfaceCollection->SourceModel->Surfs[SurfaceIndex];
|
|
SurfaceCollection->SourceModel->ModifySurf(SurfaceIndex, 0);
|
|
Surf.PolyFlags |= PF_Selected;
|
|
if (Surf.Actor != NULL)
|
|
{
|
|
SelectedActors.AddUnique(Surf.Actor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the brushes to the selected actors list...
|
|
if (SelectedActors.Num() > 0)
|
|
{
|
|
GEditor->MoveViewportCamerasToActor(SelectedActors, false);
|
|
}
|
|
|
|
GEditor->NoteSelectionChange();
|
|
}
|
|
}
|
|
}
|
|
|
|
FString FUnrealEdMisc::GenerateURL(const FString& InUDNPage)
|
|
{
|
|
if( InUDNPage.Len() > 0 )
|
|
{
|
|
FInternationalization& I18N = FInternationalization::Get();
|
|
|
|
const FString PageURL = FString::Printf( TEXT( "%s/Editor/LevelEditing/MapErrors/index.html" ), *I18N.GetCurrentCulture()->GetUnrealLegacyThreeLetterISOLanguageName() );
|
|
const FString BookmarkURL = FString::Printf( TEXT( "#%s" ), *InUDNPage );
|
|
|
|
// Developers can browse documentation included with the engine distribution, check for file presence...
|
|
FString MapErrorURL = FString::Printf( TEXT( "%sDocumentation/HTML/%s" ), *FPaths::ConvertRelativePathToFull( FPaths::EngineDir() ), *PageURL );
|
|
if (IFileManager::Get().FileSize(*MapErrorURL) != INDEX_NONE)
|
|
{
|
|
MapErrorURL = FString::Printf( TEXT( "file://%s%s" ), *MapErrorURL, *BookmarkURL );
|
|
}
|
|
// ... if it's not present, fallback to using the online version, if the full URL is provided...
|
|
else if(FUnrealEdMisc::Get().GetURL( TEXT("MapErrorURL"), MapErrorURL, true ) && MapErrorURL.EndsWith( TEXT( ".html" ) ))
|
|
{
|
|
MapErrorURL.ReplaceInline( TEXT( "/INT/" ), *FString::Printf( TEXT( "/%s/" ), *I18N.GetCurrentCulture()->GetUnrealLegacyThreeLetterISOLanguageName() ) );
|
|
MapErrorURL += BookmarkURL;
|
|
}
|
|
// ...otherwise, attempt to create the URL from what we know here...
|
|
else if(FUnrealEdMisc::Get().GetURL( TEXT("UDNDocsURL"), MapErrorURL, true ))
|
|
{
|
|
if ( !MapErrorURL.EndsWith( TEXT( "/" ) ) )
|
|
{
|
|
MapErrorURL += TEXT( "/" );
|
|
}
|
|
MapErrorURL += PageURL;
|
|
MapErrorURL += BookmarkURL;
|
|
}
|
|
// ... failing that, just try to access the UDN, period.
|
|
else
|
|
{
|
|
FUnrealEdMisc::Get().GetURL( TEXT("UDNURL"), MapErrorURL, true );
|
|
}
|
|
|
|
return MapErrorURL;
|
|
}
|
|
|
|
return FString();
|
|
}
|
|
|
|
void FUnrealEdMisc::OnGotoAsset(const FString& InAssetPath) const
|
|
{
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryName);
|
|
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
|
|
FAssetData AssetData = AssetRegistry.GetAssetByObjectPath( *InAssetPath );
|
|
if ( AssetData.IsValid() )
|
|
{
|
|
TArray<FAssetData> AssetDataToSync;
|
|
|
|
// if its a package, sync the browser to the assets inside the package
|
|
if(AssetData.GetClass() == UPackage::StaticClass())
|
|
{
|
|
TArray<UPackage*> Packages;
|
|
Packages.Add(CastChecked<UPackage>(AssetData.GetAsset()));
|
|
TArray<UObject*> ObjectsInPackages;
|
|
PackageTools::GetObjectsInPackages(&Packages, ObjectsInPackages);
|
|
|
|
for(auto It(ObjectsInPackages.CreateConstIterator()); It; ++It)
|
|
{
|
|
UObject* ObjectInPackage = *It;
|
|
if(ObjectInPackage->IsAsset())
|
|
{
|
|
FAssetData SubAssetData(ObjectInPackage);
|
|
if(SubAssetData.IsValid())
|
|
{
|
|
AssetDataToSync.Add(SubAssetData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(AssetDataToSync.Num() == 0)
|
|
{
|
|
AssetDataToSync.Add(AssetData);
|
|
}
|
|
|
|
GEditor->SyncBrowserToObjects(AssetDataToSync);
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::OnObjectSaved(UObject* SavedObject)
|
|
{
|
|
// Ensure the saved object is a non-UWorld asset (UWorlds are handled separately)
|
|
if (!SavedObject->IsA<UWorld>() && SavedObject->IsAsset())
|
|
{
|
|
LogAssetUpdate(SavedObject);
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::OnWorldSaved(uint32 SaveFlags, UWorld* SavedWorld)
|
|
{
|
|
LogAssetUpdate(SavedWorld);
|
|
}
|
|
|
|
void FUnrealEdMisc::LogAssetUpdate(UObject* UpdatedAsset)
|
|
{
|
|
UPackage* AssetPackage = UpdatedAsset->GetOutermost();
|
|
const bool bIsPIESave = AssetPackage->RootPackageHasAnyFlags(PKG_PlayInEditor);
|
|
const bool bIsAutosave = GUnrealEd->GetPackageAutoSaver().IsAutoSaving();
|
|
|
|
if (!bIsPIESave && !bIsAutosave && !GIsAutomationTesting)
|
|
{
|
|
uint32& NumUpdates = NumUpdatesByAssetName.FindOrAdd(UpdatedAsset->GetClass()->GetFName());
|
|
NumUpdates++;
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::SwitchProject(const FString& GameOrProjectFileName, bool bWarn)
|
|
{
|
|
if (GUnrealEd->WarnIfLightingBuildIsCurrentlyRunning())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bIsProjectFileName = FPaths::GetExtension(GameOrProjectFileName) == FProjectDescriptor::GetExtension();
|
|
|
|
bool bSwitch = true;
|
|
|
|
if(bWarn)
|
|
{
|
|
// Get the project name to switch to
|
|
FString ProjectDisplayName = GameOrProjectFileName;
|
|
if ( bIsProjectFileName )
|
|
{
|
|
// In rocket the display name is just the base filename of the project
|
|
ProjectDisplayName = FPaths::GetBaseFilename(GameOrProjectFileName);
|
|
}
|
|
|
|
// Warn the user that this will restart the editor. Make sure they want to continue
|
|
const FText Title = LOCTEXT( "SwitchProject", "Switch Project" );
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("CurrentProjectName"), FText::FromString( ProjectDisplayName ));
|
|
const FText Message = FText::Format( LOCTEXT( "SwitchProjectWarning", "The editor will restart to switch to the {CurrentProjectName} project. You will be prompted to save any changes before the editor restarts. Continue switching projects?" ), Arguments );
|
|
|
|
// Present the user with a warning that changing projects has to restart the editor
|
|
FSuppressableWarningDialog::FSetupInfo Info( Message, Title, "Warning_SwitchProject", GEditorSettingsIni );
|
|
Info.ConfirmText = LOCTEXT( "Yes", "Yes" );
|
|
Info.CancelText = LOCTEXT( "No", "No" );
|
|
|
|
FSuppressableWarningDialog SwitchProjectDlg( Info );
|
|
if( SwitchProjectDlg.ShowModal() == FSuppressableWarningDialog::Cancel )
|
|
{
|
|
bSwitch = false;
|
|
}
|
|
}
|
|
|
|
// If the user wants to continue with the restart set the pending project to swtich to and close the editor
|
|
if( bSwitch )
|
|
{
|
|
FString PendingProjName;
|
|
if ( bIsProjectFileName )
|
|
{
|
|
// Put quotes around the file since it may contain spaces.
|
|
PendingProjName = FString::Printf(TEXT("\"%s\""), *GameOrProjectFileName);
|
|
}
|
|
else
|
|
{
|
|
PendingProjName = GameOrProjectFileName;
|
|
}
|
|
|
|
SetPendingProjectName( PendingProjName );
|
|
|
|
// Close the editor. This will prompt the user to save changes. If they hit cancel, we abort the project switch
|
|
GEngine->DeferredCommands.Add( TEXT("CLOSE_SLATE_MAINFRAME"));
|
|
}
|
|
else
|
|
{
|
|
ClearPendingProjectName();
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::RestartEditor(bool bWarn)
|
|
{
|
|
if (GUnrealEd->WarnIfLightingBuildIsCurrentlyRunning())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( FPaths::IsProjectFilePathSet() )
|
|
{
|
|
SwitchProject(FPaths::GetProjectFilePath(), bWarn);
|
|
}
|
|
else if(FApp::HasGameName())
|
|
{
|
|
SwitchProject(FApp::GetGameName(), bWarn);
|
|
}
|
|
else
|
|
{
|
|
SwitchProject(TEXT(""), bWarn);
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::BeginPerformanceSurvey()
|
|
{
|
|
// Don't attempt to run the survey if analytics isn't available
|
|
if(!FEngineAnalytics::IsAvailable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Tell the level editor we want to be notified when selection changes
|
|
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>( LevelEditorName );
|
|
OnMapChangedDelegateHandle = LevelEditor.OnMapChanged().AddRaw( this, &FUnrealEdMisc::OnMapChanged );
|
|
|
|
// Initialize survey variables
|
|
bIsSurveyingPerformance = true;
|
|
LastFrameRateTime = FDateTime::UtcNow();
|
|
FrameRateSamples.Empty();
|
|
}
|
|
|
|
void FUnrealEdMisc::TickPerformanceAnalytics()
|
|
{
|
|
// Don't run if we've not yet loaded a project
|
|
if( !FApp::HasGameName() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Before beginning the survey wait for the asset registry to load and make sure Slate is ready
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryName);
|
|
if (AssetRegistryModule.Get().IsLoadingAssets() || !FSlateApplication::IsInitialized())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't run the survey if Slate isn't running normally
|
|
FSlateApplication& SlateApp = FSlateApplication::Get();
|
|
if (!SlateApp.IsNormalExecution())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't run the test if we are throttling (due to minimized or not in foreground) as this will
|
|
// greatly affect the framerate
|
|
if( GEditor->ShouldThrottleCPUUsage() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Update the stats needed by the analytics heartbeat
|
|
PerformanceAnalyticsStats->Update();
|
|
|
|
// Also check to see if we need to run the performance survey
|
|
if (!bIsSurveyingPerformance)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Sample the frame rate until we have enough samples to take the average
|
|
if (FrameRateSamples.Num() < PerformanceSurveyDefs::NumFrameRateSamples)
|
|
{
|
|
FDateTime Now = FDateTime::UtcNow();
|
|
if (Now - LastFrameRateTime > PerformanceSurveyDefs::FrameRateSampleInterval)
|
|
{
|
|
FrameRateSamples.Add(SlateApp.GetAverageDeltaTimeForResponsiveness());
|
|
LastFrameRateTime = Now;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We have enough samples - take the average and record with analytics
|
|
float FrameTime = 0.0f;
|
|
for (auto Iter = FrameRateSamples.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
FrameTime += *Iter;
|
|
}
|
|
float AveFrameRate = PerformanceSurveyDefs::NumFrameRateSamples / FrameTime;
|
|
|
|
if( FEngineAnalytics::IsAvailable() )
|
|
{
|
|
FString AveFrameRateString = FString::Printf( TEXT( "%.1f" ), AveFrameRate);
|
|
IAnalyticsProvider& EngineAnalytics = FEngineAnalytics::GetProvider();
|
|
EngineAnalytics.RecordEvent(TEXT( "Editor.Performance.FrameRate" ), TEXT( "MeanFrameRate" ), AveFrameRateString);
|
|
}
|
|
|
|
CancelPerformanceSurvey();
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::CancelPerformanceSurvey()
|
|
{
|
|
bIsSurveyingPerformance = false;
|
|
FrameRateSamples.Empty();
|
|
|
|
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>( LevelEditorName );
|
|
LevelEditor.OnMapChanged().Remove( OnMapChangedDelegateHandle );
|
|
}
|
|
|
|
void FUnrealEdMisc::OnMapChanged( UWorld* World, EMapChangeType::Type MapChangeType )
|
|
{
|
|
if (bIsSurveyingPerformance)
|
|
{
|
|
CancelPerformanceSurvey();
|
|
}
|
|
}
|
|
|
|
bool FUnrealEdMisc::GetURL( const TCHAR* InKey, FString& OutURL, const bool bCheckRocket/* = false*/ ) const
|
|
{
|
|
check( InKey );
|
|
check( GConfig );
|
|
OutURL.Empty();
|
|
|
|
bool bFound = false;
|
|
|
|
const FString MainUrlSection = TEXT("UnrealEd.URLs");
|
|
const FString OverrideUrlSection = TEXT("UnrealEd.URLOverrides");
|
|
const FString TestUrlSection = TEXT("UnrealEd.TestURLs");
|
|
|
|
if( !FEngineBuildSettings::IsInternalBuild() && !FEngineBuildSettings::IsPerforceBuild() )
|
|
{
|
|
// For external builds try to find in the overrides first.
|
|
bFound = GConfig->GetString(*OverrideUrlSection, InKey, OutURL, GEditorIni);
|
|
}
|
|
|
|
if( !bFound )
|
|
{
|
|
bFound = GConfig->GetString(*MainUrlSection, InKey, OutURL, GEditorIni);
|
|
}
|
|
|
|
return bFound;
|
|
}
|
|
|
|
FString FUnrealEdMisc::GetExecutableForCommandlets() const
|
|
{
|
|
FString ExecutableName = FPlatformProcess::ExecutableName(false);
|
|
#if PLATFORM_WINDOWS
|
|
// turn UE4editor into UE4editor-cmd
|
|
if(ExecutableName.EndsWith(".exe", ESearchCase::IgnoreCase) && !FPaths::GetBaseFilename(ExecutableName).EndsWith("-cmd", ESearchCase::IgnoreCase))
|
|
{
|
|
FString NewExeName = ExecutableName.Left(ExecutableName.Len() - 4) + "-Cmd.exe";
|
|
if (FPaths::FileExists(NewExeName))
|
|
{
|
|
ExecutableName = NewExeName;
|
|
}
|
|
}
|
|
#endif
|
|
return ExecutableName;
|
|
}
|
|
|
|
void FUnrealEdMisc::OnUserDefinedChordChanged(const FUICommandInfo& CommandInfo)
|
|
{
|
|
if( FEngineAnalytics::IsAvailable() )
|
|
{
|
|
FString ChordName = FString::Printf(TEXT("%s.%s"), *CommandInfo.GetBindingContext().ToString(), *CommandInfo.GetCommandName().ToString());
|
|
|
|
//@todo This shouldn't be using a localized value; GetInputText() [10/11/2013 justin.sargent]
|
|
TArray< FAnalyticsEventAttribute > ChordAttribs;
|
|
ChordAttribs.Add(FAnalyticsEventAttribute(TEXT("Context"), ChordName));
|
|
ChordAttribs.Add(FAnalyticsEventAttribute(TEXT("Shortcut"), CommandInfo.GetActiveChord()->GetInputText().ToString()));
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.KeyboardShortcut"), ChordAttribs);
|
|
}
|
|
}
|
|
|
|
void FUnrealEdMisc::MountTemplateSharedPaths()
|
|
{
|
|
FString TemplateFilename = FPaths::GetPath(FPaths::GetProjectFilePath());
|
|
UTemplateProjectDefs* TemplateInfo = GameProjectUtils::LoadTemplateDefs(TemplateFilename);
|
|
|
|
if( TemplateInfo != nullptr )
|
|
{
|
|
EFeaturePackDetailLevel EditDetail = TemplateInfo->EditDetailLevelPreference;
|
|
|
|
// Extract the mount names and insert mount points for each of the shared packs
|
|
TArray<FString> AddedMountSources;
|
|
for (int32 iShared = 0; iShared < TemplateInfo->SharedContentPacks.Num() ; iShared++)
|
|
{
|
|
EFeaturePackDetailLevel EachEditDetail = (EFeaturePackDetailLevel)EditDetail;
|
|
FString DetailString;
|
|
UEnum::GetValueAsString(TEXT("/Script/AddContentDialog.EFeaturePackDetailLevel"), EachEditDetail, DetailString);
|
|
|
|
FFeaturePackLevelSet EachPack = TemplateInfo->SharedContentPacks[iShared];
|
|
if((EachPack.DetailLevels.Num() == 1) && ( EachEditDetail != EachPack.DetailLevels[0] ))
|
|
{
|
|
// If theres only only detail level override the requirement with that
|
|
EachEditDetail = EachPack.DetailLevels[0];
|
|
// Get the name of the level we are falling back to so we can tell the user
|
|
FString FallbackDetailString;
|
|
UEnum::GetValueAsString(TEXT("/Script/AddContentDialog.EFeaturePackDetailLevel"), EachEditDetail, FallbackDetailString);
|
|
UE_LOG(LogUnrealEdMisc, Verbose, TEXT("Only 1 detail level defined for %s in %s. Cannot edit detail level %s. Will fallback to "), *EachPack.MountName, *TemplateFilename, *DetailString,*FallbackDetailString);
|
|
// Then correct the string too !
|
|
DetailString = FallbackDetailString;
|
|
|
|
}
|
|
else if (EachPack.DetailLevels.Num() == 0)
|
|
{
|
|
// If no levels are supplied we cant really use this pack !
|
|
UE_LOG(LogUnrealEdMisc, Warning, TEXT("No detail levels defined for %s in %s."), *EachPack.MountName, *TemplateFilename );
|
|
continue;
|
|
}
|
|
for (int32 iDetail = 0; iDetail < EachPack.DetailLevels.Num(); iDetail++)
|
|
{
|
|
if (EachPack.DetailLevels[iDetail] == EachEditDetail)
|
|
{
|
|
FString ShareMountName = EachPack.MountName;
|
|
if (AddedMountSources.Find(ShareMountName) == INDEX_NONE)
|
|
{
|
|
FString ResourcePath = FPaths::Combine(TEXT("Templates"), TEXT("TemplateResources"), *DetailString, *ShareMountName, TEXT("Content"));
|
|
FString FullPath = FPaths::Combine(*FPaths::RootDir(), *ResourcePath);
|
|
|
|
if (FPaths::DirectoryExists(FullPath))
|
|
{
|
|
FString MountName = FString::Printf(TEXT("/Game/%s/"), *ShareMountName);
|
|
FPackageName::RegisterMountPoint(*MountName, *FullPath);
|
|
AddedMountSources.Add(ShareMountName);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogUnrealEdMisc, Warning, TEXT("Cannot find path %s to mount for %s resource in %s."), *FullPath, *EachPack.MountName, *TemplateFilename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|