private static unsafe void OnRuntimeChanged() { if (SharedRuntimeState.IsActiveRuntime) { if (!File.Exists(mainAssemblyPath)) { SharedRuntimeState.SetHotReloadData(null); // Cancel the runtime swap SharedRuntimeState.Instance->NextRuntime = EDotNetRuntime.None; SharedRuntimeState.Instance->IsActiveRuntimeComplete = 0; return; } if (!mainContextRef.IsInvalid) { UnloadMainContext(); } Debug.Assert(mainContextRef.IsInvalid, "UnloadMainContext failed?"); Debug.Assert(!LoadAssemblyWithoutContexts, "Assembly context loading is required in order to swap runtimes"); SharedRuntimeState.Instance->IsActiveRuntimeComplete = 1; } else if (SharedRuntimeState.Instance->NextRuntime == SharedRuntimeState.CurrentRuntime) { SharedRuntimeState.Instance->ActiveRuntime = SharedRuntimeState.CurrentRuntime; SharedRuntimeState.Instance->NextRuntime = EDotNetRuntime.None; SharedRuntimeState.Instance->IsActiveRuntimeComplete = 0; ReloadMainContext(); } }
private static void UpdateAssemblyWatchers() { string[] assemblyPaths = SharedRuntimeState.GetHotReloadAssemblyPaths(); if (assemblyPaths != null) { HashSet <string> newAssemblyPaths = new HashSet <string>(); HashSet <string> removedAssemblyPaths = new HashSet <string>(); foreach (string assemblyPath in assemblyWatchers.Keys) { if (!assemblyPaths.Contains(assemblyPath)) { removedAssemblyPaths.Add(assemblyPath); } } foreach (string assemblyPath in assemblyPaths) { if (!assemblyWatchers.ContainsKey(assemblyPath)) { newAssemblyPaths.Add(assemblyPath); } } foreach (string assemblyPath in removedAssemblyPaths) { assemblyWatchers[assemblyPath].Dispose(); assemblyWatchers.Remove(assemblyPath); } foreach (string assemblyPath in newAssemblyPaths) { if (Directory.Exists(Path.GetDirectoryName(assemblyPath))) { FileSystemWatcher assemblyWatcher = new FileSystemWatcher(); assemblyWatcher.Path = Path.GetDirectoryName(assemblyPath); assemblyWatcher.Filter = Path.GetFileName(assemblyPath); assemblyWatcher.NotifyFilter = NotifyFilters.LastWrite;//NotifyFilters.CreationTime; assemblyWatcher.EnableRaisingEvents = true; assemblyWatcher.Changed += AssemblyWatcher_Changed; assemblyWatchers.Add(assemblyPath, assemblyWatcher); } } } if (assemblyWatchers.Count == 0) { SharedRuntimeState.LogWarning("No assembly watchers active for hotreload (\"USharpRuntime reload\" command can be used instead)"); } }
private static bool LoadWithoutUsingContexts() { try { AssemblyLoader loader = new AssemblyLoader( mainAssemblyPath, entryPointType, entryPointMethod, entryPointArg, false, Runtime.AssemblyContextRef.Invalid); loader.Load(); return(true); } catch (Exception e) { MessageBox("Failed to load assembly \"" + mainAssemblyPath + "\" " + Environment.NewLine + Environment.NewLine + e, errorMsgBoxTitle); SharedRuntimeState.SetHotReloadData(null); return(false); } }
private static unsafe void OnRuntimeChanged() { if (SharedRuntimeState.IsActiveRuntime) { if (!File.Exists(mainAssemblyPath)) { SharedRuntimeState.SetHotReloadData(null); // Cancel the runtime swap SharedRuntimeState.Instance->NextRuntime = EDotNetRuntime.None; SharedRuntimeState.Instance->IsActiveRuntimeComplete = 0; SharedRuntimeState.Instance->Reload = false; return; } if (SharedRuntimeState.Instance->Reload) { // This is a reload as opposed to a runtime swap, reload it now and return SharedRuntimeState.Instance->Reload = false; // Only reload if we are using assembly contexts (AppDomain / AssemblyLoadContext) if (!LoadAssemblyWithoutContexts) { ReloadMainContext(); } return; } if (!mainContextRef.IsInvalid) { UnloadMainContext(); } Debug.Assert(mainContextRef.IsInvalid, "UnloadMainContext failed?"); Debug.Assert(!LoadAssemblyWithoutContexts, "Assembly context loading is required in order to swap runtimes"); SharedRuntimeState.Instance->IsActiveRuntimeComplete = 1; } else if (SharedRuntimeState.Instance->NextRuntime == SharedRuntimeState.CurrentRuntime) { SharedRuntimeState.Instance->ActiveRuntime = SharedRuntimeState.CurrentRuntime; SharedRuntimeState.Instance->NextRuntime = EDotNetRuntime.None; SharedRuntimeState.Instance->IsActiveRuntimeComplete = 0; ReloadMainContext(); } }
public static void Unload() { DateTime beginUnloadTime = DateTime.Now; FMessage.Log("BeginUnload: " + beginUnloadTime.TimeOfDay); HotReload.OnUnload(); HotReload.Data.BeginUnloadTime = beginUnloadTime; byte[] data = HotReload.Data.Save(); HotReload.Data.Close(); SharedRuntimeState.SetHotReloadData(data); TimeSpan endUnloadTime = DateTime.Now.TimeOfDay; FMessage.Log("EndUnload: " + endUnloadTime + " (" + (endUnloadTime - beginUnloadTime.TimeOfDay) + ")"); }
public static int DllMain(string arg) { try { Args args = new Args(arg); if (!SharedRuntimeState.Initialized) { SharedRuntimeState.Initialize((IntPtr)args.GetInt64("RuntimeState")); AssemblyContextRef currentContext; AssemblyContextRef.TryParse(args.GetString("AssemblyContext"), out currentContext); AssemblyContext.Initialize(currentContext); CurrentAssemblyContext.Initialize(currentContext); } if (args.GetBool("Preloading")) { Preloading = true; IntPtr address = (IntPtr)args.GetInt64("RegisterFuncs"); if (address != IntPtr.Zero) { NativeFunctions.RegisterFunctions(address); Preloaded = true; } Preloading = false; return(0); } else { unsafe { SharedRuntimeState.Instance->ActiveRuntime = SharedRuntimeState.CurrentRuntime; } DateTime beginUnload = default(DateTime); TimeSpan beginReload = DateTime.Now.TimeOfDay; bool isReloading = false; using (var timing = HotReload.Timing.Create(HotReload.Timing.TotalLoadTime)) { using (var subTiming = HotReload.Timing.Create(HotReload.Timing.DataStore_Load)) { // If this is a hot-reload then set up the data store HotReload.Data = HotReload.DataStore.Load(SharedRuntimeState.GetHotReloadData()); beginUnload = HotReload.Data.BeginUnloadTime; } HotReload.IsReloading = args.GetBool("Reloading"); isReloading = HotReload.IsReloading; IntPtr address = (IntPtr)args.GetInt64("RegisterFuncs"); if (address != IntPtr.Zero) { NativeFunctions.RegisterFunctions(address); } } SharedRuntimeState.SetHotReloadAssemblyPaths(HotReloadAssemblyPaths); TimeSpan endTime = DateTime.Now.TimeOfDay; FMessage.Log("BeginReload: " + beginReload + " (BeginUnload-BeginReload: " + (beginReload - beginUnload.TimeOfDay) + ")"); FMessage.Log("EndReload: " + endTime + " (BeginUnload-EndReload: " + (endTime - beginUnload.TimeOfDay) + ")"); HotReload.Timing.Print(isReloading); HotReload.Timing.PrintAll(); return(0); } } catch (Exception e) { string exceptionStr = "Entry point exception (UnrealEngine.Runtime): " + e; if (SharedRuntimeState.Initialized) { SharedRuntimeState.LogError(exceptionStr); SharedRuntimeState.MessageBox(exceptionStr, "Error"); } return(1005);// AssemblyLoaderError.Exception } }
private static unsafe void MessageBox(string text, string title) { SharedRuntimeState.MessageBox(text, title); }
public static int DllMain(string arg) { try { Args args = new Args(arg); SharedRuntimeState.Initialize((IntPtr)args.GetInt64("RuntimeState")); Runtime.AssemblyContext.Initialize(); mainAssemblyPath = args.GetString("MainAssembly"); if (!string.IsNullOrEmpty(mainAssemblyPath)) { if (string.IsNullOrEmpty(mainAssemblyPath) || !File.Exists(mainAssemblyPath)) { return((int)AssemblyLoaderError.MainAssemblyNotFound); } mainAssemblyDirectory = Path.GetDirectoryName(mainAssemblyPath); } else { return((int)AssemblyLoaderError.MainAssemblyPathNotProvided); } IntPtr addTickerAddr = (IntPtr)args.GetInt64("AddTicker"); IntPtr isInGameThreadAddr = (IntPtr)args.GetInt64("IsInGameThread"); if (addTickerAddr == IntPtr.Zero || isInGameThreadAddr == IntPtr.Zero) { return((int)AssemblyLoaderError.GameThreadHelpersNull); } GameThreadHelper.Init(addTickerAddr, isInGameThreadAddr, OnRuntimeChanged); Debug.Assert(GameThreadHelper.IsInGameThread()); entryPointArg = arg; string currentAssemblyPath = Assembly.GetExecutingAssembly().Location; string currentAssemblyFileName = Path.GetFileNameWithoutExtension(currentAssemblyPath); currentAssemblyDirectory = Path.GetDirectoryName(currentAssemblyPath); if (!IsSameOrSubDirectory(currentAssemblyDirectory, mainAssemblyDirectory)) { return((int)AssemblyLoaderError.MainAssemblyPathNotProvided); } // If there is already a loaded runtime only do a pre-load if (SharedRuntimeState.GetLoadedRuntimes() != EDotNetRuntime.None) { Debug.Assert(mainContextRef.IsInvalid); // Make sure the main assembly path exists if (!File.Exists(mainAssemblyPath)) { return((int)AssemblyLoaderError.LoadFailed); } // Make sure we are using assmbly contexts loadding otherwise hotreload wont work which defeats the purpose of // using multiple runtimes if (LoadAssemblyWithoutContexts) { return((int)AssemblyLoaderError.LoadFailed); } // Preload now and then do a full load when NextRuntime is set to this runtime type PreloadNextContext(); // Watch for assembly changes (the paths should have been set up by the full load in the other runtime) UpdateAssemblyWatchers(); return(0); } unsafe { SharedRuntimeState.Instance->ActiveRuntime = SharedRuntimeState.CurrentRuntime; } bool loaded; if (LoadAssemblyWithoutContexts) { loaded = LoadWithoutUsingContexts(); } else { loaded = ReloadMainContext(); } if (!loaded) { unsafe { SharedRuntimeState.Instance->ActiveRuntime = EDotNetRuntime.None; } return((int)AssemblyLoaderError.LoadFailed); } } catch (Exception e) { string exceptionStr = "Entry point exception (Loader): " + e; if (SharedRuntimeState.Initialized) { SharedRuntimeState.LogError(exceptionStr); SharedRuntimeState.MessageBox(exceptionStr, errorMsgBoxTitle); } return((int)AssemblyLoaderError.Exception); } return(0); }
private static bool ReloadMainContext(bool threaded = true) { if (!GameThreadHelper.IsInGameThread()) { bool result = false; GameThreadHelper.Run(delegate { result = ReloadMainContext(); }); return(result); } if (!SharedRuntimeState.IsActiveRuntime) { return(false); } if (!File.Exists(mainAssemblyPath)) { SharedRuntimeState.SetHotReloadData(null); return(false); } if (!mainContextRef.IsInvalid) { UnloadMainContext(threaded); } string entryPointArgEx = entryPointArg; bool firstLoad = preloadContextWaitHandle == null; if (firstLoad) { PreloadNextContext(threaded); } else { entryPointArgEx += "|Reloading=true"; } preloadContextWaitHandle.WaitOne(Timeout.Infinite); preloadContextWaitHandle.Reset(); if (!preloadFailed) { Debug.Assert(!preloadedContextRef.IsInvalid, "Preloaded context shouldn't be invalid"); mainContextRef = preloadedContextRef; preloadedContextRef = Runtime.AssemblyContextRef.Invalid; entryPointArgEx += "|AssemblyContext=" + mainContextRef.Format(); try { AssemblyLoader loader = new AssemblyLoader(mainAssemblyPath, entryPointType, entryPointMethod, entryPointArgEx, false, mainContextRef); mainContextRef.DoCallBack(loader.Load); UpdateAssemblyWatchers(); } catch (Exception e) { MessageBox("Failed to create assembly context for \"" + mainAssemblyPath + "\" " + Environment.NewLine + Environment.NewLine + e, errorMsgBoxTitle); } } PreloadNextContext(threaded); SharedRuntimeState.SetHotReloadData(null); return(true); }
private static void AssemblyWatcher_Changed(object sender, FileSystemEventArgs e) { if (!SharedRuntimeState.IsActiveRuntime) { return; } lock (assemblyWatchers) { // Require 500 milliseconds between updates to avoid multiple reloads // // Note: This may result in a genuine change to be missed which means // that some user action will be needed // // PossibleFix: Make this delayed and only catch the latest one? (will slow // reloads based on delay interval) if (lastAssemblyUpdate < DateTime.Now - TimeSpan.FromMilliseconds(500) && !isAssemblyWatcherReloading) { bool complete = false; bool hasChanged = false; const int tries = 20; const int sleep = 40;// 40*20 = 800 milliseconds of attempts (due to file locks whilst being written by AssemblyRewriter) for (int i = 0; i < tries; i++) { try { if (File.Exists(e.FullPath)) { using (FileStream reader = File.Open(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { if (reader.Length > 8) { reader.Position = reader.Length - 8; byte[] buffer = new byte[8]; reader.Read(buffer, 0, buffer.Length); long signature = BitConverter.ToInt64(buffer, 0); if (signature == 3110675979262317867)// "+UEsRW++" { hasChanged = true; } } } } complete = true; break; } catch (IOException) { Thread.Sleep(sleep); } catch (Exception exception) { // Some unknown exception SharedRuntimeState.LogWarning("Exception whilst hotreloading '" + e.FullPath + "'\n" + exception); complete = true; break; } } if (hasChanged) { isAssemblyWatcherReloading = true; ReloadMainContext(); lastAssemblyUpdate = DateTime.Now; isAssemblyWatcherReloading = false; } else if (!complete) { SharedRuntimeState.LogWarning("Hotreload timed out for '" + e.FullPath + "'"); } } } }
private static void UpdateAssemblyWatchers() { string platformName = SharedRuntimeState.GetPlatformName(); if (SharedRuntimeState.CurrentRuntime == EDotNetRuntime.Mono && !string.IsNullOrEmpty(platformName) && platformName.ToLower() == "mac") { // libmono-native-compat.dylib (required by FileSystemWatcher) either doesn't load or is corrupted? // It crashes the Mono runtime when doing a symbol lookup (not sure which symbol) // // abort_with_payload // dyld::fastBindLazySymbol(ImageLoader**, unsigned long) // dyld_stub_binder // ---- managed frames ---- // mono_jit_runtime_invoke return; } string[] assemblyPaths = SharedRuntimeState.GetHotReloadAssemblyPaths(); if (assemblyPaths != null) { HashSet<string> newAssemblyPaths = new HashSet<string>(); HashSet<string> removedAssemblyPaths = new HashSet<string>(); foreach (string assemblyPath in assemblyWatchers.Keys) { if (!assemblyPaths.Contains(assemblyPath)) { removedAssemblyPaths.Add(assemblyPath); } } foreach (string assemblyPath in assemblyPaths) { if (!assemblyWatchers.ContainsKey(assemblyPath)) { newAssemblyPaths.Add(assemblyPath); } } foreach (string assemblyPath in removedAssemblyPaths) { assemblyWatchers[assemblyPath].Dispose(); assemblyWatchers.Remove(assemblyPath); } foreach (string assemblyPath in newAssemblyPaths) { if (Directory.Exists(Path.GetDirectoryName(assemblyPath))) { FileSystemWatcher assemblyWatcher = new FileSystemWatcher(); assemblyWatcher.Path = Path.GetDirectoryName(assemblyPath); assemblyWatcher.Filter = Path.GetFileName(assemblyPath); assemblyWatcher.NotifyFilter = NotifyFilters.LastWrite;//NotifyFilters.CreationTime; assemblyWatcher.EnableRaisingEvents = true; assemblyWatcher.Changed += AssemblyWatcher_Changed; assemblyWatchers.Add(assemblyPath, assemblyWatcher); } } } if (assemblyWatchers.Count == 0) { SharedRuntimeState.LogWarning("No assembly watchers active for hotreload (\"USharpRuntime reload\" command can be used instead)"); } }