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;
}
Example #6
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 );
}