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; } }
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(); } }
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(); } }
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; }
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; } }
static FWorldDelegates() { HotReload.RegisterNativeDelegateManager(typeof(FWorldDelegates)); }
static FEditorDelegates() { HotReload.RegisterNativeDelegateManager(typeof(FEditorDelegates)); }
public App() { HotReload.Register(); InitializeComponent(); MainPage = new NavigationPage(new MainPage()); }
static UEngineDelegates() { HotReload.RegisterNativeDelegateManager(typeof(UEngineDelegates)); }
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; }