void UWorldComposition::OnLevelPostLoad(ULevel* InLevel) { UPackage* LevelPackage = Cast<UPackage>(InLevel->GetOutermost()); if (LevelPackage && InLevel->OwningWorld) { FWorldTileInfo Info; UWorld* World = InLevel->OwningWorld; if (World->WorldComposition) { // Assign WorldLevelInfo previously loaded by world composition FWorldCompositionTile* Tile = World->WorldComposition->FindTileByName(LevelPackage->GetFName()); if (Tile) { Info = Tile->Info; } } else { #if WITH_EDITOR // Preserve FWorldTileInfo in case sub-level was loaded in the editor outside of world composition FString PackageFilename = FPackageName::LongPackageNameToFilename(LevelPackage->GetName(), FPackageName::GetMapPackageExtension()); FWorldTileInfo::Read(PackageFilename, Info); #endif //WITH_EDITOR } const bool bIsDefault = (Info == FWorldTileInfo()); if (!bIsDefault) { LevelPackage->WorldTileInfo = new FWorldTileInfo(Info); } } }
/** * Deletes the asset */ void DeleteAsset() { if (CreatedAsset) { bool bSuccessful = false; bSuccessful = ObjectTools::DeleteSingleObject(CreatedAsset, false); //If we failed to delete this object manually clear any references and try again if (!bSuccessful) { //Clear references to the object so we can delete it AutomationEditorCommonUtils::NullReferencesToObject(CreatedAsset); bSuccessful = ObjectTools::DeleteSingleObject(CreatedAsset, false); } //Delete the package if (bSuccessful) { FString PackageFilename; if (FPackageName::DoesPackageExist(AssetPackage->GetName(), NULL, &PackageFilename)) { TArray<UPackage*> PackagesToDelete; PackagesToDelete.Add(AssetPackage); // Let the package auto-saver know that it needs to ignore the deleted packages GUnrealEd->GetPackageAutoSaver().OnPackagesDeleted(PackagesToDelete); AssetPackage->SetDirtyFlag(false); // Unload the packages and collect garbage. PackageTools::UnloadPackages(PackagesToDelete); IFileManager::Get().Delete(*PackageFilename); TestStats->NumDeleted++; UE_LOG(LogEditorAssetAutomationTests, Display, TEXT("Deleted asset %s (%s)"), *AssetName, *Class->GetName()); } } else { UE_LOG(LogEditorAssetAutomationTests, Error, TEXT("Unable to delete asset: %s (%s)"), *AssetName, *Class->GetName()); } } }
void FAssetFixUpRedirectors::DetectReadOnlyPackages(TArray<FRedirectorRefs>& RedirectorsToFix, TArray<UPackage*>& InOutReferencingPackagesToSave) const { // For each valid package... for ( int32 PackageIdx = InOutReferencingPackagesToSave.Num() - 1; PackageIdx >= 0; --PackageIdx ) { UPackage* Package = InOutReferencingPackagesToSave[PackageIdx]; if ( Package ) { // Find the package filename FString Filename; if ( FPackageName::DoesPackageExist(Package->GetName(), NULL, &Filename) ) { // If the file is read only if ( IFileManager::Get().IsReadOnly(*Filename) ) { FName PackageName = Package->GetFName(); // Find all assets that were referenced by this package to create a redirector when named for ( auto RedirectorIt = RedirectorsToFix.CreateIterator(); RedirectorIt; ++RedirectorIt ) { FRedirectorRefs& RedirectorRefs = *RedirectorIt; if ( RedirectorRefs.ReferencingPackageNames.Contains(PackageName) ) { RedirectorRefs.FailureReason = FText::Format(LOCTEXT("RedirectorFixupFailed_ReadOnly", "Referencing package {0} was read-only"), FText::FromName(PackageName)); RedirectorRefs.bRedirectorValidForFixup = false; } } // Remove the package from the save list InOutReferencingPackagesToSave.RemoveAt(PackageIdx); } } } } }
int32 UGenerateDistillFileSetsCommandlet::Main( const FString& InParams ) { // Parse command line. TArray<FString> Tokens; TArray<FString> Switches; UCommandlet::ParseCommandLine(*InParams, Tokens, Switches); TArray<FString> MapList; for ( int32 MapIdx = 0; MapIdx < Tokens.Num(); ++MapIdx ) { const FString& Map = Tokens[MapIdx]; if ( FPackageName::IsShortPackageName(Map) ) { FString LongPackageName; if ( FPackageName::SearchForPackageOnDisk(Map, &LongPackageName) ) { MapList.Add(LongPackageName); } else { UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Unable to find package for map %s."), *Map); return 1; } } else { MapList.Add(Map); } } if ( MapList.Num() <= 0 ) { // No map tokens were supplied on the command line, so assume all maps TArray<FString> AllPackageFilenames; FEditorFileUtils::FindAllPackageFiles(AllPackageFilenames); for (int32 PackageIndex = 0; PackageIndex < AllPackageFilenames.Num(); PackageIndex++) { const FString& Filename = AllPackageFilenames[PackageIndex]; if (FPaths::GetExtension(Filename, true) == FPackageName::GetMapPackageExtension() ) { FString LongPackageName; if ( FPackageName::TryConvertFilenameToLongPackageName(Filename, LongPackageName) ) { // Warn about maps in "NoShip" or "TestMaps" folders. Those should have been filtered out during the Distill process! if( !Filename.Contains( "/NoShip/") && !Filename.Contains( "/TestMaps/")) { // @todo plugins add support for plugins? if ( LongPackageName.StartsWith(TEXT("/Game")) ) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Discovered map package %s..." ), *LongPackageName ); MapList.Add(LongPackageName); } } else { UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Skipping map package %s in TestMaps or NoShip folder"), *Filename); } } else { UE_LOG(LogGenerateDistillFileSetsCommandlet, Warning, TEXT("Failed to determine package name for map file %s."), *Filename); } } } } const FString TemplateFileSwitch = TEXT("Template="); const FString OutputFileSwitch = TEXT("Output="); const FString TemplateFolderSwitch = TEXT("TemplateFolder="); const FString OutputFolderSwitch = TEXT("OutputFolder="); FString TemplateFilename; FString OutputFilename; FString TemplateFolder; FString OutputFolder; for (int32 SwitchIdx = 0; SwitchIdx < Switches.Num(); ++SwitchIdx) { const FString& Switch = Switches[SwitchIdx]; if ( Switch.StartsWith(TemplateFileSwitch) ) { Switch.Split(TEXT("="), NULL, &TemplateFilename); TemplateFilename = TemplateFilename.TrimQuotes(); } else if ( Switch.StartsWith(OutputFileSwitch) ) { Switch.Split(TEXT("="), NULL, &OutputFilename); OutputFilename = OutputFilename.TrimQuotes(); } else if ( Switch.StartsWith(TemplateFolderSwitch) ) { Switch.Split(TEXT("="), NULL, &TemplateFolder); TemplateFolder = TemplateFolder.TrimQuotes(); FPaths::NormalizeFilename(TemplateFolder); if ( !TemplateFolder.EndsWith(TEXT("/")) ) { TemplateFolder += TEXT("/"); } UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Using template folder: "), *TemplateFolder); } else if ( Switch.StartsWith(OutputFolderSwitch) ) { Switch.Split(TEXT("="), NULL, &OutputFolder); OutputFolder = OutputFolder.TrimQuotes(); FPaths::NormalizeFilename(OutputFolder); if ( !OutputFolder.EndsWith(TEXT("/")) ) { OutputFolder += TEXT("/"); } UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Using output folder: "), *OutputFolder); } } if ( OutputFilename.IsEmpty() ) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("You must supply -Output=OutputFilename. These files are relative to the Game/Build directory.")); return 1; } if (OutputFolder.IsEmpty()) { OutputFolder = FPaths::GameDir() + TEXT("Build/"); } OutputFilename = OutputFolder + OutputFilename; bool bSimpleTxtOutput = false; // Load the template file FString TemplateFileContents; if (TemplateFilename.IsEmpty()) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Log, TEXT("No template specified, assuming a simple txt output.")); bSimpleTxtOutput = true; } // If no folder was specified, filenames are relative to the build dir. else { if (TemplateFolder.IsEmpty()) { TemplateFolder = FPaths::GameDir() + TEXT("Build/"); } TemplateFilename = TemplateFolder + TemplateFilename; if (!FFileHelper::LoadFileToString(TemplateFileContents, *TemplateFilename)) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Failed to load template file '%s'"), *TemplateFilename); return 1; } } // Form a full unique package list TSet<FString> AllPackageNames; //@todo SLATE: This is a hack to ensure all slate referenced assets get cooked. // Slate needs to be refactored to properly identify required assets at cook time. // Simply jamming everything in a given directory into the cook list is error-prone // on many levels - assets not required getting cooked/shipped; assets not put under // the correct folder; etc. { TArray<FString> UIContentPaths; if (GConfig->GetArray(TEXT("UI"), TEXT("ContentDirectories"), UIContentPaths, GEditorIni) > 0) { for (int32 DirIdx = 0; DirIdx < UIContentPaths.Num(); DirIdx++) { FString ContentPath = FPackageName::LongPackageNameToFilename(UIContentPaths[DirIdx]); TArray<FString> Files; IFileManager::Get().FindFilesRecursive(Files, *ContentPath, *(FString(TEXT("*")) + FPackageName::GetAssetPackageExtension()), true, false); for (int32 Index = 0; Index < Files.Num(); Index++) { FString StdFile = Files[Index]; FPaths::MakeStandardFilename(StdFile); StdFile = FPackageName::FilenameToLongPackageName(StdFile); AllPackageNames.Add(StdFile); } } } } // Load all maps { for ( auto MapIt = MapList.CreateConstIterator(); MapIt; ++MapIt ) { const FString& MapPackage = *MapIt; UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Loading %s..." ), *MapPackage ); UPackage* Package = LoadPackage( NULL, *MapPackage, LOAD_None ); if( Package != NULL ) { AllPackageNames.Add(Package->GetName()); UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Finding content referenced by %s..." ), *MapPackage ); TArray<UObject *> ObjectsInOuter; GetObjectsWithOuter(NULL, ObjectsInOuter, false); for (int32 Index = 0; Index < ObjectsInOuter.Num(); Index++) { FString OtherName = ObjectsInOuter[Index]->GetOutermost()->GetName(); if (!AllPackageNames.Contains(OtherName)) { AllPackageNames.Add(OtherName); UE_LOG(LogGenerateDistillFileSetsCommandlet, Log, TEXT("Package: %s"), *OtherName); } } UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Collecting garbage..." ) ); CollectGarbage(RF_NoFlags); } } } // Sort the results to make it easier to diff files. No necessary but useful sometimes. TArray<FString> SortedPackageNames = AllPackageNames.Array(); SortedPackageNames.Sort(); // For the list of FileSets to include in the distill FString AllFileSets; const FString FileSetPathRoot = TEXT("Content"); for (auto PackageIt = SortedPackageNames.CreateConstIterator(); PackageIt; ++PackageIt) { const FString& PackageName = *PackageIt; // @todo plugins add support for plugins? if ( PackageName.StartsWith(TEXT("/Game")) ) { const FString PathWithoutRoot( PackageName.Mid( 5 ) ); const FString FileSetPath = FileSetPathRoot + PathWithoutRoot; if (bSimpleTxtOutput) { FString ActualFile; if (FPackageName::DoesPackageExist(PackageName, NULL, &ActualFile)) { ActualFile = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ActualFile); AllFileSets += FString::Printf(TEXT("%s") LINE_TERMINATOR, *ActualFile); UE_LOG(LogGenerateDistillFileSetsCommandlet, Log, TEXT("File: %s"), *ActualFile); } } else { AllFileSets += FString::Printf(TEXT("<FileSet Path=\"%s.*\" bIsRecursive=\"false\"/>") LINE_TERMINATOR, *FileSetPath); } } } // Write the output file FString OutputFileContents; if (bSimpleTxtOutput) { OutputFileContents = AllFileSets; } else { OutputFileContents = TemplateFileContents.Replace(TEXT("%INSTALLEDCONTENTFILESETS%"), *AllFileSets, ESearchCase::CaseSensitive); if (FApp::HasGameName()) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Replacing %%GAMENAME%% with (%s)..."), FApp::GetGameName()); OutputFileContents = OutputFileContents.Replace(TEXT("%GAMENAME%"), FApp::GetGameName(), ESearchCase::CaseSensitive); } else { UE_LOG(LogGenerateDistillFileSetsCommandlet, Warning, TEXT("Failed to replace %%GAMENAME%% since we are running without a game name.")); } } if ( !FFileHelper::SaveStringToFile(OutputFileContents, *OutputFilename) ) { UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Failed to save output file '%s'"), *OutputFilename); return 1; } return 0; }
void UUnrealEdEngine::OnPackageDirtyStateUpdated( UPackage* Pkg) { // The passed in object should never be NULL check(Pkg); UPackage* Package = Pkg->GetOutermost(); const FString PackageName = Package->GetName(); // Alert the user if they have modified a package that won't be able to be saved because // it's already been saved with an engine version that is newer than the current one. if (!FUObjectThreadContext::Get().IsRoutingPostLoad && Package->IsDirty() && !PackagesCheckedForEngineVersion.Contains(PackageName)) { EWriteDisallowedWarningState WarningStateToSet = WDWS_WarningUnnecessary; FString PackageFileName; if ( FPackageName::DoesPackageExist( Package->GetName(), NULL, &PackageFileName ) ) { // If a package has never been loaded, a file reader is necessary to find the package file summary for its saved engine version. FArchive* PackageReader = IFileManager::Get().CreateFileReader( *PackageFileName ); if ( PackageReader ) { FPackageFileSummary Summary; *PackageReader << Summary; if ( Summary.GetFileVersionUE4() > GPackageFileUE4Version || !GEngineVersion.IsCompatibleWith(Summary.CompatibleWithEngineVersion) ) { WarningStateToSet = WDWS_PendingWarn; bNeedWarningForPkgEngineVer = true; } } delete PackageReader; } PackagesCheckedForEngineVersion.Add( PackageName, WarningStateToSet ); } // Alert the user if they have modified a package that they do not have sufficient permission to write to disk. // This can be due to the content being in the "Program Files" folder and the user does not have admin privileges. if (!FUObjectThreadContext::Get().IsRoutingPostLoad && Package->IsDirty() && !PackagesCheckedForWritePermission.Contains(PackageName)) { EWriteDisallowedWarningState WarningStateToSet = GetWarningStateForWritePermission(PackageName); if ( WarningStateToSet == WDWS_PendingWarn ) { bNeedWarningForWritePermission = true; } PackagesCheckedForWritePermission.Add( PackageName, WarningStateToSet ); } if( Package->IsDirty() ) { // Find out if we have already asked the user to modify this package const uint8* PromptState = PackageToNotifyState.Find( Package ); const bool bAlreadyAsked = PromptState != NULL; // During an autosave, packages are saved in the autosave directory which switches off their dirty flags. // To preserve the pre-autosave state, any saved package is then remarked as dirty because it wasn't saved in the normal location where it would be picked up by source control. // Any callback that happens during an autosave is bogus since a package wasn't marked dirty due to a user modification. const bool bIsAutoSaving = PackageAutoSaver.Get() && PackageAutoSaver->IsAutoSaving(); const UEditorLoadingSavingSettings* Settings = GetDefault<UEditorLoadingSavingSettings>(); if( !bIsAutoSaving && !GIsEditorLoadingPackage && // Don't ask if the package was modified as a result of a load !bAlreadyAsked && // Don't ask if we already asked once! (Settings->bPromptForCheckoutOnAssetModification || Settings->bAutomaticallyCheckoutOnAssetModification) ) { // Force source control state to be updated ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); TArray<FString> Files; Files.Add(SourceControlHelpers::PackageFilename(Package)); SourceControlProvider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), SourceControlHelpers::AbsoluteFilenames(Files), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateUObject(this, &UUnrealEdEngine::OnSourceControlStateUpdated, TWeakObjectPtr<UPackage>(Package))); } } else { // This package was saved, the user should be prompted again if they checked in the package PackageToNotifyState.Remove( Package ); } }
void FLevelCollectionModel::SCCDiffAgainstDepot(const FLevelModelList& InList, UEditorEngine* InEditor) { // Load the asset registry module FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools"); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); // Iterate over each selected asset for (auto It = InList.CreateConstIterator(); It; ++It) { ULevel* Level = (*It)->GetLevelObject(); if (Level == NULL) { return; } UPackage* OriginalPackage = Level->GetOutermost(); FString PackageName = OriginalPackage->GetName(); // Make sure our history is up to date auto UpdateStatusOperation = ISourceControlOperation::Create<FUpdateStatus>(); UpdateStatusOperation->SetUpdateHistory(true); SourceControlProvider.Execute(UpdateStatusOperation, OriginalPackage); // Get the SCC state FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState( OriginalPackage, EStateCacheUsage::Use ); // If the level is in SCC. if (SourceControlState.IsValid() && SourceControlState->IsSourceControlled()) { // Get the file name of package FString RelativeFileName; if(FPackageName::DoesPackageExist(PackageName, NULL, &RelativeFileName)) { if (SourceControlState->GetHistorySize() > 0) { auto Revision = SourceControlState->GetHistoryItem(0); check(Revision.IsValid()); // Get the head revision of this package from source control FString AbsoluteFileName = FPaths::ConvertRelativePathToFull(RelativeFileName); FString TempFileName; if (Revision->Get(TempFileName)) { // Forcibly disable compile on load in case we are loading old blueprints that might try to update/compile TGuardValue<bool> DisableCompileOnLoad(GForceDisableBlueprintCompileOnLoad, true); // Try and load that package FText NotMapReason; UPackage* OldPackage = LoadPackage(NULL, *TempFileName, LOAD_None); if(OldPackage != NULL && InEditor->PackageIsAMapFile(*TempFileName, NotMapReason)) { /* Set the revision information*/ UPackage* Package = OriginalPackage; FRevisionInfo OldRevision; OldRevision.Changelist = Revision->GetCheckInIdentifier(); OldRevision.Date = Revision->GetDate(); OldRevision.Revision = Revision->GetRevision(); FRevisionInfo NewRevision; NewRevision.Revision = TEXT(""); // Dump assets to temp text files FString OldTextFilename = AssetToolsModule.Get().DumpAssetToTempFile(OldPackage); FString NewTextFilename = AssetToolsModule.Get().DumpAssetToTempFile(OriginalPackage); FString DiffCommand = GetDefault<UEditorLoadingSavingSettings>()->TextDiffToolPath.FilePath; AssetToolsModule.Get().CreateDiffProcess(DiffCommand, OldTextFilename, NewTextFilename); AssetToolsModule.Get().DiffAssets(OldPackage, OriginalPackage, OldRevision, NewRevision); } } } } } } }
/** * Helper function designed to determine if the provided package should be backed up or not. * The function checks for many conditions, such as if the package is too large to backup, * if the package has a particular attribute that should prevent it from being backed up (such * as being marked for PIE-use), if cooking is in progress, etc. * * @param InPackage Package which should be checked to see if its valid for backing-up * @param OutFileName File name of the package on disk if the function determines the package * already existed * * @return true if the package is valid for backing-up; false otherwise */ bool FAutoPackageBackup::ShouldBackupPackage( const UPackage& InPackage, FString& OutFilename ) { // Check various conditions to see if the package is a valid candidate for backing up bool bShouldBackup = GIsEditor // Backing up packages only makes sense in the editor && !IsRunningCommandlet() // Don't backup saves resulting from commandlets && IsPackageBackupEnabled() // Ensure that the package backup is enabled in the first place && (InPackage.HasAnyPackageFlags(PKG_PlayInEditor) == false) // Don't back up PIE packages && (InPackage.HasAnyPackageFlags(PKG_ContainsScript) == false); // Don't back up script packages if( bShouldBackup ) { GWarn->StatusUpdate( -1, -1, NSLOCTEXT("UnrealEd", "PackageBackup_ValidityWarning", "Determining asset backup validity...") ); bShouldBackup = FPackageName::DoesPackageExist( InPackage.GetName(), NULL, &OutFilename ); // Make sure the file already exists (no sense in backing up a new package) } // If the package passed the initial backup checks, proceed to check more specific conditions // that might disqualify the package from being backed up const int32 FileSizeOfBackup = IFileManager::Get().FileSize( *OutFilename ); if ( bShouldBackup ) { // Ensure that the size the backup would require is less than that of the maximum allowed // space for backups bShouldBackup = FileSizeOfBackup <= GetMaxAllowedBackupSpace(); } // If all of the prior checks have passed, now see if the package has been backed up // too recently to be considered for an additional backup if ( bShouldBackup ) { // Ensure that the autosave/backup directory exists const FString& BackupSaveDir = GetBackupDirectory(); IFileManager::Get().MakeDirectory( *BackupSaveDir, 1 ); // Find all of the files in the backup directory TArray<FString> FilesInBackupDir; IFileManager::Get().FindFilesRecursive(FilesInBackupDir, *BackupSaveDir, TEXT("*.*"), true, false); // Extract the base file name and extension from the passed-in package file name FString ExistingBaseFileName = FPaths::GetBaseFilename(OutFilename); FString ExistingFileNameExtension = FPaths::GetExtension(OutFilename); bool bFoundExistingBackup = false; int32 DirectorySize = 0; FDateTime LastBackupTimeStamp = FDateTime::MinValue(); TArray<FBackupFileInfo> BackupFileInfoArray; // Check every file in the backup directory for matches against the passed-in package // (Additionally keep statistics on all backup files for potential maintenance) for ( TArray<FString>::TConstIterator FileIter( FilesInBackupDir ); FileIter; ++FileIter ) { const FString CurBackupFileName = FString( *FileIter ); // Create a new backup file info struct for keeping information about each backup file const int32 FileInfoIndex = BackupFileInfoArray.AddZeroed(); FBackupFileInfo& CurBackupFileInfo = BackupFileInfoArray[ FileInfoIndex ]; // Record the backup file's name, size, and timestamp CurBackupFileInfo.FileName = CurBackupFileName; CurBackupFileInfo.FileSize = IFileManager::Get().FileSize( *CurBackupFileName ); // If we failed to get a timestamp or a valid size, something has happened to the file and it shouldn't be considered CurBackupFileInfo.FileTimeStamp = IFileManager::Get().GetTimeStamp(*CurBackupFileName); if (CurBackupFileInfo.FileTimeStamp == FDateTime::MinValue() || CurBackupFileInfo.FileSize == -1) { BackupFileInfoArray.RemoveAt( BackupFileInfoArray.Num() - 1 ); continue; } // Calculate total directory size by adding the size of this backup file DirectorySize += CurBackupFileInfo.FileSize; FString CurBackupBaseFileName = FPaths::GetBaseFilename(CurBackupFileName); FString CurBackupFileNameExtension = FPaths::GetExtension(CurBackupFileName); // The base file name of the backup file is going to include an underscore followed by a timestamp, so they must be removed for comparison's sake CurBackupBaseFileName = CurBackupBaseFileName.Left( CurBackupBaseFileName.Find( TEXT("_"), ESearchCase::CaseSensitive, ESearchDir::FromEnd ) ); // If the base file names and extensions match, we've found a backup if ( CurBackupBaseFileName == ExistingBaseFileName && CurBackupFileNameExtension == ExistingFileNameExtension ) { bFoundExistingBackup = true; // Keep track of the most recent matching time stamp so we can check if the passed-in package // has been backed up too recently if ( CurBackupFileInfo.FileTimeStamp > LastBackupTimeStamp ) { LastBackupTimeStamp = CurBackupFileInfo.FileTimeStamp; } } } // If there was an existing backup, check to see if it was created too recently to allow another backup if ( bFoundExistingBackup ) { // Check the difference in timestamp seconds against the backup interval; if not enough time has elapsed since // the last backup, we don't want to make another one if ((FDateTime::UtcNow() - LastBackupTimeStamp).GetTotalSeconds() < GetBackupInterval()) { bShouldBackup = false; } } // If every other check against the package has succeeded for backup purposes, ensure there is enough directory space // available in the backup directory, as adding the new backup might use more space than the user allowed for backups. // If the backup file size + the current directory size exceeds the max allowed space, deleted old backups until there // is sufficient space. If enough space can't be freed for whatever reason, then no back-up will be created. if ( bShouldBackup && ( FileSizeOfBackup + DirectorySize > GetMaxAllowedBackupSpace() ) ) { bShouldBackup = PerformBackupSpaceMaintenance( BackupFileInfoArray, DirectorySize, FileSizeOfBackup ); } } return bShouldBackup; }
int32 UDerivedDataCacheCommandlet::Main( const FString& Params ) { TArray<FString> Tokens, Switches; ParseCommandLine(*Params, Tokens, Switches); bool bFillCache = Switches.Contains("FILL"); // do the equivalent of a "loadpackage -all" to fill the DDC bool bStartupOnly = Switches.Contains("STARTUPONLY"); // regardless of any other flags, do not iterate packages // Subsets for parallel processing uint32 SubsetMod = 0; uint32 SubsetTarget = MAX_uint32; FParse::Value(*Params, TEXT("SubsetMod="), SubsetMod); FParse::Value(*Params, TEXT("SubsetTarget="), SubsetTarget); bool bDoSubset = SubsetMod > 0 && SubsetTarget < SubsetMod; double FindProcessedPackagesTime = 0.0; double GCTime = 0.0; if (!bStartupOnly && bFillCache) { FCoreDelegates::PackageCreatedForLoad.AddUObject(this, &UDerivedDataCacheCommandlet::MaybeMarkPackageAsAlreadyLoaded); TArray<FString> FilesInPath; Tokens.Empty(2); Tokens.Add(FString("*") + FPackageName::GetAssetPackageExtension()); Tokens.Add(FString("*") + FPackageName::GetMapPackageExtension()); uint8 PackageFilter = NORMALIZE_DefaultFlags; if ( Switches.Contains(TEXT("MAPSONLY")) ) { PackageFilter |= NORMALIZE_ExcludeContentPackages; } if ( !Switches.Contains(TEXT("DEV")) ) { PackageFilter |= NORMALIZE_ExcludeDeveloperPackages; } // assume the first token is the map wildcard/pathname TArray<FString> Unused; for ( int32 TokenIndex = 0; TokenIndex < Tokens.Num(); TokenIndex++ ) { TArray<FString> TokenFiles; if ( !NormalizePackageNames( Unused, TokenFiles, Tokens[TokenIndex], PackageFilter) ) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("No packages found for parameter %i: '%s'"), TokenIndex, *Tokens[TokenIndex]); continue; } FilesInPath += TokenFiles; } if ( FilesInPath.Num() == 0 ) { UE_LOG(LogDerivedDataCacheCommandlet, Warning, TEXT("No files found.")); } ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); const TArray<ITargetPlatform*>& Platforms = TPM->GetActiveTargetPlatforms(); for (int32 Index = 0; Index < Platforms.Num(); Index++) { TArray<FName> DesiredShaderFormats; Platforms[Index]->GetShaderFormats(DesiredShaderFormats); for (int32 FormatIndex = 0; FormatIndex < DesiredShaderFormats.Num(); FormatIndex++) { const EShaderPlatform TargetPlatform = ShaderFormatToLegacyShaderPlatform(DesiredShaderFormats[FormatIndex]); // Kick off global shader compiles for each target platform GetGlobalShaderMap(TargetPlatform); } } const int32 GCInterval = 100; int32 NumProcessedSinceLastGC = GCInterval; bool bLastPackageWasMap = true; // 'true' is to prime the ProcessedPackages list TSet<FString> ProcessedPackages; UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("%d packages to load..."), FilesInPath.Num()); for( int32 FileIndex = FilesInPath.Num() - 1; ; FileIndex-- ) { // Keep track of which packages have already been processed along with the map. if (NumProcessedSinceLastGC >= GCInterval || bLastPackageWasMap || FileIndex == FilesInPath.Num() - 1) { const double FindProcessedPackagesStartTime = FPlatformTime::Seconds(); TArray<UObject *> ObjectsInOuter; GetObjectsWithOuter(NULL, ObjectsInOuter, false); for( int32 Index = 0; Index < ObjectsInOuter.Num(); Index++ ) { UPackage* Pkg = Cast<UPackage>(ObjectsInOuter[Index]); if (!Pkg) { continue; } FString Filename; if (FPackageName::DoesPackageExist(Pkg->GetName(), NULL, &Filename)) { if (!ProcessedPackages.Contains(Filename)) { ProcessedPackages.Add(Filename); PackagesToNotReload.Add(Pkg->GetName()); Pkg->PackageFlags |= PKG_ReloadingForCooker; { TArray<UObject *> ObjectsInPackage; GetObjectsWithOuter(Pkg, ObjectsInPackage, true); for( int32 IndexPackage = 0; IndexPackage < ObjectsInPackage.Num(); IndexPackage++ ) { ObjectsInPackage[IndexPackage]->CookerWillNeverCookAgain(); } } } } } FindProcessedPackagesTime += FPlatformTime::Seconds() - FindProcessedPackagesStartTime; } if (NumProcessedSinceLastGC >= GCInterval || FileIndex < 0 || bLastPackageWasMap) { const double StartGCTime = FPlatformTime::Seconds(); if (NumProcessedSinceLastGC >= GCInterval || FileIndex < 0) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("GC (Full)...")); CollectGarbage( RF_Native ); NumProcessedSinceLastGC = 0; } else { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("GC...")); CollectGarbage( RF_Native | RF_Standalone ); } GCTime += FPlatformTime::Seconds() - StartGCTime; bLastPackageWasMap = false; } if (FileIndex < 0) { break; } const FString& Filename = FilesInPath[FileIndex]; if (ProcessedPackages.Contains(Filename)) { continue; } if (bDoSubset) { const FString& PackageName = FPackageName::PackageFromPath(*Filename); if (FCrc::StrCrc_DEPRECATED(*PackageName.ToUpper()) % SubsetMod != SubsetTarget) { continue; } } UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Loading (%d) %s"), FilesInPath.Num() - FileIndex, *Filename ); UPackage* Package = LoadPackage( NULL, *Filename, LOAD_None ); if( Package == NULL ) { UE_LOG(LogDerivedDataCacheCommandlet, Error, TEXT("Error loading %s!"), *Filename ); } else { bLastPackageWasMap = Package->ContainsMap(); NumProcessedSinceLastGC++; } } } IConsoleManager::Get().ProcessUserConsoleInput(TEXT("Tex.DerivedDataTimings"), *GWarn, NULL); UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Waiting for shaders to finish.")); GShaderCompilingManager->FinishAllCompilation(); UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Done waiting for shaders to finish.")); GetDerivedDataCacheRef().WaitForQuiescence(true); UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("%.2lfs spent looking for processed packages, %.2lfs spent on GC."), FindProcessedPackagesTime, GCTime); return 0; }
bool FEditorBuildUtils::EditorAutomatedBuildAndSubmit( const FEditorAutomatedBuildSettings& BuildSettings, FText& OutErrorMessages ) { // Assume the build is successful to start bool bBuildSuccessful = true; // Keep a set of packages that should be submitted to source control at the end of a successful build. The build preparation and processing // will add and remove from the set depending on build settings, errors, etc. TSet<UPackage*> PackagesToSubmit; // Perform required preparations for the automated build process bBuildSuccessful = PrepForAutomatedBuild( BuildSettings, PackagesToSubmit, OutErrorMessages ); // If the preparation went smoothly, attempt the actual map building process if ( bBuildSuccessful ) { bBuildSuccessful = EditorBuild( GWorld, EBuildOptions::BuildAllSubmit ); // If the map build failed, log the error if ( !bBuildSuccessful ) { LogErrorMessage( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_BuildFailed", "The map build failed or was canceled."), OutErrorMessages ); } } // If any map errors resulted from the build, process them according to the behavior specified in the build settings if ( bBuildSuccessful && FMessageLog("MapCheck").NumMessages( EMessageSeverity::Warning ) > 0 ) { bBuildSuccessful = ProcessAutomatedBuildBehavior( BuildSettings.BuildErrorBehavior, NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_MapErrors", "Map errors occurred while building.\n\nAttempt to continue the build?"), OutErrorMessages ); } // If it's still safe to proceed, attempt to save all of the level packages that have been marked for submission if ( bBuildSuccessful ) { UPackage* CurOutermostPkg = GWorld->PersistentLevel->GetOutermost(); FString PackagesThatFailedToSave; // Try to save the p-level if it should be submitted if ( PackagesToSubmit.Contains( CurOutermostPkg ) && !FEditorFileUtils::SaveLevel( GWorld->PersistentLevel ) ) { // If the p-level failed to save, remove it from the set of packages to submit PackagesThatFailedToSave += FString::Printf( TEXT("%s\n"), *CurOutermostPkg->GetName() ); PackagesToSubmit.Remove( CurOutermostPkg ); } // Try to save each streaming level (if they should be submitted) for ( TArray<ULevelStreaming*>::TIterator LevelIter( GWorld->StreamingLevels ); LevelIter; ++LevelIter ) { ULevelStreaming* CurStreamingLevel = *LevelIter; if ( CurStreamingLevel != NULL ) { ULevel* Level = CurStreamingLevel->GetLoadedLevel(); if ( Level != NULL ) { CurOutermostPkg = Level->GetOutermost(); if ( PackagesToSubmit.Contains( CurOutermostPkg ) && !FEditorFileUtils::SaveLevel( Level ) ) { // If a save failed, remove the streaming level from the set of packages to submit PackagesThatFailedToSave += FString::Printf( TEXT("%s\n"), *CurOutermostPkg->GetName() ); PackagesToSubmit.Remove( CurOutermostPkg ); } } } } // If any packages failed to save, process the behavior specified by the build settings to see how the process should proceed if ( PackagesThatFailedToSave.Len() > 0 ) { bBuildSuccessful = ProcessAutomatedBuildBehavior( BuildSettings.FailedToSaveBehavior, FText::Format( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_FilesFailedSave", "The following assets failed to save and cannot be submitted:\n\n{0}\n\nAttempt to continue the build?"), FText::FromString(PackagesThatFailedToSave) ), OutErrorMessages ); } } // If still safe to proceed, make sure there are actually packages remaining to submit if ( bBuildSuccessful ) { bBuildSuccessful = PackagesToSubmit.Num() > 0; if ( !bBuildSuccessful ) { LogErrorMessage( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_NoValidLevels", "None of the current levels are valid for submission; automated build aborted."), OutErrorMessages ); } } // Finally, if everything has gone smoothly, submit the requested packages to source control if ( bBuildSuccessful ) { SubmitPackagesForAutomatedBuild( PackagesToSubmit, BuildSettings ); } // Check if the user requested the editor shutdown at the conclusion of the automated build if ( BuildSettings.bShutdownEditorOnCompletion ) { FPlatformMisc::RequestExit( false ); } return bBuildSuccessful; }
/** * Helper method designed to perform the necessary preparations required to complete an automated editor build * * @param BuildSettings Build settings that will be used for the editor build * @param OutPkgsToSubmit Set of packages that need to be saved and submitted after a successful build * @param OutErrorMessages Errors that resulted from the preparation (may or may not force the build to stop, depending on build settings) * * @return true if the preparation was successful and the build should continue; false if the preparation failed and the build should be aborted */ bool FEditorBuildUtils::PrepForAutomatedBuild( const FEditorAutomatedBuildSettings& BuildSettings, TSet<UPackage*>& OutPkgsToSubmit, FText& OutErrorMessages ) { // Assume the preparation is successful to start bool bBuildSuccessful = true; OutPkgsToSubmit.Empty(); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); // Source control is required for the automated build, so ensure that SCC support is compiled in and // that the server is enabled and available for use if ( !ISourceControlModule::Get().IsEnabled() || !SourceControlProvider.IsAvailable() ) { bBuildSuccessful = false; LogErrorMessage( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_SCCError", "Cannot connect to source control; automated build aborted."), OutErrorMessages ); } // Empty changelists aren't allowed; abort the build if one wasn't provided if ( bBuildSuccessful && BuildSettings.ChangeDescription.Len() == 0 ) { bBuildSuccessful = false; LogErrorMessage( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_NoCLDesc", "A changelist description must be provided; automated build aborted."), OutErrorMessages ); } TArray<UPackage*> PreviouslySavedWorldPackages; TArray<UPackage*> PackagesToCheckout; TArray<ULevel*> LevelsToSave; if ( bBuildSuccessful ) { TArray<UWorld*> AllWorlds; FString UnsavedWorlds; EditorLevelUtils::GetWorlds( GWorld, AllWorlds, true ); // Check all of the worlds that will be built to ensure they have been saved before and have a filename // associated with them. If they don't, they won't be able to be submitted to source control. FString CurWorldPkgFileName; for ( TArray<UWorld*>::TConstIterator WorldIter( AllWorlds ); WorldIter; ++WorldIter ) { const UWorld* CurWorld = *WorldIter; check( CurWorld ); UPackage* CurWorldPackage = CurWorld->GetOutermost(); check( CurWorldPackage ); if ( FPackageName::DoesPackageExist( CurWorldPackage->GetName(), NULL, &CurWorldPkgFileName ) ) { PreviouslySavedWorldPackages.AddUnique( CurWorldPackage ); // Add all packages which have a corresponding file to the set of packages to submit for now. As preparation continues // any packages that can't be submitted due to some error will be removed. OutPkgsToSubmit.Add( CurWorldPackage ); } else { UnsavedWorlds += FString::Printf( TEXT("%s\n"), *CurWorldPackage->GetName() ); } } // If any of the worlds haven't been saved before, process the build setting's behavior to see if the build // should proceed or not if ( UnsavedWorlds.Len() > 0 ) { bBuildSuccessful = ProcessAutomatedBuildBehavior( BuildSettings.NewMapBehavior, FText::Format( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_UnsavedMap", "The following levels have never been saved before and cannot be submitted:\n\n{0}\n\nAttempt to continue the build?"), FText::FromString(UnsavedWorlds) ), OutErrorMessages ); } } // Load the asset tools module FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools"); if ( bBuildSuccessful ) { // Update the source control status of any relevant world packages in order to determine which need to be // checked out, added to the depot, etc. SourceControlProvider.Execute( ISourceControlOperation::Create<FUpdateStatus>(), SourceControlHelpers::PackageFilenames(PreviouslySavedWorldPackages) ); FString PkgsThatCantBeCheckedOut; for ( TArray<UPackage*>::TConstIterator PkgIter( PreviouslySavedWorldPackages ); PkgIter; ++PkgIter ) { UPackage* CurPackage = *PkgIter; const FString CurPkgName = CurPackage->GetName(); FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(CurPackage, EStateCacheUsage::ForceUpdate); if( !SourceControlState.IsValid() || (!SourceControlState->IsSourceControlled() && !SourceControlState->IsUnknown() && !SourceControlState->IsIgnored())) { FString CurFilename; if ( FPackageName::DoesPackageExist( CurPkgName, NULL, &CurFilename ) ) { if ( IFileManager::Get().IsReadOnly( *CurFilename ) ) { PkgsThatCantBeCheckedOut += FString::Printf( TEXT("%s\n"), *CurPkgName ); OutPkgsToSubmit.Remove( CurPackage ); } } } else if(SourceControlState->CanCheckout()) { PackagesToCheckout.Add( CurPackage ); } else { PkgsThatCantBeCheckedOut += FString::Printf( TEXT("%s\n"), *CurPkgName ); OutPkgsToSubmit.Remove( CurPackage ); } } // If any of the packages can't be checked out or are read-only, process the build setting's behavior to see if the build // should proceed or not if ( PkgsThatCantBeCheckedOut.Len() > 0 ) { bBuildSuccessful = ProcessAutomatedBuildBehavior( BuildSettings.UnableToCheckoutFilesBehavior, FText::Format( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_UnsaveableFiles", "The following assets cannot be checked out of source control (or are read-only) and cannot be submitted:\n\n{0}\n\nAttempt to continue the build?"), FText::FromString(PkgsThatCantBeCheckedOut) ), OutErrorMessages ); } } if ( bBuildSuccessful ) { // Check out all of the packages from source control that need to be checked out if ( PackagesToCheckout.Num() > 0 ) { TArray<FString> PackageFilenames = SourceControlHelpers::PackageFilenames(PackagesToCheckout); SourceControlProvider.Execute( ISourceControlOperation::Create<FCheckOut>(), PackageFilenames ); // Update the package status of the packages that were just checked out to confirm that they // were actually checked out correctly SourceControlProvider.Execute( ISourceControlOperation::Create<FUpdateStatus>(), PackageFilenames ); FString FilesThatFailedCheckout; for ( TArray<UPackage*>::TConstIterator CheckedOutIter( PackagesToCheckout ); CheckedOutIter; ++CheckedOutIter ) { UPackage* CurPkg = *CheckedOutIter; FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(CurPkg, EStateCacheUsage::ForceUpdate); // If any of the packages failed to check out, remove them from the set of packages to submit if ( !SourceControlState.IsValid() || (!SourceControlState->IsCheckedOut() && !SourceControlState->IsAdded() && SourceControlState->IsSourceControlled()) ) { FilesThatFailedCheckout += FString::Printf( TEXT("%s\n"), *CurPkg->GetName() ); OutPkgsToSubmit.Remove( CurPkg ); } } // If any of the packages failed to check out correctly, process the build setting's behavior to see if the build // should proceed or not if ( FilesThatFailedCheckout.Len() > 0 ) { bBuildSuccessful = ProcessAutomatedBuildBehavior( BuildSettings.UnableToCheckoutFilesBehavior, FText::Format( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_FilesFailedCheckout", "The following assets failed to checkout of source control and cannot be submitted:\n{0}\n\nAttempt to continue the build?"), FText::FromString(FilesThatFailedCheckout)), OutErrorMessages ); } } } // Verify there are still actually any packages left to submit. If there aren't, abort the build and warn the user of the situation. if ( bBuildSuccessful ) { bBuildSuccessful = OutPkgsToSubmit.Num() > 0; if ( !bBuildSuccessful ) { LogErrorMessage( NSLOCTEXT("UnrealEd", "AutomatedBuild_Error_NoValidLevels", "None of the current levels are valid for submission; automated build aborted."), OutErrorMessages ); } } // If the build is safe to commence, force all of the levels visible to make sure the build operates correctly if ( bBuildSuccessful ) { bool bVisibilityToggled = false; if ( !FLevelUtils::IsLevelVisible( GWorld->PersistentLevel ) ) { EditorLevelUtils::SetLevelVisibility( GWorld->PersistentLevel, true, false ); bVisibilityToggled = true; } for ( TArray<ULevelStreaming*>::TConstIterator LevelIter( GWorld->StreamingLevels ); LevelIter; ++LevelIter ) { ULevelStreaming* CurStreamingLevel = *LevelIter; if ( CurStreamingLevel && !FLevelUtils::IsLevelVisible( CurStreamingLevel ) ) { CurStreamingLevel->bShouldBeVisibleInEditor = true; bVisibilityToggled = true; } } if ( bVisibilityToggled ) { GWorld->FlushLevelStreaming(); } } return bBuildSuccessful; }
void FLODCluster::BuildActor(ULevel* InLevel, const int32 LODIdx, const bool bCreateMeshes) { FColor Colours[8] = { FColor::Cyan, FColor::Red, FColor::Green, FColor::Blue, FColor::Yellow, FColor::Magenta, FColor::White, FColor::Black }; // do big size if (InLevel && InLevel->GetWorld()) { // create asset using Actors const FHierarchicalSimplification& LODSetup = InLevel->GetWorld()->GetWorldSettings()->HierarchicalLODSetup[LODIdx]; // Retrieve draw distance for current and next LOD level const float DrawDistance = LODSetup.DrawDistance; const int32 LODCount = InLevel->GetWorld()->GetWorldSettings()->HierarchicalLODSetup.Num(); const float NextDrawDistance = (LODIdx < (LODCount - 1)) ? InLevel->GetWorld()->GetWorldSettings()->HierarchicalLODSetup[LODIdx + 1].DrawDistance : 0.0f; // Where generated assets will be stored UPackage* AssetsOuter = InLevel->GetOutermost(); // this asset is going to save with map, this means, I'll have to delete with it if (AssetsOuter) { TArray<UStaticMeshComponent*> AllComponents; for (auto& Actor: Actors) { TArray<UStaticMeshComponent*> Components; if (Actor->IsA<ALODActor>()) { HierarchicalLODUtils::ExtractStaticMeshComponentsFromLODActor(Actor, Components); } else { Actor->GetComponents<UStaticMeshComponent>(Components); } // TODO: support instanced static meshes Components.RemoveAll([](UStaticMeshComponent* Val){ return Val->IsA(UInstancedStaticMeshComponent::StaticClass()); }); AllComponents.Append(Components); } // it shouldn't even have come here if it didn't have any staticmesh if (ensure(AllComponents.Num() > 0)) { // In case we don't have outer generated assets should have same path as LOD level const FString AssetsPath = AssetsOuter->GetName() + TEXT("/"); AActor* FirstActor = Actors[0]; TArray<UObject*> OutAssets; FVector OutProxyLocation = FVector::ZeroVector; UStaticMesh* MainMesh = nullptr; if (bCreateMeshes) { // Generate proxy mesh and proxy material assets IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities"); // should give unique name, so use level + actor name const FString PackageName = FString::Printf(TEXT("LOD_%s"), *FirstActor->GetName()); if (MeshUtilities.GetMeshMergingInterface() && LODSetup.bSimplifyMesh) { MeshUtilities.CreateProxyMesh(Actors, LODSetup.ProxySetting, AssetsOuter, PackageName, OutAssets, OutProxyLocation); } else { MeshUtilities.MergeStaticMeshComponents(AllComponents, FirstActor->GetWorld(), LODSetup.MergeSetting, AssetsOuter, PackageName, LODIdx, OutAssets, OutProxyLocation, LODSetup.DrawDistance, true); } // we make it private, so it can't be used by outside of map since it's useless, and then remove standalone for (auto& AssetIter : OutAssets) { AssetIter->ClearFlags(RF_Public | RF_Standalone); } // set staticmesh for (auto& Asset : OutAssets) { UStaticMesh* StaticMesh = Cast<UStaticMesh>(Asset); if (StaticMesh) { MainMesh = StaticMesh; } } } if (MainMesh || !bCreateMeshes) { UWorld* LevelWorld = Cast<UWorld>(InLevel->GetOuter()); check (LevelWorld); FTransform Transform; Transform.SetLocation(OutProxyLocation); // create LODActors using the current Actors ALODActor* NewActor = nullptr; NewActor = LevelWorld->SpawnActor<ALODActor>(ALODActor::StaticClass(), Transform); NewActor->SubObjects = OutAssets; NewActor->LODLevel = LODIdx+1; NewActor->LODDrawDistance = DrawDistance; NewActor->SetStaticMesh( MainMesh ); // now set as parent for(auto& Actor : Actors) { NewActor->AddSubActor(Actor); } // Mark dirty according to whether or not this is a preview build NewActor->SetIsDirty(!bCreateMeshes); } } } } }