UEnum* GetEnumForByteTrack(TSharedPtr<ISequencer> Sequencer, const FGuid& OwnerObjectHandle, FName PropertyName, UMovieSceneByteTrack* ByteTrack) { UObject* RuntimeObject = Sequencer->GetFocusedMovieSceneSequence()->FindObject(OwnerObjectHandle); TSet<UEnum*> PropertyEnums; if (RuntimeObject != nullptr) { UProperty* Property = RuntimeObject->GetClass()->FindPropertyByName(PropertyName); if (Property != nullptr) { UByteProperty* ByteProperty = Cast<UByteProperty>(Property); if (ByteProperty != nullptr && ByteProperty->Enum != nullptr) { PropertyEnums.Add(ByteProperty->Enum); } } } UEnum* TrackEnum; if (PropertyEnums.Num() == 1) { TrackEnum = PropertyEnums.Array()[0]; } else { TrackEnum = nullptr; } return TrackEnum; }
void FAssetDataGatherer::DiscoverFilesToSearch() { if( PathsToSearch.Num() > 0 ) { TArray<FString> DiscoveredFilesToSearch; TSet<FString> LocalDiscoveredPathsSet; TArray<FString> CopyOfPathsToSearch; { FScopeLock CritSectionLock(&WorkerThreadCriticalSection); CopyOfPathsToSearch = PathsToSearch; // Remove all of the existing paths from the list, since we'll process them all below. New paths may be // added to the original list on a different thread as we go along, but those new paths won't be processed // during this call of DisoverFilesToSearch(). But we'll get them on the next call! PathsToSearch.Empty(); bIsDiscoveringFiles = true; } // Iterate over any paths that we have remaining to scan for ( int32 PathIdx=0; PathIdx < CopyOfPathsToSearch.Num(); ++PathIdx ) { const FString& Path = CopyOfPathsToSearch[PathIdx]; // Convert the package path to a filename with no extension (directory) const FString FilePath = FPackageName::LongPackageNameToFilename(Path); // Gather the package files in that directory and subdirectories TArray<FString> Filenames; FPackageName::FindPackagesInDirectory(Filenames, FilePath); for (int32 FilenameIdx = 0; FilenameIdx < Filenames.Num(); FilenameIdx++) { FString Filename(Filenames[FilenameIdx]); if ( IsValidPackageFileToRead(Filename) ) { // Add the path to this asset into the list of discovered paths const FString LongPackageName = FPackageName::FilenameToLongPackageName(Filename); LocalDiscoveredPathsSet.Add( FPackageName::GetLongPackagePath(LongPackageName) ); DiscoveredFilesToSearch.Add(Filename); } } } // Turn the set into an array here before the critical section below TArray<FString> LocalDiscoveredPathsArray = LocalDiscoveredPathsSet.Array(); { // Place all the discovered files into the files to search list FScopeLock CritSectionLock(&WorkerThreadCriticalSection); FilesToSearch.Append(DiscoveredFilesToSearch); DiscoveredPaths.Append(LocalDiscoveredPathsArray); bIsDiscoveringFiles = false; } } }
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 FSpriteGeometryEditingHelper::DeleteSelectedItems() { // Determine which vertices or entire shapes should be deleted TSet<FShapeVertexPair> CompositeIndicesSet; TSet<int32> ShapesToDeleteSet; if (IsEditingGeometry()) { FSpriteGeometryCollection& Geometry = GetGeometryChecked(); for (TSharedPtr<FSelectedItem> SelectionIt : GetSelectionSet()) { if (const FSpriteSelectedVertex* SelectedVertex = SelectionIt->CastTo<const FSpriteSelectedVertex>(FSelectionTypes::Vertex)) { CompositeIndicesSet.Add(FShapeVertexPair(SelectedVertex->ShapeIndex, SelectedVertex->VertexIndex)); if (SelectedVertex->IsA(FSelectionTypes::Edge)) // add the "next" point for the edge { const int32 NextIndex = (SelectedVertex->VertexIndex + 1) % Geometry.Shapes[SelectedVertex->ShapeIndex].Vertices.Num(); CompositeIndicesSet.Add(FShapeVertexPair(SelectedVertex->ShapeIndex, NextIndex)); } } else if (const FSpriteSelectedShape* SelectedShape = SelectionIt->CastTo<const FSpriteSelectedShape>(FSelectionTypes::GeometryShape)) { ShapesToDeleteSet.Add(SelectedShape->ShapeIndex); } } } // See if anything else can be deleted bool bCanDeleteNonGeometry = false; for (const TSharedPtr<FSelectedItem> SelectedItem : GetSelectionSet()) { if (SelectedItem->CanBeDeleted()) { bCanDeleteNonGeometry = true; break; } } // Now delete the stuff that was selected in the correct order so that indices aren't messed up const bool bDeletingGeometry = (CompositeIndicesSet.Num() > 0) || (ShapesToDeleteSet.Num() > 0); if (bDeletingGeometry || bCanDeleteNonGeometry) { EditorContext->BeginTransaction(LOCTEXT("DeleteSelectionTransaction", "Delete Selection")); EditorContext->MarkTransactionAsDirty(); if (bDeletingGeometry) { FSpriteGeometryCollection& Geometry = GetGeometryChecked(); // Delete the selected vertices first, as they may cause entire shapes to need to be deleted (sort so we delete from the back first) TArray<FShapeVertexPair> CompositeIndices = CompositeIndicesSet.Array(); CompositeIndices.Sort([](const FShapeVertexPair& A, const FShapeVertexPair& B) { return (A.VertexIndex > B.VertexIndex); }); for (const FShapeVertexPair& Composite : CompositeIndices) { const int32 ShapeIndex = Composite.ShapeIndex; const int32 VertexIndex = Composite.VertexIndex; if (DeleteVertexInPolygonInternal(Geometry, ShapeIndex, VertexIndex)) { ShapesToDeleteSet.Add(ShapeIndex); } } // Delete the selected shapes (plus any shapes that became empty due to selected vertices) if (ShapesToDeleteSet.Num() > 0) { // Sort so we delete from the back first TArray<int32> ShapesToDeleteIndicies = ShapesToDeleteSet.Array(); ShapesToDeleteIndicies.Sort([](const int32& A, const int32& B) { return (A > B); }); for (const int32 ShapeToDeleteIndex : ShapesToDeleteIndicies) { Geometry.Shapes.RemoveAt(ShapeToDeleteIndex); } } Geometry.GeometryType = ESpritePolygonMode::FullyCustom; } // Delete everything else if (bCanDeleteNonGeometry) { for (TSharedPtr<FSelectedItem> SelectedItem : GetSelectionSet()) { if (SelectedItem->CanBeDeleted()) { SelectedItem->DeleteThisItem(); } } } EditorContext->EndTransaction(); } ClearSelectionSet(); ResetAddPolygonMode(); }
uint32 FAssetDataDiscovery::Run() { double DiscoverStartTime = FPlatformTime::Seconds(); int32 NumDiscoveredFiles = 0; FString LocalFilenamePathToPrioritize; TSet<FString> LocalDiscoveredPathsSet; TArray<FString> LocalDiscoveredDirectories; TArray<FDiscoveredPackageFile> LocalPriorityFilesToSearch; TArray<FDiscoveredPackageFile> LocalNonPriorityFilesToSearch; // This set contains the folders that we should hide by default unless they contain assets TSet<FString> PathsToHideIfEmpty; PathsToHideIfEmpty.Add(TEXT("/Game/Collections")); auto FlushLocalResultsIfRequired = [&]() { if (LocalPriorityFilesToSearch.Num() > 0 || LocalNonPriorityFilesToSearch.Num() > 0 || LocalDiscoveredPathsSet.Num() > 0) { TArray<FString> LocalDiscoveredPathsArray = LocalDiscoveredPathsSet.Array(); { FScopeLock CritSectionLock(&WorkerThreadCriticalSection); // Place all the discovered files into the files to search list DiscoveredPaths.Append(MoveTemp(LocalDiscoveredPathsArray)); PriorityDiscoveredFiles.Append(MoveTemp(LocalPriorityFilesToSearch)); NonPriorityDiscoveredFiles.Append(MoveTemp(LocalNonPriorityFilesToSearch)); } } LocalDiscoveredPathsSet.Reset(); LocalPriorityFilesToSearch.Reset(); LocalNonPriorityFilesToSearch.Reset(); }; auto IsPriorityFile = [&](const FString& InPackageFilename) -> bool { return !bIsSynchronous && !LocalFilenamePathToPrioritize.IsEmpty() && InPackageFilename.StartsWith(LocalFilenamePathToPrioritize); }; auto OnIterateDirectoryItem = [&](const TCHAR* InPackageFilename, const FFileStatData& InPackageStatData) -> bool { if (StopTaskCounter.GetValue() != 0) { // Requested to stop - break out of the directory iteration return false; } const FString PackageFilenameStr = InPackageFilename; if (InPackageStatData.bIsDirectory) { LocalDiscoveredDirectories.Add(PackageFilenameStr / TEXT("")); FString PackagePath; if (FPackageName::TryConvertFilenameToLongPackageName(PackageFilenameStr, PackagePath) && !PathsToHideIfEmpty.Contains(PackagePath)) { LocalDiscoveredPathsSet.Add(PackagePath); } } else if (FPackageName::IsPackageFilename(PackageFilenameStr)) { if (IsValidPackageFileToRead(PackageFilenameStr)) { const FString LongPackageNameStr = FPackageName::FilenameToLongPackageName(PackageFilenameStr); if (IsPriorityFile(PackageFilenameStr)) { LocalPriorityFilesToSearch.Add(FDiscoveredPackageFile(PackageFilenameStr, InPackageStatData.ModificationTime)); } else { LocalNonPriorityFilesToSearch.Add(FDiscoveredPackageFile(PackageFilenameStr, InPackageStatData.ModificationTime)); } LocalDiscoveredPathsSet.Add(FPackageName::GetLongPackagePath(LongPackageNameStr)); ++NumDiscoveredFiles; // Flush the data if we've processed enough if (!bIsSynchronous && (LocalPriorityFilesToSearch.Num() + LocalNonPriorityFilesToSearch.Num()) >= AssetDataGathererConstants::MaxFilesToDiscoverBeforeFlush) { FlushLocalResultsIfRequired(); } } } return true; }; bool bIsIdle = true; while (StopTaskCounter.GetValue() == 0) { FString LocalDirectoryToSearch; { FScopeLock CritSectionLock(&WorkerThreadCriticalSection); if (DirectoriesToSearch.Num() > 0) { bIsDiscoveringFiles = true; LocalFilenamePathToPrioritize = FilenamePathToPrioritize; // Pop off the first path to search LocalDirectoryToSearch = DirectoriesToSearch[0]; DirectoriesToSearch.RemoveAt(0, 1, false); } } if (LocalDirectoryToSearch.Len() > 0) { if (bIsIdle) { bIsIdle = false; // About to start work - reset these DiscoverStartTime = FPlatformTime::Seconds(); NumDiscoveredFiles = 0; } // Iterate the current search directory FLambdaDirectoryStatVisitor Visitor(OnIterateDirectoryItem); IFileManager::Get().IterateDirectoryStat(*LocalDirectoryToSearch, Visitor); { FScopeLock CritSectionLock(&WorkerThreadCriticalSection); // Push back any newly discovered sub-directories if (LocalDiscoveredDirectories.Num() > 0) { // Use LocalDiscoveredDirectories as scratch space, then move it back out - this puts the directories we just // discovered at the start of the list for the next iteration, which can help with disk locality LocalDiscoveredDirectories.Append(MoveTemp(DirectoriesToSearch)); DirectoriesToSearch = MoveTemp(LocalDiscoveredDirectories); } LocalDiscoveredDirectories.Reset(); if (!bIsSynchronous) { FlushLocalResultsIfRequired(); SortPathsByPriority(1); } } } else { if (!bIsIdle) { bIsIdle = true; { FScopeLock CritSectionLock(&WorkerThreadCriticalSection); bIsDiscoveringFiles = false; } UE_LOG(LogAssetRegistry, Verbose, TEXT("Discovery took %0.6f seconds and found %d files to process"), FPlatformTime::Seconds() - DiscoverStartTime, NumDiscoveredFiles); } // Ran out of things to do... if we have any pending results, flush those now FlushLocalResultsIfRequired(); if (bIsSynchronous) { // This is synchronous. Since our work is done, we should safely exit Stop(); } else { // No work to do. Sleep for a little and try again later. FPlatformProcess::Sleep(0.1); } } } return 0; }
/** * Helper method to submit packages to source control as part of the automated build process * * @param InPkgsToSubmit Set of packages which should be submitted to source control * @param BuildSettings Build settings used during the automated build */ void FEditorBuildUtils::SubmitPackagesForAutomatedBuild( const TSet<UPackage*>& InPkgsToSubmit, const FEditorAutomatedBuildSettings& BuildSettings ) { TArray<FString> LevelsToAdd; TArray<FString> LevelsToSubmit; ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); // first update the status of the packages SourceControlProvider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), SourceControlHelpers::PackageFilenames(InPkgsToSubmit.Array())); // Iterate over the set of packages to submit, determining if they need to be checked in or // added to the depot for the first time for ( TSet<UPackage*>::TConstIterator PkgIter( InPkgsToSubmit ); PkgIter; ++PkgIter ) { const UPackage* CurPkg = *PkgIter; const FString PkgName = CurPkg->GetName(); const FString PkgFileName = SourceControlHelpers::PackageFilename(CurPkg); FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(CurPkg, EStateCacheUsage::ForceUpdate); if(SourceControlState.IsValid()) { if ( SourceControlState->IsCheckedOut() || SourceControlState->IsAdded() ) { LevelsToSubmit.Add( PkgFileName ); } else if ( BuildSettings.bAutoAddNewFiles && !SourceControlState->IsSourceControlled() && !SourceControlState->IsIgnored() ) { LevelsToSubmit.Add( PkgFileName ); LevelsToAdd.Add( PkgFileName ); } } } // Then, if we've also opted to check in any packages, iterate over that list as well if(BuildSettings.bCheckInPackages) { TArray<FString> PackageNames = BuildSettings.PackagesToCheckIn; for ( TArray<FString>::TConstIterator PkgIterName(PackageNames); PkgIterName; PkgIterName++ ) { const FString& PkgName = *PkgIterName; const FString PkgFileName = SourceControlHelpers::PackageFilename(PkgName); FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(PkgFileName, EStateCacheUsage::ForceUpdate); if(SourceControlState.IsValid()) { if ( SourceControlState->IsCheckedOut() || SourceControlState->IsAdded() ) { LevelsToSubmit.Add( PkgFileName ); } else if ( !SourceControlState->IsSourceControlled() && !SourceControlState->IsIgnored() ) { // note we add the files we need to add to the submit list as well LevelsToSubmit.Add( PkgFileName ); LevelsToAdd.Add( PkgFileName ); } } } } // first add files that need to be added SourceControlProvider.Execute( ISourceControlOperation::Create<FMarkForAdd>(), LevelsToAdd, EConcurrency::Synchronous ); // Now check in all the changes, including the files we added above TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOperation = StaticCastSharedRef<FCheckIn>(ISourceControlOperation::Create<FCheckIn>()); CheckInOperation->SetDescription(NSLOCTEXT("UnrealEd", "AutomatedBuild_AutomaticSubmission", "[Automatic Submission]")); SourceControlProvider.Execute( CheckInOperation, LevelsToSubmit, EConcurrency::Synchronous ); }