// Copyright Epic Games, Inc. All Rights Reserved. using System.Collections.Generic; using System.Linq; using System.Threading; using EpicGames.Core; using UnrealBuildBase; namespace UnrealBuildTool { /// /// Prefetches metadata from the filesystem, by populating FileItem and DirectoryItem objects for requested directory trees. Since /// static class FileMetadataPrefetch { /// /// Queue for tasks added to the thread pool /// static ThreadPoolWorkQueue Queue = new ThreadPoolWorkQueue(); /// /// Used to cancel any queued tasks /// static CancellationTokenSource CancelSource = new CancellationTokenSource(); /// /// The cancellation token /// static CancellationToken CancelToken = CancelSource.Token; /// /// Set of all the directory trees that have been queued up, to save adding any more than once. /// static HashSet QueuedDirectories = new HashSet(); /// /// Enqueue the engine directory for prefetching /// public static void QueueEngineDirectory() { lock (QueuedDirectories) { if (QueuedDirectories.Add(Unreal.EngineDirectory)) { Enqueue(() => ScanEngineDirectory()); } } } /// /// Enqueue a project directory for prefetching /// /// The project directory to prefetch public static void QueueProjectDirectory(DirectoryReference ProjectDirectory) { lock (QueuedDirectories) { if (QueuedDirectories.Add(ProjectDirectory)) { Enqueue(() => ScanProjectDirectory(DirectoryItem.GetItemByDirectoryReference(ProjectDirectory))); } } } /// /// Enqueue a directory tree for prefetching /// /// Directory to start searching from public static void QueueDirectoryTree(DirectoryReference Directory) { lock (QueuedDirectories) { if (QueuedDirectories.Add(Directory)) { Enqueue(() => ScanDirectoryTree(DirectoryItem.GetItemByDirectoryReference(Directory))); } } } /// /// Wait for the prefetcher to complete all reqeusted tasks /// public static void Wait() { Queue.Wait(); } /// /// Stop prefetching items, and cancel all pending tasks. synchronous. /// public static void Stop() { CancelSource.Cancel(); Queue.Wait(); } /// /// Enqueue a task which checks for the cancellation token first /// /// Action to enqueue static void Enqueue(System.Action Action) { Queue.Enqueue(() => { if (!CancelToken.IsCancellationRequested) { Action(); } }); } /// /// Scans the engine directory, adding tasks for subdirectories /// static void ScanEngineDirectory() { foreach (DirectoryReference ExtensionDir in Unreal.GetExtensionDirs(Unreal.EngineDirectory)) { DirectoryItem BaseDirectory = DirectoryItem.GetItemByDirectoryReference(ExtensionDir); BaseDirectory.CacheDirectories(); DirectoryItem BasePluginsDirectory = DirectoryItem.Combine(BaseDirectory, "Plugins"); Enqueue(() => ScanPluginFolder(BasePluginsDirectory)); DirectoryItem BaseSourceDirectory = DirectoryItem.Combine(BaseDirectory, "Source"); BaseSourceDirectory.CacheDirectories(); DirectoryItem BaseSourceRuntimeDirectory = DirectoryItem.Combine(BaseSourceDirectory, "Runtime"); Enqueue(() => ScanDirectoryTree(BaseSourceRuntimeDirectory)); DirectoryItem BaseSourceDeveloperDirectory = DirectoryItem.Combine(BaseSourceDirectory, "Developer"); Enqueue(() => ScanDirectoryTree(BaseSourceDeveloperDirectory)); DirectoryItem BaseSourceEditorDirectory = DirectoryItem.Combine(BaseSourceDirectory, "Editor"); Enqueue(() => ScanDirectoryTree(BaseSourceEditorDirectory)); } } /// /// Scans a project directory, adding tasks for subdirectories /// /// The project directory to search static void ScanProjectDirectory(DirectoryItem ProjectDirectory) { foreach (DirectoryReference ExtensionDir in Unreal.GetExtensionDirs(ProjectDirectory.Location)) { DirectoryItem BaseDirectory = DirectoryItem.GetItemByDirectoryReference(ExtensionDir); BaseDirectory.CacheDirectories(); DirectoryItem BasePluginsDirectory = DirectoryItem.Combine(BaseDirectory, "Plugins"); Enqueue(() => ScanPluginFolder(BasePluginsDirectory)); DirectoryItem BaseSourceDirectory = DirectoryItem.Combine(BaseDirectory, "Source"); Enqueue(() => ScanDirectoryTree(BaseSourceDirectory)); } } /// /// Scans a plugin parent directory, adding tasks for subdirectories /// /// The directory which may contain plugin directories static void ScanPluginFolder(DirectoryItem Directory) { if (CancelToken.IsCancellationRequested || Directory.TryGetFile(".ubtignore", out FileItem? _)) { return; } foreach (DirectoryItem SubDirectory in Directory.EnumerateDirectories()) { if (SubDirectory.EnumerateFiles().Any((fi) => fi.HasExtension(".uplugin"))) { Enqueue(() => ScanDirectoryTree(DirectoryItem.Combine(SubDirectory, "Source"))); } else if (!SubDirectory.TryGetFile(".ubtignore", out FileItem? OutFile)) { Enqueue(() => ScanPluginFolder(SubDirectory)); } } } /// /// Scans an arbitrary directory tree /// /// Root of the directory tree static void ScanDirectoryTree(DirectoryItem Directory) { if (CancelToken.IsCancellationRequested || Directory.TryGetFile(".ubtignore", out FileItem? _)) { return; } foreach (DirectoryItem SubDirectory in Directory.EnumerateDirectories()) { Enqueue(() => ScanDirectoryTree(SubDirectory)); } } } }