From cb69b3839da052c303beab124b81d9d64991a80a Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 10 Dec 2024 19:00:06 +0000 Subject: [PATCH 01/49] Rework initial load to always update first --- VRCOSC.App/AppManager.cs | 5 -- VRCOSC.App/UI/Windows/MainWindow.xaml | 2 +- VRCOSC.App/UI/Windows/MainWindow.xaml.cs | 66 +++++++++++++++++------- VRCOSC.App/Updater/VelopackUpdater.cs | 16 +++--- 4 files changed, 53 insertions(+), 36 deletions(-) diff --git a/VRCOSC.App/AppManager.cs b/VRCOSC.App/AppManager.cs index bab6d54b..792fcadb 100644 --- a/VRCOSC.App/AppManager.cs +++ b/VRCOSC.App/AppManager.cs @@ -33,7 +33,6 @@ using VRCOSC.App.Startup; using VRCOSC.App.UI.Themes; using VRCOSC.App.UI.Windows; -using VRCOSC.App.Updater; using VRCOSC.App.Utils; using VRCOSC.App.VRChatAPI; using Module = VRCOSC.App.SDK.Modules.Module; @@ -59,7 +58,6 @@ public class AppManager public Observable State { get; } = new(AppManagerState.Stopped); public Observable ProxyTheme { get; } = new(Theme.Dark); - public VelopackUpdater VelopackUpdater = null!; public ConnectionManager ConnectionManager = null!; public VRChatOscClient VRChatOscClient = null!; public VRChatClient VRChatClient = null!; @@ -87,7 +85,6 @@ public void Initialise() { SettingsManager.GetInstance().GetObservable(VRCOSCSetting.Theme).Subscribe(theme => ProxyTheme.Value = theme, true); - VelopackUpdater = new VelopackUpdater(); ConnectionManager = new ConnectionManager(); VRChatOscClient = new VRChatOscClient(); VRChatClient = new VRChatClient(VRChatOscClient); @@ -163,8 +160,6 @@ public void InitialLoadComplete() openvrUpdateTask = new Repeater(updateOVRClient); openvrUpdateTask.Start(TimeSpan.FromSeconds(1d / 60d)); - - VelopackUpdater.ShowUpdateIfAvailable(); } private void updateOVRClient() diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml b/VRCOSC.App/UI/Windows/MainWindow.xaml index ad100e71..3a41e082 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml @@ -134,7 +134,7 @@ - + diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs index c19bbb43..d53f5924 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs @@ -50,40 +50,68 @@ public partial class MainWindow { public static MainWindow GetInstance() => (MainWindow)Application.Current.MainWindow; - public readonly PackagesView PackagesView; - public readonly ModulesView ModulesView; - public readonly RouterView RouterView; - public readonly SettingsView SettingsView; - public readonly ChatBoxView ChatBoxView; - public readonly StartupView StartupView; - public readonly RunView RunView; - public readonly AppDebugView AppDebugView; - public readonly ProfilesView ProfilesView; - public readonly AppSettingsView AppSettingsView; - public readonly InformationView InformationView; + public PackagesView PackagesView = null!; + public ModulesView ModulesView = null!; + public RouterView RouterView = null!; + public SettingsView SettingsView = null!; + public ChatBoxView ChatBoxView = null!; + public StartupView StartupView = null!; + public RunView RunView = null!; + public AppDebugView AppDebugView = null!; + public ProfilesView ProfilesView = null!; + public AppSettingsView AppSettingsView = null!; + public InformationView InformationView = null!; private readonly Storage storage = AppManager.GetInstance().Storage; + private VelopackUpdater velopackUpdater = null!; public Observable ShowAppDebug { get; } = new(); public Observable ShowRouter { get; } = new(); public MainWindow() { + InitializeComponent(); + DataContext = this; + backupV1Files(); + setupTrayIcon(); + startApp(); + } + private async void startApp() + { SettingsManager.GetInstance().Load(); + + velopackUpdater = new VelopackUpdater(); + + if (!velopackUpdater.IsInstalled()) + { + Logger.Log("Portable app detected. Cancelling update check"); + } + else + { + var updateLoadingAction = new DynamicAsyncProgressAction("Gathering remote info", () => velopackUpdater.CheckForUpdatesAsync()); + await ShowLoadingOverlay("Checking For Updates", updateLoadingAction); + + if (velopackUpdater.IsUpdateAvailable) + { + await velopackUpdater.ShowUpdateIfAvailable(); + return; + } + + await Task.Delay(200); + } + + Logger.Log("No updates. Proceeding with loading"); + SettingsManager.GetInstance().GetObservable(VRCOSCSetting.EnableAppDebug).Subscribe(newValue => ShowAppDebug.Value = newValue, true); SettingsManager.GetInstance().GetObservable(VRCOSCSetting.EnableRouter).Subscribe(newValue => ShowRouter.Value = newValue, true); AppManager.GetInstance().Initialise(); - InitializeComponent(); - DataContext = this; - var installedUpdateChannel = SettingsManager.GetInstance().GetValue(VRCOSCMetadata.InstalledUpdateChannel); Title = installedUpdateChannel == UpdateChannel.Beta ? $"{AppManager.APP_NAME} {AppManager.Version} BETA" : $"{AppManager.APP_NAME} {AppManager.Version}"; - setupTrayIcon(); copyOpenVrFiles(); PackagesView = new PackagesView(); @@ -100,7 +128,7 @@ public MainWindow() setContent(ModulesView); - load(); + await load(); } private void backupV1Files() @@ -134,11 +162,9 @@ private void backupV1Files() } } - private void load() + private async Task load() { var loadingAction = new CompositeProgressAction(); - - loadingAction.AddAction(new DynamicAsyncProgressAction("Checking for updates", () => AppManager.GetInstance().VelopackUpdater.CheckForUpdatesAsync())); loadingAction.AddAction(PackageManager.GetInstance().Load()); var installedVersion = SettingsManager.GetInstance().GetValue(VRCOSCMetadata.InstalledVersion); @@ -184,7 +210,7 @@ private void load() }); }; - _ = ShowLoadingOverlay("Welcome to VRCOSC", loadingAction); + await ShowLoadingOverlay("Welcome to VRCOSC", loadingAction); } private void copyOpenVrFiles() diff --git a/VRCOSC.App/Updater/VelopackUpdater.cs b/VRCOSC.App/Updater/VelopackUpdater.cs index 1e324408..8d95e5fe 100644 --- a/VRCOSC.App/Updater/VelopackUpdater.cs +++ b/VRCOSC.App/Updater/VelopackUpdater.cs @@ -27,7 +27,7 @@ public VelopackUpdater() { constructUpdateManager(); await CheckForUpdatesAsync(); - ShowUpdateIfAvailable(); + await ShowUpdateIfAvailable(); }); constructUpdateManager(); @@ -45,23 +45,19 @@ private void constructUpdateManager() }); } - public async Task CheckForUpdatesAsync() + public bool IsInstalled() => updateManager.IsInstalled; + + public async Task CheckForUpdatesAsync() { Logger.Log("Checking for update"); - if (!updateManager.IsInstalled) - { - Logger.Log("Portable app detected. Cancelling update check"); - updateInfo = null; - return false; - } + if (!IsInstalled()) return; updateInfo = await updateManager.CheckForUpdatesAsync(); Logger.Log(updateInfo is null ? "No updates available" : "Updates available"); - return updateInfo is not null; } - public async void ShowUpdateIfAvailable() + public async Task ShowUpdateIfAvailable() { if (!IsUpdateAvailable) return; From a50e7ae32956b722d2152466467555b873d585b3 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 10 Dec 2024 19:00:55 +0000 Subject: [PATCH 02/49] Log more serialisation results --- VRCOSC.App/Packages/PackageManager.cs | 2 -- VRCOSC.App/Profiles/ProfileManager.cs | 7 +++++-- .../Profiles/Serialisation/ProfileManagerSerialiser.cs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/VRCOSC.App/Packages/PackageManager.cs b/VRCOSC.App/Packages/PackageManager.cs index b31c9d6c..63ae342f 100644 --- a/VRCOSC.App/Packages/PackageManager.cs +++ b/VRCOSC.App/Packages/PackageManager.cs @@ -62,8 +62,6 @@ public CompositeProgressAction Load() return RefreshAllSources(CacheExpireTime <= DateTime.Now); } - public void Serialise() => serialisationManager.Serialise(); - public CompositeProgressAction RefreshAllSources(bool forceRemoteGrab) { var packageLoadAction = new CompositeProgressAction(); diff --git a/VRCOSC.App/Profiles/ProfileManager.cs b/VRCOSC.App/Profiles/ProfileManager.cs index 186599b5..291df1e0 100644 --- a/VRCOSC.App/Profiles/ProfileManager.cs +++ b/VRCOSC.App/Profiles/ProfileManager.cs @@ -56,11 +56,12 @@ public ProfileManager() } public void Serialise() => serialisationManager.Serialise(); - public void Deserialise() => serialisationManager.Deserialise(); public void Load() { - serialisationManager.Deserialise(false); + Logger.Log("Loading profiles"); + var deserialisationResult = serialisationManager.Deserialise(false); + Logger.Log($"Profiles ended in {deserialisationResult}"); checkForDefault(); @@ -116,6 +117,8 @@ private void checkForDefault() { if (Profiles.Any()) return; + Logger.Log("Setting up default profile"); + var defaultProfile = new Profile { Name = { Value = "Default" } diff --git a/VRCOSC.App/Profiles/Serialisation/ProfileManagerSerialiser.cs b/VRCOSC.App/Profiles/Serialisation/ProfileManagerSerialiser.cs index f503cab2..64ba07f3 100644 --- a/VRCOSC.App/Profiles/Serialisation/ProfileManagerSerialiser.cs +++ b/VRCOSC.App/Profiles/Serialisation/ProfileManagerSerialiser.cs @@ -38,4 +38,4 @@ protected override bool ExecuteAfterDeserialisation(SerialisableProfileManager d return false; } -} +} \ No newline at end of file From 29c74197349a27461bbaf1e3a336e5decfa94f28 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 10 Dec 2024 19:01:59 +0000 Subject: [PATCH 03/49] Fix importing files not getting the correct version and migrate to UTF8 --- VRCOSC.App/Serialisation/ISerialiser.cs | 4 +- .../Serialisation/SerialisationManager.cs | 4 +- VRCOSC.App/Serialisation/Serialiser.cs | 44 ++++++++++++++++--- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/VRCOSC.App/Serialisation/ISerialiser.cs b/VRCOSC.App/Serialisation/ISerialiser.cs index d8bf6648..4ff337b6 100644 --- a/VRCOSC.App/Serialisation/ISerialiser.cs +++ b/VRCOSC.App/Serialisation/ISerialiser.cs @@ -6,7 +6,7 @@ namespace VRCOSC.App.Serialisation; public interface ISerialiser { public bool DoesFileExist(); - public bool TryGetVersion(out int? version); + public bool TryGetVersion(out int? version, string filePathOverride = ""); public DeserialisationResult Deserialise(string filePathOverride); public SerialisationResult Serialise(); -} +} \ No newline at end of file diff --git a/VRCOSC.App/Serialisation/SerialisationManager.cs b/VRCOSC.App/Serialisation/SerialisationManager.cs index bca0045c..0ef80c2e 100644 --- a/VRCOSC.App/Serialisation/SerialisationManager.cs +++ b/VRCOSC.App/Serialisation/SerialisationManager.cs @@ -50,13 +50,13 @@ public DeserialisationResult Deserialise(bool serialiseOnFail = true, string fil foreach (var (version, serialiser) in serialisers.OrderBy(pair => pair.Key)) { - if (!serialiser.TryGetVersion(out var foundVersion)) continue; + if (!serialiser.TryGetVersion(out var foundVersion, filePathOverride)) continue; if (version != foundVersion) continue; return deserialise(serialiser, filePathOverride); } - // Since we've got to this point that means the file is corrupt + // At this point the file is corrupt return DeserialisationResult.CorruptFile; } diff --git a/VRCOSC.App/Serialisation/Serialiser.cs b/VRCOSC.App/Serialisation/Serialiser.cs index 762c87a6..0f7b3832 100644 --- a/VRCOSC.App/Serialisation/Serialiser.cs +++ b/VRCOSC.App/Serialisation/Serialiser.cs @@ -36,19 +36,21 @@ protected Serialiser(Storage storage, TReference reference) public string FullPath => baseStorage.GetStorageForDirectory(Directory).GetFullPath(FileName); public bool DoesFileExist() => baseStorage.GetStorageForDirectory(Directory).Exists(FileName); - public bool TryGetVersion([NotNullWhen(true)] out int? version) + public bool TryGetVersion([NotNullWhen(true)] out int? version, string filePathOverride = "") { - if (!DoesFileExist()) + if (string.IsNullOrEmpty(filePathOverride) && !DoesFileExist()) { version = null; return false; } + var filePath = string.IsNullOrEmpty(filePathOverride) ? FullPath : filePathOverride; + try { lock (serialisationLock) { - var data = JsonConvert.DeserializeObject(Encoding.Unicode.GetString(File.ReadAllBytes(FullPath))); + var data = performDeserialisation(filePath); if (data is null) { @@ -75,7 +77,7 @@ public DeserialisationResult Deserialise(string filePathOverride = "") { lock (serialisationLock) { - var data = JsonConvert.DeserializeObject(Encoding.Unicode.GetString(File.ReadAllBytes(filePath))); + var data = performDeserialisation(filePath); if (data is null) return DeserialisationResult.CorruptFile; if (ExecuteAfterDeserialisation(data)) Serialise(); @@ -97,7 +99,7 @@ public SerialisationResult Serialise() lock (serialisationLock) { var data = (TSerialisable)Activator.CreateInstance(typeof(TSerialisable), Reference)!; - var bytes = Encoding.Unicode.GetBytes(JsonConvert.SerializeObject(data, Formatting.Indented)); + var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data, Formatting.Indented)); using var stream = baseStorage.GetStorageForDirectory(Directory).CreateFileSafely(FileName); stream.Write(bytes); } @@ -111,6 +113,36 @@ public SerialisationResult Serialise() } } + private T? performDeserialisation(string filePath) where T : class + { + var bytes = File.ReadAllBytes(filePath); + + if (bytes is [0xFF, 0xFE, ..]) + { + bytes = bytes[2..]; + Logger.Log("Skipping BOM"); + } + + try + { + // read legacy files encoded as UTF-16 + return JsonConvert.DeserializeObject(Encoding.Unicode.GetString(bytes)); + } + catch + { + try + { + // read converted files encoded as UTF-8 + return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bytes)); + } + catch + { + Logger.Log($"'{filePath}' was unable to be read as UTF8"); + return null; + } + } + } + /// /// Executed after the deserialisation is complete /// @@ -158,4 +190,4 @@ protected bool TryConvertToTargetType(object? value, Type targetType, out object return false; } } -} +} \ No newline at end of file From 5ed774ea234e8587d3a8501c51af82f3a2f44871 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 10 Dec 2024 19:04:03 +0000 Subject: [PATCH 04/49] Change how filtering in the clip edit window works --- VRCOSC.App/ChatBox/Clips/Clip.cs | 10 +- VRCOSC.App/ChatBox/Clips/ClipEvent.cs | 25 +++-- VRCOSC.App/ChatBox/Clips/ClipState.cs | 26 +++-- VRCOSC.App/Settings/SettingsManager.cs | 4 +- .../ChatBox/ChatBoxClipEditWindow.xaml | 104 +++++++++--------- .../ChatBox/ChatBoxClipEditWindow.xaml.cs | 44 +++++--- 6 files changed, 128 insertions(+), 85 deletions(-) diff --git a/VRCOSC.App/ChatBox/Clips/Clip.cs b/VRCOSC.App/ChatBox/Clips/Clip.cs index 3320fb7b..c87a4a8e 100644 --- a/VRCOSC.App/ChatBox/Clips/Clip.cs +++ b/VRCOSC.App/ChatBox/Clips/Clip.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using VRCOSC.App.ChatBox.Clips.Variables; using VRCOSC.App.Modules; +using VRCOSC.App.Settings; using VRCOSC.App.Utils; namespace VRCOSC.App.ChatBox.Clips; @@ -47,7 +48,7 @@ public Dictionary> UIVariables ChatBoxManager.GetInstance().VariableReferences.Where(clipVariableReference => clipVariableReference.ModuleID is null).OrderBy(reference => reference.DisplayName.Value).ForEach(clipVariableReference => builtInVariables.Add(clipVariableReference)); finalDict.Add("Built-In", builtInVariables); - var modules = LinkedModules.Select(moduleID => ModuleManager.GetInstance().GetModuleOfID(moduleID)).OrderBy(module => module.Title); + var modules = LinkedModules.Select(moduleID => ModuleManager.GetInstance().GetModuleOfID(moduleID)).Where(module => module.Enabled.Value || !SettingsManager.GetInstance().GetValue(VRCOSCSetting.FilterByEnabledModules)).OrderBy(module => module.Title); foreach (var module in modules) { @@ -58,6 +59,11 @@ public Dictionary> UIVariables } } + public void UpdateUI() + { + OnPropertyChanged(nameof(UIVariables)); + } + public bool HasStates => States.Any(); public bool HasEvents => Events.Any(); diff --git a/VRCOSC.App/ChatBox/Clips/ClipEvent.cs b/VRCOSC.App/ChatBox/Clips/ClipEvent.cs index de30676c..46af9162 100644 --- a/VRCOSC.App/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.App/ChatBox/Clips/ClipEvent.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -47,13 +47,22 @@ public override bool ShouldBeVisible { get { - if (!SettingsManager.GetInstance().GetValue(VRCOSCSetting.ShowRelevantElementsOnly)) return true; - - var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip; - Debug.Assert(selectedClip is not null); - - var enabledModuleIDs = ModuleManager.GetInstance().GetEnabledModuleIDs().Where(moduleID => selectedClip.LinkedModules.Contains(moduleID)).OrderBy(moduleID => moduleID); - return enabledModuleIDs.Contains(ModuleID); + if (SettingsManager.GetInstance().GetValue(VRCOSCSetting.FilterByEnabledModules)) + { + var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip; + Debug.Assert(selectedClip is not null); + + var enabledModuleIDs = ModuleManager.GetInstance().GetEnabledModuleIDs().Where(moduleID => selectedClip.LinkedModules.Contains(moduleID)).OrderBy(moduleID => moduleID); + return enabledModuleIDs.Contains(ModuleID); + } + else + { + var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip; + Debug.Assert(selectedClip is not null); + + var enabledModuleIDs = ModuleManager.GetInstance().Modules.Values.SelectMany(moduleList => moduleList).Where(module => selectedClip.LinkedModules.Contains(module.FullID)).Select(module => module.FullID).OrderBy(moduleID => moduleID); + return enabledModuleIDs.Contains(ModuleID); + } } } diff --git a/VRCOSC.App/ChatBox/Clips/ClipState.cs b/VRCOSC.App/ChatBox/Clips/ClipState.cs index ecb18f6c..c23314db 100644 --- a/VRCOSC.App/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.App/ChatBox/Clips/ClipState.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -54,14 +54,24 @@ public override bool ShouldBeVisible { if (IsBuiltIn) return true; - if (!SettingsManager.GetInstance().GetValue(VRCOSCSetting.ShowRelevantElementsOnly)) return true; - - var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip; - Debug.Assert(selectedClip is not null); + if (SettingsManager.GetInstance().GetValue(VRCOSCSetting.FilterByEnabledModules)) + { + var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip; + Debug.Assert(selectedClip is not null); + + var enabledModuleIDs = ModuleManager.GetInstance().GetEnabledModuleIDs().Where(moduleID => selectedClip.LinkedModules.Contains(moduleID)).OrderBy(moduleID => moduleID); + var clipStateModuleIDs = States.Select(pair => pair.Key).OrderBy(s => s); + return enabledModuleIDs.SequenceEqual(clipStateModuleIDs); + } + else + { + var selectedClip = MainWindow.GetInstance().ChatBoxView.SelectedClip; + Debug.Assert(selectedClip is not null); - var enabledModuleIDs = ModuleManager.GetInstance().GetEnabledModuleIDs().Where(moduleID => selectedClip.LinkedModules.Contains(moduleID)).OrderBy(moduleID => moduleID); - var clipStateModuleIDs = States.Select(pair => pair.Key).OrderBy(s => s); - return enabledModuleIDs.SequenceEqual(clipStateModuleIDs); + var enabledModuleIDs = ModuleManager.GetInstance().Modules.Values.SelectMany(moduleList => moduleList).Where(module => selectedClip.LinkedModules.Contains(module.FullID)).Select(module => module.FullID).OrderBy(moduleID => moduleID); + var clipStateModuleIDs = States.Select(pair => pair.Key).OrderBy(s => s); + return enabledModuleIDs.SequenceEqual(clipStateModuleIDs); + } } } diff --git a/VRCOSC.App/Settings/SettingsManager.cs b/VRCOSC.App/Settings/SettingsManager.cs index 032cb22e..b34cedda 100644 --- a/VRCOSC.App/Settings/SettingsManager.cs +++ b/VRCOSC.App/Settings/SettingsManager.cs @@ -62,7 +62,7 @@ private void writeDefaults() setDefault(VRCOSCSetting.UpdateChannel, UpdateChannel.Live); setDefault(VRCOSCSetting.ChatBoxSendInterval, 1500); setDefault(VRCOSCSetting.ChatBoxWorldBlacklist, true); - setDefault(VRCOSCSetting.ShowRelevantElementsOnly, true); + setDefault(VRCOSCSetting.FilterByEnabledModules, true); setDefault(VRCOSCSetting.Theme, Theme.Dark); setDefault(VRCOSCSetting.OutgoingEndpoint, "127.0.0.1:9000"); setDefault(VRCOSCSetting.IncomingEndpoint, "127.0.0.1:9001"); @@ -129,7 +129,7 @@ public enum VRCOSCSetting UpdateChannel, ChatBoxSendInterval, ChatBoxWorldBlacklist, - ShowRelevantElementsOnly, + FilterByEnabledModules, Theme, OutgoingEndpoint, IncomingEndpoint, diff --git a/VRCOSC.App/UI/Windows/ChatBox/ChatBoxClipEditWindow.xaml b/VRCOSC.App/UI/Windows/ChatBox/ChatBoxClipEditWindow.xaml index 261a844b..aa7f9fec 100644 --- a/VRCOSC.App/UI/Windows/ChatBox/ChatBoxClipEditWindow.xaml +++ b/VRCOSC.App/UI/Windows/ChatBox/ChatBoxClipEditWindow.xaml @@ -10,7 +10,6 @@ xmlns:chatBox1="clr-namespace:VRCOSC.App.UI.Windows.ChatBox" mc:Ignorable="d" Width="1366" Height="768" MinWidth="1200" MinHeight="540" - Loaded="OnLoaded" Closed="OnClosed"> @@ -19,6 +18,7 @@ Colour2="{StaticResource CBackground2}" /> + @@ -32,54 +32,58 @@ Module Selection <--> - - - - - - - - - - - - - + + + + + + + + + + + + + + + + Center <--> @@ -580,7 +584,7 @@ SettingsManager.GetInstance().GetValue(VRCOSCSetting.ShowRelevantElementsOnly); + get => SettingsManager.GetInstance().GetValue(VRCOSCSetting.FilterByEnabledModules); set { - SettingsManager.GetInstance().GetObservable(VRCOSCSetting.ShowRelevantElementsOnly).Value = value; + SettingsManager.GetInstance().GetObservable(VRCOSCSetting.FilterByEnabledModules).Value = value; ReferenceClip.States.ForEach(clipState => clipState.UpdateUI()); ReferenceClip.Events.ForEach(clipEvent => clipEvent.UpdateUI()); + ReferenceClip.UpdateUI(); } } @@ -40,22 +41,20 @@ public ChatBoxClipEditWindow(Clip referenceClip) DataContext = referenceClip; ReferenceClip = referenceClip; - ModulesList.ItemsSource = ModuleManager.GetInstance().Modules.Values.SelectMany(moduleList => moduleList).Where(module => ChatBoxManager.GetInstance().DoesModuleHaveStates(module.FullID)).OrderBy(module => module.IsRemote).ThenBy(module => module.PackageID).ThenBy(module => module.Title); - ShowRelevantModulesCheckBox.DataContext = this; + SettingsManager.GetInstance().GetObservable(VRCOSCSetting.FilterByEnabledModules).Subscribe(() => + { + var selectableModules = ModuleManager.GetInstance().Modules.Values.SelectMany(moduleList => moduleList) + .Where(module => ChatBoxManager.GetInstance().DoesModuleHaveStates(module.FullID)) + .Where(module => module.Enabled.Value || !SettingsManager.GetInstance().GetValue(VRCOSCSetting.FilterByEnabledModules)) + .OrderBy(module => module.IsRemote).ThenBy(module => module.PackageID).ThenBy(module => module.Title).ToList(); - ReferenceClip.Name.Subscribe(newName => Title = $"Editing {newName} Clip", true); - } + ModulesList.ItemsSource = selectableModules; + SelectListPrompt.Visibility = selectableModules.Count == 0 ? Visibility.Visible : Visibility.Collapsed; + }, true); - private void OnLoaded(object sender, RoutedEventArgs e) - { - for (var index = 0; index < ModulesList.Items.Count; index++) - { - var listViewItem = ModulesList.ItemContainerGenerator.ContainerFromIndex(index); - var module = (Module)ModulesList.Items[index]; + ShowRelevantModulesCheckBox.DataContext = this; - var isLinkedCheckBox = listViewItem.FindVisualChild("IsLinkedCheckBox")!; - isLinkedCheckBox.IsChecked = ReferenceClip.LinkedModules.Contains(module.FullID); - } + ReferenceClip.Name.Subscribe(newName => Title = $"Editing {newName} Clip", true); } private void OnClosed(object? sender, EventArgs e) @@ -267,4 +266,19 @@ public class TextBoxParsingConverter : IValueConverter return value; } +} + +public class IsModuleSelectedConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is Module module) + { + return MainWindow.GetInstance().ChatBoxView.SelectedClip!.LinkedModules.Contains(module.FullID); + } + + return false; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => null; } \ No newline at end of file From a29f58935077aa4f3844069e99118e3f93ea260e Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 10 Dec 2024 19:04:16 +0000 Subject: [PATCH 05/49] Fix profile delete warning title --- VRCOSC.App/UI/Views/Profiles/ProfilesView.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.App/UI/Views/Profiles/ProfilesView.xaml.cs b/VRCOSC.App/UI/Views/Profiles/ProfilesView.xaml.cs index 039ed652..8a894fa9 100644 --- a/VRCOSC.App/UI/Views/Profiles/ProfilesView.xaml.cs +++ b/VRCOSC.App/UI/Views/Profiles/ProfilesView.xaml.cs @@ -30,7 +30,7 @@ private void RemoveProfile_ButtonClick(object sender, RoutedEventArgs e) var button = (Button)sender; var profile = (Profile)button.Tag; - var result = MessageBox.Show("Are you sure you want to delete this profile?\nDeleting will remove all saved module, persistence, and ChatBox data", "Uninstall Warning", MessageBoxButton.YesNo); + var result = MessageBox.Show("Are you sure you want to delete this profile?\nDeleting will remove all saved module, persistence, and ChatBox data", "Profile Delete Warning", MessageBoxButton.YesNo); if (result != MessageBoxResult.Yes) return; ProfileManager.GetInstance().Profiles.Remove(profile); From 574061b3e77d1e9baebeecbafa0c20a521b7f6a3 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 10 Dec 2024 19:04:34 +0000 Subject: [PATCH 06/49] Ensure a tracked device cannot have a battery under 0 --- VRCOSC.App/SDK/OVR/Device/TrackedDevice.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/VRCOSC.App/SDK/OVR/Device/TrackedDevice.cs b/VRCOSC.App/SDK/OVR/Device/TrackedDevice.cs index b89220c5..a55f64ee 100644 --- a/VRCOSC.App/SDK/OVR/Device/TrackedDevice.cs +++ b/VRCOSC.App/SDK/OVR/Device/TrackedDevice.cs @@ -1,6 +1,7 @@ // Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. +using System; using Valve.VR; namespace VRCOSC.App.SDK.OVR.Device; @@ -51,6 +52,6 @@ internal void Update() IsConnected = OpenVR.System.IsTrackedDeviceConnected(Index); ProvidesBatteryStatus = IsConnected && OVRHelper.GetBoolTrackedDeviceProperty(Index, ETrackedDeviceProperty.Prop_DeviceProvidesBatteryStatus_Bool); IsCharging = IsConnected && OVRHelper.GetBoolTrackedDeviceProperty(Index, ETrackedDeviceProperty.Prop_DeviceIsCharging_Bool); - BatteryPercentage = IsConnected ? OVRHelper.GetFloatTrackedDeviceProperty(Index, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float) : 0f; + BatteryPercentage = MathF.Max(0f, IsConnected ? OVRHelper.GetFloatTrackedDeviceProperty(Index, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float) : 0f); } -} +} \ No newline at end of file From f8aab8bb66511b780b0745bbb3b9762a6e40d837 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 10 Dec 2024 19:06:22 +0000 Subject: [PATCH 07/49] Don't use symbol mode for ints and floats when there's no symbols --- .../ChatBox/Clips/Variables/Instances/FloatClipVariable.cs | 4 ++-- .../ChatBox/Clips/Variables/Instances/IntClipVariable.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VRCOSC.App/ChatBox/Clips/Variables/Instances/FloatClipVariable.cs b/VRCOSC.App/ChatBox/Clips/Variables/Instances/FloatClipVariable.cs index e21e2fea..b667c946 100644 --- a/VRCOSC.App/ChatBox/Clips/Variables/Instances/FloatClipVariable.cs +++ b/VRCOSC.App/ChatBox/Clips/Variables/Instances/FloatClipVariable.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -45,7 +45,7 @@ protected override string Format(object value) if (float.IsPositiveInfinity(floatValue)) return "\u221e"; if (float.IsNegativeInfinity(floatValue)) return "-\u221e"; - if (Mode == FloatVariableMode.Standard) + if (Mode == FloatVariableMode.Standard || SymbolList.Count == 0) { try { diff --git a/VRCOSC.App/ChatBox/Clips/Variables/Instances/IntClipVariable.cs b/VRCOSC.App/ChatBox/Clips/Variables/Instances/IntClipVariable.cs index 29929921..22a45120 100644 --- a/VRCOSC.App/ChatBox/Clips/Variables/Instances/IntClipVariable.cs +++ b/VRCOSC.App/ChatBox/Clips/Variables/Instances/IntClipVariable.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -37,7 +37,7 @@ protected override string Format(object value) { var intValue = (int)value; - if (Mode == IntVariableMode.Standard) + if (Mode == IntVariableMode.Standard || SymbolList.Count == 0) { return intValue == int.MaxValue ? "\u221e" : intValue.ToString(CultureInfo.CurrentCulture); } From dd9fc8691ee4b69d09a719d4d35ed48242d283cf Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 10 Dec 2024 19:07:07 +0000 Subject: [PATCH 08/49] Don't copy clip settings when creating compounded states --- VRCOSC.App/ChatBox/Clips/Clip.cs | 8 ++++---- VRCOSC.App/ChatBox/Clips/ClipElement.cs | 17 ++++++++++------- VRCOSC.App/ChatBox/Clips/ClipEvent.cs | 16 ++++++++++------ VRCOSC.App/ChatBox/Clips/ClipState.cs | 6 +++--- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/VRCOSC.App/ChatBox/Clips/Clip.cs b/VRCOSC.App/ChatBox/Clips/Clip.cs index c87a4a8e..4ccb3a9a 100644 --- a/VRCOSC.App/ChatBox/Clips/Clip.cs +++ b/VRCOSC.App/ChatBox/Clips/Clip.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -291,12 +291,12 @@ private void addStatesOfAddedModules(NotifyCollectionChangedEventArgs e) { foreach (string moduleID in e.NewItems!) { - var statesCopy = States.Select(clipState => clipState.Clone()).ToList(); + var statesCopy = States.Select(clipState => clipState.Clone(false)).ToList(); var statesToAdd = ChatBoxManager.GetInstance().StateReferences.Where(reference => reference.ModuleID == moduleID); foreach (var stateReference in statesToAdd) { - var innerStatesCopy = statesCopy.Select(clipState => clipState.Clone()).ToList(); + var innerStatesCopy = statesCopy.Select(clipState => clipState.Clone(false)).ToList(); innerStatesCopy.ForEach(innerClipStateCopy => innerClipStateCopy.States.Add(stateReference.ModuleID, stateReference.StateID)); @@ -341,4 +341,4 @@ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } -} +} \ No newline at end of file diff --git a/VRCOSC.App/ChatBox/Clips/ClipElement.cs b/VRCOSC.App/ChatBox/Clips/ClipElement.cs index e91bf3be..56884a0e 100644 --- a/VRCOSC.App/ChatBox/Clips/ClipElement.cs +++ b/VRCOSC.App/ChatBox/Clips/ClipElement.cs @@ -46,15 +46,18 @@ public string RunFormatting() return localFormat; } - public virtual ClipElement Clone() + public virtual ClipElement Clone(bool copySettings = true) { var clone = (ClipElement)Activator.CreateInstance(GetType())!; - clone.Format.Value = Format.Value; - clone.Enabled.Value = Enabled.Value; - clone.ShowTyping.Value = ShowTyping.Value; - clone.UseMinimalBackground.Value = UseMinimalBackground.Value; - clone.Variables.AddRange(Variables.Select(variable => variable.Clone())); + if (copySettings) + { + clone.Format.Value = Format.Value; + clone.Enabled.Value = Enabled.Value; + clone.ShowTyping.Value = ShowTyping.Value; + clone.UseMinimalBackground.Value = UseMinimalBackground.Value; + clone.Variables.AddRange(Variables.Select(variable => variable.Clone())); + } return clone; } @@ -65,4 +68,4 @@ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } -} +} \ No newline at end of file diff --git a/VRCOSC.App/ChatBox/Clips/ClipEvent.cs b/VRCOSC.App/ChatBox/Clips/ClipEvent.cs index 46af9162..e1f7bac3 100644 --- a/VRCOSC.App/ChatBox/Clips/ClipEvent.cs +++ b/VRCOSC.App/ChatBox/Clips/ClipEvent.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -84,14 +84,18 @@ public ClipEvent(ClipEventReference reference) Behaviour = new Observable(reference.DefaultBehaviour); } - public override ClipEvent Clone() + public override ClipEvent Clone(bool copySettings = true) { - var clone = (ClipEvent)base.Clone(); + var clone = (ClipEvent)base.Clone(copySettings); clone.ModuleID = ModuleID; clone.EventID = EventID; - clone.Length.Value = Length.Value; - clone.Behaviour.Value = Behaviour.Value; + + if (copySettings) + { + clone.Length.Value = Length.Value; + clone.Behaviour.Value = Behaviour.Value; + } return clone; } @@ -130,4 +134,4 @@ public class ClipEventReference internal ClipEventBehaviour DefaultBehaviour { get; init; } public Observable DisplayName { get; } = new("INVALID"); -} +} \ No newline at end of file diff --git a/VRCOSC.App/ChatBox/Clips/ClipState.cs b/VRCOSC.App/ChatBox/Clips/ClipState.cs index c23314db..8de2c710 100644 --- a/VRCOSC.App/ChatBox/Clips/ClipState.cs +++ b/VRCOSC.App/ChatBox/Clips/ClipState.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -96,9 +96,9 @@ private ClipState(ClipState original) States.AddRange(original.States); } - public override ClipState Clone() + public override ClipState Clone(bool copySettings = true) { - var clone = (ClipState)base.Clone(); + var clone = (ClipState)base.Clone(copySettings); clone.States.AddRange(States); clone.IsBuiltIn = IsBuiltIn; From ea00b779503ca2937bd839f8bb862abaf99c7ee9 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 19:14:13 +0000 Subject: [PATCH 09/49] Fix thread crash when installing speech model from run view --- VRCOSC.App/AppManager.cs | 8 +++----- VRCOSC.App/UI/Windows/MainWindow.xaml.cs | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/VRCOSC.App/AppManager.cs b/VRCOSC.App/AppManager.cs index 792fcadb..d7ec95ac 100644 --- a/VRCOSC.App/AppManager.cs +++ b/VRCOSC.App/AppManager.cs @@ -437,14 +437,12 @@ private async Task startAsync() sendControlParameters(); } - private Task installSpeechModel() + private Task installSpeechModel() => Application.Current.Dispatcher.Invoke(() => { var action = new FileDownloadAction(new Uri("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin?download=true"), Storage.GetStorageForDirectory("runtime/whisper"), "ggml-tiny.bin"); - - action.OnComplete += () => { SettingsManager.GetInstance().GetObservable(VRCOSCSetting.SpeechModelPath).Value = Storage.GetStorageForDirectory("runtime/whisper").GetFullPath("ggml-tiny.bin"); }; - + action.OnComplete += () => SettingsManager.GetInstance().GetObservable(VRCOSCSetting.SpeechModelPath).Value = Storage.GetStorageForDirectory("runtime/whisper").GetFullPath("ggml-tiny.bin"); return MainWindow.GetInstance().ShowLoadingOverlay("Installing Model", action); - } + }); private void initialiseOSCClient(IPAddress sendAddress, int sendPort, IPAddress receiveAddress, int receivePort) { diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs index d53f5924..00d22fc6 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs @@ -382,7 +382,7 @@ private void handleTrayTransition() #endregion - public async Task ShowLoadingOverlay(string title, ProgressAction progressAction) => await Dispatcher.Invoke(async () => + public Task ShowLoadingOverlay(string title, ProgressAction progressAction) => Dispatcher.Invoke(() => { LoadingTitle.Text = title; @@ -409,7 +409,7 @@ public async Task ShowLoadingOverlay(string title, ProgressAction progressAction LoadingOverlay.FadeIn(150); - await progressAction.Execute(); + return progressAction.Execute(); }); public void HideLoadingOverlay() => Dispatcher.Invoke(() => LoadingOverlay.FadeOut(150)); From 4880f810015bad6bd2a2cbfeb92ccf803d178c07 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 19:14:48 +0000 Subject: [PATCH 10/49] Optimise updater for speed --- VRCOSC.App/UI/Windows/MainWindow.xaml | 2 +- VRCOSC.App/UI/Windows/MainWindow.xaml.cs | 7 +++---- VRCOSC.App/Updater/VelopackUpdater.cs | 25 ++++++++++++------------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml b/VRCOSC.App/UI/Windows/MainWindow.xaml index 3a41e082..eb1aca2c 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml @@ -134,7 +134,7 @@ - + diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs index 00d22fc6..df9407db 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs @@ -90,12 +90,11 @@ private async void startApp() } else { - var updateLoadingAction = new DynamicAsyncProgressAction("Gathering remote info", () => velopackUpdater.CheckForUpdatesAsync()); - await ShowLoadingOverlay("Checking For Updates", updateLoadingAction); + await velopackUpdater.CheckForUpdatesAsync(); - if (velopackUpdater.IsUpdateAvailable) + if (velopackUpdater.IsUpdateAvailable()) { - await velopackUpdater.ShowUpdateIfAvailable(); + await velopackUpdater.ExecuteUpdate(); return; } diff --git a/VRCOSC.App/Updater/VelopackUpdater.cs b/VRCOSC.App/Updater/VelopackUpdater.cs index 8d95e5fe..4c702bd7 100644 --- a/VRCOSC.App/Updater/VelopackUpdater.cs +++ b/VRCOSC.App/Updater/VelopackUpdater.cs @@ -19,15 +19,13 @@ public class VelopackUpdater private UpdateManager updateManager = null!; private UpdateInfo? updateInfo; - public bool IsUpdateAvailable => updateInfo is not null; - public VelopackUpdater() { SettingsManager.GetInstance().GetObservable(VRCOSCSetting.UpdateChannel).Subscribe(async () => { constructUpdateManager(); await CheckForUpdatesAsync(); - await ShowUpdateIfAvailable(); + await ExecuteUpdate(); }); constructUpdateManager(); @@ -46,6 +44,7 @@ private void constructUpdateManager() } public bool IsInstalled() => updateManager.IsInstalled; + public bool IsUpdateAvailable() => updateInfo is not null; public async Task CheckForUpdatesAsync() { @@ -57,21 +56,21 @@ public async Task CheckForUpdatesAsync() Logger.Log(updateInfo is null ? "No updates available" : "Updates available"); } - public async Task ShowUpdateIfAvailable() + public async Task ExecuteUpdate() { - if (!IsUpdateAvailable) return; + try + { + if (updateInfo is null) return; - // switching channels will cause an update to the same version. No need to update - if (SemanticVersion.Parse(AppManager.Version) == updateInfo!.TargetFullRelease.Version) return; + // switching channels will cause an update to the same version. No need to update + if (SemanticVersion.Parse(AppManager.Version) == updateInfo.TargetFullRelease.Version) return; - var upgradeMessage = $"A new update is available! Would you like to update?\n\nCurrent Version: {AppManager.Version}\nNew Version: {updateInfo.TargetFullRelease.Version}"; - var downgradeMessage = $"Updating will downgrade due to switching channels. Are you sure you want to downgrade?\n\nCurrent Version: {AppManager.Version}\nNew Version: {updateInfo.TargetFullRelease.Version}"; + var upgradeMessage = $"A new update is available for VRCOSC!\nWould you like to update?\n\nCurrent Version: {AppManager.Version}\nNew Version: {updateInfo.TargetFullRelease.Version}"; + var downgradeMessage = $"Updating will downgrade due to switching channels.\nAre you sure you want to downgrade?\n\nCurrent Version: {AppManager.Version}\nNew Version: {updateInfo.TargetFullRelease.Version}"; - var result = MessageBox.Show(updateInfo.IsDowngrade ? downgradeMessage : upgradeMessage, "Update Available", MessageBoxButtons.YesNo, MessageBoxIcon.Information); - if (result == DialogResult.No) return; + var result = MessageBox.Show(updateInfo.IsDowngrade ? downgradeMessage : upgradeMessage, "VRCOSC Update Available", MessageBoxButtons.YesNo, MessageBoxIcon.Information); + if (result == DialogResult.No) return; - try - { await updateManager.DownloadUpdatesAsync(updateInfo); SettingsManager.GetInstance().GetObservable(VRCOSCMetadata.InstalledUpdateChannel).Value = SettingsManager.GetInstance().GetValue(VRCOSCSetting.UpdateChannel); updateManager.ApplyUpdatesAndRestart(null); From 97ff5b871397f75a26cb2ae9754b1d3e13d10173 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 19:15:41 +0000 Subject: [PATCH 11/49] Fix Repeater blocking the calling thread when running immediately --- VRCOSC.App/Utils/Repeater.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/VRCOSC.App/Utils/Repeater.cs b/VRCOSC.App/Utils/Repeater.cs index 9f03d0b6..52a08ae0 100644 --- a/VRCOSC.App/Utils/Repeater.cs +++ b/VRCOSC.App/Utils/Repeater.cs @@ -23,20 +23,20 @@ public void Start(TimeSpan interval, bool runOnceImmediately = false) cancellationTokenSource = new CancellationTokenSource(); - if (runOnceImmediately) + _ = Task.Run(async () => { - try - { - action.Invoke(); - } - catch (Exception e) + if (runOnceImmediately) { - ExceptionHandler.Handle(e, $"{nameof(Repeater)} has experienced an exception"); + try + { + action.Invoke(); + } + catch (Exception e) + { + ExceptionHandler.Handle(e, $"{nameof(Repeater)} has experienced an exception"); + } } - } - _ = Task.Run(async () => - { while (!cancellationTokenSource.Token.IsCancellationRequested) { try @@ -73,4 +73,4 @@ public async Task StopAsync() cancellationTokenSource.Dispose(); cancellationTokenSource = null; } -} +} \ No newline at end of file From e8e9af06a58cf7729d880a12aa6c07a17cff7b12 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 19:17:07 +0000 Subject: [PATCH 12/49] Ensure all non-windowed managers are saved on close --- VRCOSC.App/Router/RouterManager.cs | 2 ++ VRCOSC.App/Settings/SettingsManager.cs | 2 ++ VRCOSC.App/Startup/StartupManager.cs | 4 +++- VRCOSC.App/UI/Windows/MainWindow.xaml.cs | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/VRCOSC.App/Router/RouterManager.cs b/VRCOSC.App/Router/RouterManager.cs index 0662c201..2cbdec5a 100644 --- a/VRCOSC.App/Router/RouterManager.cs +++ b/VRCOSC.App/Router/RouterManager.cs @@ -32,6 +32,8 @@ private RouterManager() serialisationManager.RegisterSerialiser(1, new RouterManagerSerialiser(AppManager.GetInstance().Storage, this)); } + public void Serialise() => serialisationManager.Serialise(); + public void Load() { started = false; diff --git a/VRCOSC.App/Settings/SettingsManager.cs b/VRCOSC.App/Settings/SettingsManager.cs index b34cedda..b311779b 100644 --- a/VRCOSC.App/Settings/SettingsManager.cs +++ b/VRCOSC.App/Settings/SettingsManager.cs @@ -28,6 +28,8 @@ private SettingsManager() serialisationManager.RegisterSerialiser(1, new SettingsManagerSerialiser(storage, this)); } + public void Serialise() => serialisationManager.Serialise(); + public void Load() { writeDefaults(); diff --git a/VRCOSC.App/Startup/StartupManager.cs b/VRCOSC.App/Startup/StartupManager.cs index a8b9fffa..92aa0053 100644 --- a/VRCOSC.App/Startup/StartupManager.cs +++ b/VRCOSC.App/Startup/StartupManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -26,6 +26,8 @@ private StartupManager() serialisationManager.RegisterSerialiser(1, new StartupManagerSerialiser(AppManager.GetInstance().Storage, this)); } + public void Serialise() => serialisationManager.Serialise(); + public void Load() { serialisationManager.Deserialise(); diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs index df9407db..4b0fae55 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs @@ -284,6 +284,9 @@ private async void MainWindow_OnClosing(object? sender, CancelEventArgs e) } OVRDeviceManager.GetInstance().Serialise(); + RouterManager.GetInstance().Serialise(); + StartupManager.GetInstance().Serialise(); + SettingsManager.GetInstance().Serialise(); } private void MainWindow_OnClosed(object? sender, EventArgs e) From 0d16ff743f2e8936d0ac129ac4b0636cd502dfaf Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 19:17:55 +0000 Subject: [PATCH 13/49] Small cleanup and optimisations --- .../UI/Views/AppSettings/AppSettingsView.xaml.cs | 11 ++++------- VRCOSC.App/UI/Views/ChatBox/ChatBoxView.xaml.cs | 3 +-- VRCOSC.App/UI/Views/Router/RouterView.xaml.cs | 5 ++--- .../Views/Run/Tabs/AvatarParameterTabView.xaml.cs | 13 ++----------- VRCOSC.App/UI/Views/Settings/SettingsView.xaml.cs | 4 ++-- VRCOSC.App/UI/Windows/MainWindow.xaml.cs | 4 ---- 6 files changed, 11 insertions(+), 29 deletions(-) diff --git a/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml.cs b/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml.cs index c4e15f05..5a8a245d 100644 --- a/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml.cs +++ b/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -261,7 +261,7 @@ private void OVRTabButton_OnClick(object sender, RoutedEventArgs e) setPage(7); } - private void AutoInstallModel_OnClick(object sender, RoutedEventArgs e) + private async void AutoInstallModel_OnClick(object sender, RoutedEventArgs e) { var action = new FileDownloadAction(new Uri("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin?download=true"), AppManager.GetInstance().Storage.GetStorageForDirectory("runtime/whisper"), "ggml-tiny.bin"); @@ -271,15 +271,12 @@ private void AutoInstallModel_OnClick(object sender, RoutedEventArgs e) OnPropertyChanged(nameof(WhisperModelFilePath)); }; - _ = MainWindow.GetInstance().ShowLoadingOverlay("Installing Model", action); + await MainWindow.GetInstance().ShowLoadingOverlay("Installing Model", action); } public event PropertyChangedEventHandler? PropertyChanged; - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public class IpPortValidationRule : ValidationRule diff --git a/VRCOSC.App/UI/Views/ChatBox/ChatBoxView.xaml.cs b/VRCOSC.App/UI/Views/ChatBox/ChatBoxView.xaml.cs index acdacc88..d6586d96 100644 --- a/VRCOSC.App/UI/Views/ChatBox/ChatBoxView.xaml.cs +++ b/VRCOSC.App/UI/Views/ChatBox/ChatBoxView.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -27,7 +27,6 @@ public partial class ChatBoxView private double mouseX; private Clip? draggingClip; private ClipDragPoint clipDragPoint; - private Window? manualInputWindow; public Clip? CopiedClip { get; private set; } public Observable PasteClipButtonVisibility { get; } = new(Visibility.Collapsed); diff --git a/VRCOSC.App/UI/Views/Router/RouterView.xaml.cs b/VRCOSC.App/UI/Views/Router/RouterView.xaml.cs index f7185559..41f49303 100644 --- a/VRCOSC.App/UI/Views/Router/RouterView.xaml.cs +++ b/VRCOSC.App/UI/Views/Router/RouterView.xaml.cs @@ -16,10 +16,9 @@ public partial class RouterView public RouterView() { + InitializeComponent(); RouterManager = RouterManager.GetInstance(); DataContext = this; - - InitializeComponent(); } private void AddInstance_OnClick(object sender, RoutedEventArgs e) @@ -39,4 +38,4 @@ private void InfoButton_OnClick(object sender, RoutedEventArgs e) { router_docs_uri.OpenExternally(); } -} +} \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Run/Tabs/AvatarParameterTabView.xaml.cs b/VRCOSC.App/UI/Views/Run/Tabs/AvatarParameterTabView.xaml.cs index 09aa65ca..6fe3a620 100644 --- a/VRCOSC.App/UI/Views/Run/Tabs/AvatarParameterTabView.xaml.cs +++ b/VRCOSC.App/UI/Views/Run/Tabs/AvatarParameterTabView.xaml.cs @@ -1,10 +1,8 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Runtime.CompilerServices; using System.Windows.Threading; using VRCOSC.App.OSC.VRChat; using VRCOSC.App.Utils; @@ -99,11 +97,4 @@ private void OnAppManagerStateChange(AppManagerState newState) => Dispatcher.Inv IncomingMessages.Clear(); } }); - - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } -} +} \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Settings/SettingsView.xaml.cs b/VRCOSC.App/UI/Views/Settings/SettingsView.xaml.cs index abe582ce..1abae678 100644 --- a/VRCOSC.App/UI/Views/Settings/SettingsView.xaml.cs +++ b/VRCOSC.App/UI/Views/Settings/SettingsView.xaml.cs @@ -16,7 +16,7 @@ public SettingsView() { StateDisplay.Text = newState.ToString(); TokenDisplay.Text = newState == AuthenticationState.LoggedIn ? AppManager.GetInstance().VRChatAPIClient.AuthHandler.AuthToken : string.Empty; - }, true); + }); } private void Login_OnClick(object sender, RoutedEventArgs e) @@ -39,4 +39,4 @@ private void TwoFactorAuth_OnClick(object sender, RoutedEventArgs e) } } -public record DeviceDisplay(string ID, string Name); +public record DeviceDisplay(string ID, string Name); \ No newline at end of file diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs index 4b0fae55..f5c66314 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs @@ -33,7 +33,6 @@ using VRCOSC.App.UI.Views.Profiles; using VRCOSC.App.UI.Views.Router; using VRCOSC.App.UI.Views.Run; -using VRCOSC.App.UI.Views.Settings; using VRCOSC.App.UI.Views.Startup; using VRCOSC.App.Updater; using VRCOSC.App.Utils; @@ -53,7 +52,6 @@ public partial class MainWindow public PackagesView PackagesView = null!; public ModulesView ModulesView = null!; public RouterView RouterView = null!; - public SettingsView SettingsView = null!; public ChatBoxView ChatBoxView = null!; public StartupView StartupView = null!; public RunView RunView = null!; @@ -116,7 +114,6 @@ private async void startApp() PackagesView = new PackagesView(); ModulesView = new ModulesView(); RouterView = new RouterView(); - SettingsView = new SettingsView(); ChatBoxView = new ChatBoxView(); StartupView = new StartupView(); RunView = new RunView(); @@ -438,7 +435,6 @@ private void RouterButton_OnClick(object sender, RoutedEventArgs e) private void SettingsButton_OnClick(object sender, RoutedEventArgs e) { - setContent(SettingsView); } private void ChatBoxButton_OnClick(object sender, RoutedEventArgs e) From 76fd7d8dcb5d0d5073b6d9ce833ed74129dccc20 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 19:18:10 +0000 Subject: [PATCH 14/49] Fix StartupManager erroring when the file path is empty --- VRCOSC.App/Startup/StartupManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/VRCOSC.App/Startup/StartupManager.cs b/VRCOSC.App/Startup/StartupManager.cs index 92aa0053..889ab509 100644 --- a/VRCOSC.App/Startup/StartupManager.cs +++ b/VRCOSC.App/Startup/StartupManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. // See the LICENSE file in the repository root for full license text. using System; @@ -53,6 +53,8 @@ public void OpenFileLocations() try { + if (string.IsNullOrEmpty(fileLocation)) continue; + if (!File.Exists(fileLocation)) { ExceptionHandler.Handle($"File location '{fileLocation}' does not exist when attempting to startup"); From 2a0bae656dd4eed72da87c84a61d35bf65c87841 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 20:59:57 +0000 Subject: [PATCH 15/49] Improve app start logic --- VRCOSC.App/UI/Windows/MainWindow.xaml.cs | 32 +++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs index f5c66314..6b6ad674 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs @@ -95,8 +95,6 @@ private async void startApp() await velopackUpdater.ExecuteUpdate(); return; } - - await Task.Delay(200); } Logger.Log("No updates. Proceeding with loading"); @@ -184,27 +182,31 @@ private async Task load() loadingAction.AddAction(new DynamicChildProgressAction(() => PackageManager.GetInstance().UpdateAllInstalledPackages())); } - loadingAction.AddAction(new DynamicProgressAction("Loading Profiles", () => ProfileManager.GetInstance().Load())); - loadingAction.AddAction(new DynamicProgressAction("Loading Modules", () => ModuleManager.GetInstance().LoadAllModules())); - loadingAction.AddAction(new DynamicProgressAction("Loading ChatBox", () => ChatBoxManager.GetInstance().Load())); - loadingAction.AddAction(new DynamicProgressAction("Loading Router", () => RouterManager.GetInstance().Load())); - loadingAction.AddAction(new DynamicProgressAction("Loading Startup", () => StartupManager.GetInstance().Load())); + loadingAction.AddAction(new DynamicProgressAction("Loading Managers", () => + { + ProfileManager.GetInstance().Load(); + ModuleManager.GetInstance().LoadAllModules(); + ChatBoxManager.GetInstance().Load(); + RouterManager.GetInstance().Load(); + StartupManager.GetInstance().Load(); + })); if (!SettingsManager.GetInstance().GetValue(VRCOSCMetadata.FirstTimeSetupComplete)) { - loadingAction.AddAction(new DynamicChildProgressAction(() => PackageManager.GetInstance().InstallPackage(PackageManager.GetInstance().OfficialModulesSource, closeWindows: false))); - loadingAction.AddAction(new DynamicProgressAction(string.Empty, () => new FirstTimeInstallWindow().Show())); + loadingAction.AddAction(new DynamicChildProgressAction(() => PackageManager.GetInstance().InstallPackage(PackageManager.GetInstance().OfficialModulesSource))); } - loadingAction.OnComplete += () => + loadingAction.OnComplete += () => Dispatcher.Invoke(() => { - Dispatcher.Invoke(() => + if (!SettingsManager.GetInstance().GetValue(VRCOSCMetadata.FirstTimeSetupComplete)) { + new FirstTimeInstallWindow().Show(); SettingsManager.GetInstance().GetObservable(VRCOSCMetadata.FirstTimeSetupComplete).Value = true; - MainWindowContent.FadeInFromZero(500); - AppManager.GetInstance().InitialLoadComplete(); - }); - }; + } + + MainWindowContent.FadeInFromZero(500); + AppManager.GetInstance().InitialLoadComplete(); + }); await ShowLoadingOverlay("Welcome to VRCOSC", loadingAction); } From d61a0c14133f1418b09ea19d46a458021761d4b9 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 21:08:02 +0000 Subject: [PATCH 16/49] Switch default speech model from tiny to small --- VRCOSC.App/AppManager.cs | 8 ++++---- .../UI/Views/AppSettings/AppSettingsView.xaml.cs | 12 +----------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/VRCOSC.App/AppManager.cs b/VRCOSC.App/AppManager.cs index d7ec95ac..7c8b1ae1 100644 --- a/VRCOSC.App/AppManager.cs +++ b/VRCOSC.App/AppManager.cs @@ -418,7 +418,7 @@ private async Task startAsync() if (result == MessageBoxResult.Yes) { - await installSpeechModel(); + await InstallSpeechModel(); } } @@ -437,10 +437,10 @@ private async Task startAsync() sendControlParameters(); } - private Task installSpeechModel() => Application.Current.Dispatcher.Invoke(() => + public Task InstallSpeechModel() => Application.Current.Dispatcher.Invoke(() => { - var action = new FileDownloadAction(new Uri("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin?download=true"), Storage.GetStorageForDirectory("runtime/whisper"), "ggml-tiny.bin"); - action.OnComplete += () => SettingsManager.GetInstance().GetObservable(VRCOSCSetting.SpeechModelPath).Value = Storage.GetStorageForDirectory("runtime/whisper").GetFullPath("ggml-tiny.bin"); + var action = new FileDownloadAction(new Uri("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin?download=true"), Storage.GetStorageForDirectory("runtime/whisper"), "ggml-small.bin"); + action.OnComplete += () => SettingsManager.GetInstance().GetObservable(VRCOSCSetting.SpeechModelPath).Value = Storage.GetStorageForDirectory("runtime/whisper").GetFullPath("ggml-small.bin"); return MainWindow.GetInstance().ShowLoadingOverlay("Installing Model", action); }); diff --git a/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml.cs b/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml.cs index 5a8a245d..308c22ec 100644 --- a/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml.cs +++ b/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml.cs @@ -11,12 +11,10 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; -using VRCOSC.App.Actions.Files; using VRCOSC.App.Audio; using VRCOSC.App.Settings; using VRCOSC.App.UI.Themes; using VRCOSC.App.UI.Views.Settings; -using VRCOSC.App.UI.Windows; using VRCOSC.App.Updater; using VRCOSC.App.Utils; @@ -263,15 +261,7 @@ private void OVRTabButton_OnClick(object sender, RoutedEventArgs e) private async void AutoInstallModel_OnClick(object sender, RoutedEventArgs e) { - var action = new FileDownloadAction(new Uri("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin?download=true"), AppManager.GetInstance().Storage.GetStorageForDirectory("runtime/whisper"), "ggml-tiny.bin"); - - action.OnComplete += () => - { - SettingsManager.GetInstance().GetObservable(VRCOSCSetting.SpeechModelPath).Value = AppManager.GetInstance().Storage.GetStorageForDirectory("runtime/whisper").GetFullPath("ggml-tiny.bin"); - OnPropertyChanged(nameof(WhisperModelFilePath)); - }; - - await MainWindow.GetInstance().ShowLoadingOverlay("Installing Model", action); + await AppManager.GetInstance().InstallSpeechModel(); } public event PropertyChangedEventHandler? PropertyChanged; From dd55808f3c109b026e28a1eb7f1f92d30faaee63 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 21:08:23 +0000 Subject: [PATCH 17/49] Fix FileDownload not providing progress correctly --- VRCOSC.App/Utils/FileDownload.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/VRCOSC.App/Utils/FileDownload.cs b/VRCOSC.App/Utils/FileDownload.cs index eb73a8d2..f0578088 100644 --- a/VRCOSC.App/Utils/FileDownload.cs +++ b/VRCOSC.App/Utils/FileDownload.cs @@ -24,9 +24,11 @@ public async Task DownloadFileAsync(Uri url, string filePath) private async Task downloadFileFromStreamAsync(Stream stream, string filePath, long? totalSize) { - using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); + const int buffer_size = 2048 * 8; - var buffer = new byte[8192]; + using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, buffer_size, true); + + var buffer = new byte[buffer_size]; var totalRead = 0L; var isMoreToRead = true; @@ -42,7 +44,7 @@ private async Task downloadFileFromStreamAsync(Stream stream, string filePath, l { await fileStream.WriteAsync(buffer.AsMemory(0, read)).ConfigureAwait(false); totalRead += read; - var progress = totalSize.HasValue ? (float)totalRead / totalSize.Value * 100 : 0; + var progress = totalSize.HasValue ? (float)totalRead / totalSize.Value : 0; ProgressChanged?.Invoke(progress); } } while (isMoreToRead); From f162faf1bbe8e2f83091e3609c8086a8a39f8153 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 21:13:12 +0000 Subject: [PATCH 18/49] Allow speech additions --- VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs b/VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs index b48d640d..5d825954 100644 --- a/VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs +++ b/VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs @@ -81,7 +81,7 @@ private async void processResult() if (result is null) return; // filter out things like [BLANK AUDIO] - if (result.Text.StartsWith('[') || result.Text.Contains('*')) return; + if (result.Text.StartsWith('[')) return; var requiredConfidence = SettingsManager.GetInstance().GetValue(VRCOSCSetting.SpeechConfidence); if (result.Confidence < requiredConfidence) return; From 6cb9c4a3d25c96cee7dfe4e6250fea4a830c50b3 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 23:20:54 +0000 Subject: [PATCH 19/49] Add names to repeaters for better error messages --- VRCOSC.App/AppManager.cs | 8 ++++---- VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs | 2 +- VRCOSC.App/ChatBox/ChatBoxManager.cs | 4 ++-- VRCOSC.App/OSC/ConnectionManager.cs | 4 ++-- VRCOSC.App/SDK/Modules/Module.cs | 2 +- VRCOSC.App/SDK/VRChat/VRChatLogReader.cs | 2 +- VRCOSC.App/UI/Views/AppDebug/AppDebugView.xaml.cs | 4 ++-- VRCOSC.App/Utils/Repeater.cs | 10 ++++++---- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/VRCOSC.App/AppManager.cs b/VRCOSC.App/AppManager.cs index 7c8b1ae1..7e79b5b0 100644 --- a/VRCOSC.App/AppManager.cs +++ b/VRCOSC.App/AppManager.cs @@ -152,13 +152,13 @@ public void InitialLoadComplete() VRChatOscClient.Init(ConnectionManager); ConnectionManager.Init(); - vrchatCheckTask = new Repeater(checkForVRChatAutoStart); + vrchatCheckTask = new Repeater($"{nameof(AppManager)}-{nameof(checkForVRChatAutoStart)}", checkForVRChatAutoStart); vrchatCheckTask.Start(TimeSpan.FromSeconds(2)); - openvrCheckTask = new Repeater(checkForOpenVR); + openvrCheckTask = new Repeater($"{nameof(AppManager)}-{nameof(checkForOpenVR)}", checkForOpenVR); openvrCheckTask.Start(TimeSpan.FromSeconds(2)); - openvrUpdateTask = new Repeater(updateOVRClient); + openvrUpdateTask = new Repeater($"{nameof(AppManager)}-{nameof(updateOVRClient)}", updateOVRClient); openvrUpdateTask.Start(TimeSpan.FromSeconds(1d / 60d)); } @@ -425,7 +425,7 @@ private async Task startAsync() SpeechEngine.Initialise(); } - updateTask = new Repeater(update); + updateTask = new Repeater($"{nameof(AppManager)}-{nameof(update)}", update); updateTask.Start(TimeSpan.FromSeconds(1d / 60d)); VRChatOscClient.OnParameterReceived += onParameterReceived; diff --git a/VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs b/VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs index 5d825954..1a58ecfa 100644 --- a/VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs +++ b/VRCOSC.App/Audio/Whisper/WhisperSpeechEngine.cs @@ -66,7 +66,7 @@ private async void onCaptureDeviceIdChanged(string newDeviceId) audioProcessor = new AudioProcessor(captureDevice); audioProcessor.Start(); - repeater = new Repeater(processResult); + repeater = new Repeater($"{nameof(WhisperSpeechEngine)}-{nameof(processResult)}", processResult); // Do not change this from 1.5 repeater.Start(TimeSpan.FromSeconds(1.5f)); } diff --git a/VRCOSC.App/ChatBox/ChatBoxManager.cs b/VRCOSC.App/ChatBox/ChatBoxManager.cs index 1f265aa7..f4759aec 100644 --- a/VRCOSC.App/ChatBox/ChatBoxManager.cs +++ b/VRCOSC.App/ChatBox/ChatBoxManager.cs @@ -198,8 +198,8 @@ public void Start() var sendInterval = SettingsManager.GetInstance().GetValue(VRCOSCSetting.ChatBoxSendInterval); startTime = DateTimeOffset.Now; - sendTask = new Repeater(chatBoxUpdate); - updateTask = new Repeater(update); + sendTask = new Repeater($"{nameof(ChatBoxManager)}-{nameof(chatBoxUpdate)}", chatBoxUpdate); + updateTask = new Repeater($"{nameof(ChatBoxManager)}-{nameof(update)}", update); SendEnabled = true; isClear = true; currentIsTyping = false; diff --git a/VRCOSC.App/OSC/ConnectionManager.cs b/VRCOSC.App/OSC/ConnectionManager.cs index 8997b64b..6596e1bc 100644 --- a/VRCOSC.App/OSC/ConnectionManager.cs +++ b/VRCOSC.App/OSC/ConnectionManager.cs @@ -38,7 +38,7 @@ public class ConnectionManager public void Init() { - refreshTask = new Repeater(refreshServices); + refreshTask = new Repeater($"{nameof(ConnectionManager)}-{nameof(refreshServices)}", refreshServices); refreshTask.Start(TimeSpan.FromMilliseconds(refresh_interval)); VRCOSCReceivePort = getAvailableUDPPort(); @@ -208,4 +208,4 @@ private void handleMatchedService(SRVRecord srvRecord) Logger.Log($"Found VRChat's OSCQuery port: {VRChatQueryPort}"); } } -} +} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Module.cs b/VRCOSC.App/SDK/Modules/Module.cs index b4347a31..7fef9417 100644 --- a/VRCOSC.App/SDK/Modules/Module.cs +++ b/VRCOSC.App/SDK/Modules/Module.cs @@ -266,7 +266,7 @@ private void initialiseUpdateAttributes(Type? type) switch (updateAttribute.Mode) { case ModuleUpdateMode.Custom: - var updateTask = new Repeater(() => invokeMethod(method)); + var updateTask = new Repeater($"{nameof(Module)}-{nameof(invokeMethod)}", () => invokeMethod(method)); updateTask.Start(TimeSpan.FromMilliseconds(updateAttribute.DeltaMilliseconds), updateAttribute.UpdateImmediately); updateTasks.Add(updateTask); break; diff --git a/VRCOSC.App/SDK/VRChat/VRChatLogReader.cs b/VRCOSC.App/SDK/VRChat/VRChatLogReader.cs index 0f33f80f..380b4e68 100644 --- a/VRCOSC.App/SDK/VRChat/VRChatLogReader.cs +++ b/VRCOSC.App/SDK/VRChat/VRChatLogReader.cs @@ -45,7 +45,7 @@ internal static void Start() return; } - processTask = new Repeater(process); + processTask = new Repeater($"{nameof(VRChatLogReader)}-{nameof(process)}", process); processTask.Start(TimeSpan.FromMilliseconds(200), true); } diff --git a/VRCOSC.App/UI/Views/AppDebug/AppDebugView.xaml.cs b/VRCOSC.App/UI/Views/AppDebug/AppDebugView.xaml.cs index ea89b5f4..ca543802 100644 --- a/VRCOSC.App/UI/Views/AppDebug/AppDebugView.xaml.cs +++ b/VRCOSC.App/UI/Views/AppDebug/AppDebugView.xaml.cs @@ -21,7 +21,7 @@ public AppDebugView() InitializeComponent(); DataContext = this; - repeater = new Repeater(() => Port9000BoundProcess.Value = $"{(executePowerShellCommand("Get-Process -Id (Get-NetUDPEndpoint -LocalPort 9000).OwningProcess | Select-Object -ExpandProperty ProcessName") ?? "Nothing").ReplaceLineEndings(string.Empty)}"); + repeater = new Repeater($"{nameof(AppDebugView)}-{nameof(executePowerShellCommand)}", () => Port9000BoundProcess.Value = $"{(executePowerShellCommand("Get-Process -Id (Get-NetUDPEndpoint -LocalPort 9000).OwningProcess | Select-Object -ExpandProperty ProcessName") ?? "Nothing").ReplaceLineEndings(string.Empty)}"); repeater.Start(TimeSpan.FromSeconds(5), true); } @@ -66,4 +66,4 @@ private void ReloadModules_OnClick(object sender, RoutedEventArgs e) { ModuleManager.GetInstance().ReloadAllModules(); } -} +} \ No newline at end of file diff --git a/VRCOSC.App/Utils/Repeater.cs b/VRCOSC.App/Utils/Repeater.cs index 52a08ae0..6f947ad4 100644 --- a/VRCOSC.App/Utils/Repeater.cs +++ b/VRCOSC.App/Utils/Repeater.cs @@ -9,17 +9,19 @@ namespace VRCOSC.App.Utils; public class Repeater { + private readonly string name; private readonly Action action; private CancellationTokenSource? cancellationTokenSource; - public Repeater(Action action) + public Repeater(string name, Action action) { + this.name = name; this.action = action; } public void Start(TimeSpan interval, bool runOnceImmediately = false) { - if (cancellationTokenSource is not null) throw new InvalidOperationException("Repeater is already started"); + if (cancellationTokenSource is not null) throw new InvalidOperationException($"{nameof(Repeater)}:{name} is already started"); cancellationTokenSource = new CancellationTokenSource(); @@ -33,7 +35,7 @@ public void Start(TimeSpan interval, bool runOnceImmediately = false) } catch (Exception e) { - ExceptionHandler.Handle(e, $"{nameof(Repeater)} has experienced an exception"); + ExceptionHandler.Handle(e, $"{nameof(Repeater)}:{name} has experienced an exception"); } } @@ -50,7 +52,7 @@ public void Start(TimeSpan interval, bool runOnceImmediately = false) } catch (Exception e) { - ExceptionHandler.Handle(e, $"{nameof(Repeater)} has experienced an exception"); + ExceptionHandler.Handle(e, $"{nameof(Repeater)}:{name} has experienced an exception"); } } }, cancellationTokenSource.Token); From 02d0ee7e7755343c2eed4bb65a073aac19948154 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 12 Dec 2024 23:59:49 +0000 Subject: [PATCH 20/49] Optimise start-up more --- VRCOSC.App/UI/Windows/MainWindow.xaml.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs index 6b6ad674..557458ec 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs @@ -73,6 +73,7 @@ public MainWindow() backupV1Files(); setupTrayIcon(); + copyOpenVrFiles(); startApp(); } @@ -95,20 +96,15 @@ private async void startApp() await velopackUpdater.ExecuteUpdate(); return; } - } - - Logger.Log("No updates. Proceeding with loading"); - SettingsManager.GetInstance().GetObservable(VRCOSCSetting.EnableAppDebug).Subscribe(newValue => ShowAppDebug.Value = newValue, true); - SettingsManager.GetInstance().GetObservable(VRCOSCSetting.EnableRouter).Subscribe(newValue => ShowRouter.Value = newValue, true); + Logger.Log("No updates. Proceeding with loading"); + } AppManager.GetInstance().Initialise(); var installedUpdateChannel = SettingsManager.GetInstance().GetValue(VRCOSCMetadata.InstalledUpdateChannel); Title = installedUpdateChannel == UpdateChannel.Beta ? $"{AppManager.APP_NAME} {AppManager.Version} BETA" : $"{AppManager.APP_NAME} {AppManager.Version}"; - copyOpenVrFiles(); - PackagesView = new PackagesView(); ModulesView = new ModulesView(); RouterView = new RouterView(); @@ -122,6 +118,9 @@ private async void startApp() setContent(ModulesView); + SettingsManager.GetInstance().GetObservable(VRCOSCSetting.EnableAppDebug).Subscribe(newValue => ShowAppDebug.Value = newValue, true); + SettingsManager.GetInstance().GetObservable(VRCOSCSetting.EnableRouter).Subscribe(newValue => ShowRouter.Value = newValue, true); + await load(); } From 058ef70cd96ad5209d613429feca7abe633ddf52 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Fri, 13 Dec 2024 21:13:11 +0000 Subject: [PATCH 21/49] Allow Whisper to not translate to English by default --- VRCOSC.App/Audio/Whisper/AudioProcessor.cs | 48 +++++++++++++------ VRCOSC.App/Settings/SettingsManager.cs | 4 +- .../UI/Views/AppSettings/AppSettingsView.xaml | 14 ++++++ .../Views/AppSettings/AppSettingsView.xaml.cs | 6 +++ 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/VRCOSC.App/Audio/Whisper/AudioProcessor.cs b/VRCOSC.App/Audio/Whisper/AudioProcessor.cs index e3a21f9f..4382d2b0 100644 --- a/VRCOSC.App/Audio/Whisper/AudioProcessor.cs +++ b/VRCOSC.App/Audio/Whisper/AudioProcessor.cs @@ -15,7 +15,7 @@ namespace VRCOSC.App.Audio.Whisper; internal class AudioProcessor { private readonly AudioCapture? audioCapture; - private readonly WhisperProcessor? whisper; + private WhisperProcessor? whisper; private const int default_samples_to_check = 24000; // sample rate is 16000 so check the last 1.5 seconds of audio @@ -23,36 +23,50 @@ internal class AudioProcessor private bool isProcessing; public AudioProcessor(MMDevice device) + { + try + { + audioCapture = new AudioCapture(device); + } + catch (Exception e) + { + audioCapture = null; + ExceptionHandler.Handle(e); + } + } + + private void buildWhisperProcessor() { var modelFilePath = SettingsManager.GetInstance().GetValue(VRCOSCSetting.SpeechModelPath); try { - var builder = WhisperFactory.FromPath(modelFilePath); + var builder = WhisperFactory.FromPath(modelFilePath).CreateBuilder(); - whisper = builder.CreateBuilder() - .WithProbabilities() + builder = builder.WithProbabilities() .WithThreads(8) .WithNoContext() .WithSingleSegment() .WithMaxSegmentLength(int.MaxValue) - .Build(); + .WithLanguageDetection(); + + if (SettingsManager.GetInstance().GetValue(VRCOSCSetting.SpeechTranslate)) + { + builder.WithLanguage("en"); + } + + // translation for any-to-any technically works but is unsupported + // for future reference, to get any-to-any to work you have to do: + // WithLanguageDetection + // WithLanguage(targetLanguage) + + whisper = builder.Build(); } catch (Exception e) { whisper = null; ExceptionHandler.Handle(e, "The Whisper model path is empty or incorrect. Please go into the app's speech settings and restore the model by clicking 'Auto Install Model'"); } - - try - { - audioCapture = new AudioCapture(device); - } - catch (Exception e) - { - audioCapture = null; - ExceptionHandler.Handle(e); - } } public void Start() @@ -60,6 +74,8 @@ public void Start() speechResult = null; isProcessing = false; + buildWhisperProcessor(); + if (whisper is null || audioCapture is null) return; audioCapture.ClearBuffer(); @@ -69,6 +85,8 @@ public void Start() public void Stop() { audioCapture?.StopCapture(); + whisper?.Dispose(); + whisper = null; } public async Task GetResultAsync() diff --git a/VRCOSC.App/Settings/SettingsManager.cs b/VRCOSC.App/Settings/SettingsManager.cs index b311779b..001e2f43 100644 --- a/VRCOSC.App/Settings/SettingsManager.cs +++ b/VRCOSC.App/Settings/SettingsManager.cs @@ -76,6 +76,7 @@ private void writeDefaults() setDefault(VRCOSCSetting.SpeechConfidence, 0.4f); setDefault(VRCOSCSetting.SpeechNoiseCutoff, 0.14f); setDefault(VRCOSCSetting.SpeechMicVolumeAdjustment, 1f); + setDefault(VRCOSCSetting.SpeechTranslate, false); setDefault(VRCOSCMetadata.InstalledVersion, string.Empty); setDefault(VRCOSCMetadata.InstalledUpdateChannel, UpdateChannel.Live); @@ -143,7 +144,8 @@ public enum VRCOSCSetting SpeechModelPath, SpeechConfidence, SpeechNoiseCutoff, - SpeechMicVolumeAdjustment + SpeechMicVolumeAdjustment, + SpeechTranslate } public enum VRCOSCMetadata diff --git a/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml b/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml index 9c8318cb..04e9425d 100644 --- a/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml +++ b/VRCOSC.App/UI/Views/AppSettings/AppSettingsView.xaml @@ -45,6 +45,7 @@ + + + + + + + + + + \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs new file mode 100644 index 00000000..448e57e1 --- /dev/null +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs @@ -0,0 +1,46 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using VRCOSC.App.SDK.Modules.Attributes.Settings; +using VRCOSC.App.SDK.Parameters; +using VRCOSC.App.SDK.Utils; +using VRCOSC.App.UI.Core; +using VRCOSC.App.Utils; + +namespace VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter; + +public partial class QueryableParameterEditWindow : IManagedWindow +{ + public IEnumerable QueryableParameterTypeItemsSource => typeof(ParameterType).GetEnumValues().Cast(); + public IEnumerable> QueryableParameterOperationItemsSource => ComparisonOperationUtils.DISPLAY_LIST; + public IEnumerable BoolValueItemsSource => typeof(BoolValue).GetEnumValues().Cast(); + public Array QueryableParameterActionTypeItemsSource => ModuleSetting.ActionType?.GetEnumValues() ?? Array.Empty(); + + public QueryableParameterListModuleSetting ModuleSetting { get; } + + public QueryableParameterEditWindow(QueryableParameterListModuleSetting moduleSetting) + { + InitializeComponent(); + ModuleSetting = moduleSetting; + DataContext = this; + + Title = $"Edit {moduleSetting.Title.Pluralise()} Queryable Parameters"; + } + + private void AddButton_OnClick(object sender, RoutedEventArgs e) + { + ModuleSetting.Add(); + } + + public object GetComparer() => ModuleSetting; +} + +public enum BoolValue +{ + True, + False +} \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml new file mode 100644 index 00000000..58b447b3 --- /dev/null +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs new file mode 100644 index 00000000..db1b2e38 --- /dev/null +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs @@ -0,0 +1,32 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System.Windows; +using VRCOSC.App.SDK.Modules; +using VRCOSC.App.SDK.Modules.Attributes.Settings; +using VRCOSC.App.UI.Core; + +namespace VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter; + +public partial class QueryableParameterSettingView +{ + private WindowManager windowManager = null!; + + public QueryableParameterListModuleSetting ModuleSetting { get; } + + public QueryableParameterSettingView(Module _, QueryableParameterListModuleSetting moduleSetting) + { + InitializeComponent(); + ModuleSetting = moduleSetting; + } + + private void EditButton_OnClick(object sender, RoutedEventArgs e) + { + windowManager.TrySpawnChild(new QueryableParameterEditWindow(ModuleSetting)); + } + + private void QueryableParameterSettingView_OnLoaded(object sender, RoutedEventArgs e) + { + windowManager = new WindowManager(this); + } +} \ No newline at end of file diff --git a/VRCOSC.App/VRCOSC.App.csproj b/VRCOSC.App/VRCOSC.App.csproj index e690bd78..817e74af 100644 --- a/VRCOSC.App/VRCOSC.App.csproj +++ b/VRCOSC.App/VRCOSC.App.csproj @@ -215,6 +215,11 @@ Wpf Designer + + MSBuild:Compile + Wpf + Designer + \ No newline at end of file From 5e5d0092dce5d9d2a8886ae748bb1abe0b0a41a7 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 16 Dec 2024 23:22:46 +0000 Subject: [PATCH 26/49] Remove generic and add query result --- .../Attributes/Settings/ListModuleSetting.cs | 28 +--- .../Attributes/Settings/QueryableParameter.cs | 126 +++++++++++++----- .../QueryableParameterEditWindow.xaml | 2 +- 3 files changed, 93 insertions(+), 63 deletions(-) diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs index 06568700..217e8254 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs @@ -7,7 +7,6 @@ using System.Collections.Specialized; using System.Linq; using Newtonsoft.Json.Linq; -using VRCOSC.App.SDK.Parameters; using VRCOSC.App.Utils; namespace VRCOSC.App.SDK.Modules.Attributes.Settings; @@ -142,30 +141,7 @@ public QueryableParameterListModuleSetting(string title, string description, Typ ActionType = actionType; } - public bool Evaluate(ReceivedParameter parameter) - { - foreach (var instance in Attribute.Where(instance => instance.Name.Value == parameter.Name && instance.Type.Value == parameter.Type)) - { - if (instance.Evaluate(parameter)) - { - return true; - } - } - - return false; - } - - public override void Add() - { - if (ActionType is null) - { - Attribute.Add(new QueryableParameter()); - } - else - { - Attribute.Add((QueryableParameter)Activator.CreateInstance(typeof(QueryableParameter<>).MakeGenericType(ActionType))!); - } - } + protected override QueryableParameter CreateItem() => ActionType is null ? new QueryableParameter() : new ActionableQueryableParameter(ActionType); public override bool Deserialise(object? value) { @@ -174,7 +150,7 @@ public override bool Deserialise(object? value) if (value is not JArray jArrayValue) return false; Attribute.Clear(); - Attribute.AddRange(jArrayValue.Select(token => token.ToObject(typeof(QueryableParameter<>).MakeGenericType(ActionType))).Cast()); + Attribute.AddRange(jArrayValue.Select(token => (QueryableParameter)token.ToObject(typeof(ActionableQueryableParameter))!)); return true; } } \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs index 4050f4bf..f712c780 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs @@ -8,41 +8,77 @@ namespace VRCOSC.App.SDK.Modules.Attributes.Settings; -public interface IQueryableParameter +public class QueryResult { + public bool IsValid { get; internal set; } + public bool JustBecameValid { get; internal set; } + public bool JustBecameInvalid { get; internal set; } } -public class QueryableParameter : QueryableParameter, IEquatable> where TAction : Enum +public class ActionableQueryResult : QueryResult { - public Observable ChosenResult { get; } = new(); + private readonly object actionResult; - public bool Evaluate(bool value, out TAction result) + public ActionableQueryResult(QueryResult other, object actionResult) { - var evaluateResult = Evaluate(value); - result = evaluateResult ? ChosenResult.Value : default!; - return evaluateResult; + IsValid = other.IsValid; + JustBecameValid = other.JustBecameValid; + JustBecameInvalid = other.JustBecameInvalid; + this.actionResult = actionResult; } - public bool Evaluate(int value, out TAction result) + public T GetActionResult() { - var evaluateResult = Evaluate(value); - result = evaluateResult ? ChosenResult.Value : default!; - return evaluateResult; + return (T)actionResult; } +} + +public class ActionableQueryableParameter : QueryableParameter, IEquatable +{ + public ActionableQueryableParameter(Type actionEnumType) + { + ActionEnumType = actionEnumType; + } + + internal Type ActionEnumType { get; } - public bool Evaluate(float value, out TAction result) + public Observable Action { get; } = new(); + + public new ActionableQueryResult Evaluate(ReceivedParameter parameter) { - var evaluateResult = Evaluate(value); - result = evaluateResult ? ChosenResult.Value : default!; - return evaluateResult; + return parameter.Type switch + { + ParameterType.Bool => Evaluate(parameter.GetValue()), + ParameterType.Int => Evaluate(parameter.GetValue()), + ParameterType.Float => Evaluate(parameter.GetValue()), + _ => throw new ArgumentOutOfRangeException() + }; } - public bool Equals(QueryableParameter? other) + protected new ActionableQueryResult Evaluate(bool value) + { + var queryResult = base.Evaluate(value); + return new ActionableQueryResult(queryResult, Action.Value); + } + + protected new ActionableQueryResult Evaluate(int value) + { + var queryResult = base.Evaluate(value); + return new ActionableQueryResult(queryResult, Action.Value); + } + + protected new ActionableQueryResult Evaluate(float value) + { + var queryResult = base.Evaluate(value); + return new ActionableQueryResult(queryResult, Action.Value); + } + + public bool Equals(ActionableQueryableParameter? other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; - return ChosenResult.Equals(other.ChosenResult); + return Action.Equals(other.Action); } public override bool Equals(object? obj) @@ -51,13 +87,13 @@ public override bool Equals(object? obj) if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; - return Equals((QueryableParameter)obj); + return Equals((ActionableQueryableParameter)obj); } - public override int GetHashCode() => ChosenResult.GetHashCode(); + public override int GetHashCode() => Action.GetHashCode(); } -public class QueryableParameter : IQueryableParameter, ICloneable, IEquatable +public class QueryableParameter : ICloneable, IEquatable { public Observable Name { get; } = new(string.Empty); public Observable Type { get; } = new(); @@ -73,7 +109,9 @@ public class QueryableParameter : IQueryableParameter, ICloneable, IEquatable Comparison.Value switch diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml index 9bce2581..233400c0 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml @@ -43,7 +43,7 @@ - + From afaeb4059c83124b2ac582fdecfb77985672aaea Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 16 Dec 2024 23:50:34 +0000 Subject: [PATCH 27/49] Improve module settings --- .../Serialisation/SerialisableModule.cs | 4 +- .../SDK/Modules/Attributes/ModuleAttribute.cs | 78 -------- .../Attributes/Settings/ListModuleSetting.cs | 94 ++++----- .../Attributes/Settings/ModuleSetting.cs | 51 ++--- .../Attributes/Settings/ValueModuleSetting.cs | 186 +++++++++++++----- .../Attributes/Types/MutableKeyValuePair.cs | 4 +- .../SDK/Modules/Heartrate/HeartrateModule.cs | 4 +- VRCOSC.App/SDK/Modules/Module.cs | 3 - .../Windows/Modules/ModuleSettingsWindow.xaml | 4 +- 9 files changed, 206 insertions(+), 222 deletions(-) delete mode 100644 VRCOSC.App/SDK/Modules/Attributes/ModuleAttribute.cs diff --git a/VRCOSC.App/Modules/Serialisation/SerialisableModule.cs b/VRCOSC.App/Modules/Serialisation/SerialisableModule.cs index 920d1b31..3720da75 100644 --- a/VRCOSC.App/Modules/Serialisation/SerialisableModule.cs +++ b/VRCOSC.App/Modules/Serialisation/SerialisableModule.cs @@ -31,7 +31,7 @@ public SerialisableModule(Module module) Version = 1; Enabled = module.Enabled.Value; - module.Settings.Where(pair => !pair.Value.IsDefault()).ForEach(pair => Settings.Add(pair.Key, pair.Value.GetSerialisableValue())); + module.Settings.Where(pair => !pair.Value.IsDefault()).ForEach(pair => Settings.Add(pair.Key, pair.Value.Serialise())); module.Parameters.Where(pair => !pair.Value.IsDefault()).ForEach(pair => Parameters.Add(pair.Key.ToLookup(), new SerialisableParameter(pair.Value))); } } @@ -53,4 +53,4 @@ public SerialisableParameter(ModuleParameter moduleParameter) Enabled = moduleParameter.Enabled.Value; ParameterName = moduleParameter.Name.Value; } -} +} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/ModuleAttribute.cs b/VRCOSC.App/SDK/Modules/Attributes/ModuleAttribute.cs deleted file mode 100644 index f15041d7..00000000 --- a/VRCOSC.App/SDK/Modules/Attributes/ModuleAttribute.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using System; - -namespace VRCOSC.App.SDK.Modules.Attributes; - -public abstract class ModuleAttribute -{ - public string Title { get; } - public string Description { get; } - - /// - /// Called before this deserialises - /// - public virtual void PreDeserialise() - { - } - - /// - /// Called after this deserialises - /// - public virtual void PostDeserialise() - { - } - - /// - /// Resets this 's value to its default value - /// - public abstract void SetDefault(); - - /// - /// If this 's value is currently the default value - /// - /// - public abstract bool IsDefault(); - - /// - /// Attempts to deserialise an object into this 's value's type - /// - /// The value to attempt to deserialise - /// True if the deserialisation was successful, otherwise false - public abstract bool Deserialise(object? ingestValue); - - /// - /// Retrieves the value for this using a provided expected type - /// - /// The type to attempt to convert the value to - /// True if the value was converted successfully, otherwise false - public virtual bool GetValue(out TValueType outValue) - { - var value = GetRawValue(); - - if (value is TValueType valueAsType) - { - outValue = valueAsType; - return true; - } - - throw new InvalidOperationException($"Unable to cast value of module attribute to '{typeof(TValueType)}'"); - } - - /// - /// Retrieves the unknown raw typed value for this . - /// - public abstract object? GetRawValue(); - - /// - /// Retrieves the unknown serialisable value for this - /// - public virtual object? GetSerialisableValue() => GetRawValue(); - - protected ModuleAttribute(string title, string description) - { - Title = title; - Description = description; - } -} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs index f08a9659..96a6864b 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections.Specialized; using System.Linq; using Newtonsoft.Json.Linq; using VRCOSC.App.Utils; @@ -20,89 +19,70 @@ protected ListModuleSetting(string title, string description, Type viewType) public abstract int Count(); public abstract void Add(); - public abstract void Remove(object instance); + public abstract void Remove(object item); } -public abstract class ListModuleSetting : ListModuleSetting where T : ICloneable, IEquatable, new() +public abstract class ListModuleSetting : ListModuleSetting { - public ObservableCollection Attribute { get; private set; } = null!; - protected IEnumerable DefaultValues { get; } - - public override object GetRawValue() => Attribute.ToList(); - public override bool IsDefault() => Attribute.SequenceEqual(DefaultValues); - - public override void PreDeserialise() - { - Attribute = new ObservableCollection(getClonedDefaults()); - Attribute.CollectionChanged += (_, _) => OnSettingChange?.Invoke(); - } - - public override void SetDefault() - { - Attribute.Clear(); - getClonedDefaults().ForEach(item => Attribute.Add(item)); - } - - private IEnumerable getClonedDefaults() => DefaultValues.Select(value => (T)value.Clone()); - - protected virtual T CreateItem() => new(); - - public override bool Deserialise(object? value) - { - if (value is not JArray jArrayValue) return false; - - Attribute.Clear(); - Attribute.AddRange(jArrayValue.Select(token => token.ToObject()!)); - return true; - } + protected readonly IEnumerable DefaultValues; + public ObservableCollection Attribute { get; } protected ListModuleSetting(string title, string description, Type viewType, IEnumerable defaultValues) : base(title, description, viewType) { DefaultValues = defaultValues; + Attribute = new ObservableCollection(defaultValues); + Attribute.OnCollectionChanged((_, _) => OnSettingChange?.Invoke()); } + internal override bool IsDefault() => Attribute.SequenceEqual(DefaultValues); + public override int Count() => Attribute.Count; + public override void Add() => Attribute.Add(CreateItem()); - public override void Add() + public override void Remove(object item) { - Attribute.Add(CreateItem()); + if (item is not T castItem) throw new InvalidOperationException($"Cannot remove type {item.GetType().ToReadableName()} from list with type {typeof(T).ToReadableName()}"); + + Attribute.Remove(castItem); } - public override void Remove(object instance) + protected abstract T CreateItem(); + + public override bool GetValue(out TOut returnValue) { - if (instance is not T castInstance) throw new InvalidOperationException($"Cannot remove type {instance.GetType()} from list with type {typeof(T)}"); + if (typeof(List).IsAssignableTo(typeof(TOut))) + { + returnValue = (TOut)Convert.ChangeType(Attribute.ToList(), typeof(TOut)); + return true; + } - Attribute.Remove(castInstance); + returnValue = (TOut)Convert.ChangeType(Array.Empty(), typeof(TOut)); + return false; } -} -public abstract class ValueListModuleSetting : ListModuleSetting> -{ - public override object GetRawValue() => Attribute.Select(observable => observable.Value).ToList(); + internal override object Serialise() => Attribute.ToList(); - public override void PreDeserialise() + internal override bool Deserialise(object? ingestValue) { - base.PreDeserialise(); - - Attribute.CollectionChanged += (_, e) => subscribeToNewItems(e); - return; + if (ingestValue is not JArray jArrayValue) return false; - void subscribeToNewItems(NotifyCollectionChangedEventArgs e) - { - if (e.NewItems is null) return; - - foreach (Observable newItem in e.NewItems) - { - newItem.Subscribe(_ => OnSettingChange?.Invoke()); - } - } + Attribute.Clear(); + Attribute.AddRange(jArrayValue.Select(token => token.ToObject()!)); + return true; } +} +public abstract class ValueListModuleSetting : ListModuleSetting> +{ protected ValueListModuleSetting(string title, string description, Type viewType, IEnumerable> defaultValues) : base(title, description, viewType, defaultValues) { } + + internal override bool IsDefault() => Attribute.Count == DefaultValues.Count() && Attribute.All(o => o.IsDefault); + + protected override Observable CreateItem() => new(); } public class StringListModuleSetting : ValueListModuleSetting @@ -129,4 +109,4 @@ public FloatListModuleSetting(string title, string description, Type viewType, I : base(title, description, viewType, defaultValues.Select(value => new Observable(value))) { } -} +} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ModuleSetting.cs index c0521426..db8cabe0 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ModuleSetting.cs @@ -2,26 +2,29 @@ // See the LICENSE file in the repository root for full license text. using System; -using System.ComponentModel; -using System.Runtime.CompilerServices; using System.Windows.Controls; using VRCOSC.App.Utils; namespace VRCOSC.App.SDK.Modules.Attributes.Settings; -public abstract class ModuleSetting : ModuleAttribute, INotifyPropertyChanged +public abstract class ModuleSetting { - internal Module ParentModule { get; set; } + internal Module ParentModule { get; set; } = null!; /// - /// The type of to instance when this needs to be rendered + /// The title of this /// - public Type ViewType { get; } + public string Title { get; } /// - /// Called whenever this 's value changed + /// The description of this /// - public Action? OnSettingChange; + public string Description { get; } + + /// + /// The type of to instance when this needs to be rendered + /// + public Type ViewType { get; } /// /// Creates a new instance of @@ -42,31 +45,19 @@ public UserControl? ViewInstance } } - private bool isEnabled = true; + public Action? OnSettingChange; - /// - /// When true, the user can edit this , otherwise, the UI prevents the user from editing this - /// - public bool IsEnabled - { - get => isEnabled; - set - { - isEnabled = value; - OnPropertyChanged(); - } - } + public Observable IsEnabled { get; } = new(true); + + internal abstract bool Deserialise(object? ingestValue); + internal abstract object? Serialise(); + internal abstract bool IsDefault(); + public abstract bool GetValue(out T returnValue); protected ModuleSetting(string title, string description, Type viewType) - : base(title, description) { + Title = title; + Description = description; ViewType = viewType; } - - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } -} +} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs index 64969ff5..450803dd 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs @@ -14,47 +14,45 @@ namespace VRCOSC.App.SDK.Modules.Attributes.Settings; /// public abstract class ValueModuleSetting : ModuleSetting { - public Observable Attribute { get; private set; } = null!; + public Observable Attribute { get; } - protected readonly T DefaultValue; - - public override void PreDeserialise() + protected ValueModuleSetting(string title, string description, Type viewType, T defaultValue) + : base(title, description, viewType) { - Attribute = new Observable(DefaultValue); + Attribute = new Observable(defaultValue); Attribute.Subscribe(_ => OnSettingChange?.Invoke()); } - public override bool IsDefault() => Attribute.IsDefault; - public override void SetDefault() => Attribute.SetDefault(); - public override object? GetRawValue() => Attribute.Value; + internal override bool IsDefault() => Attribute.IsDefault; +} - public override bool Deserialise(object? ingestValue) +public class BoolModuleSetting : ValueModuleSetting +{ + public BoolModuleSetting(string title, string description, Type viewType, bool defaultValue) + : base(title, description, viewType, defaultValue) { - if (ingestValue is null) return false; - - try - { - Attribute.Value = (T)Convert.ChangeType(ingestValue, typeof(T)); - return true; - } - catch (Exception) - { - return false; - } } - protected ValueModuleSetting(string title, string description, Type viewType, T defaultValue) - : base(title, description, viewType) + internal override bool Deserialise(object? ingestValue) { - DefaultValue = defaultValue; + if (ingestValue is not bool boolValue) return false; + + Attribute.Value = boolValue; + return true; } -} -public class BoolModuleSetting : ValueModuleSetting -{ - public BoolModuleSetting(string title, string description, Type viewType, bool defaultValue) - : base(title, description, viewType, defaultValue) + internal override object Serialise() => Attribute.Value; + + public override bool GetValue(out T returnValue) { + if (typeof(T) == typeof(bool)) + { + returnValue = (T)Convert.ChangeType(Attribute.Value, typeof(T)); + return true; + } + + returnValue = (T)Convert.ChangeType(false, typeof(T)); + return false; } } @@ -64,6 +62,28 @@ public StringModuleSetting(string title, string description, Type viewType, stri : base(title, description, viewType, defaultValue) { } + + internal override bool Deserialise(object? ingestValue) + { + if (ingestValue is not string stringValue) return false; + + Attribute.Value = stringValue; + return true; + } + + internal override object Serialise() => Attribute.Value; + + public override bool GetValue(out T returnValue) + { + if (typeof(T) == typeof(string)) + { + returnValue = (T)Convert.ChangeType(Attribute.Value, typeof(T)); + return true; + } + + returnValue = (T)Convert.ChangeType(string.Empty, typeof(T)); + return false; + } } public class IntModuleSetting : ValueModuleSetting @@ -72,6 +92,29 @@ public IntModuleSetting(string title, string description, Type viewType, int def : base(title, description, viewType, defaultValue) { } + + internal override bool Deserialise(object? ingestValue) + { + // json stores as long + if (ingestValue is not long longValue) return false; + + Attribute.Value = (int)longValue; + return true; + } + + internal override object Serialise() => Attribute.Value; + + public override bool GetValue(out T returnValue) + { + if (typeof(T) == typeof(int)) + { + returnValue = (T)Convert.ChangeType(Attribute.Value, typeof(T)); + return true; + } + + returnValue = (T)Convert.ChangeType(0, typeof(T)); + return false; + } } public class FloatModuleSetting : ValueModuleSetting @@ -80,11 +123,34 @@ public FloatModuleSetting(string title, string description, Type viewType, float : base(title, description, viewType, defaultValue) { } + + internal override bool Deserialise(object? ingestValue) + { + // json stores as double + if (ingestValue is not double doubleValue) return false; + + Attribute.Value = (float)doubleValue; + return true; + } + + internal override object Serialise() => Attribute.Value; + + public override bool GetValue(out T returnValue) + { + if (typeof(T) == typeof(float)) + { + returnValue = (T)Convert.ChangeType(Attribute.Value, typeof(T)); + return true; + } + + returnValue = (T)Convert.ChangeType(0f, typeof(T)); + return false; + } } public class EnumModuleSetting : ValueModuleSetting { - public Type EnumType { get; } + internal readonly Type EnumType; public EnumModuleSetting(string title, string description, Type viewType, int defaultValue, Type enumType) : base(title, description, viewType, defaultValue) @@ -92,15 +158,25 @@ public EnumModuleSetting(string title, string description, Type viewType, int de EnumType = enumType; } - public override bool GetValue(out TValueType? outValue) where TValueType : default + internal override bool Deserialise(object? ingestValue) { - if (typeof(TValueType) == EnumType) + if (ingestValue is not int intValue) return false; + + Attribute.Value = intValue; + return true; + } + + internal override object Serialise() => Attribute.Value; + + public override bool GetValue(out TOut returnValue) + { + if (EnumType == typeof(TOut)) { - outValue = (TValueType)Enum.ToObject(EnumType, Attribute.Value); + returnValue = (TOut)Enum.ToObject(EnumType, Attribute.Value); return true; } - outValue = default; + returnValue = (TOut)Enum.ToObject(typeof(TOut), 0); return false; } } @@ -144,30 +220,34 @@ public SliderModuleSetting(string title, string description, Type viewType, int TickFrequency = tickFrequency; } - public override bool Deserialise(object? ingestValue) + internal override object Serialise() => Attribute.Value; + + internal override bool Deserialise(object? ingestValue) { - var result = base.Deserialise(ingestValue); - Attribute.Value = Math.Clamp(Attribute.Value, MinValue, MaxValue); - return result; + // json stores as double + if (ingestValue is not double doubleValue) return false; + + var floatValue = (float)doubleValue; + Attribute.Value = Math.Clamp(floatValue, MinValue, MaxValue); + + return true; } - public override bool GetValue(out TValueType? outValue) where TValueType : default + public override bool GetValue(out TOut returnValue) { - var value = (float)GetRawValue()!; - - if (typeof(TValueType) == typeof(float)) + if (typeof(TOut) == typeof(float) && ValueType == typeof(float)) { - outValue = (TValueType)Convert.ChangeType(value, TypeCode.Single); + returnValue = (TOut)Convert.ChangeType(Attribute.Value, typeof(TOut)); return true; } - if (typeof(TValueType) == typeof(int)) + if (typeof(TOut) == typeof(int) && ValueType == typeof(int)) { - outValue = (TValueType)Convert.ChangeType(value, TypeCode.Int32); + returnValue = (TOut)Convert.ChangeType(Attribute.Value, typeof(TOut)); return true; } - outValue = default; + returnValue = (TOut)Convert.ChangeType(0f, typeof(TOut)); return false; } } @@ -182,9 +262,9 @@ public DateTimeModuleSetting(string title, string description, Type viewType, Da { } - public override object GetSerialisableValue() => Attribute.Value.UtcTicks; + internal override object Serialise() => Attribute.Value.UtcTicks; - public override bool Deserialise(object? ingestValue) + internal override bool Deserialise(object? ingestValue) { if (ingestValue is not long ingestUtcTicks) return false; @@ -199,4 +279,16 @@ public override bool Deserialise(object? ingestValue) return true; } + + public override bool GetValue(out T returnValue) + { + if (typeof(T) == typeof(DateTimeOffset)) + { + returnValue = (T)Convert.ChangeType(Attribute.Value, typeof(T)); + return true; + } + + returnValue = (T)Convert.ChangeType(DateTimeOffset.FromUnixTimeSeconds(0), typeof(T)); + return false; + } } \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/Types/MutableKeyValuePair.cs b/VRCOSC.App/SDK/Modules/Attributes/Types/MutableKeyValuePair.cs index 7284414b..5f09a987 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Types/MutableKeyValuePair.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Types/MutableKeyValuePair.cs @@ -48,4 +48,6 @@ public MutableKeyValuePairListModuleSetting(string title, string description, Ty KeyTitle = keyTitle; ValueTitle = valueTitle; } -} + + protected override MutableKeyValuePair CreateItem() => new(); +} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Heartrate/HeartrateModule.cs b/VRCOSC.App/SDK/Modules/Heartrate/HeartrateModule.cs index e9b5271f..7298ccaa 100644 --- a/VRCOSC.App/SDK/Modules/Heartrate/HeartrateModule.cs +++ b/VRCOSC.App/SDK/Modules/Heartrate/HeartrateModule.cs @@ -72,8 +72,8 @@ protected override void OnPostLoad() CreateState(HeartrateState.Connected, "Connected", "Heartrate: {0}", new[] { currentReference }); CreateState(HeartrateState.Disconnected, "Disconnected", string.Empty); - GetSetting(HeartrateSetting.SmoothValue)!.OnSettingChange += () => GetSetting(HeartrateSetting.SmoothValueLength)!.IsEnabled = GetSettingValue(HeartrateSetting.SmoothValue); - GetSetting(HeartrateSetting.SmoothAverage)!.OnSettingChange += () => GetSetting(HeartrateSetting.SmoothAverageLength)!.IsEnabled = GetSettingValue(HeartrateSetting.SmoothAverage); + GetSetting(HeartrateSetting.SmoothValue).OnSettingChange += () => GetSetting(HeartrateSetting.SmoothValueLength).IsEnabled.Value = GetSettingValue(HeartrateSetting.SmoothValue); + GetSetting(HeartrateSetting.SmoothAverage).OnSettingChange += () => GetSetting(HeartrateSetting.SmoothAverageLength).IsEnabled.Value = GetSettingValue(HeartrateSetting.SmoothAverage); } protected override async Task OnModuleStart() diff --git a/VRCOSC.App/SDK/Modules/Module.cs b/VRCOSC.App/SDK/Modules/Module.cs index 6cc6f708..5343add5 100644 --- a/VRCOSC.App/SDK/Modules/Module.cs +++ b/VRCOSC.App/SDK/Modules/Module.cs @@ -16,7 +16,6 @@ using VRCOSC.App.ChatBox.Clips.Variables.Instances; using VRCOSC.App.Modules; using VRCOSC.App.OSC.VRChat; -using VRCOSC.App.SDK.Modules.Attributes; using VRCOSC.App.SDK.Modules.Attributes.Settings; using VRCOSC.App.SDK.Modules.Attributes.Types; using VRCOSC.App.SDK.OVR; @@ -116,9 +115,7 @@ internal void Load(string filePathOverride = "") OnPreLoad(); - Settings.Values.ForEach(moduleSetting => moduleSetting.PreDeserialise()); moduleSerialisationManager.Deserialise(string.IsNullOrEmpty(filePathOverride), filePathOverride); - Settings.Values.ForEach(moduleSetting => moduleSetting.PostDeserialise()); cachePersistentProperties(); diff --git a/VRCOSC.App/UI/Windows/Modules/ModuleSettingsWindow.xaml b/VRCOSC.App/UI/Windows/Modules/ModuleSettingsWindow.xaml index e3ddf666..78699bf8 100644 --- a/VRCOSC.App/UI/Windows/Modules/ModuleSettingsWindow.xaml +++ b/VRCOSC.App/UI/Windows/Modules/ModuleSettingsWindow.xaml @@ -37,7 +37,7 @@ + Visibility="{Binding IsEnabled.Value, Converter={StaticResource BoolToVisibilityConverter}}" /> @@ -54,4 +54,4 @@ - + \ No newline at end of file From 834a628e6e140c7eefcf34f32a978279776ce6c2 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 00:17:44 +0000 Subject: [PATCH 28/49] Allow Dropdown module setting to take in any class --- .../Attributes/Settings/ValueModuleSetting.cs | 30 ++++++++++++++++--- .../Modules/Attributes/Types/DropdownItem.cs | 16 ---------- VRCOSC.App/SDK/Modules/Module.cs | 25 ++++++++++++++-- .../Settings/ListItemDropdownSettingView.xaml | 2 +- 4 files changed, 50 insertions(+), 23 deletions(-) delete mode 100644 VRCOSC.App/SDK/Modules/Attributes/Types/DropdownItem.cs diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs index 450803dd..a09308e6 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using VRCOSC.App.SDK.Modules.Attributes.Types; using VRCOSC.App.Utils; namespace VRCOSC.App.SDK.Modules.Attributes.Settings; @@ -183,14 +182,37 @@ public override bool GetValue(out TOut returnValue) public class DropdownListModuleSetting : StringModuleSetting { - public IEnumerable Items { get; internal set; } + private readonly Type itemType; - public DropdownListModuleSetting(string title, string description, Type viewType, IEnumerable items, DropdownItem defaultItem) - : base(title, description, viewType, defaultItem.ID) + public string TitlePath { get; } + public string ValuePath { get; } + + public IEnumerable Items { get; } + + public DropdownListModuleSetting(string title, string description, Type viewType, IEnumerable items, string defaultValue, string titlePath, string valuePath) + : base(title, description, viewType, defaultValue) { + itemType = items.GetType().GenericTypeArguments[0]; + + TitlePath = titlePath; + ValuePath = valuePath; + // take a copy to stop developers holding a reference Items = items.ToList().AsReadOnly(); } + + public override bool GetValue(out T returnValue) + { + if (typeof(T) == itemType) + { + var valueProperty = itemType.GetProperty(ValuePath); + returnValue = (T)Items.First(item => valueProperty!.GetValue(item)!.ToString()! == Attribute.Value); + return true; + } + + returnValue = (T)new object(); + return false; + } } public class SliderModuleSetting : ValueModuleSetting diff --git a/VRCOSC.App/SDK/Modules/Attributes/Types/DropdownItem.cs b/VRCOSC.App/SDK/Modules/Attributes/Types/DropdownItem.cs deleted file mode 100644 index 5099ecc2..00000000 --- a/VRCOSC.App/SDK/Modules/Attributes/Types/DropdownItem.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -namespace VRCOSC.App.SDK.Modules.Attributes.Types; - -public class DropdownItem -{ - public string Title { get; } - public string ID { get; } - - public DropdownItem(string title, string id) - { - Title = title; - ID = id; - } -} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Module.cs b/VRCOSC.App/SDK/Modules/Module.cs index 5343add5..99c6336c 100644 --- a/VRCOSC.App/SDK/Modules/Module.cs +++ b/VRCOSC.App/SDK/Modules/Module.cs @@ -444,9 +444,30 @@ protected void CreateDropdown(Enum lookup, string title, string description, addSetting(lookup, new EnumModuleSetting(title, description, typeof(EnumDropdownSettingView), Convert.ToInt32(defaultValue), typeof(T))); } - protected void CreateDropdown(Enum lookup, string title, string description, IEnumerable items, DropdownItem defaultItem) + protected void CreateDropdown(Enum lookup, string title, string description, IEnumerable items, object defaultItem, string titlePath, string valuePath) { - addSetting(lookup, new DropdownListModuleSetting(title, description, typeof(ListItemDropdownSettingView), items, defaultItem)); + var titleProperty = defaultItem.GetType().GetProperty(titlePath); + var valueProperty = defaultItem.GetType().GetProperty(valuePath); + + if (titleProperty is null || valueProperty is null) + { + throw new InvalidOperationException("You must have a property for both the titlePath and valuePath"); + } + + var titleValue = titleProperty.GetValue(defaultItem); + var valueValue = valueProperty.GetValue(defaultItem); + + if (titleValue is null || valueValue is null) + { + throw new InvalidOperationException("You must have a property for both the titlePath and valuePath"); + } + + if (titleValue.ToString() is null || valueValue.ToString() is null) + { + throw new InvalidOperationException("Your titlePath and valuePath properties must be convertable to a string"); + } + + addSetting(lookup, new DropdownListModuleSetting(title, description, typeof(ListItemDropdownSettingView), items, valueValue.ToString()!, titlePath, valuePath)); } protected void CreateDateTime(Enum lookup, string title, string description, DateTimeOffset defaultValue) diff --git a/VRCOSC.App/UI/Views/Modules/Settings/ListItemDropdownSettingView.xaml b/VRCOSC.App/UI/Views/Modules/Settings/ListItemDropdownSettingView.xaml index 84f9bcb1..2a836e5f 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/ListItemDropdownSettingView.xaml +++ b/VRCOSC.App/UI/Views/Modules/Settings/ListItemDropdownSettingView.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"> \ No newline at end of file From a1514fa6900f2245d93dd7a9e209e956f48954b7 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 00:33:46 +0000 Subject: [PATCH 29/49] Ensure value module settings can't be null --- .../Attributes/Settings/ValueModuleSetting.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs index a09308e6..2dc84997 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs @@ -11,7 +11,7 @@ namespace VRCOSC.App.SDK.Modules.Attributes.Settings; /// /// For use with value types /// -public abstract class ValueModuleSetting : ModuleSetting +public abstract class ValueModuleSetting : ModuleSetting where T : notnull { public Observable Attribute { get; } @@ -22,6 +22,8 @@ protected ValueModuleSetting(string title, string description, Type viewType, T Attribute.Subscribe(_ => OnSettingChange?.Invoke()); } + internal override object Serialise() => Attribute.Value; + internal override bool IsDefault() => Attribute.IsDefault; } @@ -40,8 +42,6 @@ internal override bool Deserialise(object? ingestValue) return true; } - internal override object Serialise() => Attribute.Value; - public override bool GetValue(out T returnValue) { if (typeof(T) == typeof(bool)) @@ -70,8 +70,6 @@ internal override bool Deserialise(object? ingestValue) return true; } - internal override object Serialise() => Attribute.Value; - public override bool GetValue(out T returnValue) { if (typeof(T) == typeof(string)) @@ -101,8 +99,6 @@ internal override bool Deserialise(object? ingestValue) return true; } - internal override object Serialise() => Attribute.Value; - public override bool GetValue(out T returnValue) { if (typeof(T) == typeof(int)) @@ -132,8 +128,6 @@ internal override bool Deserialise(object? ingestValue) return true; } - internal override object Serialise() => Attribute.Value; - public override bool GetValue(out T returnValue) { if (typeof(T) == typeof(float)) @@ -165,8 +159,6 @@ internal override bool Deserialise(object? ingestValue) return true; } - internal override object Serialise() => Attribute.Value; - public override bool GetValue(out TOut returnValue) { if (EnumType == typeof(TOut)) @@ -242,8 +234,6 @@ public SliderModuleSetting(string title, string description, Type viewType, int TickFrequency = tickFrequency; } - internal override object Serialise() => Attribute.Value; - internal override bool Deserialise(object? ingestValue) { // json stores as double From d85f66e478cc4650c284441e2547980fbe50b888 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 01:00:44 +0000 Subject: [PATCH 30/49] Ensure all items of ListModuleSetting are IEquatable --- .../Modules/Attributes/Settings/ListModuleSetting.cs | 2 +- .../Modules/Attributes/Types/MutableKeyValuePair.cs | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs index 96a6864b..77c03794 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs @@ -22,7 +22,7 @@ protected ListModuleSetting(string title, string description, Type viewType) public abstract void Remove(object item); } -public abstract class ListModuleSetting : ListModuleSetting +public abstract class ListModuleSetting : ListModuleSetting where T : IEquatable { protected readonly IEnumerable DefaultValues; public ObservableCollection Attribute { get; } diff --git a/VRCOSC.App/SDK/Modules/Attributes/Types/MutableKeyValuePair.cs b/VRCOSC.App/SDK/Modules/Attributes/Types/MutableKeyValuePair.cs index 5f09a987..368ef1ab 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Types/MutableKeyValuePair.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Types/MutableKeyValuePair.cs @@ -9,7 +9,8 @@ namespace VRCOSC.App.SDK.Modules.Attributes.Types; -public class MutableKeyValuePair : IEquatable, ICloneable +[JsonObject(MemberSerialization.OptIn)] +public class MutableKeyValuePair : IEquatable { [JsonProperty("key")] public Observable Key { get; } = new(string.Empty); @@ -17,24 +18,17 @@ public class MutableKeyValuePair : IEquatable, ICloneable [JsonProperty("value")] public Observable Value { get; } = new(string.Empty); + [JsonConstructor] public MutableKeyValuePair() { } - public MutableKeyValuePair(MutableKeyValuePair other) - { - Key.Value = other.Key.Value; - Value.Value = other.Value.Value; - } - public bool Equals(MutableKeyValuePair? other) { if (ReferenceEquals(null, other)) return false; return Key.Value.Equals(other.Key.Value) && Value.Value.Equals(other.Value.Value); } - - public object Clone() => new MutableKeyValuePair(this); } public class MutableKeyValuePairListModuleSetting : ListModuleSetting From bb2e5a187b6d3b82f34e76baebc3d84a4588174d Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 01:01:01 +0000 Subject: [PATCH 31/49] Remove pointless equality check for Observable --- VRCOSC.App/Utils/Observable.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/VRCOSC.App/Utils/Observable.cs b/VRCOSC.App/Utils/Observable.cs index 6318b617..d8a93ea8 100644 --- a/VRCOSC.App/Utils/Observable.cs +++ b/VRCOSC.App/Utils/Observable.cs @@ -173,6 +173,4 @@ public bool Equals(Observable? other) return EqualityComparer.Default.Equals(Value, other.Value); } - - public override bool Equals(object? obj) => obj is Observable observable && Equals(observable); -} +} \ No newline at end of file From ac8b726ec05257ec3c69ea774d85c61e2f6eb0a2 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 01:01:14 +0000 Subject: [PATCH 32/49] Improve EnumModuleSetting --- .../Attributes/Settings/ValueModuleSetting.cs | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs index 2dc84997..c124a134 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ValueModuleSetting.cs @@ -141,7 +141,7 @@ public override bool GetValue(out T returnValue) } } -public class EnumModuleSetting : ValueModuleSetting +public class EnumModuleSetting : IntModuleSetting { internal readonly Type EnumType; @@ -151,23 +151,15 @@ public EnumModuleSetting(string title, string description, Type viewType, int de EnumType = enumType; } - internal override bool Deserialise(object? ingestValue) - { - if (ingestValue is not int intValue) return false; - - Attribute.Value = intValue; - return true; - } - - public override bool GetValue(out TOut returnValue) + public override bool GetValue(out T returnValue) { - if (EnumType == typeof(TOut)) + if (typeof(T) == EnumType) { - returnValue = (TOut)Enum.ToObject(EnumType, Attribute.Value); + returnValue = (T)Enum.ToObject(EnumType, Attribute.Value); return true; } - returnValue = (TOut)Enum.ToObject(typeof(TOut), 0); + returnValue = (T)Enum.ToObject(typeof(T), 0); return false; } } @@ -245,21 +237,21 @@ internal override bool Deserialise(object? ingestValue) return true; } - public override bool GetValue(out TOut returnValue) + public override bool GetValue(out T returnValue) { - if (typeof(TOut) == typeof(float) && ValueType == typeof(float)) + if (typeof(T) == typeof(float) && ValueType == typeof(float)) { - returnValue = (TOut)Convert.ChangeType(Attribute.Value, typeof(TOut)); + returnValue = (T)Convert.ChangeType(Attribute.Value, typeof(T)); return true; } - if (typeof(TOut) == typeof(int) && ValueType == typeof(int)) + if (typeof(T) == typeof(int) && ValueType == typeof(int)) { - returnValue = (TOut)Convert.ChangeType(Attribute.Value, typeof(TOut)); + returnValue = (T)Convert.ChangeType(Attribute.Value, typeof(T)); return true; } - returnValue = (TOut)Convert.ChangeType(0f, typeof(TOut)); + returnValue = (T)Convert.ChangeType(0f, typeof(T)); return false; } } From e7d5a7ca244e1408c422d731eb7460893d2d0207 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 01:15:35 +0000 Subject: [PATCH 33/49] Get actionable queryable parameter working --- .../Attributes/Settings/ListModuleSetting.cs | 25 +++++++++---------- VRCOSC.App/SDK/Modules/Module.cs | 2 +- .../QueryableParameterConverters.cs | 9 +++++-- .../QueryableParameterEditWindow.xaml.cs | 15 +++++++++-- .../QueryableParameterSettingView.xaml.cs | 18 +++++++++++-- 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs index 0d1db3d9..f9f60a67 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs @@ -113,24 +113,23 @@ public FloatListModuleSetting(string title, string description, Type viewType, I public class QueryableParameterListModuleSetting : ListModuleSetting { - internal readonly Type? ActionType; - - public QueryableParameterListModuleSetting(string title, string description, Type viewType, Type? actionType = null) + public QueryableParameterListModuleSetting(string title, string description, Type viewType) : base(title, description, viewType, []) { - ActionType = actionType; } - protected override QueryableParameter CreateItem() => ActionType is null ? new QueryableParameter() : new ActionableQueryableParameter(ActionType); - - public override bool Deserialise(object? value) - { - if (ActionType is null) return base.Deserialise(value); + protected override QueryableParameter CreateItem() => new(); +} - if (value is not JArray jArrayValue) return false; +public class ActionableQueryableParameterListModuleSetting : ListModuleSetting +{ + public readonly Type ActionType; - Attribute.Clear(); - Attribute.AddRange(jArrayValue.Select(token => (QueryableParameter)token.ToObject(typeof(ActionableQueryableParameter))!)); - return true; + public ActionableQueryableParameterListModuleSetting(string title, string description, Type viewType, Type actionType) + : base(title, description, viewType, []) + { + ActionType = actionType; } + + protected override ActionableQueryableParameter CreateItem() => new(ActionType); } \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Module.cs b/VRCOSC.App/SDK/Modules/Module.cs index 7e1fd6d5..544ee634 100644 --- a/VRCOSC.App/SDK/Modules/Module.cs +++ b/VRCOSC.App/SDK/Modules/Module.cs @@ -503,7 +503,7 @@ protected void CreateQueryableParameterList(Enum lookup, string title, string de protected void CreateQueryableParameterList(Enum lookup, string title, string description) where TAction : Enum { - addSetting(lookup, new QueryableParameterListModuleSetting(title, description, typeof(QueryableParameterSettingView), typeof(TAction))); + addSetting(lookup, new ActionableQueryableParameterListModuleSetting(title, description, typeof(QueryableParameterSettingView), typeof(TAction))); } private void addSetting(Enum lookup, ModuleSetting moduleSetting) diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterConverters.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterConverters.cs index fe8cd784..820a59a7 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterConverters.cs +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterConverters.cs @@ -75,9 +75,14 @@ public class QueryableParameterHasActionVisibilityConverter : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (value is QueryableParameterListModuleSetting moduleSetting) + if (value is QueryableParameterListModuleSetting _) { - return moduleSetting.ActionType is null ? Visibility.Collapsed : Visibility.Visible; + return Visibility.Collapsed; + } + + if (value is ActionableQueryableParameterListModuleSetting _) + { + return Visibility.Visible; } return null; diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs index 448e57e1..f2ef0b19 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs @@ -18,9 +18,9 @@ public partial class QueryableParameterEditWindow : IManagedWindow public IEnumerable QueryableParameterTypeItemsSource => typeof(ParameterType).GetEnumValues().Cast(); public IEnumerable> QueryableParameterOperationItemsSource => ComparisonOperationUtils.DISPLAY_LIST; public IEnumerable BoolValueItemsSource => typeof(BoolValue).GetEnumValues().Cast(); - public Array QueryableParameterActionTypeItemsSource => ModuleSetting.ActionType?.GetEnumValues() ?? Array.Empty(); + public Array QueryableParameterActionTypeItemsSource { get; init; } = Array.Empty(); - public QueryableParameterListModuleSetting ModuleSetting { get; } + public ListModuleSetting ModuleSetting { get; } public QueryableParameterEditWindow(QueryableParameterListModuleSetting moduleSetting) { @@ -31,6 +31,17 @@ public QueryableParameterEditWindow(QueryableParameterListModuleSetting moduleSe Title = $"Edit {moduleSetting.Title.Pluralise()} Queryable Parameters"; } + public QueryableParameterEditWindow(ActionableQueryableParameterListModuleSetting moduleSetting) + { + InitializeComponent(); + ModuleSetting = moduleSetting; + DataContext = this; + + Title = $"Edit {moduleSetting.Title.Pluralise()} Queryable Parameters"; + + QueryableParameterActionTypeItemsSource = moduleSetting.ActionType.GetEnumValues(); + } + private void AddButton_OnClick(object sender, RoutedEventArgs e) { ModuleSetting.Add(); diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs index db1b2e38..62f4d907 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs @@ -12,7 +12,7 @@ public partial class QueryableParameterSettingView { private WindowManager windowManager = null!; - public QueryableParameterListModuleSetting ModuleSetting { get; } + public object ModuleSetting { get; } public QueryableParameterSettingView(Module _, QueryableParameterListModuleSetting moduleSetting) { @@ -20,9 +20,23 @@ public QueryableParameterSettingView(Module _, QueryableParameterListModuleSetti ModuleSetting = moduleSetting; } + public QueryableParameterSettingView(Module _, ActionableQueryableParameterListModuleSetting moduleSetting) + { + InitializeComponent(); + ModuleSetting = moduleSetting; + } + private void EditButton_OnClick(object sender, RoutedEventArgs e) { - windowManager.TrySpawnChild(new QueryableParameterEditWindow(ModuleSetting)); + if (ModuleSetting is QueryableParameterListModuleSetting queryableParameterListModuleSetting) + { + windowManager.TrySpawnChild(new QueryableParameterEditWindow(queryableParameterListModuleSetting)); + } + + if (ModuleSetting is ActionableQueryableParameterListModuleSetting actionableQueryableParameterListModuleSetting) + { + windowManager.TrySpawnChild(new QueryableParameterEditWindow(actionableQueryableParameterListModuleSetting)); + } } private void QueryableParameterSettingView_OnLoaded(object sender, RoutedEventArgs e) From bebc4d08a2356dd02ea570faf179a39101e12866 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 01:30:38 +0000 Subject: [PATCH 34/49] Remove pointless equality checking --- .../Attributes/Settings/QueryableParameter.cs | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs index f712c780..dbd0ef5e 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs @@ -73,27 +73,10 @@ public ActionableQueryableParameter(Type actionEnumType) return new ActionableQueryResult(queryResult, Action.Value); } - public bool Equals(ActionableQueryableParameter? other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - - return Action.Equals(other.Action); - } - - public override bool Equals(object? obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - - return Equals((ActionableQueryableParameter)obj); - } - - public override int GetHashCode() => Action.GetHashCode(); + public bool Equals(ActionableQueryableParameter? other) => base.Equals(other) && Action.Equals(other.Action); } -public class QueryableParameter : ICloneable, IEquatable +public class QueryableParameter : IEquatable { public Observable Name { get; } = new(string.Empty); public Observable Type { get; } = new(); @@ -214,13 +197,11 @@ protected QueryResult Evaluate(float value) _ => throw new ArgumentOutOfRangeException() }; - public object Clone() - { - return this; - } - public bool Equals(QueryableParameter? other) { - return false; + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + + return Name.Equals(other.Name) && Type.Equals(other.Type) && Comparison.Equals(other.Comparison) && BoolValue.Equals(other.BoolValue) && IntValue.Equals(other.IntValue) && FloatValue.Equals(other.FloatValue); } } \ No newline at end of file From 8d67142cd167ebd45c502935e736280ab7bd3c02 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 21:40:38 +0000 Subject: [PATCH 35/49] Further improve QueryableParameters --- .../Attributes/Settings/ListModuleSetting.cs | 33 ++- .../Attributes/Settings/QueryableParameter.cs | 207 ----------------- .../Attributes/Types/QueryableParameter.cs | 218 ++++++++++++++++++ .../QueryableParameterEditWindow.xaml | 8 +- 4 files changed, 252 insertions(+), 214 deletions(-) delete mode 100644 VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs create mode 100644 VRCOSC.App/SDK/Modules/Attributes/Types/QueryableParameter.cs diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs index f9f60a67..c7fe382f 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading.Tasks; using Newtonsoft.Json.Linq; +using VRCOSC.App.SDK.Modules.Attributes.Types; using VRCOSC.App.Utils; namespace VRCOSC.App.SDK.Modules.Attributes.Settings; @@ -119,11 +121,19 @@ public QueryableParameterListModuleSetting(string title, string description, Typ } protected override QueryableParameter CreateItem() => new(); + + public async Task Init() + { + foreach (var queryableParameter in Attribute) + { + await queryableParameter.Init(); + } + } } -public class ActionableQueryableParameterListModuleSetting : ListModuleSetting +public class ActionableQueryableParameterListModuleSetting : ListModuleSetting { - public readonly Type ActionType; + public Type ActionType { get; } public ActionableQueryableParameterListModuleSetting(string title, string description, Type viewType, Type actionType) : base(title, description, viewType, []) @@ -131,5 +141,22 @@ public ActionableQueryableParameterListModuleSetting(string title, string descri ActionType = actionType; } - protected override ActionableQueryableParameter CreateItem() => new(ActionType); + protected override QueryableParameter CreateItem() => (QueryableParameter)Activator.CreateInstance(typeof(ActionableQueryableParameter<>).MakeGenericType(ActionType))!; + + internal override bool Deserialise(object? ingestValue) + { + if (ingestValue is not JArray jArrayValue) return false; + + Attribute.Clear(); + Attribute.AddRange(jArrayValue.Select(token => (QueryableParameter)token.ToObject(typeof(ActionableQueryableParameter<>).MakeGenericType(ActionType))!)); + return true; + } + + public async Task Init() + { + foreach (var queryableParameter in Attribute) + { + await queryableParameter.Init(); + } + } } \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs deleted file mode 100644 index dbd0ef5e..00000000 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameter.cs +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. -// See the LICENSE file in the repository root for full license text. - -using System; -using VRCOSC.App.SDK.Parameters; -using VRCOSC.App.SDK.Utils; -using VRCOSC.App.Utils; - -namespace VRCOSC.App.SDK.Modules.Attributes.Settings; - -public class QueryResult -{ - public bool IsValid { get; internal set; } - public bool JustBecameValid { get; internal set; } - public bool JustBecameInvalid { get; internal set; } -} - -public class ActionableQueryResult : QueryResult -{ - private readonly object actionResult; - - public ActionableQueryResult(QueryResult other, object actionResult) - { - IsValid = other.IsValid; - JustBecameValid = other.JustBecameValid; - JustBecameInvalid = other.JustBecameInvalid; - this.actionResult = actionResult; - } - - public T GetActionResult() - { - return (T)actionResult; - } -} - -public class ActionableQueryableParameter : QueryableParameter, IEquatable -{ - public ActionableQueryableParameter(Type actionEnumType) - { - ActionEnumType = actionEnumType; - } - - internal Type ActionEnumType { get; } - - public Observable Action { get; } = new(); - - public new ActionableQueryResult Evaluate(ReceivedParameter parameter) - { - return parameter.Type switch - { - ParameterType.Bool => Evaluate(parameter.GetValue()), - ParameterType.Int => Evaluate(parameter.GetValue()), - ParameterType.Float => Evaluate(parameter.GetValue()), - _ => throw new ArgumentOutOfRangeException() - }; - } - - protected new ActionableQueryResult Evaluate(bool value) - { - var queryResult = base.Evaluate(value); - return new ActionableQueryResult(queryResult, Action.Value); - } - - protected new ActionableQueryResult Evaluate(int value) - { - var queryResult = base.Evaluate(value); - return new ActionableQueryResult(queryResult, Action.Value); - } - - protected new ActionableQueryResult Evaluate(float value) - { - var queryResult = base.Evaluate(value); - return new ActionableQueryResult(queryResult, Action.Value); - } - - public bool Equals(ActionableQueryableParameter? other) => base.Equals(other) && Action.Equals(other.Action); -} - -public class QueryableParameter : IEquatable -{ - public Observable Name { get; } = new(string.Empty); - public Observable Type { get; } = new(); - public Observable Comparison { get; } = new(); - - public Observable BoolValue { get; } = new(); - public Observable IntValue { get; } = new(); - public Observable FloatValue { get; } = new(); - - // TODO: Should parameters be cached globally and this can just reference that? - // The cache can just update all the parameters after module update - private bool previousBoolValue; - private int previousIntValue; - private float previousFloatValue; - - private bool previousValid; - - public QueryResult Evaluate(ReceivedParameter parameter) - { - return parameter.Type switch - { - ParameterType.Bool => Evaluate(parameter.GetValue()), - ParameterType.Int => Evaluate(parameter.GetValue()), - ParameterType.Float => Evaluate(parameter.GetValue()), - _ => throw new ArgumentOutOfRangeException() - }; - } - - protected QueryResult Evaluate(bool value) - { - var queryResult = new QueryResult(); - - var valid = evaluate(value); - - queryResult.IsValid = valid; - queryResult.JustBecameValid = valid && !previousValid; - queryResult.JustBecameInvalid = !valid && previousValid; - - previousBoolValue = value; - previousValid = valid; - - return queryResult; - } - - protected QueryResult Evaluate(int value) - { - var queryResult = new QueryResult(); - - var valid = evaluate(value); - - queryResult.IsValid = valid; - queryResult.JustBecameValid = valid && !previousValid; - queryResult.JustBecameInvalid = !valid && previousValid; - - previousIntValue = value; - previousValid = valid; - - return queryResult; - } - - protected QueryResult Evaluate(float value) - { - var queryResult = new QueryResult(); - - var valid = evaluate(value); - - queryResult.IsValid = valid; - queryResult.JustBecameValid = valid && !previousValid; - queryResult.JustBecameInvalid = !valid && previousValid; - - previousFloatValue = value; - previousValid = valid; - - return queryResult; - } - - private bool evaluate(object value) => Comparison.Value switch - { - ComparisonOperation.Changed => handleChangedOperation(value), - ComparisonOperation.EqualTo => handleEqualToOperation(value), - ComparisonOperation.NotEqualTo => !handleEqualToOperation(value), - ComparisonOperation.LessThan => handleLessThanOperation(value), - ComparisonOperation.GreaterThan => handleMoreThanOperation(value), - ComparisonOperation.LessThanOrEqualTo => false, - ComparisonOperation.GreaterThanOrEqualTo => false, - _ => throw new ArgumentOutOfRangeException() - }; - - private bool handleChangedOperation(object value) => value switch - { - bool boolValue => previousBoolValue != boolValue, - int intValue => previousIntValue != intValue, - float floatValue => Math.Abs(previousFloatValue - floatValue) > float.Epsilon, - _ => throw new InvalidOperationException() - }; - - private bool handleEqualToOperation(object value) => value switch - { - bool boolValue => BoolValue.Value == boolValue && previousBoolValue != boolValue, - int intValue => IntValue.Value == intValue && previousIntValue != intValue, - float floatValue => Math.Abs(FloatValue.Value - floatValue) < float.Epsilon && Math.Abs(previousFloatValue - floatValue) > float.Epsilon, - _ => throw new ArgumentOutOfRangeException() - }; - - private bool handleLessThanOperation(object value) => value switch - { - bool => throw new InvalidOperationException(), - int intValue => previousIntValue >= IntValue.Value && intValue < IntValue.Value, - float floatValue => previousFloatValue >= FloatValue.Value && floatValue < FloatValue.Value, - _ => throw new ArgumentOutOfRangeException() - }; - - private bool handleMoreThanOperation(object value) => value switch - { - bool => throw new InvalidOperationException(), - int intValue => previousIntValue <= IntValue.Value && intValue > IntValue.Value, - float floatValue => previousFloatValue <= FloatValue.Value && floatValue > FloatValue.Value, - _ => throw new ArgumentOutOfRangeException() - }; - - public bool Equals(QueryableParameter? other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - - return Name.Equals(other.Name) && Type.Equals(other.Type) && Comparison.Equals(other.Comparison) && BoolValue.Equals(other.BoolValue) && IntValue.Equals(other.IntValue) && FloatValue.Equals(other.FloatValue); - } -} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/Types/QueryableParameter.cs b/VRCOSC.App/SDK/Modules/Attributes/Types/QueryableParameter.cs new file mode 100644 index 00000000..20666a6e --- /dev/null +++ b/VRCOSC.App/SDK/Modules/Attributes/Types/QueryableParameter.cs @@ -0,0 +1,218 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System; +using System.Threading.Tasks; +using Newtonsoft.Json; +using VRCOSC.App.SDK.Parameters; +using VRCOSC.App.SDK.Utils; +using VRCOSC.App.Utils; + +namespace VRCOSC.App.SDK.Modules.Attributes.Types; + +public class QueryResult +{ + public bool IsValid { get; internal init; } + public bool JustBecameValid { get; internal init; } + public bool JustBecameInvalid { get; internal init; } +} + +public class ActionableQueryResult : QueryResult +{ + public T Action { get; } + + public ActionableQueryResult(QueryResult other, T action) + { + IsValid = other.IsValid; + JustBecameValid = other.JustBecameValid; + JustBecameInvalid = other.JustBecameInvalid; + Action = action; + } +} + +public interface IQueryableParameter +{ +} + +[JsonObject(MemberSerialization.OptIn)] +public class ActionableQueryableParameter : QueryableParameter, IEquatable> where T : Enum +{ + [JsonProperty("action")] + public Observable Action { get; } = new(); + + public new ActionableQueryResult Evaluate(ReceivedParameter parameter) + { + var queryResult = base.Evaluate(parameter); + return new ActionableQueryResult(queryResult, Action.Value); + } + + public bool Equals(ActionableQueryableParameter? other) => base.Equals(other) && Action.Equals(other.Action); +} + +[JsonObject(MemberSerialization.OptIn)] +public class QueryableParameter : IQueryableParameter, IEquatable +{ + [JsonProperty("name")] + public Observable Name { get; } = new(string.Empty); + + [JsonProperty("type")] + public Observable Type { get; } = new(); + + [JsonProperty("comparison")] + public Observable Comparison { get; } = new(); + + [JsonProperty("bool_value")] + public Observable BoolValue { get; } = new(); + + [JsonProperty("int_value")] + public Observable IntValue { get; } = new(); + + [JsonProperty("float_value")] + public Observable FloatValue { get; } = new(); + + private bool previousBoolValue; + private int previousIntValue; + private float previousFloatValue; + + private bool previousValid; + + public async Task Init() + { + previousValid = false; + + var parameter = await AppManager.GetInstance().VRChatOscClient.FindParameter(Name.Value); + if (parameter is null) return; + + switch (parameter.Type) + { + case ParameterType.Bool: + previousBoolValue = parameter.GetValue(); + previousValid = evaluate(previousBoolValue); + break; + + case ParameterType.Int: + previousIntValue = parameter.GetValue(); + previousValid = evaluate(previousIntValue); + break; + + case ParameterType.Float: + previousFloatValue = parameter.GetValue(); + previousValid = evaluate(previousFloatValue); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + + public QueryResult Evaluate(ReceivedParameter parameter) + { + var valid = parameter.Type switch + { + ParameterType.Bool => Evaluate(parameter.GetValue()), + ParameterType.Int => Evaluate(parameter.GetValue()), + ParameterType.Float => Evaluate(parameter.GetValue()), + _ => throw new ArgumentOutOfRangeException(nameof(parameter.Type), parameter.Type, null) + }; + + var queryResult = new QueryResult + { + IsValid = valid, + JustBecameValid = valid && !previousValid, + JustBecameInvalid = !valid && previousValid + }; + + previousValid = valid; + + return queryResult; + } + + protected bool Evaluate(bool value) + { + var valid = evaluate(value); + previousBoolValue = value; + return valid; + } + + protected bool Evaluate(int value) + { + var valid = evaluate(value); + previousIntValue = value; + return valid; + } + + protected bool Evaluate(float value) + { + var valid = evaluate(value); + previousFloatValue = value; + return valid; + } + + private bool evaluate(object value) => Comparison.Value switch + { + ComparisonOperation.Changed => handleChangedOperation(value), + ComparisonOperation.EqualTo => handleEqualToOperation(value), + ComparisonOperation.NotEqualTo => !handleEqualToOperation(value), + ComparisonOperation.LessThan => handleLessThanOperation(value), + ComparisonOperation.GreaterThan => handleGreaterThanOperation(value), + ComparisonOperation.LessThanOrEqualTo => handleLessThanOrEqualToOperation(value), + ComparisonOperation.GreaterThanOrEqualTo => handleGreaterThanOrEqualToOperation(value), + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + private bool handleChangedOperation(object value) => value switch + { + bool boolValue => previousBoolValue != boolValue, + int intValue => previousIntValue != intValue, + float floatValue => Math.Abs(previousFloatValue - floatValue) > float.Epsilon, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + private bool handleEqualToOperation(object value) => value switch + { + bool boolValue => BoolValue.Value == boolValue && previousBoolValue != boolValue, + int intValue => IntValue.Value == intValue && previousIntValue != intValue, + float floatValue => Math.Abs(FloatValue.Value - floatValue) < float.Epsilon && Math.Abs(previousFloatValue - floatValue) > float.Epsilon, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + private bool handleLessThanOperation(object value) => value switch + { + bool => throw new InvalidOperationException(), + int intValue => previousIntValue >= IntValue.Value && intValue < IntValue.Value, + float floatValue => previousFloatValue >= FloatValue.Value && floatValue < FloatValue.Value, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + private bool handleGreaterThanOperation(object value) => value switch + { + bool => throw new InvalidOperationException(), + int intValue => previousIntValue <= IntValue.Value && intValue > IntValue.Value, + float floatValue => previousFloatValue <= FloatValue.Value && floatValue > FloatValue.Value, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + private bool handleLessThanOrEqualToOperation(object value) => value switch + { + bool => throw new InvalidOperationException(), + int intValue => previousIntValue > IntValue.Value && intValue <= IntValue.Value, + float floatValue => previousFloatValue > FloatValue.Value && floatValue <= FloatValue.Value, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + private bool handleGreaterThanOrEqualToOperation(object value) => value switch + { + bool => throw new InvalidOperationException(), + int intValue => previousIntValue < IntValue.Value && intValue >= IntValue.Value, + float floatValue => previousFloatValue < FloatValue.Value && floatValue >= FloatValue.Value, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + public bool Equals(QueryableParameter? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + + return Name.Equals(other.Name) && Type.Equals(other.Type) && Comparison.Equals(other.Comparison) && BoolValue.Equals(other.BoolValue) && IntValue.Equals(other.IntValue) && FloatValue.Equals(other.FloatValue); + } +} \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml index 233400c0..7032d90e 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml @@ -33,17 +33,17 @@ - + - + - + - + From ea149e62181f68caa99c14bd613f404d1eb4bfbc Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 21:41:49 +0000 Subject: [PATCH 36/49] Change VRChatOscClient to return ReceivedParameter for OSCQuery --- VRCOSC.App/OSC/VRChat/VRChatOscClient.cs | 32 +++++++++++------------- VRCOSC.App/SDK/Modules/Module.cs | 24 +++++++++--------- VRCOSC.App/SDK/VRChat/Player.cs | 8 +++--- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/VRCOSC.App/OSC/VRChat/VRChatOscClient.cs b/VRCOSC.App/OSC/VRChat/VRChatOscClient.cs index 0ef107e1..6d7582f1 100644 --- a/VRCOSC.App/OSC/VRChat/VRChatOscClient.cs +++ b/VRCOSC.App/OSC/VRChat/VRChatOscClient.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using VRCOSC.App.OSC.Client; using VRCOSC.App.OSC.Query; +using VRCOSC.App.SDK.Parameters; using VRCOSC.App.Utils; namespace VRCOSC.App.OSC.VRChat; @@ -66,35 +67,32 @@ public void Init(ConnectionManager connectionManager) } } - public async Task FindParameterValue(string parameterName) + public async Task FindParameter(string parameterName) { var node = await findParameter(parameterName); if (node?.Value is null || node.Value.Length == 0) return null; - return node.OscType switch + object parameterValue = node.OscType switch { - "s" => Convert.ToString(node.Value[0]), "f" => Convert.ToSingle(node.Value[0]), "i" => Convert.ToInt32(node.Value[0]), "T" => Convert.ToBoolean(node.Value[0]), "F" => Convert.ToBoolean(node.Value[0]), _ => throw new InvalidOperationException($"Unknown type '{node.OscType}'") }; + + return new ReceivedParameter(parameterName, parameterValue); } - public async Task FindParameterType(string parameterName) - { - var node = await findParameter(parameterName); - if (node is null) return null; + [Obsolete($"Use {nameof(FindParameter)} instead", true)] + public async Task FindParameterValue(string parameterName) => (await FindParameter(parameterName))?.Value; - return node.OscType switch - { - "s" => TypeCode.String, - "f" => TypeCode.Single, - "i" => TypeCode.Int32, - "T" => TypeCode.Boolean, - "F" => TypeCode.Boolean, - _ => throw new InvalidOperationException($"Unknown type '{node.OscType}'") - }; - } + [Obsolete($"Use {nameof(FindParameter)} instead", true)] + public async Task FindParameterType(string parameterName) => (await FindParameter(parameterName))?.Type switch + { + ParameterType.Bool => TypeCode.Boolean, + ParameterType.Int => TypeCode.Int32, + ParameterType.Float => TypeCode.Single, + _ => throw new ArgumentOutOfRangeException() + }; } \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Module.cs b/VRCOSC.App/SDK/Modules/Module.cs index 99c6336c..bfa0fc83 100644 --- a/VRCOSC.App/SDK/Modules/Module.cs +++ b/VRCOSC.App/SDK/Modules/Module.cs @@ -894,35 +894,35 @@ protected T GetSettingValue(Enum lookup) /// Retrieves a parameter's value using OSCQuery /// /// The lookup of the registered parameter + protected Task FindParameter(Enum lookup) => FindParameter(Parameters[lookup].Name.Value); + + /// + /// Retrieves a parameter's value using OSCQuery + /// + /// The name of the parameter + protected Task FindParameter(string parameterName) => AppManager.GetInstance().VRChatOscClient.FindParameter(parameterName); + + [Obsolete($"Use {nameof(FindParameter)} instead", true)] protected Task FindParameterValue(Enum lookup) { var parameterName = Parameters[lookup].Name.Value; return FindParameterValue(parameterName); } - /// - /// Retrieves a parameter's value using OSCQuery - /// - /// The name of the parameter + [Obsolete($"Use {nameof(FindParameter)} instead", true)] protected Task FindParameterValue(string parameterName) { return AppManager.GetInstance().VRChatOscClient.FindParameterValue(parameterName); } - /// - /// Retrieves a parameter's type using OSCQuery - /// - /// The lookup of the registered parameter + [Obsolete($"Use {nameof(FindParameter)} instead", true)] protected Task FindParameterType(Enum lookup) { var parameterName = Parameters[lookup].Name.Value; return FindParameterType(parameterName); } - /// - /// Retrieves a parameter's type using OSCQuery - /// - /// The name of the parameter + [Obsolete($"Use {nameof(FindParameter)} instead", true)] protected Task FindParameterType(string parameterName) { return AppManager.GetInstance().VRChatOscClient.FindParameterType(parameterName); diff --git a/VRCOSC.App/SDK/VRChat/Player.cs b/VRCOSC.App/SDK/VRChat/Player.cs index 04816e03..f0496228 100644 --- a/VRCOSC.App/SDK/VRChat/Player.cs +++ b/VRCOSC.App/SDK/VRChat/Player.cs @@ -53,10 +53,10 @@ internal async Task RetrieveAll() private async Task retrieve(string parameterName) { - var value = await AppManager.GetInstance().VRChatOscClient.FindParameterValue(parameterName); - if (value is null) return; + var receivedParameter = await AppManager.GetInstance().VRChatOscClient.FindParameter(parameterName); + if (receivedParameter is null) return; - Update(parameterName, value); + Update(parameterName, receivedParameter.Value); } internal bool Update(string parameterName, object value) @@ -491,4 +491,4 @@ public enum VRChatAvatarParameter ScaleFactorInverse, EyeHeightAsMeters, EyeHeightAsPercent -} +} \ No newline at end of file From 9665540f999938ac2e18cae9a7732f2e2ed96172 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Tue, 17 Dec 2024 21:41:57 +0000 Subject: [PATCH 37/49] Remove count from ListModuleSetting --- VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs index 77c03794..f2262a28 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs @@ -17,7 +17,6 @@ protected ListModuleSetting(string title, string description, Type viewType) { } - public abstract int Count(); public abstract void Add(); public abstract void Remove(object item); } @@ -37,7 +36,6 @@ protected ListModuleSetting(string title, string description, Type viewType, IEn internal override bool IsDefault() => Attribute.SequenceEqual(DefaultValues); - public override int Count() => Attribute.Count; public override void Add() => Attribute.Add(CreateItem()); public override void Remove(object item) From ece7b350e2f8225524b10d9ba69cf3c1680ae254 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 21 Dec 2024 14:30:51 +0000 Subject: [PATCH 38/49] Improve ParameterType and ReceivedParameters --- VRCOSC.App/SDK/Modules/Module.cs | 2 +- VRCOSC.App/SDK/Parameters/ParameterType.cs | 22 ++++++++++++------- .../SDK/Parameters/ReceivedParameter.cs | 20 ++++++++++------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/VRCOSC.App/SDK/Modules/Module.cs b/VRCOSC.App/SDK/Modules/Module.cs index bfa0fc83..87daaabd 100644 --- a/VRCOSC.App/SDK/Modules/Module.cs +++ b/VRCOSC.App/SDK/Modules/Module.cs @@ -374,7 +374,7 @@ protected void RegisterParameter(Enum lookup, string defaultName, ParameterMo if (typeof(T) != typeof(bool) && typeof(T) != typeof(int) && typeof(T) != typeof(float)) throw new InvalidOperationException($"{FullID} attempted to register a parameter with an invalid type"); - Parameters.Add(lookup, new ModuleParameter(title, description, defaultName, mode, ParameterTypeUtils.GetTypeFromType(), legacy)); + Parameters.Add(lookup, new ModuleParameter(title, description, defaultName, mode, ParameterTypeFactory.CreateFrom(), legacy)); } /// diff --git a/VRCOSC.App/SDK/Parameters/ParameterType.cs b/VRCOSC.App/SDK/Parameters/ParameterType.cs index d1f4a2fb..b811cabf 100644 --- a/VRCOSC.App/SDK/Parameters/ParameterType.cs +++ b/VRCOSC.App/SDK/Parameters/ParameterType.cs @@ -7,23 +7,29 @@ namespace VRCOSC.App.SDK.Parameters; public enum ParameterType { - Bool, - Int, - Float + Bool = 0, + Int = 1, + Float = 2 } -public static class ParameterTypeUtils +public static class ParameterTypeFactory { - public static ParameterType GetTypeFromType(Type type) + public static ParameterType CreateFrom(Type type) { if (type == typeof(bool)) return ParameterType.Bool; if (type == typeof(int)) return ParameterType.Int; if (type == typeof(float)) return ParameterType.Float; - throw new InvalidOperationException("Parameters can only be of type bool, int, or float"); + throw new InvalidOperationException($"Parameters can only be of type bool, int, or float"); } - public static ParameterType GetTypeFromType() => GetTypeFromType(typeof(T)); + /// + /// Takes in and attempts to create a from it + /// + public static ParameterType CreateFrom() => CreateFrom(typeof(T)); - public static ParameterType GetTypeFromValue(object value) => GetTypeFromType(value.GetType()); + /// + /// Takes in and attempts to create a from its + /// + public static ParameterType CreateFrom(object value) => CreateFrom(value.GetType()); } \ No newline at end of file diff --git a/VRCOSC.App/SDK/Parameters/ReceivedParameter.cs b/VRCOSC.App/SDK/Parameters/ReceivedParameter.cs index d9ed61d4..4a8aaaa9 100644 --- a/VRCOSC.App/SDK/Parameters/ReceivedParameter.cs +++ b/VRCOSC.App/SDK/Parameters/ReceivedParameter.cs @@ -18,18 +18,22 @@ public class ReceivedParameter /// public string Name { get; } + /// + /// The raw value of this parameter. Useful when used alongside + /// + /// If you want to get this as a specific value, call + public object Value { get; } + /// /// The type of this parameter /// public ParameterType Type { get; } - internal readonly object Value; - internal ReceivedParameter(string name, object value) { Name = name; Value = value; - Type = ParameterTypeUtils.GetTypeFromValue(value); + Type = ParameterTypeFactory.CreateFrom(value); } internal ReceivedParameter(ReceivedParameter other) @@ -62,21 +66,21 @@ public virtual T GetValue() /// Checks if the received value is of type /// /// True if the value is exactly the type passed, otherwise false - public bool IsValueType(Type type) => ParameterTypeUtils.GetTypeFromType(type) == Type; + public bool IsValueType(Type type) => ParameterTypeFactory.CreateFrom(type) == Type; } /// /// A that is associated with a /// -public class RegisteredParameter : ReceivedParameter +public sealed class RegisteredParameter : ReceivedParameter { /// /// The lookup of the module parameter /// - public readonly Enum Lookup; + public Enum Lookup { get; } private readonly ModuleParameter moduleParameter; - private readonly List wildcards = new(); + private readonly List wildcards = []; internal RegisteredParameter(ReceivedParameter other, Enum lookup, ModuleParameter moduleParameter) : base(other) @@ -116,7 +120,7 @@ private void decodeWildcards() public override T GetValue() { - if (ParameterTypeUtils.GetTypeFromType() != moduleParameter.ExpectedType) + if (ParameterTypeFactory.CreateFrom() != moduleParameter.ExpectedType) throw new InvalidCastException($"Parameter's value was expected as {moduleParameter.ExpectedType} and you're trying to use it as {typeof(T).ToReadableName()}"); return base.GetValue(); From 9acea992b23dbde3068e0874f7017ec17fe01ea1 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 21 Dec 2024 14:31:13 +0000 Subject: [PATCH 39/49] Fix disallowing string parameters throwing module exceptions --- VRCOSC.App/AppManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VRCOSC.App/AppManager.cs b/VRCOSC.App/AppManager.cs index 6e61236b..93c0202d 100644 --- a/VRCOSC.App/AppManager.cs +++ b/VRCOSC.App/AppManager.cs @@ -236,9 +236,9 @@ private void processOscMessageQueue() if (wasPlayerUpdated) ModuleManager.GetInstance().PlayerUpdate(); if (message.ParameterName.StartsWith("VRCOSC/Controls")) handleControlParameter(new ReceivedParameter(message.ParameterName, message.ParameterValue)); - } - ModuleManager.GetInstance().ParameterReceived(message); + ModuleManager.GetInstance().ParameterReceived(message); + } } } From c93c886621f8d66369510738f1813a1d67d9d703 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 21 Dec 2024 16:25:21 +0000 Subject: [PATCH 40/49] Get QueryableParameters working --- .../Attributes/Settings/ListModuleSetting.cs | 50 ------------- .../QueryableParameterModuleSetting.cs | 70 +++++++++++++++++++ VRCOSC.App/SDK/Modules/Module.cs | 5 +- .../Queryable}/QueryableParameter.cs | 9 +-- .../Queryable/QueryableParameterList.cs | 43 ++++++++++++ .../QueryableParameterEditWindow.xaml | 2 +- .../QueryableParameterEditWindow.xaml.cs | 32 ++++----- ...eryableParameterListModuleSettingView.xaml | 9 +++ ...ableParameterListModuleSettingView.xaml.cs | 16 +++++ .../QueryableParameterSettingView.xaml.cs | 33 ++++----- VRCOSC.App/VRCOSC.App.csproj | 2 +- 11 files changed, 170 insertions(+), 101 deletions(-) create mode 100644 VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameterModuleSetting.cs rename VRCOSC.App/SDK/{Modules/Attributes/Types => Parameters/Queryable}/QueryableParameter.cs (97%) create mode 100644 VRCOSC.App/SDK/Parameters/Queryable/QueryableParameterList.cs create mode 100644 VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterListModuleSettingView.xaml create mode 100644 VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterListModuleSettingView.xaml.cs diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs index 89fda953..f2262a28 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/ListModuleSetting.cs @@ -5,9 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Threading.Tasks; using Newtonsoft.Json.Linq; -using VRCOSC.App.SDK.Modules.Attributes.Types; using VRCOSC.App.Utils; namespace VRCOSC.App.SDK.Modules.Attributes.Settings; @@ -109,52 +107,4 @@ public FloatListModuleSetting(string title, string description, Type viewType, I : base(title, description, viewType, defaultValues.Select(value => new Observable(value))) { } -} - -public class QueryableParameterListModuleSetting : ListModuleSetting -{ - public QueryableParameterListModuleSetting(string title, string description, Type viewType) - : base(title, description, viewType, []) - { - } - - protected override QueryableParameter CreateItem() => new(); - - public async Task Init() - { - foreach (var queryableParameter in Attribute) - { - await queryableParameter.Init(); - } - } -} - -public class ActionableQueryableParameterListModuleSetting : ListModuleSetting -{ - public Type ActionType { get; } - - public ActionableQueryableParameterListModuleSetting(string title, string description, Type viewType, Type actionType) - : base(title, description, viewType, []) - { - ActionType = actionType; - } - - protected override QueryableParameter CreateItem() => (QueryableParameter)Activator.CreateInstance(typeof(ActionableQueryableParameter<>).MakeGenericType(ActionType))!; - - internal override bool Deserialise(object? ingestValue) - { - if (ingestValue is not JArray jArrayValue) return false; - - Attribute.Clear(); - Attribute.AddRange(jArrayValue.Select(token => (QueryableParameter)token.ToObject(typeof(ActionableQueryableParameter<>).MakeGenericType(ActionType))!)); - return true; - } - - public async Task Init() - { - foreach (var queryableParameter in Attribute) - { - await queryableParameter.Init(); - } - } } \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameterModuleSetting.cs b/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameterModuleSetting.cs new file mode 100644 index 00000000..f21b4699 --- /dev/null +++ b/VRCOSC.App/SDK/Modules/Attributes/Settings/QueryableParameterModuleSetting.cs @@ -0,0 +1,70 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System; +using System.Linq; +using Newtonsoft.Json.Linq; +using VRCOSC.App.SDK.Parameters.Queryable; +using VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter; + +namespace VRCOSC.App.SDK.Modules.Attributes.Settings; + +public class QueryableParameterListModuleSetting : ModuleSetting +{ + public QueryableParameterList QueryableParameterList { get; } + + public QueryableParameterListModuleSetting(string title, string description) + : base(title, description, typeof(QueryableParameterListModuleSettingView)) + { + QueryableParameterList = CreateParameterList(); + } + + internal override bool Deserialise(object? ingestValue) + { + if (ingestValue is not JArray jArrayValue) return false; + + QueryableParameterList.Parameters.Clear(); + + foreach (var ingestItem in jArrayValue.Select(CreateQueryableParameter)) + { + QueryableParameterList.Parameters.Add(ingestItem); + } + + return true; + } + + protected virtual object CreateQueryableParameter(JToken token) => token.ToObject()!; + + internal override object Serialise() => QueryableParameterList.Parameters; + + internal override bool IsDefault() => QueryableParameterList.Parameters.Count == 0; + + public override bool GetValue(out T returnValue) + { + if (typeof(T) != typeof(QueryableParameterList)) + { + returnValue = default(T); + return false; + } + + returnValue = (T)Convert.ChangeType(QueryableParameterList, typeof(T)); + return true; + } + + protected virtual QueryableParameterList CreateParameterList() => new(typeof(QueryableParameter)); +} + +public sealed class ActionableQueryableParameterListModuleSetting : QueryableParameterListModuleSetting +{ + public Type ActionType { get; } + + public ActionableQueryableParameterListModuleSetting(string title, string description, Type actionType) + : base(title, description) + { + ActionType = actionType; + } + + protected override object CreateQueryableParameter(JToken token) => token.ToObject(typeof(ActionableQueryableParameter<>).MakeGenericType(ActionType))!; + + protected override QueryableParameterList CreateParameterList() => new(typeof(ActionableQueryableParameter<>).MakeGenericType(ActionType)); +} \ No newline at end of file diff --git a/VRCOSC.App/SDK/Modules/Module.cs b/VRCOSC.App/SDK/Modules/Module.cs index b76def5a..e89d745e 100644 --- a/VRCOSC.App/SDK/Modules/Module.cs +++ b/VRCOSC.App/SDK/Modules/Module.cs @@ -24,7 +24,6 @@ using VRCOSC.App.Serialisation; using VRCOSC.App.Settings; using VRCOSC.App.UI.Views.Modules.Settings; -using VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter; using VRCOSC.App.Utils; namespace VRCOSC.App.SDK.Modules; @@ -498,12 +497,12 @@ protected void CreateKeyValuePairList(Enum lookup, string title, string descript protected void CreateQueryableParameterList(Enum lookup, string title, string description) { - addSetting(lookup, new QueryableParameterListModuleSetting(title, description, typeof(QueryableParameterSettingView))); + addSetting(lookup, new QueryableParameterListModuleSetting(title, description)); } protected void CreateQueryableParameterList(Enum lookup, string title, string description) where TAction : Enum { - addSetting(lookup, new ActionableQueryableParameterListModuleSetting(title, description, typeof(QueryableParameterSettingView), typeof(TAction))); + addSetting(lookup, new ActionableQueryableParameterListModuleSetting(title, description, typeof(TAction))); } private void addSetting(Enum lookup, ModuleSetting moduleSetting) diff --git a/VRCOSC.App/SDK/Modules/Attributes/Types/QueryableParameter.cs b/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameter.cs similarity index 97% rename from VRCOSC.App/SDK/Modules/Attributes/Types/QueryableParameter.cs rename to VRCOSC.App/SDK/Parameters/Queryable/QueryableParameter.cs index 20666a6e..e0e7476c 100644 --- a/VRCOSC.App/SDK/Modules/Attributes/Types/QueryableParameter.cs +++ b/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameter.cs @@ -4,11 +4,10 @@ using System; using System.Threading.Tasks; using Newtonsoft.Json; -using VRCOSC.App.SDK.Parameters; using VRCOSC.App.SDK.Utils; using VRCOSC.App.Utils; -namespace VRCOSC.App.SDK.Modules.Attributes.Types; +namespace VRCOSC.App.SDK.Parameters.Queryable; public class QueryResult { @@ -30,10 +29,6 @@ public ActionableQueryResult(QueryResult other, T action) } } -public interface IQueryableParameter -{ -} - [JsonObject(MemberSerialization.OptIn)] public class ActionableQueryableParameter : QueryableParameter, IEquatable> where T : Enum { @@ -50,7 +45,7 @@ public class ActionableQueryableParameter : QueryableParameter, IEquatable +public class QueryableParameter : IEquatable { [JsonProperty("name")] public Observable Name { get; } = new(string.Empty); diff --git a/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameterList.cs b/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameterList.cs new file mode 100644 index 00000000..c37080d5 --- /dev/null +++ b/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameterList.cs @@ -0,0 +1,43 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using System; +using System.Collections; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace VRCOSC.App.SDK.Parameters.Queryable; + +[JsonConverter(typeof(QueryableParameterListConverter))] +public class QueryableParameterList +{ + private readonly Type queryableParameterType; + + public IList Parameters { get; internal set; } + public Array ActionTypeSource => queryableParameterType.GenericTypeArguments.Length > 0 ? Enum.GetValues(queryableParameterType.GenericTypeArguments[0]) : Array.Empty(); + + public QueryableParameterList(Type queryableParameterType) + { + this.queryableParameterType = queryableParameterType; + Parameters = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(queryableParameterType))!; + } + + internal void Add() + { + Parameters.Add(Activator.CreateInstance(queryableParameterType)!); + } +} + +public class QueryableParameterListConverter : JsonConverter +{ + public override void WriteJson(JsonWriter writer, QueryableParameterList? value, JsonSerializer serializer) + { + serializer.Serialize(writer, value!.Parameters); + } + + public override QueryableParameterList ReadJson(JsonReader reader, Type objectType, QueryableParameterList? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + existingValue!.Parameters = (IList)serializer.Deserialize(reader, existingValue.Parameters.GetType())!; + return existingValue; + } +} \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml index 7032d90e..408ba70b 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml @@ -18,7 +18,7 @@ - + diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs index f2ef0b19..8a6e59f8 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs @@ -5,11 +5,10 @@ using System.Collections.Generic; using System.Linq; using System.Windows; -using VRCOSC.App.SDK.Modules.Attributes.Settings; using VRCOSC.App.SDK.Parameters; +using VRCOSC.App.SDK.Parameters.Queryable; using VRCOSC.App.SDK.Utils; using VRCOSC.App.UI.Core; -using VRCOSC.App.Utils; namespace VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter; @@ -18,36 +17,31 @@ public partial class QueryableParameterEditWindow : IManagedWindow public IEnumerable QueryableParameterTypeItemsSource => typeof(ParameterType).GetEnumValues().Cast(); public IEnumerable> QueryableParameterOperationItemsSource => ComparisonOperationUtils.DISPLAY_LIST; public IEnumerable BoolValueItemsSource => typeof(BoolValue).GetEnumValues().Cast(); - public Array QueryableParameterActionTypeItemsSource { get; init; } = Array.Empty(); + public Array QueryableParameterActionTypeItemsSource => QueryableParameterList.ActionTypeSource; - public ListModuleSetting ModuleSetting { get; } + public QueryableParameterList QueryableParameterList { get; } - public QueryableParameterEditWindow(QueryableParameterListModuleSetting moduleSetting) + public QueryableParameterEditWindow(QueryableParameterList queryableParameterList) { InitializeComponent(); - ModuleSetting = moduleSetting; + QueryableParameterList = queryableParameterList; DataContext = this; - - Title = $"Edit {moduleSetting.Title.Pluralise()} Queryable Parameters"; + refreshParameterList(); } - public QueryableParameterEditWindow(ActionableQueryableParameterListModuleSetting moduleSetting) + private void AddButton_OnClick(object sender, RoutedEventArgs e) { - InitializeComponent(); - ModuleSetting = moduleSetting; - DataContext = this; - - Title = $"Edit {moduleSetting.Title.Pluralise()} Queryable Parameters"; - - QueryableParameterActionTypeItemsSource = moduleSetting.ActionType.GetEnumValues(); + QueryableParameterList.Add(); + refreshParameterList(); } - private void AddButton_OnClick(object sender, RoutedEventArgs e) + private void refreshParameterList() { - ModuleSetting.Add(); + ParameterList.ItemsSource = null; + ParameterList.ItemsSource = QueryableParameterList.Parameters; } - public object GetComparer() => ModuleSetting; + public object GetComparer() => QueryableParameterList; } public enum BoolValue diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterListModuleSettingView.xaml b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterListModuleSettingView.xaml new file mode 100644 index 00000000..1863055b --- /dev/null +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterListModuleSettingView.xaml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterListModuleSettingView.xaml.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterListModuleSettingView.xaml.cs new file mode 100644 index 00000000..35aa193d --- /dev/null +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterListModuleSettingView.xaml.cs @@ -0,0 +1,16 @@ +// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License. +// See the LICENSE file in the repository root for full license text. + +using VRCOSC.App.SDK.Modules; +using VRCOSC.App.SDK.Modules.Attributes.Settings; + +namespace VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter; + +public partial class QueryableParameterListModuleSettingView +{ + public QueryableParameterListModuleSettingView(Module _, QueryableParameterListModuleSetting moduleSetting) + { + InitializeComponent(); + DataContext = moduleSetting; + } +} \ No newline at end of file diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs index 62f4d907..1d8b690a 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml.cs @@ -2,45 +2,38 @@ // See the LICENSE file in the repository root for full license text. using System.Windows; -using VRCOSC.App.SDK.Modules; -using VRCOSC.App.SDK.Modules.Attributes.Settings; +using VRCOSC.App.SDK.Parameters.Queryable; using VRCOSC.App.UI.Core; +// ReSharper disable InconsistentNaming + namespace VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter; public partial class QueryableParameterSettingView { private WindowManager windowManager = null!; - public object ModuleSetting { get; } + public static readonly DependencyProperty QueryableParameterListProperty = + DependencyProperty.Register(nameof(QueryableParameterList), typeof(QueryableParameterList), typeof(QueryableParameterSettingView), new PropertyMetadata(null)); - public QueryableParameterSettingView(Module _, QueryableParameterListModuleSetting moduleSetting) + public QueryableParameterList QueryableParameterList { - InitializeComponent(); - ModuleSetting = moduleSetting; + get => (QueryableParameterList)GetValue(QueryableParameterListProperty); + set => SetValue(QueryableParameterListProperty, value); } - public QueryableParameterSettingView(Module _, ActionableQueryableParameterListModuleSetting moduleSetting) + public QueryableParameterSettingView() { InitializeComponent(); - ModuleSetting = moduleSetting; } - private void EditButton_OnClick(object sender, RoutedEventArgs e) + private void QueryableParameterSettingView_OnLoaded(object sender, RoutedEventArgs e) { - if (ModuleSetting is QueryableParameterListModuleSetting queryableParameterListModuleSetting) - { - windowManager.TrySpawnChild(new QueryableParameterEditWindow(queryableParameterListModuleSetting)); - } - - if (ModuleSetting is ActionableQueryableParameterListModuleSetting actionableQueryableParameterListModuleSetting) - { - windowManager.TrySpawnChild(new QueryableParameterEditWindow(actionableQueryableParameterListModuleSetting)); - } + windowManager = new WindowManager(this); } - private void QueryableParameterSettingView_OnLoaded(object sender, RoutedEventArgs e) + private void EditButton_OnClick(object sender, RoutedEventArgs e) { - windowManager = new WindowManager(this); + windowManager.TrySpawnChild(new QueryableParameterEditWindow(QueryableParameterList)); } } \ No newline at end of file diff --git a/VRCOSC.App/VRCOSC.App.csproj b/VRCOSC.App/VRCOSC.App.csproj index 817e74af..b031ce55 100644 --- a/VRCOSC.App/VRCOSC.App.csproj +++ b/VRCOSC.App/VRCOSC.App.csproj @@ -215,7 +215,7 @@ Wpf Designer - + MSBuild:Compile Wpf Designer From 92e4d4702b7a9f33b000c606ae0b37ef39a63e46 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 22 Dec 2024 16:00:09 +0000 Subject: [PATCH 41/49] Finalise and improve QueryableParameter UI --- .../Queryable/QueryableParameter.cs | 40 +++++++++------- .../Queryable/QueryableParameterList.cs | 5 ++ VRCOSC.App/SDK/Utils/ComparisonOperation.cs | 9 +++- .../QueryableParameterEditWindow.xaml | 46 ++++++++++++------- .../QueryableParameterEditWindow.xaml.cs | 27 ++++++++++- .../QueryableParameterSettingView.xaml | 15 ++---- 6 files changed, 96 insertions(+), 46 deletions(-) diff --git a/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameter.cs b/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameter.cs index e0e7476c..46b66a06 100644 --- a/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameter.cs +++ b/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameter.cs @@ -102,24 +102,32 @@ public async Task Init() public QueryResult Evaluate(ReceivedParameter parameter) { - var valid = parameter.Type switch + try { - ParameterType.Bool => Evaluate(parameter.GetValue()), - ParameterType.Int => Evaluate(parameter.GetValue()), - ParameterType.Float => Evaluate(parameter.GetValue()), - _ => throw new ArgumentOutOfRangeException(nameof(parameter.Type), parameter.Type, null) - }; - - var queryResult = new QueryResult + var valid = parameter.Type switch + { + ParameterType.Bool => Evaluate(parameter.GetValue()), + ParameterType.Int => Evaluate(parameter.GetValue()), + ParameterType.Float => Evaluate(parameter.GetValue()), + _ => throw new ArgumentOutOfRangeException(nameof(parameter.Type), parameter.Type, null) + }; + + var queryResult = new QueryResult + { + IsValid = valid, + JustBecameValid = valid && !previousValid, + JustBecameInvalid = !valid && previousValid + }; + + previousValid = valid; + + return queryResult; + } + catch (Exception e) { - IsValid = valid, - JustBecameValid = valid && !previousValid, - JustBecameInvalid = !valid && previousValid - }; - - previousValid = valid; - - return queryResult; + ExceptionHandler.Handle(e, $"{nameof(QueryableParameter)} has experienced an exception when evaluating"); + return new QueryResult(); + } } protected bool Evaluate(bool value) diff --git a/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameterList.cs b/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameterList.cs index c37080d5..c31e53fc 100644 --- a/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameterList.cs +++ b/VRCOSC.App/SDK/Parameters/Queryable/QueryableParameterList.cs @@ -26,6 +26,11 @@ internal void Add() { Parameters.Add(Activator.CreateInstance(queryableParameterType)!); } + + public void Remove(object instance) + { + Parameters.Remove(instance); + } } public class QueryableParameterListConverter : JsonConverter diff --git a/VRCOSC.App/SDK/Utils/ComparisonOperation.cs b/VRCOSC.App/SDK/Utils/ComparisonOperation.cs index 3ed1bdd2..b8bcf7df 100644 --- a/VRCOSC.App/SDK/Utils/ComparisonOperation.cs +++ b/VRCOSC.App/SDK/Utils/ComparisonOperation.cs @@ -18,7 +18,14 @@ public enum ComparisonOperation public static class ComparisonOperationUtils { - public static readonly IEnumerable> DISPLAY_LIST = new List> + public static readonly IEnumerable> BOOL_DISPLAY_LIST = new List> + { + new("Changed", ComparisonOperation.Changed), + new("Equal To", ComparisonOperation.EqualTo), + new("Not Equal To", ComparisonOperation.NotEqualTo), + }; + + public static readonly IEnumerable> VALUE_DISPLAY_LIST = new List> { new("Changed", ComparisonOperation.Changed), new("Equal To", ComparisonOperation.EqualTo), diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml index 408ba70b..6bc089f3 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml @@ -5,8 +5,9 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:core="clr-namespace:VRCOSC.App.UI.Core" xmlns:queryableParameter="clr-namespace:VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter" + xmlns:fa6="http://schemas.fontawesome.com/icons/fonts" mc:Ignorable="d" - Title="QueryableParameterEditWindow" Height="450" Width="800"> + Title="Edit Parameters" Height="450" Width="1200"> @@ -16,6 +17,7 @@ + @@ -29,23 +31,33 @@ - - - - - - - - - - - - - - + Padding="5"> + + + + + + + + + + + + + + + + + - + + + + diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs index 8a6e59f8..0488fa4c 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterEditWindow.xaml.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Windows; +using System.Windows.Data; using VRCOSC.App.SDK.Parameters; using VRCOSC.App.SDK.Parameters.Queryable; using VRCOSC.App.SDK.Utils; @@ -15,7 +17,6 @@ namespace VRCOSC.App.UI.Views.Modules.Settings.QueryableParameter; public partial class QueryableParameterEditWindow : IManagedWindow { public IEnumerable QueryableParameterTypeItemsSource => typeof(ParameterType).GetEnumValues().Cast(); - public IEnumerable> QueryableParameterOperationItemsSource => ComparisonOperationUtils.DISPLAY_LIST; public IEnumerable BoolValueItemsSource => typeof(BoolValue).GetEnumValues().Cast(); public Array QueryableParameterActionTypeItemsSource => QueryableParameterList.ActionTypeSource; @@ -42,6 +43,30 @@ private void refreshParameterList() } public object GetComparer() => QueryableParameterList; + + private void RemoveButton_OnClick(object sender, RoutedEventArgs e) + { + var element = (FrameworkElement)sender; + var instance = element.Tag; + + QueryableParameterList.Remove(instance); + refreshParameterList(); + } +} + +public class ComparisonComboBoxItemsSourceConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is ParameterType type) + { + return type == ParameterType.Bool ? ComparisonOperationUtils.BOOL_DISPLAY_LIST : ComparisonOperationUtils.VALUE_DISPLAY_LIST; + } + + return null; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => null; } public enum BoolValue diff --git a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml index 58b447b3..b9bebe43 100644 --- a/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml +++ b/VRCOSC.App/UI/Views/Modules/Settings/QueryableParameter/QueryableParameterSettingView.xaml @@ -7,15 +7,8 @@ mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" Loaded="QueryableParameterSettingView_OnLoaded"> - - - - - - - - - - + + + \ No newline at end of file From 2f367c59e015f1e49d5c4830b87762cc5a2ecf5d Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 23 Dec 2024 13:32:00 +0000 Subject: [PATCH 42/49] Ensure ChatBox text is cleared when live text eraser is pressed --- VRCOSC.App/UI/Views/Run/Tabs/ChatBoxTabView.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/VRCOSC.App/UI/Views/Run/Tabs/ChatBoxTabView.xaml.cs b/VRCOSC.App/UI/Views/Run/Tabs/ChatBoxTabView.xaml.cs index 26cffbf7..86dff831 100644 --- a/VRCOSC.App/UI/Views/Run/Tabs/ChatBoxTabView.xaml.cs +++ b/VRCOSC.App/UI/Views/Run/Tabs/ChatBoxTabView.xaml.cs @@ -32,6 +32,7 @@ private void LiveTextEraser_OnClick(object sender, RoutedEventArgs e) { LiveTextTextBox.Text = string.Empty; LiveTextTextBox.Focus(); + ChatBoxManager.GetInstance().ClearText(); } private void ChatBoxTabView_OnLoaded(object sender, RoutedEventArgs e) From 76c61c467e2ed0662cd5bfd4124dda60eda654d6 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 23 Dec 2024 13:47:04 +0000 Subject: [PATCH 43/49] Ensure the app loads when an update is refused --- VRCOSC.App/UI/Windows/MainWindow.xaml.cs | 4 ++-- VRCOSC.App/Updater/VelopackUpdater.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs index 44304bab..30d22dba 100644 --- a/VRCOSC.App/UI/Windows/MainWindow.xaml.cs +++ b/VRCOSC.App/UI/Windows/MainWindow.xaml.cs @@ -93,8 +93,8 @@ private async void startApp() if (velopackUpdater.IsUpdateAvailable()) { - await velopackUpdater.ExecuteUpdate(); - return; + var isUpdating = await velopackUpdater.ExecuteUpdate(); + if (isUpdating) return; } Logger.Log("No updates. Proceeding with loading"); diff --git a/VRCOSC.App/Updater/VelopackUpdater.cs b/VRCOSC.App/Updater/VelopackUpdater.cs index 4c702bd7..da707e99 100644 --- a/VRCOSC.App/Updater/VelopackUpdater.cs +++ b/VRCOSC.App/Updater/VelopackUpdater.cs @@ -56,28 +56,30 @@ public async Task CheckForUpdatesAsync() Logger.Log(updateInfo is null ? "No updates available" : "Updates available"); } - public async Task ExecuteUpdate() + public async Task ExecuteUpdate() { try { - if (updateInfo is null) return; + if (updateInfo is null) return false; // switching channels will cause an update to the same version. No need to update - if (SemanticVersion.Parse(AppManager.Version) == updateInfo.TargetFullRelease.Version) return; + if (SemanticVersion.Parse(AppManager.Version) == updateInfo.TargetFullRelease.Version) return false; var upgradeMessage = $"A new update is available for VRCOSC!\nWould you like to update?\n\nCurrent Version: {AppManager.Version}\nNew Version: {updateInfo.TargetFullRelease.Version}"; var downgradeMessage = $"Updating will downgrade due to switching channels.\nAre you sure you want to downgrade?\n\nCurrent Version: {AppManager.Version}\nNew Version: {updateInfo.TargetFullRelease.Version}"; var result = MessageBox.Show(updateInfo.IsDowngrade ? downgradeMessage : upgradeMessage, "VRCOSC Update Available", MessageBoxButtons.YesNo, MessageBoxIcon.Information); - if (result == DialogResult.No) return; + if (result == DialogResult.No) return false; await updateManager.DownloadUpdatesAsync(updateInfo); SettingsManager.GetInstance().GetObservable(VRCOSCMetadata.InstalledUpdateChannel).Value = SettingsManager.GetInstance().GetValue(VRCOSCSetting.UpdateChannel); updateManager.ApplyUpdatesAndRestart(null); + return true; } catch (Exception e) { ExceptionHandler.Handle(e, "An error occurred when trying to update"); + return false; } } } \ No newline at end of file From cc1390a85b55ffa340ea59a1b8b7d19acd79f680 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 23 Dec 2024 13:53:14 +0000 Subject: [PATCH 44/49] Bump Velopack --- VRCOSC.App/VRCOSC.App.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.App/VRCOSC.App.csproj b/VRCOSC.App/VRCOSC.App.csproj index b031ce55..3e5b1ff7 100644 --- a/VRCOSC.App/VRCOSC.App.csproj +++ b/VRCOSC.App/VRCOSC.App.csproj @@ -34,7 +34,7 @@ - + From ba51a75181e5fa3e3f03db7dfc28164c8a43ab5a Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 23 Dec 2024 13:54:05 +0000 Subject: [PATCH 45/49] Guard against an Edge race condition --- VRCOSC.App/SDK/Providers/Media/WindowsMediaProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VRCOSC.App/SDK/Providers/Media/WindowsMediaProvider.cs b/VRCOSC.App/SDK/Providers/Media/WindowsMediaProvider.cs index 951ea581..f689b0e0 100644 --- a/VRCOSC.App/SDK/Providers/Media/WindowsMediaProvider.cs +++ b/VRCOSC.App/SDK/Providers/Media/WindowsMediaProvider.cs @@ -20,7 +20,7 @@ public class WindowsMediaProvider public ObservableCollection Sessions { get; } = new(); public Dictionary States { get; } = new(); - public MediaState CurrentState => CurrentSession is not null ? States[CurrentSession.SourceAppUserModelId] : new MediaState(); + public MediaState CurrentState => CurrentSession is not null ? States.TryGetValue(CurrentSession.SourceAppUserModelId, out var session) ? session : new MediaState() : new MediaState(); public GlobalSystemMediaTransportControlsSession? CurrentSession => cachedSessions.FirstOrDefault(session => session.SourceAppUserModelId == (focusedSessionId ?? currentSessionId)); private string? focusedSessionId; @@ -304,4 +304,4 @@ public float TryGetVolume() return 1f; } } -} +} \ No newline at end of file From ebca0af1bac3ff93910694ec57561342508621ff Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 23 Dec 2024 14:08:31 +0000 Subject: [PATCH 46/49] Improve serialiser encoding detection --- VRCOSC.App/Serialisation/Serialiser.cs | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/VRCOSC.App/Serialisation/Serialiser.cs b/VRCOSC.App/Serialisation/Serialiser.cs index 0f7b3832..03e7a7a4 100644 --- a/VRCOSC.App/Serialisation/Serialiser.cs +++ b/VRCOSC.App/Serialisation/Serialiser.cs @@ -120,27 +120,27 @@ public SerialisationResult Serialise() if (bytes is [0xFF, 0xFE, ..]) { bytes = bytes[2..]; - Logger.Log("Skipping BOM"); + Logger.Log("Found BOM. Deserialising as UTF16"); + return JsonConvert.DeserializeObject(Encoding.Unicode.GetString(bytes)); } - try + var utf16Str = Encoding.Unicode.GetString(bytes); + var utf8Str = Encoding.UTF8.GetString(bytes); + + if (utf16Str.StartsWith('{')) { - // read legacy files encoded as UTF-16 - return JsonConvert.DeserializeObject(Encoding.Unicode.GetString(bytes)); + Logger.Log($"Deserialising {filePath} as UTF16", LoggingTarget.Information); + return JsonConvert.DeserializeObject(utf16Str); } - catch + + if (utf8Str.StartsWith('{')) { - try - { - // read converted files encoded as UTF-8 - return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bytes)); - } - catch - { - Logger.Log($"'{filePath}' was unable to be read as UTF8"); - return null; - } + Logger.Log($"Deserialising {filePath} as UTF8", LoggingTarget.Information); + return JsonConvert.DeserializeObject(utf8Str); } + + Logger.Log($"'{filePath}' was unable to be deserialised"); + return null; } /// From 6a03022ef1b3b4f615c617af05bf9965ed48a02a Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 23 Dec 2024 14:19:15 +0000 Subject: [PATCH 47/49] Fix thread access exception when auto-installing speech model --- VRCOSC.App/AppManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VRCOSC.App/AppManager.cs b/VRCOSC.App/AppManager.cs index 93c0202d..f1e4e875 100644 --- a/VRCOSC.App/AppManager.cs +++ b/VRCOSC.App/AppManager.cs @@ -437,12 +437,12 @@ private async Task startAsync() sendControlParameters(); } - public Task InstallSpeechModel() + public Task InstallSpeechModel() => Application.Current.Dispatcher.Invoke(() => { var action = new FileDownloadAction(new Uri("https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin?download=true"), Storage.GetStorageForDirectory("runtime/whisper"), "ggml-small.bin"); action.OnComplete += () => SettingsManager.GetInstance().GetObservable(VRCOSCSetting.SpeechModelPath).Value = Storage.GetStorageForDirectory("runtime/whisper").GetFullPath("ggml-small.bin"); return MainWindow.GetInstance().ShowLoadingOverlay("Installing Model", action); - } + }); private void initialiseOSCClient(IPAddress sendAddress, int sendPort, IPAddress receiveAddress, int receivePort) { From 6217e38b71095bc4b449b4cedc34733aa9533f5f Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 23 Dec 2024 14:23:17 +0000 Subject: [PATCH 48/49] Fix terminal logging sometimes including information logs --- VRCOSC.App/Audio/Whisper/AudioProcessor.cs | 12 +++--------- VRCOSC.App/Router/RouterManager.cs | 4 ++-- VRCOSC.App/SDK/Modules/Module.cs | 2 +- VRCOSC.App/UI/Views/Run/RunView.xaml.cs | 2 +- VRCOSC.App/Utils/Logger.cs | 6 ++++-- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/VRCOSC.App/Audio/Whisper/AudioProcessor.cs b/VRCOSC.App/Audio/Whisper/AudioProcessor.cs index 4382d2b0..26ab94d4 100644 --- a/VRCOSC.App/Audio/Whisper/AudioProcessor.cs +++ b/VRCOSC.App/Audio/Whisper/AudioProcessor.cs @@ -113,9 +113,7 @@ public void Stop() if (finalSpeechResult is not null) { -#if DEBUG - Logger.Log($"Final result: {finalSpeechResult.Text} - {finalSpeechResult.Confidence}"); -#endif + Logger.Log($"Final result: {finalSpeechResult.Text} - {finalSpeechResult.Confidence}", LoggingTarget.Information); } speechResult = null; @@ -128,9 +126,7 @@ public void Stop() speechResult = await processWithWhisper(data, false); isProcessing = false; -#if DEBUG - Logger.Log($"Result: {speechResult?.Text}"); -#endif + Logger.Log($"Result: {speechResult?.Text}", LoggingTarget.Information); return speechResult; } @@ -168,9 +164,7 @@ private bool isSilent(float[] buffer) } var rms = Math.Sqrt(sum / samplesToCheck); -#if DEBUG - Logger.Log($"RMS: {rms}"); -#endif + Logger.Log($"RMS: {rms}", LoggingTarget.Information); return rms < SettingsManager.GetInstance().GetValue(VRCOSCSetting.SpeechNoiseCutoff); } diff --git a/VRCOSC.App/Router/RouterManager.cs b/VRCOSC.App/Router/RouterManager.cs index 2cbdec5a..432d5df7 100644 --- a/VRCOSC.App/Router/RouterManager.cs +++ b/VRCOSC.App/Router/RouterManager.cs @@ -66,7 +66,7 @@ public void Start() var endpoint = new IPEndPoint(IPAddress.Parse(address), port); - Logger.Log($"Starting router instance `{route.Name.Value}` on {endpoint}"); + Logger.Log($"Starting router instance `{route.Name.Value}` on {endpoint}", LoggingTarget.Terminal); var sender = new OscSender(); sender.Initialise(endpoint); @@ -93,7 +93,7 @@ public void Stop() foreach (var (route, sender) in senders) { - Logger.Log($"Stopping router instance '{route.Name.Value}'"); + Logger.Log($"Stopping router instance '{route.Name.Value}'", LoggingTarget.Terminal); sender.Disable(); } diff --git a/VRCOSC.App/SDK/Modules/Module.cs b/VRCOSC.App/SDK/Modules/Module.cs index e89d745e..6a13be49 100644 --- a/VRCOSC.App/SDK/Modules/Module.cs +++ b/VRCOSC.App/SDK/Modules/Module.cs @@ -339,7 +339,7 @@ protected virtual void OnPlayerUpdate() /// The message to log to the terminal protected void Log(string message) { - Logger.Log($"[{Title}]: {message}", "terminal"); + Logger.Log($"[{Title}]: {message}", LoggingTarget.Terminal); } /// diff --git a/VRCOSC.App/UI/Views/Run/RunView.xaml.cs b/VRCOSC.App/UI/Views/Run/RunView.xaml.cs index c6c43481..db6736f8 100644 --- a/VRCOSC.App/UI/Views/Run/RunView.xaml.cs +++ b/VRCOSC.App/UI/Views/Run/RunView.xaml.cs @@ -127,7 +127,7 @@ private void onAppManagerStateChange(AppManagerState newState) => Dispatcher.Inv private void onLogEntry(LogEntry e) => Dispatcher.Invoke(() => { - if (e.LoggerName != "terminal" && !e.Message!.Contains("router")) return; + if (e.Target != LoggingTarget.Terminal) return; var dateTimeText = $"[{DateTime.Now:HH:mm:ss}] {e.Message}"; diff --git a/VRCOSC.App/Utils/Logger.cs b/VRCOSC.App/Utils/Logger.cs index 7b226f34..1f02147f 100644 --- a/VRCOSC.App/Utils/Logger.cs +++ b/VRCOSC.App/Utils/Logger.cs @@ -539,5 +539,7 @@ public enum LoggingTarget /// /// Logging target for network-related events. /// - Network -} + Network, + + Terminal +} \ No newline at end of file From d1568fe093aed6c80fe2910783b518a4b8bc1d5c Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 23 Dec 2024 14:36:01 +0000 Subject: [PATCH 49/49] Fix readme --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 24350d23..b6d085c3 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,9 @@ and act as a centralised source for useful OSC programs so that a user will only Our framework supports developing your own modules to save you the trouble of having to set up everything yourself, as well as allowing other people to seamlessly use your module on their PC. See how to create a module [here](https://vrcosc.com/docs/v2/sdk/getting-started). -VRCOSC's powerful ChatBox design system allows you to display what you want, when you want, how you want. Check out the ChatBox-Config forum channel of our [Discord Server](https://discord.gg/vj4brHyvT5) to see some of the configs people have created! +VRCOSC's powerful ChatBox design system allows you to display what you want, when you want, how you want. Check out the ChatBox-Config forum channel of our [Discord Server](https://discord.gg/vj4brHyvT5) to see some of the configs people have created or you can make your own by following [the docs](https://vrcosc.com/docs/v2/chatbox)! The ChatBox uses a [community-created list](https://github.com/cyberkitsune/chatbox-club-blacklist/blob/master/npblacklist.json) to block the ChatBox from being used in certain worlds. You can turn this off in the app settings, but we recommend you keep it on for respect. -Our powerful ChatBox animation system allows you to display what you want, when you want, how you want. -Community configs are available, or you can make your own by following [the docs](https://vrcosc.com/docs/v2/chatbox)! - Featuring: - Modern GUI - Automated configuration management @@ -44,8 +41,8 @@ Featuring: ## Getting Started - Download and run `VRCOSCSetup.exe` from the [Releases](https://github.com/VolcanicArts/VRCOSC/releases/latest) page -- Tick the modules you'd like to use -- (Optional) Download any prefabs you want and add them to your avatar (Guides are available inside each prefab) +- Enable the modules you'd like to use +- (Optional) Download any prefabs you want and add them to your avatar from [here](https://vrcosc.com/docs/downloads#prefabs) - Press the run button! Check the [FAQ](https://vrcosc.com/docs/faq) if you have any issues with installing, using the app, or using any of the prefabs. @@ -84,4 +81,4 @@ The official modules source code is located [here](https://github.com/VolcanicAr ## License This program is licensed under the [GNU General Public License V3](https://www.gnu.org/licenses/gpl-3.0.en.html). Please see [the license file](LICENSE) for more information. -Other libraries included in this project may contain different licenses. See the license files in their repos for more information. +Other libraries included in this project may contain different licenses. See the license files in their repos for more information. \ No newline at end of file