Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/EditorCommandLineUtils.cpp
2025-05-18 13:04:45 +08:00

776 lines
25 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditorCommandLineUtils.h"
#include "AssetDefinitionRegistry.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Stats/Stats.h"
#include "Misc/App.h"
#include "Misc/AssetRegistryInterface.h"
#include "Modules/ModuleManager.h"
#include "UObject/UObjectHash.h"
#include "Misc/PackageName.h"
#include "Widgets/SWindow.h"
#include "Framework/Application/SlateApplication.h"
#include "TickableEditorObject.h"
#include "Commandlets/Commandlet.h"
#include "EngineGlobals.h"
#include "Editor.h"
#include "Dialogs/Dialogs.h"
#include "Interfaces/IMainFrameModule.h"
#include "IAssetTools.h"
#include "IAssetTypeActions.h"
#include "AssetToolsModule.h"
#include "ProjectDescriptor.h"
#include "UObject/SavePackage.h"
#define LOCTEXT_NAMESPACE "EditorCommandLineUtils"
// Forward Declarations
struct FMergeAsset;
/*******************************************************************************
* EditorCommandLineUtilsImpl Declaration
******************************************************************************/
/** */
namespace EditorCommandLineUtilsImpl
{
static const TCHAR* DebugLightmassCommandSwitch = TEXT("LIGHTMASSDEBUG");
static const TCHAR* LightmassStatsCommandSwitch = TEXT("LIGHTMASSSTATS");
static const TCHAR* DiffCommandSwitch = TEXT("diff");
static const FText DiffCommandHelpTxt = INVTEXT("\
Usage: \n\
-diff [options] left right \n\
-diff [options] remote local base result \n\
\n\
Options: \n\
-echo Prints back the command arguments and then exits. \n\
-help, -h, -? Display this message and then exits. \n");
/** */
static bool ParseCommandArgs(const TCHAR* FullEditorCmdLine, const TCHAR* CmdSwitch, FString& CmdArgsOut);
/** */
static FString FindProjectFile(const FString& AssetFilePath);
/** */
static void RaiseEditorMessageBox(const FText& Title, const FText& BodyText, const bool bExitOnClose);
/** */
static void ForceCloseEditor();
/** */
static void RunAssetDiffCommand(TSharedPtr<SWindow> MainEditorWindow, bool bIsRunningProjBrowser, FString CommandArgs);
/** */
static void RunAssetMerge(FMergeAsset const& Base, FMergeAsset const& Remote, FMergeAsset const& Local, FMergeAsset const& Result);
/** */
static UObject* ExtractAssetFromPackage(UPackage* Package);
}
/*******************************************************************************
* FCommandLineErrorReporter
******************************************************************************/
/** */
struct FCommandLineErrorReporter
{
FCommandLineErrorReporter(const FString& Command, const FString& CommandArgs)
: CommandSwitch(FText::FromString(Command))
, FullCommand(FText::FromString("-" + Command + " " + CommandArgs))
, bHasBlockingError(false)
{}
/** */
void ReportFatalError(const FText& Title, const FText& ErrorMsg);
/** */
void ReportError(const FText& Title, const FText& ErrorMsg, bool bIsFatal);
/** */
bool HasBlockingError() const;
private:
FText CommandSwitch;
FText FullCommand;
bool bHasBlockingError;
};
//------------------------------------------------------------------------------
void FCommandLineErrorReporter::ReportFatalError(const FText& Title, const FText& ErrorMsg)
{
ReportError(Title, ErrorMsg, /*bIsFatal =*/true);
}
//------------------------------------------------------------------------------
void FCommandLineErrorReporter::ReportError(const FText& Title, const FText& ErrorMsg, bool bIsFatal)
{
if (bHasBlockingError)
{
return;
}
FText FullErrorMsg = FText::Format(LOCTEXT("CommandLineError", "Erroneous editor command: {0}\n\n{1}\n\nRun '-{2} -h' for more help."),
FullCommand, ErrorMsg, CommandSwitch);
bHasBlockingError = bIsFatal;
EditorCommandLineUtilsImpl::RaiseEditorMessageBox(Title, FullErrorMsg, /*bShutdownOnOk =*/bIsFatal);
}
//------------------------------------------------------------------------------
bool FCommandLineErrorReporter::HasBlockingError() const
{
return bHasBlockingError;
}
/*******************************************************************************
* FFauxStandaloneToolManager Implementation
******************************************************************************/
/**
* Helps keep up the facade for tools can launch "stand-alone"... Hides the main
* editor window, and monitors for when all visible windows are closed (so it
* can kill the editor process).
*/
class FFauxStandaloneToolManager : FTickableEditorObject
{
public:
FFauxStandaloneToolManager(TSharedPtr<SWindow> MainEditorWindow);
// FTickableEditorObject interface
virtual TStatId GetStatId() const override;
virtual ETickableTickType GetTickableTickType() const override { return ETickableTickType::Always; }
virtual void Tick(float DeltaTime) override;
// End FTickableEditorObject interface
/** */
void Disable();
private:
TWeakPtr<SWindow> MainEditorWindow;
};
//------------------------------------------------------------------------------
FFauxStandaloneToolManager::FFauxStandaloneToolManager(TSharedPtr<SWindow> InMainEditorWindow)
: MainEditorWindow(InMainEditorWindow)
{
// present the illusion that this is a stand-alone editor by hiding the
// root level editor window
InMainEditorWindow->HideWindow();
}
//------------------------------------------------------------------------------
TStatId FFauxStandaloneToolManager::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FFauxStandaloneToolManager, STATGROUP_Tickables);
}
//------------------------------------------------------------------------------
void FFauxStandaloneToolManager::Tick(float DeltaTime)
{
if (MainEditorWindow.IsValid())
{
FSlateApplication& WindowManager = FSlateApplication::Get();
TArray< TSharedRef<SWindow> > ActiveWindows = WindowManager.GetInteractiveTopLevelWindows();
bool bVisibleWindowFound = false;
for (TSharedRef<SWindow> Window : ActiveWindows)
{
if (Window->IsVisible())
{
bVisibleWindowFound = true;
break;
}
}
if (!bVisibleWindowFound)
{
EditorCommandLineUtilsImpl::ForceCloseEditor();
}
}
else
{
EditorCommandLineUtilsImpl::ForceCloseEditor();
}
}
//------------------------------------------------------------------------------
void FFauxStandaloneToolManager::Disable()
{
if (MainEditorWindow.IsValid())
{
MainEditorWindow.Pin()->ShowWindow();
}
}
/*******************************************************************************
* FMergeAsset
******************************************************************************/
struct FMergeAsset
{
public:
FMergeAsset(const TCHAR* DstFileName);
/** */
bool SetSourceFile(const FString& SrcFilePathIn, FCommandLineErrorReporter& ErrorReporter);
/** */
bool Load(FCommandLineErrorReporter& ErrorReporter);
/** */
UClass* GetClass() const;
/** */
UObject* GetAssetObj() const;
/** */
FRevisionInfo GetRevisionInfo() const;
/** */
const FString& GetSourceFilePath() const;
/** */
const FString& GetAssetFilePath() const;
UPackage* GetPackage() const;
private:
UPackage* Package;
UObject* AssetObj;
FString DestFilePath;
FString SrcFilePath;
};
//------------------------------------------------------------------------------
FMergeAsset::FMergeAsset(const TCHAR* DstFileName)
: Package(nullptr)
, AssetObj(nullptr)
, DestFilePath(FPaths::Combine(*FPaths::DiffDir(), DstFileName))
{
const FString& AssetExt = FPackageName::GetAssetPackageExtension();
if (!DestFilePath.EndsWith(AssetExt))
{
DestFilePath += AssetExt;
}
}
//------------------------------------------------------------------------------
bool FMergeAsset::SetSourceFile(const FString& SrcFilePathIn, FCommandLineErrorReporter& ErrorReporter)
{
SrcFilePath.Empty();
if (!FPaths::FileExists(SrcFilePathIn))
{
ErrorReporter.ReportFatalError(LOCTEXT("BadFilePathTitle", "Bad File Path"),
FText::Format(LOCTEXT("BadFilePathError", "'{0}' is an invalid file."), FText::FromString(SrcFilePathIn)));
}
else
{
SrcFilePath = SrcFilePathIn;
}
return !SrcFilePath.IsEmpty();
}
//------------------------------------------------------------------------------
bool FMergeAsset::Load(FCommandLineErrorReporter& ErrorReporter)
{
if (SrcFilePath.IsEmpty())
{
// was SetSourceFile() called prior to this?
return false;
}
// UE cannot open files with certain special characters in them (like
// the # symbol), so we make a copy of the file with a more UE digestible
// path (since this may be a perforce temp file)
if (IFileManager::Get().Copy(*DestFilePath, *SrcFilePath) != COPY_OK)
{
ErrorReporter.ReportFatalError(LOCTEXT("LoadFailedTitle", "Unable to Copy File"),
FText::Format(LOCTEXT("LoadFailedError", "Failed to make a local copy of the asset file: '{0}'."), FText::FromString(SrcFilePath)));
}
else if (UPackage* AssetPkg = LoadPackage(/*Outer =*/nullptr, *DestFilePath, LOAD_None))
{
if (UObject* ExtractedAsset = EditorCommandLineUtilsImpl::ExtractAssetFromPackage(AssetPkg))
{
Package = AssetPkg;
AssetObj = ExtractedAsset;
}
else
{
ErrorReporter.ReportFatalError(LOCTEXT("AssetNotFoundTitle", "Asset Not Found"),
FText::Format(LOCTEXT("AssetNotFoundError", "Failed to find the asset object inside the package file: '{0}'."), FText::FromString(SrcFilePath)));
}
}
return (AssetObj != nullptr);
}
//------------------------------------------------------------------------------
UClass* FMergeAsset::GetClass() const
{
return (AssetObj != nullptr) ? AssetObj->GetClass() : nullptr;
}
//------------------------------------------------------------------------------
UObject* FMergeAsset::GetAssetObj() const
{
return AssetObj;
}
//------------------------------------------------------------------------------
FRevisionInfo FMergeAsset::GetRevisionInfo() const
{
FString SrcFileName = FPaths::GetBaseFilename(SrcFilePath);
FRevisionInfo RevisionInfoOut = FRevisionInfo::InvalidRevision();
FString BaseFileName, RevisionStr;
if (SrcFileName.Split(TEXT("#"), &BaseFileName, &RevisionStr))
{
// @TODO: if connected to source-control, extract changelist and date info
RevisionInfoOut.Revision = *RevisionStr;
}
return RevisionInfoOut;
}
//------------------------------------------------------------------------------
const FString& FMergeAsset::GetSourceFilePath() const
{
return SrcFilePath;
}
//------------------------------------------------------------------------------
const FString& FMergeAsset::GetAssetFilePath() const
{
return DestFilePath;
}
UPackage* FMergeAsset::GetPackage() const
{
return Package;
}
/*******************************************************************************
* EditorCommandLineUtilsImpl Implementation
******************************************************************************/
//------------------------------------------------------------------------------
static bool EditorCommandLineUtilsImpl::ParseCommandArgs(const TCHAR* FullEditorCmdLine, const TCHAR* CmdSwitch, FString& CmdArgsOut)
{
if (FParse::Param(FullEditorCmdLine, CmdSwitch))
{
FString CmdPrefix;
FString(FullEditorCmdLine).Split(FString("-") + CmdSwitch, &CmdPrefix, &CmdArgsOut);
return true;
}
return false;
}
//------------------------------------------------------------------------------
static FString EditorCommandLineUtilsImpl::FindProjectFile(const FString& AssetFilePathIn)
{
FString FoundProjectPath;
FString AssetFilePath = AssetFilePathIn;
FPaths::NormalizeFilename(AssetFilePath);
const TCHAR* const ContentDirName = TEXT("/Content/");
FString ProjectDir, AssetSubPath;
if (AssetFilePath.Split(ContentDirName, &ProjectDir, &AssetSubPath))
{
const FString UProjExt = TEXT(".") + FProjectDescriptor::GetExtension();
const FString ProjectWildcardPath = FPaths::Combine(*ProjectDir, *FString(TEXT("*") + UProjExt));
TArray<FString> FoundFiles;
IFileManager::Get().FindFiles(FoundFiles, *ProjectWildcardPath, /*Files =*/true, /*Directories =*/false);
if (FoundFiles.Num() > 0)
{
FoundProjectPath = FPaths::Combine(*ProjectDir, *FoundFiles[0]);
const FString DirName = FPaths::GetBaseFilename(ProjectDir);
for (FString FileName : FoundFiles)
{
// favor project files that match the directory name
if (FPaths::GetBaseFilename(FileName) == DirName)
{
FoundProjectPath = FPaths::Combine(*ProjectDir, *FileName);
break;
}
}
}
else
{
// guess at what the project path would be (in case this is a
// perforce temp file, and its path mimics the real asset file's
// directory structure)
FString GameName = FPaths::GetCleanFilename(ProjectDir);
FoundProjectPath = FPaths::Combine(*FPaths::RootDir(), *GameName, *FString(GameName + UProjExt));
// make sure what we're guessing at exists...
if (!FPaths::FileExists(FoundProjectPath))
{
FoundProjectPath.Empty();
}
}
}
return FoundProjectPath;
}
//------------------------------------------------------------------------------
static void EditorCommandLineUtilsImpl::RaiseEditorMessageBox(const FText& Title, const FText& BodyText, const bool bExitOnClose)
{
FOnMsgDlgResult OnDialogClosed;
if (bExitOnClose)
{
auto OnDialogClosedLambda = [](const TSharedRef<SWindow>&, EAppReturnType::Type)
{
ForceCloseEditor();
};
OnDialogClosed = FOnMsgDlgResult::CreateStatic(OnDialogClosedLambda);
}
OpenMsgDlgInt_NonModal(EAppMsgType::Ok, BodyText, Title, OnDialogClosed)->ShowWindow();
}
//------------------------------------------------------------------------------
static void EditorCommandLineUtilsImpl::ForceCloseEditor()
{
// We used to call IMainFrameModule::RequestCloseEditor, but that runs a lot of logic that should only be
// run for the real project editor (notably UThumbnailManager::CaptureProjectThumbnail was causing a crash on shutdown
// but INI serialization was running when it should not have as well). Instead, we just raise the QUIT_EDITOR command:
GEngine->DeferredCommands.Add(TEXT("QUIT_EDITOR"));
}
//------------------------------------------------------------------------------
static void EditorCommandLineUtilsImpl::RunAssetDiffCommand(TSharedPtr<SWindow> MainEditorWindow, bool bIsRunningStartupDialog, FString CommandArgs)
{
// if the editor is running the project browser (or another like dialog on startup),
// then the user has to first make a selection (and then the editor will re-launch with this command).
if (bIsRunningStartupDialog)
{
// @TODO: can we run without loading a project?
return;
}
// static so it exists past this function, but doesn't get instantiated
// until this function is called
static FFauxStandaloneToolManager FauxStandaloneToolManager(MainEditorWindow);
TMap<FString, FString> Params;
TArray<FString> Tokens;
TArray<FString> Switches;
UCommandlet::ParseCommandLine(*CommandArgs, Tokens, Switches, Params);
if (Switches.Contains("h") ||
Switches.Contains("?") ||
Switches.Contains("help"))
{
RaiseEditorMessageBox(LOCTEXT("DiffCommandHelp", "Diff/Merge Command-Line Help"), DiffCommandHelpTxt, /*bExitOnClose =*/true);
return;
}
if (Switches.Contains("echo"))
{
RaiseEditorMessageBox(LOCTEXT("PassedCommandArgs", "Passed Command Arguments"),
FText::FromString(CommandArgs), /*bExitOnClose =*/true);
return;
}
const int32 FilesNeededForDiff = 2;
const int32 FilesNeededForMerge = 4;
const int32 MaxFilesNeeded = FilesNeededForMerge;
FMergeAsset MergeAssets[MaxFilesNeeded] = {
FMergeAsset(TEXT("MergeTool-Left")),
FMergeAsset(TEXT("MergeTool-Right")),
FMergeAsset(TEXT("MergeTool-Base")),
FMergeAsset(TEXT("MergeTool-Merge")),
};
FMergeAsset& LeftAsset = MergeAssets[0];
FMergeAsset& ThierAsset = LeftAsset;
FMergeAsset& RightAsset = MergeAssets[1];
FMergeAsset& OurAsset = RightAsset;
FMergeAsset& BaseAsset = MergeAssets[2];
FMergeAsset& MergeResult = MergeAssets[3];
//--------------------------------------
// Parse file paths from command-line
//--------------------------------------
FCommandLineErrorReporter ErrorReporter(DiffCommandSwitch, CommandArgs);
int32 ParsedFileCount = 0;
for (int32 FileIndex = 0; FileIndex < Tokens.Num() && ParsedFileCount < MaxFilesNeeded; ++FileIndex)
{
FString& FilePath = Tokens[FileIndex];
FMergeAsset& MergeAsset = MergeAssets[ParsedFileCount];
if (MergeAsset.SetSourceFile(FilePath, ErrorReporter))
{
++ParsedFileCount;
}
}
//--------------------------------------
// Verify file count
//--------------------------------------
const bool bWantsMerge = (ParsedFileCount > FilesNeededForDiff);
if (ParsedFileCount < FilesNeededForDiff)
{
ErrorReporter.ReportFatalError(LOCTEXT("TooFewParamsTitle", "Too Few Parameters"),
LOCTEXT("TooFewParamsError", "At least two files are needed (for a diff)."));
}
else if (bWantsMerge && (ParsedFileCount < FilesNeededForMerge))
{
ErrorReporter.ReportFatalError(LOCTEXT("TooFewParamsTitle", "Too Few Parameters"),
LOCTEXT("TooFewMergeParamsError", "To merge, at least two files are needed."));
}
else if (Tokens.Num() > FilesNeededForMerge)
{
ErrorReporter.ReportFatalError(LOCTEXT("TooManyParamsTitle", "Too Many Parameters"),
FText::Format( LOCTEXT("TooManyParamsError", "There were too many command arguments supplied. The maximum files needed are {0} (for merging)"), FText::AsNumber(FilesNeededForMerge) ));
}
//--------------------------------------
// Load diff/merge asset files
//--------------------------------------
bool bLoadSuccess = true;
if (bWantsMerge)
{
bLoadSuccess &= ThierAsset.Load(ErrorReporter);
bLoadSuccess &= OurAsset.Load(ErrorReporter);
bLoadSuccess &= BaseAsset.Load(ErrorReporter);
}
else
{
bLoadSuccess &= LeftAsset.Load(ErrorReporter);
bLoadSuccess &= RightAsset.Load(ErrorReporter);
}
//--------------------------------------
// Verify asset types
//--------------------------------------
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
if (bLoadSuccess)
{
if (LeftAsset.GetClass() != RightAsset.GetClass())
{
ErrorReporter.ReportFatalError(LOCTEXT("TypeMismatchTitle", "Asset Type Mismatch"),
LOCTEXT("TypeMismatchError", "Cannot compare files of different asset types."));
}
else if (bWantsMerge)
{
UClass* AssetClass = OurAsset.GetClass();
TWeakPtr<IAssetTypeActions> AssetActions = AssetTools.GetAssetTypeActionsForClass(AssetClass);
if (AssetClass != BaseAsset.GetClass())
{
ErrorReporter.ReportFatalError(LOCTEXT("TypeMismatchTitle", "Asset Type Mismatch"),
LOCTEXT("MergeTypeMismatchError", "Cannot merge files of different asset types."));
}
else if(!AssetActions.IsValid() || !AssetActions.Pin()->CanMerge())
{
ErrorReporter.ReportFatalError(LOCTEXT("CannotMergeTitle", "Cannot Merge"),
FText::Format(LOCTEXT("CannotMergeError", "{0} asset files can not be merged."), FText::FromName(AssetClass->GetFName())));
}
}
}
//--------------------------------------
// Preform diff/merge
//--------------------------------------
if (bLoadSuccess && !ErrorReporter.HasBlockingError())
{
if (bWantsMerge)
{
// unlike with diffing, for merging we rely on asset editors for
// merging, and those windows get childed to the main window (so it
// needs to be visible)
//
// @TODO: get it so asset editor windows can be shown standalone
FauxStandaloneToolManager.Disable();
RunAssetMerge(BaseAsset, ThierAsset, OurAsset, MergeResult);
}
else
{
AssetTools.DiffAssets(LeftAsset.GetAssetObj(), RightAsset.GetAssetObj(), LeftAsset.GetRevisionInfo(), RightAsset.GetRevisionInfo());
}
}
}
//------------------------------------------------------------------------------
static void EditorCommandLineUtilsImpl::RunAssetMerge(FMergeAsset const& Base, FMergeAsset const& Remote, FMergeAsset const& Local, FMergeAsset const& Result)
{
class FMergeResolutionHandler : public TSharedFromThis<FMergeResolutionHandler>
{
public:
FMergeResolutionHandler(FMergeAsset const& LocalIn, const FString& DstFilePathIn)
: MergingAsset(LocalIn)
, Resolution(EAssetMergeResult::Unknown)
, DstFilePath(DstFilePathIn)
{
// force the user to save the result file (so we know if they "accepted" the merge)
MergingAsset.GetPackage()->SetDirtyFlag(true);
}
/** Records the user's selected resolution, and closes the editor. */
void HandleMergeResolution(UPackage* MergedPackageIn, EAssetMergeResult ResolutionIn)
{
if (MergedPackageIn == MergingAsset.GetPackage())
{
if (ResolutionIn == EAssetMergeResult::Cancelled)
{
// they don't want to save any changes, so clear the flag
MergingAsset.GetPackage()->SetDirtyFlag(false);
}
if (Resolution == EAssetMergeResult::Unknown)
{
Resolution = ResolutionIn;
EditorCommandLineUtilsImpl::ForceCloseEditor();
}
if (ResolutionIn == EAssetMergeResult::Completed)
{
FSavePackageArgs SaveArgs;
SaveArgs.TopLevelFlags = RF_Standalone;
SaveArgs.Error = GLog;
UPackage::SavePackage(MergingAsset.GetPackage(), MergingAsset.GetAssetObj(), *DstFilePath, SaveArgs);
}
}
}
private:
FMergeAsset MergingAsset;
EAssetMergeResult Resolution;
FString DstFilePath;
};
const FString& ResultFilePath = (!Result.GetSourceFilePath().IsEmpty()) ? Result.GetSourceFilePath() : Local.GetSourceFilePath();
TSharedRef<FMergeResolutionHandler> MergeHandler = MakeShareable(new FMergeResolutionHandler(Local, ResultFilePath));
// we use a lambda delegate to route the call into MergeHandler (we require
// this intermediate to hold onto a MergeHandler ref, so it doesn't get
// prematurely destroyed at the end of this function)
auto HandleMergeResolution = [MergeHandler](const FAssetMergeResults& Results)
{
MergeHandler->HandleMergeResolution(Results.MergedPackage, Results.Result);
};
const FOnAssetMergeResolved MergeResolutionDelegate = FOnAssetMergeResolved::CreateLambda(HandleMergeResolution);
// have to mount the save directory so that the BP-editor can save
// the merged asset packages
FPackageName::RegisterMountPoint(TEXT("/Temp/"), FPaths::ProjectSavedDir());
const UClass* AssetClass = Local.GetClass();
check(AssetClass != nullptr);
// bring up the merge tool...
const UAssetDefinition* AssetDefinition = UAssetDefinitionRegistry::Get()->GetAssetDefinitionForClass(AssetClass);
if (AssetDefinition->CanMerge())
{
FAssetManualMergeArgs MergeArgs;
MergeArgs.BaseAsset = Base.GetAssetObj();
MergeArgs.RemoteAsset = Remote.GetAssetObj();
MergeArgs.LocalAsset = Local.GetAssetObj();
MergeArgs.ResolutionCallback = MergeResolutionDelegate;
MergeArgs.Flags = MF_NO_GUI;
AssetDefinition->Merge(MergeArgs);
}
}
//------------------------------------------------------------------------------
static UObject* EditorCommandLineUtilsImpl::ExtractAssetFromPackage(UPackage* Package)
{
return Package->FindAssetInPackage();
}
/*******************************************************************************
* FEditorCommandLineUtils Definition
******************************************************************************/
//------------------------------------------------------------------------------
bool FEditorCommandLineUtils::ParseGameProjectPath(const TCHAR* CmdLine, FString& ProjPathOut, FString& GameNameOut)
{
using namespace EditorCommandLineUtilsImpl; // for ParseCommandArgs(), etc.
FString DiffArgs;
if (ParseCommandArgs(CmdLine, DiffCommandSwitch, DiffArgs))
{
TArray<FString> Tokens, Switches;
UCommandlet::ParseCommandLine(*DiffArgs, Tokens, Switches);
for (FString FilePath : Tokens)
{
FPaths::NormalizeFilename(FilePath);
ProjPathOut = FindProjectFile(FilePath);
if (!ProjPathOut.IsEmpty())
{
GameNameOut = FPaths::GetBaseFilename(ProjPathOut);
// favor project files that are in the same directory tree as
// the supplied file
if ( FilePath.StartsWith(FPaths::GetPath(ProjPathOut)) )
{
break;
}
}
}
}
return FPaths::FileExists(ProjPathOut);
}
//------------------------------------------------------------------------------
void FEditorCommandLineUtils::ProcessEditorCommands(const TCHAR* EditorCmdLine)
{
using namespace EditorCommandLineUtilsImpl; // for DiffCommandSwitch, etc.
// If specified, Lightmass has to be launched manually with -debug (e.g. through a debugger).
// This creates a job with a hard-coded GUID, and allows Lightmass to be executed multiple times (even stand-alone).
if (FParse::Param(EditorCmdLine, DebugLightmassCommandSwitch))
{
extern bool GLightmassDebugMode;
GLightmassDebugMode = true;
UE_LOG(LogInit, Log, TEXT("Running Engine with Lightmass Debug Mode ENABLED"));
}
// If specified, all participating Lightmass agents will report back detailed stats to the log.
if (FParse::Param(EditorCmdLine, LightmassStatsCommandSwitch))
{
extern bool GLightmassStatsMode;
GLightmassStatsMode = true;
UE_LOG(LogInit, Log, TEXT("Running Engine with Lightmass Stats Mode ENABLED"));
}
FString DiffArgs;
if (ParseCommandArgs(EditorCmdLine, DiffCommandSwitch, DiffArgs))
{
IMainFrameModule& MainFrameModule = IMainFrameModule::Get();
const bool bIsMainFramInitialized = MainFrameModule.IsWindowInitialized();
if (bIsMainFramInitialized)
{
RunAssetDiffCommand(MainFrameModule.GetParentWindow(), /*bIsNewProjectWindow =*/FApp::IsProjectNameEmpty(), DiffArgs);
}
else
{
MainFrameModule.OnMainFrameCreationFinished().AddStatic(&RunAssetDiffCommand, DiffArgs);
}
}
}
#undef LOCTEXT_NAMESPACE