2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-11-17 15:04:07 -05:00
# include "UnrealEd.h"
# include "EditorCommandLineUtils.h"
# include "Commandlets/Commandlet.h" // for ParseCommandLine()
# include "Paths.h"
# include "App.h" // for IsGameNameEmpty()
# include "Dialogs.h" // for OpenMsgDlgInt_NonModal()
# include "TickableEditorObject.h"
# include "Editor/MainFrame/Public/Interfaces/IMainFrameModule.h"
# include "AssetRegistryModule.h"
2014-11-17 17:35:07 -05:00
# include "IAssetTypeActions.h" // for FRevisionInfo
2014-11-18 03:15:12 -05:00
# include "AssetToolsModule.h"
2014-11-21 12:21:17 -05:00
# include "AssetEditorManager.h"
# include "Editor.h" // for OnShutdownPostPackagesSaved
2015-04-24 13:39:16 -04:00
# include "ProjectDescriptor.h"
2014-11-17 15:04:07 -05:00
# define LOCTEXT_NAMESPACE "EditorCommandLineUtils"
2014-11-21 12:21:17 -05:00
// Forward Declarations
struct FMergeAsset ;
2014-11-17 15:04:07 -05:00
/*******************************************************************************
* 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 = LOCTEXT ( " DiffCommandeHelpText " , " \
Usage : \ n \
- diff [ options ] left right \ n \
2014-11-21 12:21:17 -05:00
- diff [ options ] remote local base result \ n \
2014-11-17 15:04:07 -05:00
\ 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 ) ;
2014-11-21 12:21:17 -05:00
/** */
static FString FindProjectFile ( const FString & AssetFilePath ) ;
2014-11-17 15:04:07 -05:00
/** */
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 ) ;
/** */
2014-11-21 12:21:17 -05:00
static void RunAssetMerge ( FMergeAsset const & Base , FMergeAsset const & Remote , FMergeAsset const & Local , FMergeAsset const & Result ) ;
2014-11-17 15:04:07 -05:00
/** */
2014-11-21 12:21:17 -05:00
static UObject * ExtractAssetFromPackage ( UPackage * Package ) ;
2014-11-17 15:04:07 -05:00
}
/*******************************************************************************
* 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 \n Run '-{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 bool IsTickable ( ) const override { return true ; }
virtual void Tick ( float DeltaTime ) override ;
// End FTickableEditorObject interface
2014-11-21 12:21:17 -05:00
/** */
void Disable ( ) ;
2014-11-17 15:04:07 -05:00
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 ( ) ;
}
}
2014-11-21 12:21:17 -05:00
//------------------------------------------------------------------------------
void FFauxStandaloneToolManager : : Disable ( )
{
if ( MainEditorWindow . IsValid ( ) )
{
MainEditorWindow . Pin ( ) - > ShowWindow ( ) ;
}
}
/*******************************************************************************
* FMergeAsset
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct FMergeAsset
{
public :
2014-11-21 13:51:41 -05:00
FMergeAsset ( const TCHAR * DstFileName ) ;
2014-11-21 12:21:17 -05:00
/** */
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 ;
private :
UPackage * Package ;
UObject * AssetObj ;
FString DestFilePath ;
FString SrcFilePath ;
} ;
//------------------------------------------------------------------------------
2014-11-21 13:51:41 -05:00
FMergeAsset : : FMergeAsset ( const TCHAR * DstFileName )
2014-11-21 12:21:17 -05:00
: 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 ;
}
// UE4 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 ;
2015-01-20 05:48:14 -05:00
if ( SrcFileName . Split ( TEXT ( " # " ) , & BaseFileName , & RevisionStr ) )
2014-11-21 12:21:17 -05:00
{
// @TODO: if connected to source-control, extract changelist and date info
2015-01-20 05:48:14 -05:00
RevisionInfoOut . Revision = * RevisionStr ;
2014-11-21 12:21:17 -05:00
}
return RevisionInfoOut ;
}
//------------------------------------------------------------------------------
const FString & FMergeAsset : : GetSourceFilePath ( ) const
{
return SrcFilePath ;
}
//------------------------------------------------------------------------------
const FString & FMergeAsset : : GetAssetFilePath ( ) const
{
return DestFilePath ;
}
2014-11-17 15:04:07 -05:00
/*******************************************************************************
* 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 ;
}
2014-11-21 12:21:17 -05:00
//------------------------------------------------------------------------------
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 ;
}
2014-11-17 15:04:07 -05:00
//------------------------------------------------------------------------------
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 ( )
{
2015-04-21 16:06:46 -04:00
// 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 " ) ) ;
2014-11-17 15:04:07 -05:00
}
//------------------------------------------------------------------------------
static void EditorCommandLineUtilsImpl : : RunAssetDiffCommand ( TSharedPtr < SWindow > MainEditorWindow , bool bIsRunningProjBrowser , FString CommandArgs )
{
// if the editor is running the project browser, then the user has to first
// select a project (and then the editor will re-launch with this command).
if ( bIsRunningProjBrowser )
{
2014-11-21 12:21:17 -05:00
// @TODO: can we run without loading a project?
2014-11-17 15:04:07 -05:00
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 " ) )
{
2014-11-21 12:21:17 -05:00
RaiseEditorMessageBox ( LOCTEXT ( " DiffCommandHelp " , " Diff/Merge Command-Line Help " ) , DiffCommandHelpTxt , /*bExitOnClose =*/ true ) ;
2014-11-17 15:04:07 -05:00
return ;
}
if ( Switches . Contains ( " echo " ) )
{
2014-11-21 12:21:17 -05:00
RaiseEditorMessageBox ( LOCTEXT ( " DiffCommandHelp " , " Passed Command Arguments " ) ,
FText : : FromString ( CommandArgs ) , /*bExitOnClose =*/ true ) ;
2014-11-17 15:04:07 -05:00
return ;
}
2014-11-21 12:21:17 -05:00
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
//--------------------------------------
2014-11-17 15:04:07 -05:00
FCommandLineErrorReporter ErrorReporter ( DiffCommandSwitch , CommandArgs ) ;
int32 ParsedFileCount = 0 ;
2014-11-21 12:21:17 -05:00
for ( int32 FileIndex = 0 ; FileIndex < Tokens . Num ( ) & & ParsedFileCount < MaxFilesNeeded ; + + FileIndex )
2014-11-17 15:04:07 -05:00
{
FString & FilePath = Tokens [ FileIndex ] ;
2014-11-21 12:21:17 -05:00
FMergeAsset & MergeAsset = MergeAssets [ ParsedFileCount ] ;
if ( MergeAsset . SetSourceFile ( FilePath , ErrorReporter ) )
2014-11-17 15:04:07 -05:00
{
2014-11-21 12:21:17 -05:00
+ + ParsedFileCount ;
2014-11-17 15:04:07 -05:00
}
2014-11-21 12:21:17 -05:00
}
2014-11-17 15:04:07 -05:00
2014-11-21 12:21:17 -05:00
//--------------------------------------
// Verify file count
//--------------------------------------
2014-11-17 15:04:07 -05:00
2014-11-21 12:21:17 -05:00
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 ( ) )
2014-11-17 15:04:07 -05:00
{
2014-11-21 12:21:17 -05:00
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 ( ) )
2014-11-17 15:04:07 -05:00
{
2014-11-21 12:21:17 -05:00
ErrorReporter . ReportFatalError ( LOCTEXT ( " TypeMismatchTitle " , " Asset Type Mismatch " ) ,
LOCTEXT ( " MergeTypeMismatchError " , " Cannot merge files of different asset types. " ) ) ;
2014-11-17 15:04:07 -05:00
}
2014-11-21 12:21:17 -05:00
else if ( ! AssetActions . IsValid ( ) | | ! AssetActions . Pin ( ) - > CanMerge ( ) )
2014-11-17 15:04:07 -05:00
{
2014-11-21 12:21:17 -05:00
ErrorReporter . ReportFatalError ( LOCTEXT ( " CannotMergeTitle " , " Cannot Merge " ) ,
FText : : Format ( LOCTEXT ( " CannotMergeError " , " {0} asset files can not be merged. " ) , FText : : FromName ( AssetClass - > GetFName ( ) ) ) ) ;
2014-11-17 15:04:07 -05:00
}
}
2014-11-21 12:21:17 -05:00
}
//--------------------------------------
// Preform diff/merge
//--------------------------------------
if ( bLoadSuccess & & ! ErrorReporter . HasBlockingError ( ) )
{
if ( bWantsMerge )
{
// unlike with diff'ing, 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 ) ;
}
2014-11-17 15:04:07 -05:00
else
{
2014-11-21 12:21:17 -05:00
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 )
{
2014-11-24 17:38:44 -05:00
class FMergeResolutionHandler : public TSharedFromThis < FMergeResolutionHandler >
{
public :
FMergeResolutionHandler ( UPackage * MergingPkgIn , const FString & DstFilePathIn )
: MergingPackage ( MergingPkgIn )
, Resolution ( EMergeResult : : Unknown )
, DstFilePath ( DstFilePathIn )
{
// force the user to save the result file (so we know if they "accepted" the merge)
MergingPkgIn - > SetDirtyFlag ( true ) ;
}
2014-11-21 12:21:17 -05:00
2014-11-24 17:38:44 -05:00
/** Records the user's selected resolution, and closes the editor. */
void HandleMergeResolution ( UPackage * MergedPackageIn , EMergeResult : : Type ResolutionIn )
{
if ( MergedPackageIn = = MergingPackage )
{
if ( ResolutionIn = = EMergeResult : : Cancelled )
{
// they don't want to save any changes, so clear the flag
MergingPackage - > SetDirtyFlag ( false ) ;
}
if ( Resolution = = EMergeResult : : Unknown )
{
Resolution = ResolutionIn ;
EditorCommandLineUtilsImpl : : ForceCloseEditor ( ) ;
}
}
}
/** Copies the modified file if the user saved changes (and didn't cancel). */
void HandleEditorClose ( )
{
if ( ( Resolution ! = EMergeResult : : Cancelled ) & & ! MergingPackage - > IsDirty ( ) )
{
FString SrcFilePath = MergingPackage - > FileName . ToString ( ) ;
IFileManager : : Get ( ) . Copy ( * DstFilePath , * SrcFilePath ) ;
}
}
private :
UPackage * MergingPackage ;
EMergeResult : : Type Resolution ;
FString DstFilePath ;
} ;
UPackage * MergeResultPkg = Local . GetAssetObj ( ) - > GetOutermost ( ) ;
const FString & ResultFilePath = ( ! Result . GetSourceFilePath ( ) . IsEmpty ( ) ) ? Result . GetSourceFilePath ( ) : Local . GetSourceFilePath ( ) ;
TSharedRef < FMergeResolutionHandler > MergeHandler = MakeShareable ( new FMergeResolutionHandler ( MergeResultPkg , 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 ] ( UPackage * MergedPackageIn , EMergeResult : : Type ResolutionIn )
{
MergeHandler - > HandleMergeResolution ( MergedPackageIn , ResolutionIn ) ;
} ;
const FOnMergeResolved MergeResolutionDelegate = FOnMergeResolved : : CreateLambda ( HandleMergeResolution ) ;
2014-11-21 12:21:17 -05:00
// have to mount the save directory so that the BP-editor can save
// the merged asset packages
FPackageName : : RegisterMountPoint ( TEXT ( " /Temp/ " ) , FPaths : : GameSavedDir ( ) ) ;
2014-11-24 17:38:44 -05:00
IAssetTools & AssetTools = FModuleManager : : GetModuleChecked < FAssetToolsModule > ( " AssetTools " ) . Get ( ) ;
UClass * AssetClass = Local . GetClass ( ) ;
check ( AssetClass ! = nullptr ) ;
TWeakPtr < IAssetTypeActions > AssetActions = AssetTools . GetAssetTypeActionsForClass ( AssetClass ) ;
check ( AssetActions . IsValid ( ) ) ;
2014-11-21 12:21:17 -05:00
// bring up the merge tool...
2014-11-24 17:38:44 -05:00
AssetActions . Pin ( ) - > Merge ( Base . GetAssetObj ( ) , Remote . GetAssetObj ( ) , Local . GetAssetObj ( ) , MergeResolutionDelegate ) ;
2014-11-21 12:21:17 -05:00
2014-11-24 17:38:44 -05:00
// 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)
2015-02-25 12:55:30 -05:00
auto HandleEditorClose = [ ] ( TSharedRef < FMergeResolutionHandler > InMergeHandler )
2014-11-21 12:21:17 -05:00
{
2015-02-25 12:55:30 -05:00
InMergeHandler - > HandleEditorClose ( ) ;
2014-11-24 17:38:44 -05:00
} ;
2014-11-21 12:21:17 -05:00
// have to copy the file into the expected result file when we're done
2014-11-24 17:38:44 -05:00
FEditorDelegates : : OnShutdownPostPackagesSaved . AddStatic ( HandleEditorClose , MergeHandler ) ;
2014-11-21 12:21:17 -05:00
}
//------------------------------------------------------------------------------
static UObject * EditorCommandLineUtilsImpl : : ExtractAssetFromPackage ( UPackage * Package )
{
TArray < UObject * > ObjectsWithOuter ;
GetObjectsWithOuter ( Package , ObjectsWithOuter , /*bIncludeNestedObjects =*/ false ) ;
UObject * FoundAsset = nullptr ;
for ( UObject * PackageObj : ObjectsWithOuter )
{
if ( PackageObj - > IsAsset ( ) )
{
FoundAsset = PackageObj ;
break ;
2014-11-17 15:04:07 -05:00
}
}
2014-11-21 12:21:17 -05:00
return FoundAsset ;
2014-11-17 15:04:07 -05:00
}
/*******************************************************************************
* 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 ) ;
2014-11-21 12:21:17 -05:00
for ( FString FilePath : Tokens )
2014-11-17 15:04:07 -05:00
{
2014-11-21 12:21:17 -05:00
FPaths : : NormalizeFilename ( FilePath ) ;
ProjPathOut = FindProjectFile ( FilePath ) ;
2014-11-17 15:04:07 -05:00
2014-11-21 12:21:17 -05:00
if ( ! ProjPathOut . IsEmpty ( ) )
2014-11-17 15:04:07 -05:00
{
2014-11-21 12:21:17 -05:00
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 ;
}
2014-11-17 15:04:07 -05:00
}
}
}
2014-11-21 12:21:17 -05:00
return FPaths : : FileExists ( ProjPathOut ) ;
2014-11-17 15:04:07 -05:00
}
//------------------------------------------------------------------------------
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 ) )
2014-11-21 12:21:17 -05:00
{
2014-11-17 15:04:07 -05:00
IMainFrameModule & MainFrameModule = IMainFrameModule : : Get ( ) ;
const bool bIsMainFramInitialized = MainFrameModule . IsWindowInitialized ( ) ;
if ( bIsMainFramInitialized )
{
RunAssetDiffCommand ( MainFrameModule . GetParentWindow ( ) , /*bIsNewProjectWindow =*/ FApp : : IsGameNameEmpty ( ) , DiffArgs ) ;
}
else
{
MainFrameModule . OnMainFrameCreationFinished ( ) . AddStatic ( & RunAssetDiffCommand , DiffArgs ) ;
}
}
}
# undef LOCTEXT_NAMESPACE