2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
# include "UnrealEd.h"
# include "PackageTools.h"
# include "ObjectTools.h"
# include "BusyCursor.h"
# include "ISourceControlModule.h"
# include "AssetToolsModule.h"
# include "DesktopPlatformModule.h"
# include "MainFrame.h"
# include "MessageLog.h"
2014-06-18 07:25:31 -04:00
# include "ComponentReregisterContext.h"
2014-11-12 04:43:54 -05:00
# include "Engine/Selection.h"
2014-03-14 14:13:41 -04:00
# define LOCTEXT_NAMESPACE "PackageTools"
DEFINE_LOG_CATEGORY_STATIC ( LogPackageTools , Log , All ) ;
/** Pointer to a function Called during GC, after reachability analysis is performed but before garbage is purged. */
typedef void ( * EditorPostReachabilityAnalysisCallbackType ) ( ) ;
extern CORE_API EditorPostReachabilityAnalysisCallbackType EditorPostReachabilityAnalysisCallback ;
namespace PackageTools
{
/** State passed to RestoreStandaloneOnReachableObjects. */
static UPackage * PackageBeingUnloaded = NULL ;
static TMap < UObject * , UObject * > ObjectsThatHadFlagsCleared ;
/**
* Called during GC , after reachability analysis is performed but before garbage is purged .
* Restores RF_Standalone to objects in the package - to - be - unloaded that are still reachable .
*/
void RestoreStandaloneOnReachableObjects ( )
{
2014-10-28 16:03:39 -04:00
TArray < UObject * > ObjectsInPackage ;
GetObjectsWithOuter ( PackageBeingUnloaded , ObjectsInPackage ) ;
for ( UObject * Object : ObjectsInPackage )
2014-03-14 14:13:41 -04:00
{
2014-10-28 16:03:39 -04:00
if ( ! Object - > HasAnyFlags ( RF_Unreachable ) )
2014-03-14 14:13:41 -04:00
{
if ( ObjectsThatHadFlagsCleared . Find ( Object ) )
{
Object - > SetFlags ( RF_Standalone ) ;
}
}
}
}
/**
* Filters the global set of packages .
*
* @ param OutGroupPackages The map that receives the filtered list of group packages .
* @ param OutPackageList The array that will contain the list of filtered packages .
*/
void GetFilteredPackageList ( TSet < const UPackage * > & OutFilteredPackageMap ,
TSet < UPackage * > * OutGroupPackages ,
TArray < UPackage * > & OutPackageList )
{
// The UObject list is iterated rather than the UPackage list because we need to be sure we are only adding
// group packages that contain things the generic browser cares about. The packages are derived by walking
// the outer chain of each object.
// Assemble a list of packages. Only show packages that match the current resource type filter.
for ( auto * Obj : TObjectRange < UObject > ( ) )
{
// Make sure that we support displaying this object type
bool bIsSupported = ObjectTools : : IsObjectBrowsable ( Obj ) ;
if ( bIsSupported )
{
UPackage * ObjectPackage = Cast < UPackage > ( Obj - > GetOutermost ( ) ) ;
if ( ObjectPackage ! = NULL )
{
OutFilteredPackageMap . Add ( ObjectPackage ) ;
}
if ( OutGroupPackages ! = NULL )
{
for ( UObject * NextOuter = Obj - > GetOuter ( ) ; NextOuter & & NextOuter - > GetOuter ( ) ; NextOuter = NextOuter - > GetOuter ( ) )
{
UPackage * NextGroup = Cast < UPackage > ( NextOuter ) ;
if ( NextGroup ! = NULL & & ! OutGroupPackages - > Contains ( NextGroup ) )
{
OutGroupPackages - > Add ( NextGroup ) ;
}
}
}
}
}
// Make a TArray copy of PackageMap.
for ( TSet < const UPackage * > : : TConstIterator It ( OutFilteredPackageMap ) ; It ; + + It )
{
OutPackageList . Add ( const_cast < UPackage * > ( * It ) ) ;
}
}
/**
* Fills the OutObjects list with all valid objects that are supported by the current
* browser settings and that reside withing the set of specified packages .
*
* @ param InPackages Filters objects based on package .
* @ param OutObjects [ out ] Receives the list of objects
* @ param bMustBeBrowsable If specified , does a check to see if object is browsable . Defaults to true .
*/
void GetObjectsInPackages ( const TArray < UPackage * > * InPackages ,
TArray < UObject * > & OutObjects )
{
// Iterate over all objects.
for ( TObjectIterator < UObject > It ; It ; + + It )
{
UObject * Obj = * It ;
// Filter out invalid objects early.
if ( ! ObjectTools : : IsObjectBrowsable ( Obj ) )
{
continue ;
}
// Should we filter based on a list of allowed packages?
if ( InPackages ! = NULL )
{
// Make sure this object resides in one of the specified packages.
bool bIsInPackage = false ;
for ( int32 PackageIndex = 0 ; PackageIndex < InPackages - > Num ( ) ; + + PackageIndex )
{
const UPackage * Package = ( * InPackages ) [ PackageIndex ] ;
if ( Obj - > IsIn ( Package ) )
{
bIsInPackage = true ;
break ;
}
}
if ( ! bIsInPackage )
{
continue ;
}
}
// Add to the list.
OutObjects . Add ( Obj ) ;
}
}
bool HandleFullyLoadingPackages ( const TArray < UPackage * > & TopLevelPackages , const FText & OperationText )
{
bool bSuccessfullyCompleted = true ;
// whether or not to suppress the ask to fully load message
2015-04-20 10:12:55 -04:00
bool bSuppress = GetDefault < UEditorPerProjectUserSettings > ( ) - > bSuppressFullyLoadPrompt ;
2014-03-14 14:13:41 -04:00
// Make sure they are all fully loaded.
bool bNeedsUpdate = false ;
for ( int32 PackageIndex = 0 ; PackageIndex < TopLevelPackages . Num ( ) ; PackageIndex + + )
{
UPackage * TopLevelPackage = TopLevelPackages [ PackageIndex ] ;
check ( TopLevelPackage ) ;
check ( TopLevelPackage - > GetOuter ( ) = = NULL ) ;
if ( ! TopLevelPackage - > IsFullyLoaded ( ) )
{
// Ask user to fully load or suppress the message and just fully load
if ( bSuppress | | EAppReturnType : : Yes = = FMessageDialog : : Open ( EAppMsgType : : YesNo , FText : : Format (
NSLOCTEXT ( " UnrealEd " , " NeedsToFullyLoadPackageF " , " Package {0} is not fully loaded. Do you want to fully load it? Not doing so will abort the '{1}' operation. " ) ,
FText : : FromString ( TopLevelPackage - > GetName ( ) ) , OperationText ) ) )
{
// Fully load package.
const FScopedBusyCursor BusyCursor ;
GWarn - > BeginSlowTask ( NSLOCTEXT ( " UnrealEd " , " FullyLoadingPackages " , " Fully loading packages " ) , true ) ;
TopLevelPackage - > FullyLoad ( ) ;
GWarn - > EndSlowTask ( ) ;
bNeedsUpdate = true ;
}
// User declined abort operation.
else
{
bSuccessfullyCompleted = false ;
UE_LOG ( LogPackageTools , Log , TEXT ( " Aborting operation as %s was not fully loaded. " ) , * TopLevelPackage - > GetName ( ) ) ;
break ;
}
}
}
// no need to refresh content browser here as UPackage::FullyLoad() already does this
return bSuccessfullyCompleted ;
}
/**
* Loads the specified package file ( or returns an existing package if it ' s already loaded . )
*
* @ param InFilename File name of package to load
*
* @ return The loaded package ( or NULL if something went wrong . )
*/
UPackage * LoadPackage ( FString InFilename )
{
// Detach all components while loading a package.
// This is necessary for the cases where the load replaces existing objects which may be referenced by the attached components.
FGlobalComponentReregisterContext ReregisterContext ;
// record the name of this file to make sure we load objects in this package on top of in-memory objects in this package
GEditor - > UserOpenedFile = InFilename ;
// clear any previous load errors
FFormatNamedArguments Arguments ;
Arguments . Add ( TEXT ( " PackageName " ) , FText : : FromString ( InFilename ) ) ;
FMessageLog ( " LoadErrors " ) . NewPage ( FText : : Format ( LOCTEXT ( " LoadPackageLogPage " , " Loading package: {PackageName} " ) , Arguments ) ) ;
UPackage * Package = Cast < UPackage > ( : : LoadPackage ( NULL , * InFilename , 0 ) ) ;
// display any load errors that happened while loading the package
FEditorDelegates : : DisplayLoadErrors . Broadcast ( ) ;
// reset the opened package to nothing
GEditor - > UserOpenedFile = FString ( ) ;
// If a script package was loaded, update the
// actor browser in case a script package was loaded
if ( Package ! = NULL )
{
if ( ( Package - > PackageFlags & PKG_ContainsScript ) ! = 0 )
{
GEditor - > BroadcastClassPackageLoadedOrUnloaded ( ) ;
}
}
return Package ;
}
bool UnloadPackages ( const TArray < UPackage * > & TopLevelPackages )
2014-04-23 18:51:56 -04:00
{
FText ErrorMessage ;
bool bResult = UnloadPackages ( TopLevelPackages , ErrorMessage ) ;
if ( ! ErrorMessage . IsEmpty ( ) )
{
FMessageDialog : : Open ( EAppMsgType : : Ok , ErrorMessage ) ;
}
return bResult ;
}
bool UnloadPackages ( const TArray < UPackage * > & TopLevelPackages , FText & OutErrorMessage )
2014-03-14 14:13:41 -04:00
{
bool bResult = false ;
// Get outermost packages, in case groups were selected.
TArray < UPackage * > PackagesToUnload ;
// Split the set of selected top level packages into packages which are dirty (and thus cannot be unloaded)
// and packages that are not dirty (and thus can be unloaded).
TArray < UPackage * > DirtyPackages ;
for ( int32 PackageIndex = 0 ; PackageIndex < TopLevelPackages . Num ( ) ; + + PackageIndex )
{
UPackage * Package = TopLevelPackages [ PackageIndex ] ;
if ( Package ! = NULL )
{
if ( Package - > IsDirty ( ) )
{
DirtyPackages . Add ( Package ) ;
}
else
{
PackagesToUnload . AddUnique ( Package - > GetOutermost ( ) ? Package - > GetOutermost ( ) : Package ) ;
}
}
}
// Inform the user that dirty packages won't be unloaded.
if ( DirtyPackages . Num ( ) > 0 )
{
FString DirtyPackagesList ;
for ( int32 PackageIndex = 0 ; PackageIndex < DirtyPackages . Num ( ) ; + + PackageIndex )
{
DirtyPackagesList + = FString : : Printf ( TEXT ( " \n %s " ) , * DirtyPackages [ PackageIndex ] - > GetName ( ) ) ;
}
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " DirtyPackages " ) , FText : : FromString ( DirtyPackagesList ) ) ;
2014-04-23 18:51:56 -04:00
OutErrorMessage = FText : : Format ( NSLOCTEXT ( " UnrealEd " , " UnloadDirtyPackagesList " , " The following assets have been modified and cannot be unloaded:{DirtyPackages} \n Saving these assets will allow them to be unloaded. " ) , Args ) ;
2014-03-14 14:13:41 -04:00
}
if ( PackagesToUnload . Num ( ) > 0 )
{
const FScopedBusyCursor BusyCursor ;
// Complete any load/streaming requests, then lock IO.
FlushAsyncLoading ( ) ;
( * GFlushStreamingFunc ) ( ) ;
// Remove potential references to to-be deleted objects from the GB selection set.
GEditor - > GetSelectedObjects ( ) - > DeselectAll ( ) ;
// Set the callback for restoring RF_Standalone post reachability analysis.
// GC will call this function before purging objects, allowing us to restore RF_Standalone
// to any objects that have not been marked RF_Unreachable.
EditorPostReachabilityAnalysisCallback = RestoreStandaloneOnReachableObjects ;
bool bScriptPackageWasUnloaded = false ;
GWarn - > BeginSlowTask ( NSLOCTEXT ( " UnrealEd " , " Unloading " , " Unloading " ) , true ) ;
// First add all packages to unload to the root set so they don't get garbage collected while we are operating on them
TArray < UPackage * > PackagesAddedToRoot ;
for ( int32 PackageIndex = 0 ; PackageIndex < PackagesToUnload . Num ( ) ; + + PackageIndex )
{
UPackage * Pkg = PackagesToUnload [ PackageIndex ] ;
if ( ! Pkg - > IsRooted ( ) )
{
Pkg - > AddToRoot ( ) ;
PackagesAddedToRoot . Add ( Pkg ) ;
}
}
// Now try to clean up assets in all packages to unload.
for ( int32 PackageIndex = 0 ; PackageIndex < PackagesToUnload . Num ( ) ; + + PackageIndex )
{
PackageBeingUnloaded = PackagesToUnload [ PackageIndex ] ;
2014-10-23 13:51:34 -04:00
2014-03-14 14:13:41 -04:00
GWarn - > StatusUpdate ( PackageIndex , PackagesToUnload . Num ( ) , FText : : Format ( NSLOCTEXT ( " UnrealEd " , " Unloadingf " , " Unloading {0}... " ) , FText : : FromString ( PackageBeingUnloaded - > GetName ( ) ) ) ) ;
PackageBeingUnloaded - > bHasBeenFullyLoaded = false ;
2014-10-16 11:03:16 -04:00
PackageBeingUnloaded - > ClearFlags ( RF_WasLoaded ) ;
2014-03-14 14:13:41 -04:00
if ( PackageBeingUnloaded - > PackageFlags & PKG_ContainsScript )
{
bScriptPackageWasUnloaded = true ;
}
// Clear RF_Standalone flag from objects in the package to be unloaded so they get GC'd.
{
2014-10-28 16:03:39 -04:00
TArray < UObject * > ObjectsInPackage ;
GetObjectsWithOuter ( PackageBeingUnloaded , ObjectsInPackage ) ;
for ( UObject * Object : ObjectsInPackage )
2014-03-14 14:13:41 -04:00
{
2014-10-28 16:03:39 -04:00
if ( Object - > HasAnyFlags ( RF_Standalone ) )
{
Object - > ClearFlags ( RF_Standalone ) ;
ObjectsThatHadFlagsCleared . Add ( Object , Object ) ;
}
2014-03-14 14:13:41 -04:00
}
}
// Reset loaders
ResetLoaders ( PackageBeingUnloaded ) ;
// Collect garbage.
CollectGarbage ( GARBAGE_COLLECTION_KEEPFLAGS ) ;
2014-10-28 14:58:26 -04:00
if ( PackageBeingUnloaded - > IsDirty ( ) )
{
// The package was marked dirty as a result of something that happened above (e.g callbacks in CollectGarbage).
// Dirty packages we actually care about unloading were filtered above so if the package becomes dirty here it should still be unloaded
PackageBeingUnloaded - > SetDirtyFlag ( false ) ;
}
2014-03-14 14:13:41 -04:00
// Cleanup.
ObjectsThatHadFlagsCleared . Empty ( ) ;
PackageBeingUnloaded = NULL ;
bResult = true ;
}
// Now remove from root all the packages we added earlier so they may be GCed if possible
for ( int32 PackageIndex = 0 ; PackageIndex < PackagesAddedToRoot . Num ( ) ; + + PackageIndex )
{
PackagesAddedToRoot [ PackageIndex ] - > RemoveFromRoot ( ) ;
}
PackagesAddedToRoot . Empty ( ) ;
GWarn - > EndSlowTask ( ) ;
// Set the post reachability callback.
EditorPostReachabilityAnalysisCallback = NULL ;
2014-10-28 16:03:39 -04:00
// Clear the standalone flag on metadata objects that are going to be GC'd below.
// This resolves the circular dependency between metadata and packages.
TArray < TWeakObjectPtr < UMetaData > > PackageMetaDataWithClearedStandaloneFlag ;
for ( UPackage * PackageToUnload : PackagesToUnload )
{
UMetaData * PackageMetaData = PackageToUnload ? PackageToUnload - > MetaData : nullptr ;
if ( PackageMetaData & & PackageMetaData - > HasAnyFlags ( RF_Standalone ) )
{
PackageMetaData - > ClearFlags ( RF_Standalone ) ;
PackageMetaDataWithClearedStandaloneFlag . Add ( PackageMetaData ) ;
}
}
2014-03-14 14:13:41 -04:00
CollectGarbage ( GARBAGE_COLLECTION_KEEPFLAGS ) ;
2014-10-28 16:03:39 -04:00
// Restore the standalone flag on any metadata objects that survived the GC
for ( const TWeakObjectPtr < UMetaData > & WeakPackageMetaData : PackageMetaDataWithClearedStandaloneFlag )
{
UMetaData * MetaData = WeakPackageMetaData . Get ( ) ;
if ( MetaData )
{
MetaData - > SetFlags ( RF_Standalone ) ;
}
}
2014-03-14 14:13:41 -04:00
// Update the actor browser if a script package was unloaded
if ( bScriptPackageWasUnloaded )
{
GEditor - > BroadcastClassPackageLoadedOrUnloaded ( ) ;
}
}
return bResult ;
}
/**
* Wrapper method for multiple objects at once .
*
* @ param TopLevelPackages the packages to be export
* @ param LastExportPath the path that the user last exported assets to
* @ param FilteredClasses if specified , set of classes that should be the only types exported if not exporting to single file
* @ param bUseProvidedExportPath If true , use LastExportPath as the user ' s export path w / o prompting for a directory , where applicable
*
* @ return the path that the user chose for the export .
*/
FString DoBulkExport ( const TArray < UPackage * > & TopLevelPackages , FString LastExportPath , const TSet < UClass * > * FilteredClasses /* = NULL */ , bool bUseProvidedExportPath /* = false*/ )
{
// Disallow export if any packages are cooked.
if ( HandleFullyLoadingPackages ( TopLevelPackages , NSLOCTEXT ( " UnrealEd " , " BulkExportE " , " Bulk Export... " ) ) )
{
TArray < UObject * > ObjectsInPackages ;
GetObjectsInPackages ( & TopLevelPackages , ObjectsInPackages ) ;
// See if any filtering has been requested. Objects can be filtered by class and/or localization filter.
TArray < UObject * > FilteredObjects ;
if ( FilteredClasses )
{
2015-03-24 15:51:28 -04:00
// Present the user with a warning that only the filtered types are being exported
FSuppressableWarningDialog : : FSetupInfo Info ( NSLOCTEXT ( " UnrealEd " , " BulkExport_FilteredWarning " , " Asset types are currently filtered within the Content Browser. Only objects of the filtered types will be exported. " ) ,
LOCTEXT ( " BulkExport_FilteredWarning_Title " , " Asset Filter in Effect " ) , " BulkExportFilterWarning " ) ;
Info . ConfirmText = NSLOCTEXT ( " ModalDialogs " , " BulkExport_FilteredWarningConfirm " , " Close " ) ;
2014-03-14 14:13:41 -04:00
2015-03-24 15:51:28 -04:00
FSuppressableWarningDialog PromptAboutFiltering ( Info ) ;
PromptAboutFiltering . ShowModal ( ) ;
2014-03-14 14:13:41 -04:00
for ( TArray < UObject * > : : TConstIterator ObjIter ( ObjectsInPackages ) ; ObjIter ; + + ObjIter )
{
UObject * CurObj = * ObjIter ;
// Only add the object if it passes all of the specified filters
2015-03-31 20:12:31 -04:00
if ( CurObj & & FilteredClasses - > Contains ( CurObj - > GetClass ( ) ) )
2014-03-14 14:13:41 -04:00
{
FilteredObjects . Add ( CurObj ) ;
}
}
}
// If a filtered set was provided, export the filtered objects array; otherwise, export all objects in the packages
TArray < UObject * > & ObjectsToExport = FilteredClasses ? FilteredObjects : ObjectsInPackages ;
// Prompt the user about how many objects will be exported before proceeding.
const bool bProceed = EAppReturnType : : Yes = = FMessageDialog : : Open ( EAppMsgType : : YesNo , FText : : Format (
NSLOCTEXT ( " UnrealEd " , " Prompt_AboutToBulkExportNItems_F " , " About to bulk export {0} items. Proceed? " ) , FText : : AsNumber ( ObjectsToExport . Num ( ) ) ) ) ;
if ( bProceed )
{
ObjectTools : : ExportObjects ( ObjectsToExport , false , & LastExportPath , bUseProvidedExportPath ) ;
}
}
return LastExportPath ;
}
void CheckOutRootPackages ( const TArray < UPackage * > & Packages )
{
if ( ISourceControlModule : : Get ( ) . IsEnabled ( ) )
{
ISourceControlProvider & SourceControlProvider = ISourceControlModule : : Get ( ) . GetProvider ( ) ;
// Update to the latest source control state.
SourceControlProvider . Execute ( ISourceControlOperation : : Create < FUpdateStatus > ( ) , Packages ) ;
TArray < FString > TouchedPackageNames ;
bool bCheckedSomethingOut = false ;
for ( int32 PackageIndex = 0 ; PackageIndex < Packages . Num ( ) ; + + PackageIndex )
{
UPackage * Package = Packages [ PackageIndex ] ;
FSourceControlStatePtr SourceControlState = SourceControlProvider . GetState ( Package , EStateCacheUsage : : Use ) ;
if ( SourceControlState . IsValid ( ) & & SourceControlState - > CanCheckout ( ) )
{
// The package is still available, so do the check out.
bCheckedSomethingOut = true ;
TouchedPackageNames . Add ( Package - > GetName ( ) ) ;
}
else
{
// The status on the package has changed to something inaccessible, so we have to disallow the check out.
// Don't warn if the file isn't in the depot.
if ( SourceControlState . IsValid ( ) & & SourceControlState - > IsSourceControlled ( ) )
{
FMessageDialog : : Open ( EAppMsgType : : Ok , NSLOCTEXT ( " UnrealEd " , " Error_PackageStatusChanged " , " Package can't be checked out - status has changed! " ) ) ;
}
}
}
// Synchronize source control state if something was checked out.
SourceControlProvider . Execute ( ISourceControlOperation : : Create < FCheckOut > ( ) , SourceControlHelpers : : PackageFilenames ( TouchedPackageNames ) ) ;
}
}
/**
* Checks if the passed in path is in an external directory . I . E Ones not found automatically in the content directory
*
* @ param PackagePath Path of the package to check , relative or absolute
* @ return true if PackagePath points to an external location
*/
bool IsPackagePathExternal ( const FString & PackagePath )
{
bool bIsExternal = true ;
TArray < FString > Paths ;
GConfig - > GetArray ( TEXT ( " Core.System " ) , TEXT ( " Paths " ) , Paths , GEngineIni ) ;
FString PackageFilename = FPaths : : ConvertRelativePathToFull ( PackagePath ) ;
// absolute path of the package that was passed in, without the actual name of the package
FString PackageFullPath = FPaths : : GetPath ( PackageFilename ) ;
for ( int32 pathIdx = 0 ; pathIdx < Paths . Num ( ) ; + + pathIdx )
{
FString AbsolutePathName = FPaths : : ConvertRelativePathToFull ( Paths [ pathIdx ] ) ;
// check if the package path is within the list of paths the engine searches.
if ( PackageFullPath . Contains ( AbsolutePathName ) )
{
bIsExternal = false ;
break ;
}
}
return bIsExternal ;
}
/**
* Checks if the passed in package ' s filename is in an external directory . I . E Ones not found automatically in the content directory
*
* @ param Package The package to check
* @ return true if the package points to an external filename
*/
bool IsPackageExternal ( const UPackage & Package )
{
FString FileString ;
FPackageName : : DoesPackageExist ( Package . GetName ( ) , NULL , & FileString ) ;
return IsPackagePathExternal ( FileString ) ;
}
/**
* Checks if the passed in packages have any references to externally loaded packages . I . E Ones not found automatically in the content directory
*
* @ param PackagesToCheck The packages to check
* @ param OutPackagesWithExternalRefs Optional list of packages that have external references
* @ param LevelToCheck The ULevel to check
* @ param OutObjectsWithExternalRefs List of objects gathered from within the given ULevel that have external references
* @ return true if PackageToCheck has references to an externally loaded package
*/
bool CheckForReferencesToExternalPackages ( const TArray < UPackage * > * PackagesToCheck , TArray < UPackage * > * OutPackagesWithExternalRefs , ULevel * LevelToCheck /*=NULL*/ , TArray < UObject * > * OutObjectsWithExternalRefs /*=NULL*/ )
{
bool bHasExternalPackageRefs = false ;
// Find all external packages
TSet < const UPackage * > FilteredPackageMap ;
TArray < UPackage * > LoadedPackageList ;
GetFilteredPackageList ( FilteredPackageMap , NULL , LoadedPackageList ) ;
TArray < UPackage * > ExternalPackages ;
for ( int32 pkgIdx = 0 ; pkgIdx < LoadedPackageList . Num ( ) ; + + pkgIdx )
{
UPackage * Pkg = LoadedPackageList [ pkgIdx ] ;
FString OutFilename ;
const FString PackageName = Pkg - > GetName ( ) ;
const FGuid PackageGuid = Pkg - > GetGuid ( ) ;
FPackageName : : DoesPackageExist ( PackageName , & PackageGuid , & OutFilename ) ;
if ( OutFilename . Len ( ) > 0 & & IsPackageExternal ( * Pkg ) )
{
ExternalPackages . Add ( Pkg ) ;
}
}
// get all the objects in the external packages and make sure they aren't referenced by objects in a package being checked
TArray < UObject * > ObjectsInExternalPackages ;
TArray < UObject * > ObjectsInPackageToCheck ;
if ( PackagesToCheck )
{
GetObjectsInPackages ( & ExternalPackages , ObjectsInExternalPackages ) ;
GetObjectsInPackages ( PackagesToCheck , ObjectsInPackageToCheck ) ;
}
else
{
for ( TObjectIterator < UObject > It ; It ; + + It )
{
// Gather all the objects in our level
UObject * Obj = * It ;
if ( Obj - > IsIn ( LevelToCheck ) )
{
ObjectsInPackageToCheck . Add ( Obj ) ;
}
// Gather all objects in any loaded external packages
for ( int32 PackageIndex = 0 ; PackageIndex < ExternalPackages . Num ( ) ; + + PackageIndex )
{
const UPackage * Package = ExternalPackages [ PackageIndex ] ;
if ( Obj - > IsIn ( Package ) & & ObjectTools : : IsObjectBrowsable ( Obj ) )
{
ObjectsInExternalPackages . Add ( Obj ) ;
break ;
}
}
}
}
// compare
for ( int32 ExtObjIdx = 0 ; ExtObjIdx < ObjectsInExternalPackages . Num ( ) ; + + ExtObjIdx )
{
UObject * ExternalObject = ObjectsInExternalPackages [ ExtObjIdx ] ;
// only check objects which are in packages to be saved. This should greatly reduce the overhead by not searching through objects we don't intend to save
for ( int32 CheckObjIdx = 0 ; CheckObjIdx < ObjectsInPackageToCheck . Num ( ) ; + + CheckObjIdx )
{
UObject * CheckObject = ObjectsInPackageToCheck [ CheckObjIdx ] ;
FArchiveFindCulprit ArFind ( ExternalObject , CheckObject , false ) ;
if ( ArFind . GetCount ( ) > 0 )
{
if ( OutPackagesWithExternalRefs )
{
OutPackagesWithExternalRefs - > Add ( CheckObject - > GetOutermost ( ) ) ;
}
if ( OutObjectsWithExternalRefs )
{
OutObjectsWithExternalRefs - > Add ( CheckObject ) ;
}
bHasExternalPackageRefs = true ;
}
}
}
return bHasExternalPackageRefs ;
}
bool IsSingleAssetPackage ( const FString & PackageName )
{
FString PackageFileName ;
if ( FPackageName : : DoesPackageExist ( PackageName , NULL , & PackageFileName ) )
{
return FPaths : : GetExtension ( PackageFileName , /*bIncludeDot=*/ true ) . ToLower ( ) = = FPackageName : : GetAssetPackageExtension ( ) ;
}
// If it wasn't found in the package file cache, this package does not yet
// exist so it is assumed to be saved as a UAsset file.
return true ;
}
FString SanitizePackageName ( const FString & InPackageName )
{
FString SanitizedName ;
FString InvalidChars = INVALID_LONGPACKAGE_CHARACTERS ;
// See if the name contains invalid characters.
FString Char ;
for ( int32 CharIdx = 0 ; CharIdx < InPackageName . Len ( ) ; + + CharIdx )
{
Char = InPackageName . Mid ( CharIdx , 1 ) ;
if ( InvalidChars . Contains ( * Char ) )
{
SanitizedName + = TEXT ( " _ " ) ;
}
else
{
SanitizedName + = Char ;
}
}
return SanitizedName ;
}
}
# undef LOCTEXT_NAMESPACE
// EOF