From 271c0cbd5eaa7caf34cffd257f2bcddc607462c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E5=BD=A6=E8=B5=A4=E5=B1=8B=E5=85=88?= Date: Tue, 21 Feb 2023 01:28:31 +0900 Subject: [PATCH] Resource strings hot reload for plugins (only current, valid resources) --- Amethyst/Classes/Interfacing.cs | 36 ++++++++++++++++++++++++++++++- Amethyst/MVVM/PluginHost.cs | 7 +++--- Amethyst/MVVM/ServiceEndpoint.cs | 37 ++++++++++++++++++++++++++++++++ Amethyst/MVVM/TrackingDevice.cs | 33 ++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 5 deletions(-) diff --git a/Amethyst/Classes/Interfacing.cs b/Amethyst/Classes/Interfacing.cs index 23d50903..f945bf38 100644 --- a/Amethyst/Classes/Interfacing.cs +++ b/Amethyst/Classes/Interfacing.cs @@ -735,7 +735,24 @@ public static bool SetLocalizationResourcesRoot(string path, string guid) Logger.Info($"[Requested by device with GUID {guid}] " + "Successfully loaded language resources with key " + $"\"{AppData.Settings.AppLanguage}\"!"); - + + // Setup string hot reload watchdog + device.AssetsWatcher = new FileSystemWatcher + { + Path = device.LocalizationResourcesRoot.Directory, + NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.FileName | + NotifyFilters.LastWrite | NotifyFilters.DirectoryName, + IncludeSubdirectories = true, + Filter = "*.json", + EnableRaisingEvents = true + }; + + // Add event handlers : local + device.AssetsWatcher.Changed += device.AssetsChanged; + device.AssetsWatcher.Created += device.AssetsChanged; + device.AssetsWatcher.Deleted += device.AssetsChanged; + device.AssetsWatcher.Renamed += device.AssetsChanged; + return true; // Winning it, yay! } @@ -761,6 +778,23 @@ public static bool SetLocalizationResourcesRoot(string path, string guid) "Successfully loaded language resources with key " + $"\"{AppData.Settings.AppLanguage}\"!"); + // Setup string hot reload watchdog + service.AssetsWatcher = new FileSystemWatcher + { + Path = service.LocalizationResourcesRoot.Directory, + NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.FileName | + NotifyFilters.LastWrite | NotifyFilters.DirectoryName, + IncludeSubdirectories = true, + Filter = "*.json", + EnableRaisingEvents = true + }; + + // Add event handlers : local + service.AssetsWatcher.Changed += service.AssetsChanged; + service.AssetsWatcher.Created += service.AssetsChanged; + service.AssetsWatcher.Deleted += service.AssetsChanged; + service.AssetsWatcher.Renamed += service.AssetsChanged; + return true; // Winning it, yay! } diff --git a/Amethyst/MVVM/PluginHost.cs b/Amethyst/MVVM/PluginHost.cs index 8f11ec45..bd8a8777 100644 --- a/Amethyst/MVVM/PluginHost.cs +++ b/Amethyst/MVVM/PluginHost.cs @@ -18,7 +18,6 @@ using Amethyst.Utils; using AmethystSupport; using Microsoft.AppCenter.Crashes; -using Microsoft.UI.Xaml; using static Amethyst.Classes.Interfacing; namespace Amethyst.MVVM; @@ -481,7 +480,7 @@ public static bool AddPlugin(this ICollection collection, DirectoryInfo it if (!assemblyCatalog.Parts.Any(x => x.ExportDefinitions .Any(y => y.ContractName == typeof(ITrackingDevice).FullName || y.ContractName == typeof(IServiceEndpoint).FullName))) continue; - + collection.Add((T)(object)assemblyCatalog); return true; // This plugin is probably supported, yay! } @@ -499,7 +498,7 @@ public static bool AddPlugin(this ICollection collection, DirectoryInfo it TrackingDevices.LoadAttemptedPluginsList.Add(new LoadAttemptedPlugin { Name = $"{item.Name}/{fileInfo.Name}", - Error = e.Message, Folder = item.FullName, + Error = $"{e.Message}\n\n{e.StackTrace}", Folder = item.FullName, Status = TrackingDevices.PluginLoadError.NoPluginDll }); @@ -520,7 +519,7 @@ public static bool AddPlugin(this ICollection collection, DirectoryInfo it TrackingDevices.LoadAttemptedPluginsList.Add(new LoadAttemptedPlugin { Name = $"{item.Name}/{fileInfo.Name}", - Error = e.Message, Folder = item.FullName, + Error = $"{e.Message}\n\n{e.StackTrace}", Folder = item.FullName, Status = TrackingDevices.PluginLoadError.NoPluginDll }); diff --git a/Amethyst/MVVM/ServiceEndpoint.cs b/Amethyst/MVVM/ServiceEndpoint.cs index 4a4479fa..ab869ae1 100644 --- a/Amethyst/MVVM/ServiceEndpoint.cs +++ b/Amethyst/MVVM/ServiceEndpoint.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Numerics; using System.Threading.Tasks; using Windows.Data.Json; using Amethyst.Classes; using Amethyst.Plugins.Contract; +using Amethyst.Utils; +using System.Linq; namespace Amethyst.MVVM; @@ -113,6 +116,9 @@ public bool AutoCloseAmethyst // You'll need to provide this to support automatic calibration public (Vector3 Position, Quaternion Orientation)? HeadsetPose => Service.HeadsetPose; + // Hot reload handler + public FileSystemWatcher AssetsWatcher { get; set; } + // Property changed event public event PropertyChangedEventHandler PropertyChanged; @@ -191,4 +197,35 @@ public void OnPropertyChanged(string propName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); } + + public void AssetsChanged(object o, FileSystemEventArgs fileSystemEventArgs) + { + Shared.Main.DispatcherQueue.TryEnqueue(() => + { + // Hot reload device string resources + Logger.Info($"Service ({Guid}, {Name}) assets have changed, reloading!"); + Logger.Info($"What happened: {fileSystemEventArgs.ChangeType}"); + Logger.Info($"Where: {fileSystemEventArgs.FullPath} ({fileSystemEventArgs.Name})"); + + // Sanity check + if (!Shared.Main.MainWindowLoaded) return; + + // Reload plugins' language resources + Interfacing.Plugins.SetLocalizationResourcesRoot( + LocalizationResourcesRoot.Directory, Guid); + + // Reload everything we can + Shared.Devices.DevicesJointsValid = false; + Service.OnLoad(); // Reload settings + + // Force refresh all the valid pages + Shared.Events.RequestInterfaceReload(false); + + if (AppData.Settings.ServiceEndpointGuid == Guid) + Interfacing.UpdateServerStatus(); // Refresh + + // We're done with our changes now! + Shared.Devices.DevicesJointsValid = true; + }); + } } \ No newline at end of file diff --git a/Amethyst/MVVM/TrackingDevice.cs b/Amethyst/MVVM/TrackingDevice.cs index c50ed077..bfcfc696 100644 --- a/Amethyst/MVVM/TrackingDevice.cs +++ b/Amethyst/MVVM/TrackingDevice.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Linq; using Windows.Data.Json; using Amethyst.Classes; using Amethyst.Plugins.Contract; +using Amethyst.Utils; using static Amethyst.Classes.Interfacing; namespace Amethyst.MVVM; @@ -113,6 +115,9 @@ public TrackingDevice(string name, string guid, string path, ITrackingDevice dev // Is the device used as anything, quick check? public bool IsUsed => IsBase || IsOverride; + // Hot reload handler + public FileSystemWatcher AssetsWatcher { get; set; } + // Property changed event public event PropertyChangedEventHandler PropertyChanged; @@ -202,4 +207,32 @@ public void RefreshWatchHandlers() Device.TrackedJoints.CollectionChanged -= TrackedJoints_CollectionChanged; Device.TrackedJoints.CollectionChanged += TrackedJoints_CollectionChanged; } + + public void AssetsChanged(object o, FileSystemEventArgs fileSystemEventArgs) + { + Shared.Main.DispatcherQueue.TryEnqueue(() => + { + // Hot reload device string resources + Logger.Info($"Device ({Guid}, {Name}) assets have changed, reloading!"); + Logger.Info($"What happened: {fileSystemEventArgs.ChangeType}"); + Logger.Info($"Where: {fileSystemEventArgs.FullPath} ({fileSystemEventArgs.Name})"); + + // Sanity check + if (!Shared.Main.MainWindowLoaded) return; + + // Reload plugins' language resources + Interfacing.Plugins.SetLocalizationResourcesRoot( + LocalizationResourcesRoot.Directory, Guid); + + // Reload everything we can + Shared.Devices.DevicesJointsValid = false; + Device.OnLoad(); // Reload settings + + // Force refresh all the valid pages + Shared.Events.RequestInterfaceReload(false); + + // We're done with our changes now! + Shared.Devices.DevicesJointsValid = true; + }); + } } \ No newline at end of file