Ejemplo n.º 1
0
        public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
        {
            var cancelledTaskSource = new TaskCompletionSource();

            cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(),
                                       cancelledTaskSource);

            var processSpec = context.ProcessSpec;

            if (context.SuppressMSBuildIncrementalism)
            {
                _reporter.Verbose("MSBuild incremental optimizations suppressed.");
            }

            while (true)
            {
                context.Iteration++;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                // Reset for next run
                context.RequiresMSBuildRevaluation = false;

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (!fileSet.Project.IsNetCoreApp60OrNewer())
                {
                    _reporter.Error($"Hot reload based watching is only supported in .NET 6.0 or newer apps. Update the project's launchSettings.json to disable this feature.");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                ConfigureExecutable(context, processSpec);

                using var currentRunCancellationSource = new CancellationTokenSource();
                using var combinedCancellationSource   = CancellationTokenSource.CreateLinkedTokenSource(
                          cancellationToken,
                          currentRunCancellationSource.Token);
                using var fileSetWatcher = new FileSetWatcher(fileSet, _reporter);
                try
                {
                    using var hotReload = new HotReload(_reporter);
                    await hotReload.InitializeAsync(context, cancellationToken);

                    var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                    var args        = string.Join(" ", processSpec.Arguments);
                    _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                    _reporter.Output("Started");

                    Task <FileItem?> fileSetTask;
                    Task             finishedTask;

                    while (true)
                    {
                        fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                        finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);

                        if (finishedTask != fileSetTask || fileSetTask.Result is not FileItem fileItem)
                        {
                            // The app exited.
                            break;
                        }
                        else
                        {
                            _reporter.Output($"File changed: {fileItem.FilePath}.");

                            var start = Stopwatch.GetTimestamp();
                            if (await hotReload.TryHandleFileChange(context, fileItem, combinedCancellationSource.Token))
                            {
                                var totalTime = TimeSpan.FromTicks(Stopwatch.GetTimestamp() - start);
                                _reporter.Verbose($"Successfully handled changes to {fileItem.FilePath} in {totalTime.TotalMilliseconds}ms.");
                            }
                            else
                            {
                                _reporter.Verbose($"Unable to handle changes to {fileItem.FilePath}. Rebuilding the app.");
                                break;
                            }
                        }
                    }

                    // Regardless of the which task finished first, make sure everything is cancelled
                    // and wait for dotnet to exit. We don't want orphan processes
                    currentRunCancellationSource.Cancel();

                    await Task.WhenAll(processTask, fileSetTask);

                    if (processTask.Result != 0 && finishedTask == processTask && !cancellationToken.IsCancellationRequested)
                    {
                        // Only show this error message if the process exited non-zero due to a normal process exit.
                        // Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user
                        _reporter.Error($"Exited with error code {processTask.Result}");
                    }
                    else
                    {
                        _reporter.Output("Exited");
                    }

                    if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested)
                    {
                        return;
                    }

                    if (finishedTask == processTask)
                    {
                        // Process exited. Redo evaludation
                        context.RequiresMSBuildRevaluation = true;
                        // Now wait for a file to change before restarting process
                        context.ChangedFile = await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet..."));
                    }
                    else
                    {
                        Debug.Assert(finishedTask == fileSetTask);
                        var changedFile = fileSetTask.Result;
                        context.ChangedFile = changedFile;
                    }
                }
Ejemplo n.º 2
0
        private static void OnNativeFunctionsRegistered()
        {
            bool reloading = HotReload.IsReloading;

            HotReload.MinimalReload = Native_SharpHotReloadUtils.Get_MinimalHotReload();

            // HACK: Removing EPackageFlags.EditorOnly on the USharp package so that C# classes aren't tagged as
            //       EObjectMark.EditorOnly. The correct thing to do would be to seperate USharp into seperate
            //       Editor/Runtime modules.
            IntPtr package = NativeReflection.FindPackage(IntPtr.Zero, "/Script/USharp");

            if (package != IntPtr.Zero)
            {
                Native_UPackage.ClearPackageFlags(package, EPackageFlags.EditorOnly);
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.NativeFunctions_LoadAssemblies))
            {
                // Load managed assemblies (game assembly, and any others which may need loading)
                LoadAssemblies();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.UnrealTypes_Load))
            {
                UnrealTypes.Load();
            }

            if (HotReload.IsReloading)
            {
                HotReload.OnPreReloadBegin();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.UnrealTypes_LoadNative))
            {
                // Load the underlying native type info for generated types (class address/properties/functions/offsets)
                UnrealTypes.LoadNative();
            }

            // If any assemblies are loaded after this point make sure to load their unreal types
            AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;

            using (var timing = HotReload.Timing.Create(HotReload.Timing.UClass_Load))
            {
                // Load native classes
                UClass.Load();
            }

            if (HotReload.IsReloading)
            {
                HotReload.OnPreReloadEnd();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.ManagedUnrealModuleInfo_Load))
            {
                // Load managed module infos
                ManagedUnrealModuleInfo.Load();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.ManagedUnrealTypes_Load))
            {
                // Load / register managed unreal types
                ManagedUnrealTypes.Load();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.HotReload_OnReload))
            {
                // Let HotReload handle reloading if this is a reload
                if (HotReload.IsReloading)
                {
                    HotReload.OnReload();
                }
            }

            // Clear the hot-reload data store if it isn't cleared already
            if (HotReload.Data != null)
            {
                HotReload.Data.Close();
                HotReload.Data = null;
            }

            if (FBuild.WithEditor && reloading)
            {
                using (var timing = HotReload.Timing.Create(HotReload.Timing.UObject_CollectGarbage))
                {
                    // If we are hotreloading collect garbage to clean up trashed types / reinstanced objects
                    UObject.CollectGarbage(GCHelper.GarbageCollectionKeepFlags, true);
                }

                if (!ManagedUnrealTypes.SkipBroadcastHotReload)
                {
                    using (var timing = HotReload.Timing.Create(HotReload.Timing.SharpHotReloadUtils_BroadcastOnHotReload))
                    {
                        // Broadcast the native OnHotReload (if we don't do this we would need to reimplement various
                        // handlers to ensure correct reloading. One example is FBlueprintActionDatabase::ReloadAll
                        // which needs to be called otherwise the action database will hold onto our old class members
                        // and would produce erros when opening blueprints).

                        // true will show the C++ "Hot Reload Complete!" notification (are there any other differences?)
                        Native_SharpHotReloadUtils.BroadcastOnHotReload(true);
                    }
                }
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.GC_Collect))
            {
                // We likely created a bunch of garbage, best to clean it up now.
                GC.Collect();
            }
        }
Ejemplo n.º 3
0
        private static void OnNativeFunctionsRegistered()
        {
            bool reloading = HotReload.IsReloading;

            HotReload.MinimalReload = Native_SharpHotReloadUtils.Get_MinimalHotReload();

            FMessage.Log("Runtime: " + SharedRuntimeState.GetRuntimeInfo(false));

            // HACK: Removing EPackageFlags.EditorOnly on the USharp package so that C# classes aren't tagged as
            //       EObjectMark.EditorOnly. The correct thing to do would be to seperate USharp into seperate
            //       Editor/Runtime modules.
            IntPtr package = NativeReflection.FindPackage(IntPtr.Zero, "/Script/USharp");

            if (package != IntPtr.Zero)
            {
                Native_UPackage.ClearPackageFlags(package, EPackageFlags.EditorOnly);
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.UnrealTypes_Load))
            {
                UnrealTypes.Load();
            }

            if (HotReload.IsReloading)
            {
                HotReload.OnPreReloadBegin();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.UnrealTypes_LoadNative))
            {
                // Load the underlying native type info for generated types (class address/properties/functions/offsets)
                UnrealTypes.LoadNative();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.UClass_Load))
            {
                // Load native classes
                UClass.Load();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.NativeFunctions_GenerateAndCompileMissingAssemblies))
            {
                // Update the C# game project props file
                ProjectProps.Update();

                // Prompt to compile the C# engine wrapper code / C# game code (if it isn't already compiled)
                GenerateAndCompileMissingAssemblies();
            }

            // If any assemblies are loaded make sure to load their unreal types
            if (!AssemblyContext.IsCoreCLR || CurrentAssemblyContext.Reference.IsInvalid)
            {
                // .NET Core should resolve with AssemblyLoadContext.Resolving (unless the contexts aren't set up)
                CurrentAssemblyContext.AssemblyResolve += CurrentDomain_AssemblyResolve;
            }
            CurrentAssemblyContext.AssemblyLoad += OnAssemblyLoad;
            CurrentAssemblyContext.Resolving    += CurrentAssemblyContext_Resolving;

            using (var timing = HotReload.Timing.Create(HotReload.Timing.NativeFunctions_LoadAssemblies))
            {
                // Load managed assemblies (game assembly, and any others which may need loading)
                LoadAssemblies();
            }

            if (HotReload.IsReloading)
            {
                HotReload.OnPreReloadEnd();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.ManagedUnrealModuleInfo_Load))
            {
                // Load managed module infos
                ManagedUnrealModuleInfo.Load();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.ManagedUnrealTypes_Load))
            {
                // Load / register managed unreal types
                ManagedUnrealTypes.Load();
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.HotReload_OnReload))
            {
                // Let HotReload handle reloading if this is a reload
                if (HotReload.IsReloading)
                {
                    HotReload.OnReload();
                }
            }

            // Clear the hot-reload data store if it isn't cleared already
            if (HotReload.Data != null)
            {
                HotReload.Data.Close();
                HotReload.Data = null;
            }

            if (FBuild.WithEditor && reloading)
            {
                using (var timing = HotReload.Timing.Create(HotReload.Timing.UObject_CollectGarbage))
                {
                    // If we are hotreloading collect garbage to clean up trashed types / reinstanced objects
                    UObject.CollectGarbage(GCHelper.GarbageCollectionKeepFlags, true);
                }

                if (!ManagedUnrealTypes.SkipBroadcastHotReload)
                {
                    using (var timing = HotReload.Timing.Create(HotReload.Timing.SharpHotReloadUtils_BroadcastOnHotReload))
                    {
                        // Broadcast the native OnHotReload (if we don't do this we would need to reimplement various
                        // handlers to ensure correct reloading. One example is FBlueprintActionDatabase::ReloadAll
                        // which needs to be called otherwise the action database will hold onto our old class members
                        // and would produce erros when opening blueprints).

                        // true will show the C++ "Hot Reload Complete!" notification (are there any other differences?)
                        //Native_SharpHotReloadUtils.BroadcastOnHotReload(true);

                        // The notification rendering gets messed up the longer hotreload takes. Wait 1 frame to ensure
                        // that the notification gets fully rendered (though the audio still seems to mess up)
                        Coroutine.StartCoroutine(null, DeferBroadcastHotReload());
                    }
                }
            }

            using (var timing = HotReload.Timing.Create(HotReload.Timing.GC_Collect))
            {
                // We likely created a bunch of garbage, best to clean it up now.
                GC.Collect();
            }
        }
Ejemplo n.º 4
0
        public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
        {
            var processSpec = context.ProcessSpec;

            _reporter.Output("Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload. " +
                             "Press \"Ctrl + R\" to restart.");

            var forceReload = new CancellationTokenSource();

            _console.KeyPressed += (key) =>
            {
                var modifiers = ConsoleModifiers.Control;
                if ((key.Modifiers & modifiers) == modifiers && key.Key == ConsoleKey.R)
                {
                    var cancellationTokenSource = Interlocked.Exchange(ref forceReload, new CancellationTokenSource());
                    cancellationTokenSource.Cancel();
                }
            };

            while (true)
            {
                context.Iteration++;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (!fileSet.Project.IsNetCoreApp60OrNewer())
                {
                    _reporter.Error($"Hot reload based watching is only supported in .NET 6.0 or newer apps. Update the project's launchSettings.json to disable this feature.");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                if (context.Iteration == 0)
                {
                    ConfigureExecutable(context, processSpec);
                }

                using var currentRunCancellationSource = new CancellationTokenSource();
                using var combinedCancellationSource   = CancellationTokenSource.CreateLinkedTokenSource(
                          cancellationToken,
                          currentRunCancellationSource.Token,
                          forceReload.Token);
                using var fileSetWatcher = new HotReloadFileSetWatcher(fileSet, _reporter);

                try
                {
                    using var hotReload = new HotReload(_processRunner, _reporter);
                    await hotReload.InitializeAsync(context, cancellationToken);

                    var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                    var args        = string.Join(" ", processSpec.Arguments);
                    _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                    _reporter.Output("Started");

                    Task <FileItem[]> fileSetTask;
                    Task finishedTask;

                    while (true)
                    {
                        fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                        finishedTask = await Task.WhenAny(processTask, fileSetTask).WaitAsync(combinedCancellationSource.Token);

                        if (finishedTask != fileSetTask || fileSetTask.Result is not FileItem[] fileItems)
                        {
                            // The app exited.
                            break;
                        }
                        else
                        {
                            if (MayRequireRecompilation(context, fileItems) is { } newFile)
                            {
                                _reporter.Output($"New file: {newFile.FilePath}. Rebuilding the application.");
                                break;
                            }
Ejemplo n.º 5
0
        public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
        {
            var processSpec = context.ProcessSpec;

            if (context.SuppressMSBuildIncrementalism)
            {
                _reporter.Verbose("MSBuild incremental optimizations suppressed.");
            }

            _reporter.Output("Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload. " +
                             "Press \"Ctrl + Shift + R\" to restart.");

            var forceReload = new CancellationTokenSource();

            _console.KeyPressed += (key) =>
            {
                var controlShift = ConsoleModifiers.Control | ConsoleModifiers.Shift;
                if ((key.Modifiers & controlShift) == controlShift && key.Key == ConsoleKey.R)
                {
                    var cancellationTokenSource = Interlocked.Exchange(ref forceReload, new CancellationTokenSource());
                    cancellationTokenSource.Cancel();
                }
            };

            while (true)
            {
                context.Iteration++;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                // Reset for next run
                context.RequiresMSBuildRevaluation = false;

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (!fileSet.Project.IsNetCoreApp60OrNewer())
                {
                    _reporter.Error($"Hot reload based watching is only supported in .NET 6.0 or newer apps. Update the project's launchSettings.json to disable this feature.");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                if (context.Iteration == 0)
                {
                    ConfigureExecutable(context, processSpec);
                }

                using var currentRunCancellationSource = new CancellationTokenSource();
                using var combinedCancellationSource   = CancellationTokenSource.CreateLinkedTokenSource(
                          cancellationToken,
                          currentRunCancellationSource.Token,
                          forceReload.Token);
                using var fileSetWatcher = new FileSetWatcher(fileSet, _reporter)
                      {
                          WatchForNewFiles = true
                      };

                try
                {
                    using var hotReload = new HotReload(_processRunner, _reporter);
                    await hotReload.InitializeAsync(context, cancellationToken);

                    var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                    var args        = string.Join(" ", processSpec.Arguments);
                    _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                    _reporter.Output("Started");

                    Task <FileItem?> fileSetTask;
                    Task             finishedTask;

                    while (true)
                    {
                        fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                        finishedTask = await Task.WhenAny(processTask, fileSetTask).WaitAsync(combinedCancellationSource.Token);

                        if (finishedTask != fileSetTask || fileSetTask.Result is not FileItem fileItem)
                        {
                            // The app exited.
                            break;
                        }
                        else
                        {
                            if (fileItem.IsNewFile)
                            {
                                if (MayRequireRecompilation(context, fileItem.FilePath))
                                {
                                    _reporter.Output($"New file: {fileItem.FilePath}. Rebuilding the application.");
                                    context.RequiresMSBuildRevaluation = true;
                                    break;
                                }

                                // If it's not a file that requires recompilation (such as a css, js etc) file, we do not have to do anything special.
                                continue;
                            }

                            _reporter.Output($"File changed: {fileItem.FilePath}.");
                            var start = Stopwatch.GetTimestamp();
                            if (await hotReload.TryHandleFileChange(context, fileItem, combinedCancellationSource.Token))
                            {
                                var totalTime = TimeSpan.FromTicks(Stopwatch.GetTimestamp() - start);
                                _reporter.Verbose($"Hot reload change handled in {totalTime.TotalMilliseconds}ms.");
                            }
                            else
                            {
                                _reporter.Output($"Unable to handle changes to {fileItem.FilePath}.");
                                await _rudeEditDialog.EvaluateAsync(combinedCancellationSource.Token);

                                break;
                            }
                        }
                    }

                    // Regardless of the which task finished first, make sure everything is cancelled
                    // and wait for dotnet to exit. We don't want orphan processes
                    currentRunCancellationSource.Cancel();

                    await Task.WhenAll(processTask, fileSetTask);

                    if (processTask.Result != 0 && finishedTask == processTask && !cancellationToken.IsCancellationRequested)
                    {
                        // Only show this error message if the process exited non-zero due to a normal process exit.
                        // Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user
                        _reporter.Error($"Exited with error code {processTask.Result}");
                    }
                    else
                    {
                        _reporter.Output("Exited");
                    }

                    if (finishedTask == processTask)
                    {
                        // Process exited. Redo evaludation
                        context.RequiresMSBuildRevaluation = true;
                        // Now wait for a file to change before restarting process
                        context.ChangedFile = await fileSetWatcher.GetChangedFileAsync(cancellationToken, () => _reporter.Warn("Waiting for a file to change before restarting dotnet..."));
                    }
                    else
                    {
                        Debug.Assert(finishedTask == fileSetTask);
                        var changedFile = fileSetTask.Result;
                        context.RequiresMSBuildRevaluation = changedFile.Value.IsNewFile;
                        context.ChangedFile = changedFile;
                    }
                }
Ejemplo n.º 6
0
 static FWorldDelegates()
 {
     HotReload.RegisterNativeDelegateManager(typeof(FWorldDelegates));
 }
Ejemplo n.º 7
0
 static FEditorDelegates()
 {
     HotReload.RegisterNativeDelegateManager(typeof(FEditorDelegates));
 }
Ejemplo n.º 8
0
 public App()
 {
     HotReload.Register();
     InitializeComponent();
     MainPage = new NavigationPage(new MainPage());
 }
Ejemplo n.º 9
0
 static UEngineDelegates()
 {
     HotReload.RegisterNativeDelegateManager(typeof(UEngineDelegates));
 }
Ejemplo n.º 10
0
        public async Task WatchAsync(DotNetWatchContext context, CancellationToken cancellationToken)
        {
            var processSpec = context.ProcessSpec;

            var forceReload             = new CancellationTokenSource();
            var hotReloadEnabledMessage = "Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload.";

            if (!_dotNetWatchOptions.NonInteractive)
            {
                _reporter.Output($"{hotReloadEnabledMessage}{Environment.NewLine}  {(_dotNetWatchOptions.SuppressEmojis ? string.Empty : "💡")} Press \"Ctrl + R\" to restart.", emoji: "🔥");

                _console.KeyPressed += (key) =>
                {
                    var modifiers = ConsoleModifiers.Control;
                    if ((key.Modifiers & modifiers) == modifiers && key.Key == ConsoleKey.R)
                    {
                        var cancellationTokenSource = Interlocked.Exchange(ref forceReload, new CancellationTokenSource());
                        cancellationTokenSource.Cancel();
                    }
                };
            }
            else
            {
                _reporter.Output(hotReloadEnabledMessage, emoji: "🔥");
            }

            while (true)
            {
                context.Iteration++;

                for (var i = 0; i < _filters.Length; i++)
                {
                    await _filters[i].ProcessAsync(context, cancellationToken);
                }

                processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);

                var fileSet = context.FileSet;
                if (fileSet == null)
                {
                    _reporter.Error("Failed to find a list of files to watch");
                    return;
                }

                if (!fileSet.Project.IsNetCoreApp60OrNewer())
                {
                    _reporter.Error($"Hot reload based watching is only supported in .NET 6.0 or newer apps. Update the project's launchSettings.json to disable this feature.");
                    return;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                if (context.Iteration == 0)
                {
                    ConfigureExecutable(context, processSpec);
                }

                using var currentRunCancellationSource = new CancellationTokenSource();
                using var combinedCancellationSource   = CancellationTokenSource.CreateLinkedTokenSource(
                          cancellationToken,
                          currentRunCancellationSource.Token,
                          forceReload.Token);
                using var fileSetWatcher = new HotReloadFileSetWatcher(fileSet, _reporter);

                try
                {
                    using var hotReload = new HotReload(_processRunner, _reporter);
                    await hotReload.InitializeAsync(context, cancellationToken);

                    var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
                    var args        = string.Join(" ", processSpec.Arguments);
                    _reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");

                    _reporter.Output("Started", emoji: "🚀");

                    Task <FileItem[]?> fileSetTask;
                    Task finishedTask;

                    while (true)
                    {
                        fileSetTask  = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
                        finishedTask = await Task.WhenAny(processTask, fileSetTask).WaitAsync(combinedCancellationSource.Token);

                        if (finishedTask != fileSetTask || fileSetTask.Result is not FileItem[] fileItems)
                        {
                            if (processTask.IsFaulted && finishedTask == processTask && !cancellationToken.IsCancellationRequested)
                            {
                                // Only show this error message if the process exited non-zero due to a normal process exit.
                                // Don't show this if dotnet-watch killed the inner process due to file change or CTRL+C by the user
                                _reporter.Error($"Application failed to start: {processTask.Exception?.InnerException?.Message}");
                            }
                            break;
                        }
                        else
                        {
                            if (MayRequireRecompilation(context, fileItems) is { } newFile)
                            {
                                _reporter.Output($"New file: {GetRelativeFilePath(newFile.FilePath)}. Rebuilding the application.");
                                break;
                            }