// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "UnrealFrontendMain.h" #include "RequiredProgramMainCPPInclude.h" #include "AutomationController.h" #include "DeviceManager.h" #include "ProfilerClient.h" #include "SessionFrontend.h" #include "StatsData.h" #include "StatsFile.h" #include "EditorStyle.h" #include "SlateReflector.h" IMPLEMENT_APPLICATION(UnrealFrontend, "UnrealFrontend"); #define IDEAL_FRAMERATE 60; namespace WorkspaceMenu { TSharedRef DeveloperTools = FWorkspaceItem::NewGroup( NSLOCTEXT("UnrealFrontend", "DeveloperToolsMenu", "Developer Tools") ); } void RunPackageCommand() { FString SourceDir; FParse::Value(FCommandLine::Get(), TEXT("-SOURCEDIR="), SourceDir); ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); if (TPM) { const TArray& Platforms = TPM->GetActiveTargetPlatforms(); for (int32 Index = 0; Index < Platforms.Num(); ++Index) { if (Platforms[Index]->PackageBuild(SourceDir)) { } } } } bool RunDeployCommand() { bool bDeployed = false; // get the target device FString Device; FParse::Value(FCommandLine::Get(), TEXT("-DEVICE="), Device); // get the file manifest FString Manifest; FParse::Value(FCommandLine::Get(), TEXT("-MANIFEST="), Manifest); FString SourceDir; FParse::Value(FCommandLine::Get(), TEXT("-SOURCEDIR="), SourceDir); ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); if (!TPM) { return false; } // Initialize the messaging subsystem so we can do device discovery. FModuleManager::LoadModuleChecked("Messaging"); // load plug-in modules // @todo: allow for better plug-in support in standalone Slate apps IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault); double DeltaTime = 0.0; double LastTime = FPlatformTime::Seconds(); // We track the message sent time because we have to keep updating the loop until the message is *actually sent*. (ie all packets queued, sent, buffer flushed, etc.) double MessageSentTime = 0.0; bool bMessageSent = false; while ( !GIsRequestingExit && (MessageSentTime > LastTime + 1.0 || MessageSentTime <= 0.1) ) { FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); FTicker::GetCoreTicker().Tick(DeltaTime); FPlatformProcess::Sleep(0); DeltaTime = FPlatformTime::Seconds() - LastTime; LastTime = FPlatformTime::Seconds(); if ( !bMessageSent ) { const TArray& Platforms = TPM->GetActiveTargetPlatforms(); FString Platform; FString DeviceName; Device.Split(TEXT("@"), &Platform, &DeviceName); FTargetDeviceId DeviceId(Platform, DeviceName); ITargetDevicePtr TargetDevice; for (int32 Index = 0; Index < Platforms.Num(); ++Index) { TargetDevice = Platforms[Index]->GetDevice(DeviceId); if (TargetDevice.IsValid()) { FString OutId; if (Platforms[Index]->PackageBuild(SourceDir)) { if (TargetDevice->Deploy(SourceDir, OutId)) { bDeployed = true; } MessageSentTime = LastTime; bMessageSent = true; } else { MessageSentTime = LastTime; bMessageSent = true; } } } } } return bDeployed; } bool RunLaunchCommand(const FString& Params) { bool bLaunched = false; // get the target device FString Device; FParse::Value(FCommandLine::Get(), TEXT("-DEVICE="), Device); // get the executable to launch FString Executable; FParse::Value(FCommandLine::Get(), TEXT("-EXE="), Executable); ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); if (!TPM) { return false; } // Initialize the messaging subsystem so we can do device discovery. FModuleManager::LoadModuleChecked("Messaging"); // load plug-in modules // @todo: allow for better plug-in support in standalone Slate apps IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault); double DeltaTime = 0.0; double LastTime = FPlatformTime::Seconds(); static int32 MasterDisableChangeTagStartFrame = -1; // We track the message sent time because we have to keep updating the loop until the message is *actually sent*. (ie all packets queued, sent, buffer flushed, etc.) double MessageSentTime = 0.0; bool bMessageSent = false; while ( !GIsRequestingExit && (MessageSentTime > LastTime + 1.0 || MessageSentTime <= 0.1) ) { FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); FTicker::GetCoreTicker().Tick(DeltaTime); FPlatformProcess::Sleep(0); DeltaTime = FPlatformTime::Seconds() - LastTime; LastTime = FPlatformTime::Seconds(); if ( !bMessageSent ) { const TArray& Platforms = TPM->GetActiveTargetPlatforms(); FString Platform; FString DeviceName; Device.Split(TEXT("@"), &Platform, &DeviceName); FTargetDeviceId DeviceId(Platform, DeviceName); ITargetDevicePtr TargetDevice; for (int32 Index = 0; Index < Platforms.Num(); ++Index) { TargetDevice = Platforms[Index]->GetDevice(DeviceId); if (TargetDevice.IsValid()) { uint32 OutId; if (TargetDevice->Run(Executable, Params, &OutId)) { MessageSentTime = LastTime; bMessageSent = true; bLaunched = true; } else { MessageSentTime = LastTime; bMessageSent = true; } } } } } return bLaunched; } void WriteString(FArchive* File, const ANSICHAR* Format, ...) { if( File != NULL ) { check(File); ANSICHAR Array[1024]; va_list ArgPtr; va_start(ArgPtr,Format); // Build the string int32 Result = FCStringAnsi::GetVarArgs(Array,ARRAY_COUNT(Array),ARRAY_COUNT(Array)-1,Format,ArgPtr); // Now write that to the file File->Serialize((void*)Array,Result); } } void RunStatsConvertCommand() { #if STATS // get the target file FString TargetFile; FParse::Value(FCommandLine::Get(), TEXT("-INFILE="), TargetFile); FString OutFile; FParse::Value(FCommandLine::Get(), TEXT("-OUTFILE="), OutFile); FString StatListString; FParse::Value(FCommandLine::Get(), TEXT("-STATLIST="), StatListString); // get the list of stats TArray StatList; if (StatListString.ParseIntoArray(&StatList, TEXT("+"), true) == 0) { StatList.Add(TEXT("STAT_FrameTime")); } // open a csv file for write TAutoPtr FileWriter( IFileManager::Get().CreateFileWriter( *OutFile ) ); if (!FileWriter) { UE_LOG( LogStats, Error, TEXT( "Could not open output file: %s" ), *OutFile ); return; } // @TODO yrx 2014-03-24 move to function // attempt to read the data and convert to csv const int64 Size = IFileManager::Get().FileSize( *TargetFile ); if( Size < 4 ) { UE_LOG( LogStats, Error, TEXT( "Could not open input file: %s" ), *TargetFile ); return; } TAutoPtr FileReader( IFileManager::Get().CreateFileReader( *TargetFile ) ); if( !FileReader ) { UE_LOG( LogStats, Error, TEXT( "Could not open input file: %s" ), *TargetFile ); return; } FStatsReadStream Stream; if( !Stream.ReadHeader( *FileReader ) ) { UE_LOG( LogStats, Error, TEXT( "Could not open input file, bad magic: %s" ), *TargetFile ); return; } // This is not supported yet. if (Stream.Header.bRawStatsFile) { UE_LOG( LogStats, Error, TEXT( "Could not open input file, not supported type (raw): %s" ), *TargetFile ); return; } // output the csv header WriteString( FileWriter, "Frame,Name,Value\r\n" ); // read in the data TArray Messages; FStatsThreadState ThreadState; while(FileReader->Tell() < FileReader->TotalSize()) { // read the message FStatMessage Message(Stream.ReadMessage(*FileReader)); if (Message.NameAndInfo.GetShortName() != TEXT("Unknown FName")) { if (Message.NameAndInfo.GetField() == EStatOperation::AdvanceFrameEventGameThread) { new (Messages) FStatMessage(Message); ThreadState.AddMessages(Messages); Messages.Reset(Messages.Num()); // get the frame time and the render time if (ThreadState.CurrentGameFrame > 1) { // get the thread stats TArray Stats; ThreadState.GetInclusiveAggregateStackStats(ThreadState.CurrentGameFrame, Stats); for (int32 Index = 0; Index < Stats.Num(); ++Index) { FStatMessage const& Meta = Stats[Index]; // UE_LOG(LogTemp, Display, TEXT("Stat: %s"), *Meta.NameAndInfo.GetShortName().ToString()); for (int32 Jndex = 0; Jndex < StatList.Num(); ++Jndex) { if (Meta.NameAndInfo.GetShortName().ToString() == StatList[Jndex]) { float CycleTime = 0.0f; if (Meta.NameAndInfo.GetFlag(EStatMetaFlags::IsPackedCCAndDuration)) { CycleTime = FPlatformTime::ToMilliseconds( FromPackedCallCountDuration_Duration(Meta.GetValue_int64()) ); } else if (Meta.NameAndInfo.GetFlag(EStatMetaFlags::IsCycle)) { CycleTime = FPlatformTime::ToMilliseconds( Meta.GetValue_int64() ); } else { CycleTime = Meta.GetValue_int64(); } // write out to the csv file WriteString(FileWriter, "%d,%S,%f\r\n", ThreadState.CurrentGameFrame, *StatList[Jndex], CycleTime); } } } } } new (Messages) FStatMessage(Message); } else { break; } } #endif // STATS } void RunUI() { FString UnrealFrontendLayoutIni = FPaths::GetPath(GEngineIni) + "/Layout.ini"; FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer()); // The frontend currently relies on EditorStyle being loaded FModuleManager::LoadModuleChecked("EditorStyle"); // initialize messaging subsystem FModuleManager::LoadModuleChecked("Messaging"); // load plug-in modules // @todo: allow for better plug-in support in standalone Slate apps IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault); // initialize automation tests IAutomationControllerModule& AutomationControllerModule = FModuleManager::LoadModuleChecked("AutomationController"); AutomationControllerModule.Init(); // initialize profiler FModuleManager::Get().LoadModule("ProfilerClient"); // initialize user interface FModuleManager::Get().LoadModule("DeviceManager"); FModuleManager::Get().LoadModule("SessionFrontend"); FModuleManager::Get().LoadModule("SessionLauncher"); FModuleManager::Get().LoadModule("SettingsEditor"); // Create developer tools menu with widget reflector. FModuleManager::LoadModuleChecked("SlateReflector").RegisterTabSpawner(WorkspaceMenu::DeveloperTools); // check to see if want the widget reflector const bool bAllowDebugTools = FParse::Param(FCommandLine::Get(), TEXT("DebugTools")); // set the application name FGlobalTabmanager::Get()->SetApplicationTitle(NSLOCTEXT("UnrealFrontend", "AppTitle", "Unreal Frontend")); // restore application layout TSharedRef NewLayout = FTabManager::NewLayout("SessionFrontendLayout_v1.1") ->AddArea ( FTabManager::NewArea(1280.f, 720.0f) ->Split ( FTabManager::NewStack() ->AddTab(FName("DeviceManager"), ETabState::OpenedTab) ->AddTab(FName("MessagingDebugger"), ETabState::ClosedTab) ->AddTab(FName("SessionLauncher"), ETabState::OpenedTab) ->AddTab(FName("SessionFrontend"), ETabState::OpenedTab) ) ) ->AddArea ( FTabManager::NewArea(600.0f, 600.0f) ->SetWindow(FVector2D(10.0f, 10.0f), false) ->Split ( FTabManager::NewStack()->AddTab("WidgetReflector", bAllowDebugTools ? ETabState::OpenedTab : ETabState::ClosedTab) ) ); TSharedRef UserConfiguredNewLayout = FLayoutSaveRestore::LoadFromConfig(UnrealFrontendLayoutIni, NewLayout); FGlobalTabmanager::Get()->RestoreFrom(UserConfiguredNewLayout, TSharedPtr()); // enter main loop double DeltaTime = 0.0; double LastTime = FPlatformTime::Seconds(); const float IdealFrameTime = 1.0f / IDEAL_FRAMERATE; while (!GIsRequestingExit) { //Save the state of the tabs here rather than after close of application (the tabs are undesirably saved out with ClosedTab state on application close) //UserConfiguredNewLayout = FGlobalTabmanager::Get()->PersistLayout(); FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); FSlateApplication::Get().PumpMessages(); FSlateApplication::Get().Tick(); FTicker::GetCoreTicker().Tick(DeltaTime); AutomationControllerModule.Tick(); // throttle frame rate FPlatformProcess::Sleep(FMath::Max(0.0f, IdealFrameTime - (FPlatformTime::Seconds() - LastTime))); double CurrentTime = FPlatformTime::Seconds(); DeltaTime = CurrentTime - LastTime; LastTime = CurrentTime; FStats::AdvanceFrame( false ); GLog->FlushThreadedLogs(); } // save application layout FLayoutSaveRestore::SaveToConfig(UnrealFrontendLayoutIni, UserConfiguredNewLayout); GConfig->Flush(false, UnrealFrontendLayoutIni); // shut down application FSlateApplication::Shutdown(); } int32 UnrealFrontendMain( const TCHAR* CommandLine ) { // check to see if we should run in command line mode FString Command; FString Params; FString NewCommandLine = CommandLine; bool bRunCommand = FParse::Value(*NewCommandLine, TEXT("-RUN="), Command); if (bRunCommand) { // extract off any -PARAM= parameters so that they aren't accidentally parsed by engine init FParse::Value(*NewCommandLine, TEXT("-PARAMS="), Params); if (Params.Len() > 0) { // remove from the command line NewCommandLine = NewCommandLine.Replace(*Params, TEXT("")); // trim the quotes Params = Params.TrimQuotes(); } } if (!FParse::Param(*NewCommandLine, TEXT("-Messaging"))) { NewCommandLine += TEXT(" -Messaging"); } GEngineLoop.PreInit(*NewCommandLine); // Tell the module manager is may now process newly-loaded UObjects when new C++ modules are loaded FModuleManager::Get().StartProcessingNewlyLoadedObjects(); bool bReturnValue = true; if (bRunCommand) { if (Command.Equals(TEXT("PACKAGE"), ESearchCase::IgnoreCase)) { RunPackageCommand(); } else if (Command.Equals(TEXT("DEPLOY"), ESearchCase::IgnoreCase)) { bReturnValue = RunDeployCommand(); } else if (Command.Equals(TEXT("LAUNCH"), ESearchCase::IgnoreCase)) { bReturnValue = RunLaunchCommand(Params); } else if (Command.Equals(TEXT("CONVERT"), ESearchCase::IgnoreCase)) { RunStatsConvertCommand(); } } else { RunUI(); } FEngineLoop::AppPreExit(); FModuleManager::Get().UnloadModulesAtShutdown(); #if STATS FThreadStats::StopThread(); #endif FTaskGraphInterface::Shutdown(); return bReturnValue ? 0 : -1; }