From 96a26422c9ad12b226f0d8a6b1361910ec9c80c4 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Wed, 9 Aug 2023 17:21:06 +0100 Subject: [PATCH 01/19] Set to winexe --- VRCOSC.Desktop/VRCOSC.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Desktop/VRCOSC.Desktop.csproj b/VRCOSC.Desktop/VRCOSC.Desktop.csproj index 639f7d4f..78a853aa 100644 --- a/VRCOSC.Desktop/VRCOSC.Desktop.csproj +++ b/VRCOSC.Desktop/VRCOSC.Desktop.csproj @@ -1,7 +1,7 @@ net6.0-windows10.0.22621.0 - Exe + WinExe VRCOSC game.ico app.manifest From 3b1876682f0828009ab9c28c9d2ec4ece3d36eac Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Wed, 9 Aug 2023 17:21:23 +0100 Subject: [PATCH 02/19] Improve start in tray behaviour --- VRCOSC.Game/VRCOSCGame.cs | 69 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/VRCOSC.Game/VRCOSCGame.cs b/VRCOSC.Game/VRCOSCGame.cs index 2e3f494c..668adfd4 100644 --- a/VRCOSC.Game/VRCOSCGame.cs +++ b/VRCOSC.Game/VRCOSCGame.cs @@ -60,6 +60,7 @@ public abstract partial class VRCOSCGame : VRCOSCGameBase private NotificationContainer notificationContainer = null!; private VRCOSCUpdateManager updateManager = null!; private Bindable uiScaleBindable = null!; + private bool startInTrayComplete; protected VRCOSCGame(bool enableModuleDebugMode) { @@ -94,7 +95,6 @@ private void load() uiScaleBindable.BindValueChanged(e => updateUiScale(e.NewValue), true); host.Window.Resized += () => updateUiScale(uiScaleBindable.Value); - inTray.Value = ConfigManager.Get(VRCOSCSetting.StartInTray); setupTrayIcon(); } @@ -145,17 +145,28 @@ protected override void LoadComplete() { if (e.NewValue is null && e.OldValue is not null) e.OldValue.Serialise(); }, true); - - Task.Run(async () => - { - await Task.Delay(1000); - handleTrayTransition(false); - }); } protected override void Update() { - if (host.Window.WindowState == WindowState.Minimised || inTray.Value) + if (windowHandle is null) + { + var localWindowHandle = Process.GetCurrentProcess().MainWindowHandle; + if (localWindowHandle != IntPtr.Zero) windowHandle ??= localWindowHandle; + } + + if (!startInTrayComplete && windowHandle is not null) + { + if (ConfigManager.Get(VRCOSCSetting.StartInTray)) + { + inTray = true; + handleTrayTransition(); + } + + startInTrayComplete = true; + } + + if (host.Window.WindowState == WindowState.Minimised || inTray) host.DrawThread.InactiveHz = 1; else host.DrawThread.InactiveHz = 60; @@ -213,13 +224,17 @@ private void copyOpenVrFiles() #region Tray - private readonly Bindable inTray = new(); + private bool inTray; private readonly NotifyIcon trayIcon = new(); private IntPtr? windowHandle; private void setupTrayIcon() { - trayIcon.DoubleClick += (_, _) => handleTrayTransition(true); + trayIcon.DoubleClick += (_, _) => + { + inTray = !inTray; + handleTrayTransition(); + }; trayIcon.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); trayIcon.Visible = true; @@ -232,23 +247,17 @@ private void setupTrayIcon() { "VRCOSC", null, (_, _) => { - inTray.Value = false; - handleTrayTransition(false); + inTray = false; + handleTrayTransition(); } }, new ToolStripSeparator(), { - "Check For Updates", null, (_, _) => - { - Schedule(checkUpdates); - } + "Check For Updates", null, (_, _) => Schedule(checkUpdates) }, new ToolStripSeparator(), { - "Exit", null, (_, _) => - { - Schedule(prepareForExit); - } + "Exit", null, (_, _) => Schedule(prepareForExit) } } }; @@ -256,20 +265,12 @@ private void setupTrayIcon() trayIcon.ContextMenuStrip = contextMenu; } - private void handleTrayTransition(bool toggle) + private void handleTrayTransition() { - var localWindowHandle = Process.GetCurrentProcess().MainWindowHandle; + if (windowHandle is null) return; - if (localWindowHandle != IntPtr.Zero) - { - windowHandle ??= localWindowHandle; - } - - if (windowHandle is not null) - { - if (toggle) inTray.Value = !inTray.Value; - User32.ShowWindow(windowHandle.Value, inTray.Value ? User32.WindowShowStyle.SW_HIDE : User32.WindowShowStyle.SW_SHOWDEFAULT); - } + User32.ShowWindow(windowHandle.Value, inTray ? User32.WindowShowStyle.SW_HIDE : User32.WindowShowStyle.SW_SHOWDEFAULT); + if (!inTray) User32.SetForegroundWindow(windowHandle.Value); } #endregion @@ -280,8 +281,8 @@ protected override bool OnExiting() if (ConfigManager.Get(VRCOSCSetting.TrayOnClose)) { - inTray.Value = true; - handleTrayTransition(false); + inTray = true; + handleTrayTransition(); return true; } From 0235f0642c613d50203ced4611241417c4030b1f Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Wed, 9 Aug 2023 18:13:48 +0100 Subject: [PATCH 03/19] Dependency bumps --- VRCOSC.Game/VRCOSC.Game.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index 0410f4e7..6015cf79 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -19,8 +19,8 @@ - - + + all @@ -29,6 +29,6 @@ - + From 40ba4391bc319ce2f6c70f028712150db6feb85e Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 10 Aug 2023 17:24:50 +0100 Subject: [PATCH 04/19] OSC stability improvements --- VRCOSC.Game/App/AppManager.cs | 23 ++++------------------- VRCOSC.Game/OSC/Client/OscClient.cs | 22 ++++------------------ VRCOSC.Game/OSC/Client/OscReceiver.cs | 11 +++++++---- VRCOSC.Game/OSC/Client/OscSender.cs | 6 +++++- 4 files changed, 20 insertions(+), 42 deletions(-) diff --git a/VRCOSC.Game/App/AppManager.cs b/VRCOSC.Game/App/AppManager.cs index 75b52bfd..4724748c 100644 --- a/VRCOSC.Game/App/AppManager.cs +++ b/VRCOSC.Game/App/AppManager.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; @@ -22,7 +22,6 @@ using VRCOSC.Game.OpenVR; using VRCOSC.Game.OpenVR.Metadata; using VRCOSC.Game.OSC; -using VRCOSC.Game.OSC.Client; using VRCOSC.Game.OSC.VRChat; namespace VRCOSC.Game.App; @@ -216,6 +215,7 @@ private void sendModuleRunningState(Module module, bool running) public void Start() { if (!initialiseOSCClient()) return; + if (!OSCClient.EnableSend() || !OSCClient.EnableReceive()) return; State.Value = AppManagerState.Starting; @@ -224,12 +224,10 @@ public void Start() { ChatBoxManager.Start(); StartupManager.Start(); - enableOSCFlag(OscClientFlag.Send); ModuleManager.Start(); scheduleModuleEnabledParameters(); sendControlParameters(); startOSCRouter(); - enableOSCFlag(OscClientFlag.Receive); State.Value = AppManagerState.Started; }); @@ -253,19 +251,6 @@ private bool initialiseOSCClient() } } - private void enableOSCFlag(OscClientFlag flag) - { - try - { - OSCClient.Enable(flag); - } - catch (Exception e) - { - Notifications.Notify(new PortInUseNotification(flag == OscClientFlag.Send ? configManager.Get(VRCOSCSetting.SendPort) : configManager.Get(VRCOSCSetting.ReceivePort))); - Logger.Error(e, $"{nameof(AppManager)} experienced an exception"); - } - } - private void startOSCRouter() { try @@ -302,13 +287,13 @@ public async Task StopAsync() { State.Value = AppManagerState.Stopping; - await OSCClient.Disable(OscClientFlag.Receive); + await OSCClient.DisableReceive(); await OSCRouter.Disable(); cancelRunningModulesDelegate(); ModuleManager.Stop(); ChatBoxManager.Teardown(); VRChat.Teardown(); - await OSCClient.Disable(OscClientFlag.Send); + OSCClient.DisableSend(); oscMessageQueue.Clear(); State.Value = AppManagerState.Stopped; diff --git a/VRCOSC.Game/OSC/Client/OscClient.cs b/VRCOSC.Game/OSC/Client/OscClient.cs index 347eca53..960c3ace 100644 --- a/VRCOSC.Game/OSC/Client/OscClient.cs +++ b/VRCOSC.Game/OSC/Client/OscClient.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Net; using System.Threading.Tasks; -using osu.Framework.Extensions.EnumExtensions; namespace VRCOSC.Game.OSC.Client; @@ -39,17 +38,11 @@ public void Initialise(IPEndPoint sendEndpoint, IPEndPoint receiveEndpoint) receiver.Initialise(receiveEndpoint); } - public void Enable(OscClientFlag flag) - { - if (flag.HasFlagFast(OscClientFlag.Send)) sender.Enable(); - if (flag.HasFlagFast(OscClientFlag.Receive)) receiver.Enable(); - } + public bool EnableSend() => sender.Enable(); + public bool EnableReceive() => receiver.Enable(); - public async Task Disable(OscClientFlag flag) - { - if (flag.HasFlagFast(OscClientFlag.Send)) sender.Disable(); - if (flag.HasFlagFast(OscClientFlag.Receive)) await receiver.Disable(); - } + public void DisableSend() => sender.Disable(); + public Task DisableReceive() => receiver.Disable(); public void SendValue(string address, object value) => SendValues(address, new List { value }); public void SendValues(string address, List values) => SendMessage(new OscMessage(address, values)); @@ -66,10 +59,3 @@ public void Send(byte[] data) OnDataSent?.Invoke(data); } } - -[Flags] -public enum OscClientFlag -{ - Send = 1 << 0, - Receive = 1 << 1 -} diff --git a/VRCOSC.Game/OSC/Client/OscReceiver.cs b/VRCOSC.Game/OSC/Client/OscReceiver.cs index 81ce7c88..383496ee 100644 --- a/VRCOSC.Game/OSC/Client/OscReceiver.cs +++ b/VRCOSC.Game/OSC/Client/OscReceiver.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using osu.Framework.Logging; namespace VRCOSC.Game.OSC.Client; @@ -25,21 +26,23 @@ public void Initialise(IPEndPoint endPoint) this.endPoint = endPoint; } - public void Enable() + public bool Enable() { socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); try { socket.Bind(endPoint); + tokenSource = new CancellationTokenSource(); + incomingTask = Task.Run(runReceiveLoop); + return true; } catch (Exception e) { Notifications.Notify(e); + Logger.Error(e, $"{nameof(OscReceiver)} experienced an exception"); + return false; } - - tokenSource = new CancellationTokenSource(); - incomingTask = Task.Run(runReceiveLoop); } public async Task Disable() diff --git a/VRCOSC.Game/OSC/Client/OscSender.cs b/VRCOSC.Game/OSC/Client/OscSender.cs index 90fbc28d..bd8a8f85 100644 --- a/VRCOSC.Game/OSC/Client/OscSender.cs +++ b/VRCOSC.Game/OSC/Client/OscSender.cs @@ -4,6 +4,7 @@ using System; using System.Net; using System.Net.Sockets; +using osu.Framework.Logging; namespace VRCOSC.Game.OSC.Client; @@ -17,17 +18,20 @@ public void Initialise(IPEndPoint endPoint) this.endPoint = endPoint; } - public void Enable() + public bool Enable() { socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); try { socket.Connect(endPoint); + return true; } catch (Exception e) { Notifications.Notify(e); + Logger.Error(e, $"{nameof(OscSender)} experienced an exception"); + return false; } } From d4b5a17f16efe72fb6d9e9e73bfbad53871954d0 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 10 Aug 2023 17:25:28 +0100 Subject: [PATCH 05/19] SpeechToTextProvider stability improvements --- .../Providers/SpeechToText/MicrophoneHook.cs | 1 + VRCOSC.Modules/PiShock/PiShockModule.cs | 32 +++++++++++-------- .../SpeechToText/SpeechToTextModule.cs | 24 +++++++------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/VRCOSC.Game/Providers/SpeechToText/MicrophoneHook.cs b/VRCOSC.Game/Providers/SpeechToText/MicrophoneHook.cs index 4245461e..cc254343 100644 --- a/VRCOSC.Game/Providers/SpeechToText/MicrophoneHook.cs +++ b/VRCOSC.Game/Providers/SpeechToText/MicrophoneHook.cs @@ -41,6 +41,7 @@ public void Update() public void UnHook() { AudioCapture?.StopRecording(); + AudioCapture?.Dispose(); AudioCapture = null; } diff --git a/VRCOSC.Modules/PiShock/PiShockModule.cs b/VRCOSC.Modules/PiShock/PiShockModule.cs index 949abfc3..09aad40c 100644 --- a/VRCOSC.Modules/PiShock/PiShockModule.cs +++ b/VRCOSC.Modules/PiShock/PiShockModule.cs @@ -15,8 +15,8 @@ namespace VRCOSC.Modules.PiShock; [ModulePrefab("VRCOSC-PiShock", "https://github.com/VolcanicArts/VRCOSC/releases/download/latest/VRCOSC-PiShock.unitypackage")] public class PiShockModule : AvatarModule { - private readonly PiShockProvider piShockProvider = new(); - private readonly SpeechToTextProvider speechToTextProvider = new(); + private PiShockProvider? piShockProvider; + private SpeechToTextProvider? speechToTextProvider; private int group; private float duration; @@ -32,12 +32,6 @@ public class PiShockModule : AvatarModule private int convertedDuration => (int)Math.Round(Map(duration, 0, 1, 1, GetSetting(PiShockSetting.MaxDuration))); private int convertedIntensity => (int)Math.Round(Map(intensity, 0, 1, 1, GetSetting(PiShockSetting.MaxIntensity))); - public PiShockModule() - { - speechToTextProvider.OnLog += Log; - speechToTextProvider.OnFinalResult += onNewSentenceSpoken; - } - protected override void CreateAttributes() { CreateSetting(PiShockSetting.Username, "Username", "Your PiShock username", string.Empty); @@ -95,14 +89,23 @@ protected override void OnModuleStart() vibrateExecuted = false; beepExecuted = false; - if (GetSetting(PiShockSetting.EnableVoiceControl)) speechToTextProvider.Initialise(GetSetting(PiShockSetting.SpeechModelLocation)); + piShockProvider = new PiShockProvider(); + + if (GetSetting(PiShockSetting.EnableVoiceControl)) + { + speechToTextProvider = new SpeechToTextProvider(); + speechToTextProvider.OnLog += Log; + speechToTextProvider.OnFinalResult += onNewSentenceSpoken; + speechToTextProvider.Initialise(GetSetting(PiShockSetting.SpeechModelLocation)); + } sendParameters(); } protected override void OnModuleStop() { - speechToTextProvider.Teardown(); + speechToTextProvider?.Teardown(); + speechToTextProvider = null; } protected override void OnAvatarChange() @@ -113,13 +116,14 @@ protected override void OnAvatarChange() [ModuleUpdate(ModuleUpdateMode.Custom, true, 5000)] private void onModuleUpdate() { - speechToTextProvider.Update(); + speechToTextProvider?.Update(); } [ModuleUpdate(ModuleUpdateMode.Custom)] private void setSpeechToTextParameters() { - speechToTextProvider.RequiredConfidence = GetSetting(PiShockSetting.SpeechConfidence) / 100f; + if (speechToTextProvider is not null) + speechToTextProvider.RequiredConfidence = GetSetting(PiShockSetting.SpeechConfidence) / 100f; } [ModuleUpdate(ModuleUpdateMode.Custom)] @@ -164,7 +168,7 @@ private async void onNewSentenceSpoken(bool success, string sentence) if (shockerInstance is null) continue; Log($"Executing {wordInstance.Mode.Value} on {wordInstance.ShockerKey.Value} with duration {wordInstance.Duration.Value}s and intensity {wordInstance.Intensity.Value}%"); - await piShockProvider.Execute(GetSetting(PiShockSetting.Username), GetSetting(PiShockSetting.APIKey), shockerInstance.Sharecode.Value, wordInstance.Mode.Value, wordInstance.Duration.Value, wordInstance.Intensity.Value); + await piShockProvider!.Execute(GetSetting(PiShockSetting.Username), GetSetting(PiShockSetting.APIKey), shockerInstance.Sharecode.Value, wordInstance.Mode.Value, wordInstance.Duration.Value, wordInstance.Intensity.Value); } } @@ -192,7 +196,7 @@ private async void executePiShockMode(PiShockMode mode) private async Task sendPiShockData(PiShockMode mode, PiShockShockerInstance instance) { Log($"Executing {mode} on {instance.Key.Value} with duration {convertedDuration}s and intensity {convertedIntensity}%"); - var response = await piShockProvider.Execute(GetSetting(PiShockSetting.Username), GetSetting(PiShockSetting.APIKey), instance.Sharecode.Value, mode, convertedDuration, convertedIntensity); + var response = await piShockProvider!.Execute(GetSetting(PiShockSetting.Username), GetSetting(PiShockSetting.APIKey), instance.Sharecode.Value, mode, convertedDuration, convertedIntensity); Log(response.Message); if (response.Success) diff --git a/VRCOSC.Modules/SpeechToText/SpeechToTextModule.cs b/VRCOSC.Modules/SpeechToText/SpeechToTextModule.cs index a0be3a5a..4ed77050 100644 --- a/VRCOSC.Modules/SpeechToText/SpeechToTextModule.cs +++ b/VRCOSC.Modules/SpeechToText/SpeechToTextModule.cs @@ -14,20 +14,11 @@ namespace VRCOSC.Modules.SpeechToText; [ModuleGroup(ModuleType.Accessibility)] public class SpeechToTextModule : ChatBoxModule { - private readonly SpeechToTextProvider speechToTextProvider; + private SpeechToTextProvider? speechToTextProvider; private bool listening; private bool playerMuted; - public SpeechToTextModule() - { - speechToTextProvider = new SpeechToTextProvider(); - speechToTextProvider.OnLog += Log; - speechToTextProvider.OnBeforeAnalysis += onBeforeAnalysis; - speechToTextProvider.OnPartialResult += onPartialResult; - speechToTextProvider.OnFinalResult += onFinalResult; - } - protected override void CreateAttributes() { CreateSetting(SpeechToTextSetting.ModelLocation, "Model Location", "The folder location of the speech model you'd like to use\nRecommended default: vosk-model-small-en-us-0.15", string.Empty, "Download a model", () => OpenUrlExternally("https://alphacephei.com/vosk/models")); @@ -48,6 +39,12 @@ protected override void CreateAttributes() protected override void OnModuleStart() { + speechToTextProvider = new SpeechToTextProvider(); + speechToTextProvider.OnLog += Log; + speechToTextProvider.OnBeforeAnalysis += onBeforeAnalysis; + speechToTextProvider.OnPartialResult += onPartialResult; + speechToTextProvider.OnFinalResult += onFinalResult; + speechToTextProvider.Initialise(GetSetting(SpeechToTextSetting.ModelLocation)); listening = true; resetState(); @@ -57,7 +54,7 @@ protected override void OnModuleStart() [ModuleUpdate(ModuleUpdateMode.Custom, true, 5000)] private void onModuleUpdate() { - speechToTextProvider.Update(); + speechToTextProvider!.Update(); } protected override void OnPlayerUpdate() @@ -71,7 +68,8 @@ protected override void OnPlayerUpdate() protected override void OnModuleStop() { - speechToTextProvider.Teardown(); + speechToTextProvider?.Teardown(); + speechToTextProvider = null; } protected override void OnRegisteredParameterReceived(AvatarParameter parameter) @@ -90,7 +88,7 @@ protected override void OnRegisteredParameterReceived(AvatarParameter parameter) private void onBeforeAnalysis() { - speechToTextProvider.AnalysisEnabled = listening && (!GetSetting(SpeechToTextSetting.FollowMute) || playerMuted); + speechToTextProvider!.AnalysisEnabled = listening && (!GetSetting(SpeechToTextSetting.FollowMute) || playerMuted); speechToTextProvider.RequiredConfidence = GetSetting(SpeechToTextSetting.Confidence) / 100f; } From 53d41162ef15c9a820f686936f2f741efdf0f7f8 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 10 Aug 2023 17:25:48 +0100 Subject: [PATCH 06/19] HeartrateProvider stability improvements --- .../Modules/Bases/Heartrate/HeartrateModule.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs b/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs index bd20c7c9..c05fcf4c 100644 --- a/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs +++ b/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs @@ -70,13 +70,21 @@ private void attemptReconnection() { Log("Attempting reconnection..."); Thread.Sleep(2000); - attemptConnection(); + HeartrateProvider?.Initialise(); } protected override void OnModuleStop() { - HeartrateProvider?.Teardown(); - HeartrateProvider = null; + if (HeartrateProvider is not null) + { + HeartrateProvider.OnHeartrateUpdate = null; + HeartrateProvider.OnConnected = null; + HeartrateProvider.OnDisconnected = null; + HeartrateProvider.OnLog = null; + + HeartrateProvider.Teardown(); + HeartrateProvider = null; + } SendParameter(HeartrateParameter.Enabled, false); } From a3fec30da908cab10641124a8f2066772ecbad1d Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 10 Aug 2023 17:26:19 +0100 Subject: [PATCH 07/19] Ensure app cannot get into a bad state --- VRCOSC.Game/App/AppManager.cs | 6 +++++- VRCOSC.Game/VRCOSCGame.cs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/VRCOSC.Game/App/AppManager.cs b/VRCOSC.Game/App/AppManager.cs index 4724748c..0dd7918e 100644 --- a/VRCOSC.Game/App/AppManager.cs +++ b/VRCOSC.Game/App/AppManager.cs @@ -214,6 +214,8 @@ private void sendModuleRunningState(Module module, bool running) public void Start() { + if (State.Value is AppManagerState.Starting or AppManagerState.Started) return; + if (!initialiseOSCClient()) return; if (!OSCClient.EnableSend() || !OSCClient.EnableReceive()) return; @@ -273,7 +275,7 @@ private void startOSCRouter() public async Task RestartAsync() { await StopAsync(); - await Task.Delay(250); + await Task.Delay(500); Start(); } @@ -285,6 +287,8 @@ public async Task RestartAsync() public async Task StopAsync() { + if (State.Value is AppManagerState.Stopping or AppManagerState.Stopped) return; + State.Value = AppManagerState.Stopping; await OSCClient.DisableReceive(); diff --git a/VRCOSC.Game/VRCOSCGame.cs b/VRCOSC.Game/VRCOSCGame.cs index 668adfd4..b2c57599 100644 --- a/VRCOSC.Game/VRCOSCGame.cs +++ b/VRCOSC.Game/VRCOSCGame.cs @@ -299,7 +299,7 @@ private void prepareForExit() Task.WhenAll(new[] { appManager.StopAsync() - }).ContinueWith(_ => Schedule(performExit)); + }).ContinueWith(_ => Scheduler.AddDelayed(performExit, 500)); } private void performExit() From 31ccfdfdf8017555ff815b1f9e453862205db8e4 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Thu, 10 Aug 2023 17:26:30 +0100 Subject: [PATCH 08/19] Allow Check For Updates in tray to bypass UpdateMode --- VRCOSC.Game/VRCOSCGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Game/VRCOSCGame.cs b/VRCOSC.Game/VRCOSCGame.cs index b2c57599..73ec61b4 100644 --- a/VRCOSC.Game/VRCOSCGame.cs +++ b/VRCOSC.Game/VRCOSCGame.cs @@ -253,7 +253,7 @@ private void setupTrayIcon() }, new ToolStripSeparator(), { - "Check For Updates", null, (_, _) => Schedule(checkUpdates) + "Check For Updates", null, (_, _) => Schedule(() => updateManager.PerformUpdateCheck()) }, new ToolStripSeparator(), { From 7a5320e9a4d62b3fd0ff47cd36c2118a48cae8bc Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 12 Aug 2023 10:01:15 +0100 Subject: [PATCH 09/19] Allow PiShockProvider to return final duration and intensity --- VRCOSC.Game/Providers/PiShock/PiShockProvider.cs | 4 ++-- VRCOSC.Modules/PiShock/PiShockModule.cs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/VRCOSC.Game/Providers/PiShock/PiShockProvider.cs b/VRCOSC.Game/Providers/PiShock/PiShockProvider.cs index fc19a782..f104e6ae 100644 --- a/VRCOSC.Game/Providers/PiShock/PiShockProvider.cs +++ b/VRCOSC.Game/Providers/PiShock/PiShockProvider.cs @@ -42,7 +42,7 @@ public async Task Execute(string username, string apiKey, strin { var response = await client.PostAsync(action_api_url, new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json")); var responseString = await response.Content.ReadAsStringAsync(); - return new PiShockResponse(responseString == "Operation Succeeded.", responseString); + return new PiShockResponse(responseString == "Operation Succeeded.", responseString, duration, intensity); } catch (Exception e) { @@ -91,7 +91,7 @@ public async Task Execute(string username, string apiKey, strin }; } -public record PiShockResponse(bool Success, string Message); +public record PiShockResponse(bool Success, string Message, int FinalDuration = -1, int FinalIntensity = -1); public class PiShockShocker { diff --git a/VRCOSC.Modules/PiShock/PiShockModule.cs b/VRCOSC.Modules/PiShock/PiShockModule.cs index 09aad40c..3af5490b 100644 --- a/VRCOSC.Modules/PiShock/PiShockModule.cs +++ b/VRCOSC.Modules/PiShock/PiShockModule.cs @@ -167,8 +167,9 @@ private async void onNewSentenceSpoken(bool success, string sentence) var shockerInstance = getShockerInstanceFromKey(wordInstance.ShockerKey.Value); if (shockerInstance is null) continue; - Log($"Executing {wordInstance.Mode.Value} on {wordInstance.ShockerKey.Value} with duration {wordInstance.Duration.Value}s and intensity {wordInstance.Intensity.Value}%"); - await piShockProvider!.Execute(GetSetting(PiShockSetting.Username), GetSetting(PiShockSetting.APIKey), shockerInstance.Sharecode.Value, wordInstance.Mode.Value, wordInstance.Duration.Value, wordInstance.Intensity.Value); + var response = await piShockProvider!.Execute(GetSetting(PiShockSetting.Username), GetSetting(PiShockSetting.APIKey), shockerInstance.Sharecode.Value, wordInstance.Mode.Value, wordInstance.Duration.Value, wordInstance.Intensity.Value); + + Log(response.Success ? $"Executing {wordInstance.Mode.Value} on {wordInstance.ShockerKey.Value} with duration {response.FinalDuration}s and intensity {response.FinalIntensity}%" : response.Message); } } @@ -195,9 +196,9 @@ private async void executePiShockMode(PiShockMode mode) private async Task sendPiShockData(PiShockMode mode, PiShockShockerInstance instance) { - Log($"Executing {mode} on {instance.Key.Value} with duration {convertedDuration}s and intensity {convertedIntensity}%"); var response = await piShockProvider!.Execute(GetSetting(PiShockSetting.Username), GetSetting(PiShockSetting.APIKey), instance.Sharecode.Value, mode, convertedDuration, convertedIntensity); - Log(response.Message); + + Log(response.Success ? $"Executing {mode} on {instance.Sharecode.Value} with duration {response.FinalDuration}s and intensity {response.FinalIntensity}%" : response.Message); if (response.Success) { From d2eb11e4251329c868b99c96313fc49b0eefb312 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 12 Aug 2023 10:05:07 +0100 Subject: [PATCH 10/19] Ensure only loaded modules can be migrated --- VRCOSC.Game/Managers/ModuleManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Game/Managers/ModuleManager.cs b/VRCOSC.Game/Managers/ModuleManager.cs index 59813eaf..45836492 100644 --- a/VRCOSC.Game/Managers/ModuleManager.cs +++ b/VRCOSC.Game/Managers/ModuleManager.cs @@ -56,7 +56,7 @@ public void Load() public bool DoesModuleExist(string serialisedName) => assemblyContexts.Any(context => context.Assemblies.Any(assembly => assembly.ExportedTypes.Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract).Any(type => type.Name.ToLowerInvariant() == serialisedName))); public bool IsModuleLoaded(string serialisedName) => GetModule(serialisedName) is not null; public bool IsModuleEnabled(string serialisedName) => GetModule(serialisedName)?.Enabled.Value ?? false; - public List<(string, string)> GetMigrations() => (from module in Modules where module.LegacySerialisedName is not null select (module.LegacySerialisedName, module.SerialisedName)).ToList(); + public List<(string, string)> GetMigrations() => Modules.Where(module => module.LegacySerialisedName is not null && IsModuleLoaded(module.SerialisedName)).Select(module => (module.LegacySerialisedName!, module.SerialisedName)).ToList(); private void loadModules() { From fed4c9c29895e22bef078f4c2308d765d576c877 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sat, 12 Aug 2023 10:06:28 +0100 Subject: [PATCH 11/19] Ensure outdated modules with old references return false for exist check --- VRCOSC.Game/Managers/ModuleManager.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/VRCOSC.Game/Managers/ModuleManager.cs b/VRCOSC.Game/Managers/ModuleManager.cs index 45836492..c2a77d97 100644 --- a/VRCOSC.Game/Managers/ModuleManager.cs +++ b/VRCOSC.Game/Managers/ModuleManager.cs @@ -53,7 +53,18 @@ public void Load() loadModules(); } - public bool DoesModuleExist(string serialisedName) => assemblyContexts.Any(context => context.Assemblies.Any(assembly => assembly.ExportedTypes.Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract).Any(type => type.Name.ToLowerInvariant() == serialisedName))); + public bool DoesModuleExist(string serialisedName) + { + try + { + return assemblyContexts.Any(context => context.Assemblies.Any(assembly => assembly.ExportedTypes.Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract).Any(type => type.Name.ToLowerInvariant() == serialisedName))); + } + catch (Exception) + { + return false; + } + } + public bool IsModuleLoaded(string serialisedName) => GetModule(serialisedName) is not null; public bool IsModuleEnabled(string serialisedName) => GetModule(serialisedName)?.Enabled.Value ?? false; public List<(string, string)> GetMigrations() => Modules.Where(module => module.LegacySerialisedName is not null && IsModuleLoaded(module.SerialisedName)).Select(module => (module.LegacySerialisedName!, module.SerialisedName)).ToList(); From a83d3278702058b6425ea134d0a0d04b0b484541 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 13 Aug 2023 03:46:58 +0100 Subject: [PATCH 12/19] Ensure registered parameters are sent using the registered type --- VRCOSC.Game/Modules/Module.cs | 1 + .../HardwareStats/HardwareStatsModule.cs | 10 +++++----- VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs | 14 +++++++------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/VRCOSC.Game/Modules/Module.cs b/VRCOSC.Game/Modules/Module.cs index 95568c30..b732350f 100644 --- a/VRCOSC.Game/Modules/Module.cs +++ b/VRCOSC.Game/Modules/Module.cs @@ -425,6 +425,7 @@ protected void SendParameter(Enum lookup, T value) where T : struct var data = Parameters[lookup]; if (!data.Mode.HasFlagFast(ParameterMode.Write)) throw new InvalidOperationException($"Parameter {lookup.GetType().Name}.{lookup} is a read-only parameter and therefore can't be sent!"); + if (data.ExpectedType != typeof(T)) throw new InvalidOperationException($"Parameter {lookup.GetType().Name}.{lookup} expects type {data.ExpectedType.ToReadableName()} but you tried to send {typeof(T).ToReadableName()}"); scheduler.Add(() => oscClient.SendValue(data.ParameterAddress, value)); } diff --git a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs index 132677cc..d361a0e3 100644 --- a/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs +++ b/VRCOSC.Modules/HardwareStats/HardwareStatsModule.cs @@ -21,11 +21,11 @@ protected override void CreateAttributes() CreateSetting(HardwareStatsSetting.SelectedGPU, "Selected GPU", "Enter the (0th based) index of the GPU you want to track", 0); CreateParameter(HardwareStatsParameter.CpuUsage, ParameterMode.Write, "VRCOSC/Hardware/CPUUsage", "CPU Usage", "The CPU usage normalised"); - CreateParameter(HardwareStatsParameter.CpuPower, ParameterMode.Write, "VRCOSC/Hardware/CPUPower", "CPU Power", "The power usage of your CPU in Watts"); + CreateParameter(HardwareStatsParameter.CpuPower, ParameterMode.Write, "VRCOSC/Hardware/CPUPower", "CPU Power", "The power usage of your CPU in Watts"); CreateParameter(HardwareStatsParameter.CpuTemp, ParameterMode.Write, "VRCOSC/Hardware/CPUTemp", "CPU Temp", "The CPU temp in C"); CreateParameter(HardwareStatsParameter.CpuTempNormalised, ParameterMode.Write, "VRCOSC/Hardware/CPUTempNormalised", "CPU Temp Normalised", "The CPU temp mapping 0-100c as 0-1"); CreateParameter(HardwareStatsParameter.GpuUsage, ParameterMode.Write, "VRCOSC/Hardware/GPUUsage", "GPU Usage", "The GPU usage normalised"); - CreateParameter(HardwareStatsParameter.GpuPower, ParameterMode.Write, "VRCOSC/Hardware/GPUPower", "GPU Power", "The power usage of your GPU in Watts"); + CreateParameter(HardwareStatsParameter.GpuPower, ParameterMode.Write, "VRCOSC/Hardware/GPUPower", "GPU Power", "The power usage of your GPU in Watts"); CreateParameter(HardwareStatsParameter.GpuTemp, ParameterMode.Write, "VRCOSC/Hardware/GPUTemp", "GPU Temp", "The GPU temp in C "); CreateParameter(HardwareStatsParameter.GpuTempNormalised, ParameterMode.Write, "VRCOSC/Hardware/GPUTempNormalised", "GPU Temp Normalised", "The GPU temp mapping 0-100c as 0-1"); CreateParameter(HardwareStatsParameter.RamUsage, ParameterMode.Write, "VRCOSC/Hardware/RAMUsage", "RAM Usage", "The RAM usage normalised"); @@ -104,9 +104,9 @@ private async void updateParameters() SendParameter(HardwareStatsParameter.GpuTemp, gpu.Temperature); SendParameter(HardwareStatsParameter.GpuTempNormalised, gpu.Temperature / 100f); SendParameter(HardwareStatsParameter.RamUsage, ram.Usage / 100f); - SendParameter(HardwareStatsParameter.RamTotal, ram.Total); - SendParameter(HardwareStatsParameter.RamUsed, ram.Used); - SendParameter(HardwareStatsParameter.RamAvailable, ram.Available); + SendParameter(HardwareStatsParameter.RamTotal, (int)ram.Total); + SendParameter(HardwareStatsParameter.RamUsed, (int)ram.Used); + SendParameter(HardwareStatsParameter.RamAvailable, (int)ram.Available); SendParameter(HardwareStatsParameter.VRamUsage, gpu.MemoryUsage); SendParameter(HardwareStatsParameter.VRamFree, gpu.MemoryFree / 1000f); SendParameter(HardwareStatsParameter.VRamUsed, gpu.MemoryUsed / 1000f); diff --git a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs index 991512dd..3fed16f9 100644 --- a/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs +++ b/VRCOSC.Modules/OpenVR/OpenVRStatisticsModule.cs @@ -85,21 +85,21 @@ private void updateVariablesAndParameters() SetVariableValue(OpenVrVariable.AverageTrackerBattery, "0"); SendParameter(OpenVrParameter.HMD_Connected, false); - SendParameter(OpenVrParameter.HMD_Battery, 0); + SendParameter(OpenVrParameter.HMD_Battery, 0f); SendParameter(OpenVrParameter.HMD_Charging, false); SendParameter(OpenVrParameter.LeftController_Connected, false); - SendParameter(OpenVrParameter.LeftController_Battery, 0); + SendParameter(OpenVrParameter.LeftController_Battery, 0f); SendParameter(OpenVrParameter.LeftController_Charging, false); SendParameter(OpenVrParameter.RightController_Connected, false); - SendParameter(OpenVrParameter.RightController_Battery, 0); + SendParameter(OpenVrParameter.RightController_Battery, 0f); SendParameter(OpenVrParameter.RightController_Charging, false); for (int i = 0; i < OVRSystem.MAX_TRACKER_COUNT; i++) { SendParameter(OpenVrParameter.Tracker1_Connected + i, false); - SendParameter(OpenVrParameter.Tracker1_Battery + i, 0); + SendParameter(OpenVrParameter.Tracker1_Battery + i, 0f); SendParameter(OpenVrParameter.Tracker1_Charging + i, false); } } @@ -127,7 +127,7 @@ private void updateLeftController() } else { - SendParameter(OpenVrParameter.LeftController_Battery, 0); + SendParameter(OpenVrParameter.LeftController_Battery, 0f); SendParameter(OpenVrParameter.LeftController_Charging, false); } } @@ -143,7 +143,7 @@ private void updateRightController() } else { - SendParameter(OpenVrParameter.RightController_Battery, 0); + SendParameter(OpenVrParameter.RightController_Battery, 0f); SendParameter(OpenVrParameter.RightController_Charging, false); } } @@ -165,7 +165,7 @@ private void updateTrackers() } else { - SendParameter(OpenVrParameter.Tracker1_Battery + i, 0); + SendParameter(OpenVrParameter.Tracker1_Battery + i, 0f); SendParameter(OpenVrParameter.Tracker1_Charging + i, false); } } From 939064711edd9124c0bb940ec096d486d81e2050 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 13 Aug 2023 03:47:13 +0100 Subject: [PATCH 13/19] Remove redundant count check in ModuleAttribute --- VRCOSC.Game/Modules/Attributes/ModuleAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Game/Modules/Attributes/ModuleAttribute.cs b/VRCOSC.Game/Modules/Attributes/ModuleAttribute.cs index b73edf0f..c9739633 100644 --- a/VRCOSC.Game/Modules/Attributes/ModuleAttribute.cs +++ b/VRCOSC.Game/Modules/Attributes/ModuleAttribute.cs @@ -142,7 +142,7 @@ public abstract class ModuleAttributeList : ModuleAttribute protected abstract IEnumerable GetClonedDefaults(); protected abstract IEnumerable JArrayToType(JArray array); - public override bool IsDefault() => Attribute.Count == Default.Count && Attribute.SequenceEqual(Default); + public override bool IsDefault() => Attribute.SequenceEqual(Default); public override void SetDefault() => Attribute.ReplaceItems(GetClonedDefaults()); public override void DeserialiseValue(object value) => Attribute.ReplaceItems(JArrayToType((JArray)value)); } From 42a3a60ed79a18d0d18ed2f0877537e77df8415d Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 13 Aug 2023 03:47:27 +0100 Subject: [PATCH 14/19] Add more remarks to ModuleUpdateAttribute --- VRCOSC.Game/Modules/ModuleMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCOSC.Game/Modules/ModuleMetadata.cs b/VRCOSC.Game/Modules/ModuleMetadata.cs index b88a5ef9..ea824d86 100644 --- a/VRCOSC.Game/Modules/ModuleMetadata.cs +++ b/VRCOSC.Game/Modules/ModuleMetadata.cs @@ -130,7 +130,7 @@ public class ModuleUpdateAttribute : Attribute /// The mode this update is defined as /// Whether this method should be called immediately after . This is only used when is /// The time between this method being called in milliseconds. This is only used when is - /// defaults to the fastest update rate you should need for sending parameters + /// defaults to the fastest update rate you should need for sending parameters. If multiple of this attribute that have the same are defined in a class they will execute top to bottom public ModuleUpdateAttribute(ModuleUpdateMode mode, bool updateImmediately = true, double deltaMilliseconds = VRChatOscConstants.UPDATE_DELTA_MILLISECONDS) { Mode = mode; From 9c72754642023b9aadb7682b1b0e37b969c22051 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 13 Aug 2023 03:47:35 +0100 Subject: [PATCH 15/19] Internalise TerminalLogger --- VRCOSC.Game/Util/TerminalLogger.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/VRCOSC.Game/Util/TerminalLogger.cs b/VRCOSC.Game/Util/TerminalLogger.cs index 7a2cb632..37a8d415 100644 --- a/VRCOSC.Game/Util/TerminalLogger.cs +++ b/VRCOSC.Game/Util/TerminalLogger.cs @@ -6,16 +6,16 @@ namespace VRCOSC.Game.Util; -public sealed class TerminalLogger +internal sealed class TerminalLogger { private readonly string name; - public TerminalLogger(string name) + internal TerminalLogger(string name) { this.name = name; } - public void Log(string message) + internal void Log(string message) { message.Split('\n').ForEach(msg => Logger.Log($"[{name}]: {msg}", "terminal")); } From ff6ffc0ef3d9d451e34eff89a1f255bcec1ae4a4 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 13 Aug 2023 03:52:31 +0100 Subject: [PATCH 16/19] Reorganise HeartateModule --- .../Bases/Heartrate/HeartrateModule.cs | 78 ++++++++++--------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs b/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs index c05fcf4c..c5fa0025 100644 --- a/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs +++ b/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs @@ -13,6 +13,9 @@ namespace VRCOSC.Game.Modules.Bases.Heartrate; [ModulePrefab("VRCOSC-Heartrate", "https://github.com/VolcanicArts/VRCOSC/releases/download/latest/VRCOSC-Heartrate.unitypackage")] public abstract class HeartrateModule : ChatBoxModule where T : HeartrateProvider { + private const int reconnection_delay = 500; + private const int reconnection_limit = 3; + protected T? HeartrateProvider; private float currentHeartrate; @@ -23,54 +26,52 @@ public abstract class HeartrateModule : ChatBoxModule where T : HeartrateProv protected override void CreateAttributes() { - CreateSetting(HeartrateSetting.Smoothed, @"Smoothed", @"Whether the current heartrate should jump or smoothly converge to the target heartrate", false); - CreateSetting(HeartrateSetting.SmoothingLength, @"Smoothing Length", @"The length of time (in milliseconds) the current heartrate should take to converge to the target heartrate", 1000, () => GetSetting(HeartrateSetting.Smoothed)); - CreateSetting(HeartrateSetting.NormalisedLowerbound, @"Normalised Lowerbound", @"The lower bound BPM the normalised parameter should use", 0); - CreateSetting(HeartrateSetting.NormalisedUpperbound, @"Normalised Upperbound", @"The upper bound BPM the normalised parameter should use", 240); + CreateSetting(HeartrateSetting.Smoothed, "Smoothed", "Whether the current heartrate should jump or smoothly converge to the target heartrate", false); + CreateSetting(HeartrateSetting.SmoothingLength, "Smoothing Length", "The length of time (in milliseconds) the current heartrate should take to converge to the target heartrate", 1000, () => GetSetting(HeartrateSetting.Smoothed)); + CreateSetting(HeartrateSetting.NormalisedLowerbound, "Normalised Lowerbound", "The lower bound BPM the normalised parameter should use", 0); + CreateSetting(HeartrateSetting.NormalisedUpperbound, "Normalised Upperbound", "The upper bound BPM the normalised parameter should use", 240); - CreateParameter(HeartrateParameter.Enabled, ParameterMode.Write, @"VRCOSC/Heartrate/Enabled", @"Enabled", @"Whether this module is connected and receiving values"); - CreateParameter(HeartrateParameter.Value, ParameterMode.Write, @"VRCOSC/Heartrate/Value", @"Value", @"The raw value of your heartrate"); - CreateParameter(HeartrateParameter.Normalised, ParameterMode.Write, @"VRCOSC/Heartrate/Normalised", @"Normalised", @"The heartrate value normalised to the set bounds"); - CreateParameter(HeartrateParameter.Units, ParameterMode.Write, @"VRCOSC/Heartrate/Units", @"Units", @"The units digit 0-9 mapped to a float"); - CreateParameter(HeartrateParameter.Tens, ParameterMode.Write, @"VRCOSC/Heartrate/Tens", @"Tens", @"The tens digit 0-9 mapped to a float"); - CreateParameter(HeartrateParameter.Hundreds, ParameterMode.Write, @"VRCOSC/Heartrate/Hundreds", @"Hundreds", @"The hundreds digit 0-9 mapped to a float"); + CreateParameter(HeartrateParameter.Enabled, ParameterMode.Write, "VRCOSC/Heartrate/Enabled", "Enabled", "Whether this module is connected and receiving values"); + CreateParameter(HeartrateParameter.Value, ParameterMode.Write, "VRCOSC/Heartrate/Value", "Value", "The raw value of your heartrate"); + CreateParameter(HeartrateParameter.Normalised, ParameterMode.Write, "VRCOSC/Heartrate/Normalised", "Normalised", "The heartrate value normalised to the set bounds"); + CreateParameter(HeartrateParameter.Units, ParameterMode.Write, "VRCOSC/Heartrate/Units", "Units", "The units digit 0-9 mapped to a float"); + CreateParameter(HeartrateParameter.Tens, ParameterMode.Write, "VRCOSC/Heartrate/Tens", "Tens", "The tens digit 0-9 mapped to a float"); + CreateParameter(HeartrateParameter.Hundreds, ParameterMode.Write, "VRCOSC/Heartrate/Hundreds", "Hundreds", "The hundreds digit 0-9 mapped to a float"); - CreateVariable(HeartrateVariable.Heartrate, @"Heartrate", @"hr"); + CreateVariable(HeartrateVariable.Heartrate, "Heartrate", "hr"); - CreateState(HeartrateState.Default, @"Default", $@"Heartrate/v{GetVariableFormat(HeartrateVariable.Heartrate)} bpm"); + CreateState(HeartrateState.Default, "Default", $"Heartrate/v{GetVariableFormat(HeartrateVariable.Heartrate)} bpm"); } protected override void OnModuleStart() { currentHeartrate = 0; targetHeartrate = 0; - connectionCount = 0; - ChangeStateTo(HeartrateState.Default); - attemptConnection(); - } - - private void attemptConnection() - { - if (connectionCount >= 3) - { - Log(@"Connection cannot be established"); - return; - } + connectionCount = 1; - connectionCount++; HeartrateProvider = CreateProvider(); HeartrateProvider.OnHeartrateUpdate += newHeartrate => targetHeartrate = newHeartrate; HeartrateProvider.OnConnected += () => connectionCount = 0; HeartrateProvider.OnDisconnected += attemptReconnection; HeartrateProvider.OnLog += Log; HeartrateProvider.Initialise(); + + ChangeStateTo(HeartrateState.Default); } private void attemptReconnection() { + if (connectionCount >= reconnection_limit) + { + Log("Connection cannot be established"); + return; + } + Log("Attempting reconnection..."); - Thread.Sleep(2000); + Thread.Sleep(reconnection_delay); + HeartrateProvider?.Initialise(); + connectionCount++; } protected override void OnModuleStop() @@ -90,7 +91,7 @@ protected override void OnModuleStop() } [ModuleUpdate(ModuleUpdateMode.Custom)] - private void updateParameters() + private void updateCurrentHeartrate() { if (GetSetting(HeartrateSetting.Smoothed)) { @@ -100,11 +101,10 @@ private void updateParameters() { currentHeartrate = targetHeartrate; } - - sendParameters(); } - private void sendParameters() + [ModuleUpdate(ModuleUpdateMode.Custom)] + private void updateParameters() { var isReceiving = HeartrateProvider?.IsReceiving ?? false; @@ -122,24 +122,26 @@ private void sendParameters() SendParameter(HeartrateParameter.Units, individualValues[2] / 10f); SendParameter(HeartrateParameter.Tens, individualValues[1] / 10f); SendParameter(HeartrateParameter.Hundreds, individualValues[0] / 10f); - SetVariableValue(HeartrateVariable.Heartrate, intHeartrate.ToString()); } else { - SendParameter(HeartrateParameter.Normalised, 0); + SendParameter(HeartrateParameter.Normalised, 0f); SendParameter(HeartrateParameter.Value, 0); - SendParameter(HeartrateParameter.Units, 0); - SendParameter(HeartrateParameter.Tens, 0); - SendParameter(HeartrateParameter.Hundreds, 0); - SetVariableValue(HeartrateVariable.Heartrate, @"0"); + SendParameter(HeartrateParameter.Units, 0f); + SendParameter(HeartrateParameter.Tens, 0f); + SendParameter(HeartrateParameter.Hundreds, 0f); } } - private static int[] toDigitArray(int num, int totalWidth) + [ModuleUpdate(ModuleUpdateMode.ChatBox)] + private void updateVariables() { - return num.ToString().PadLeft(totalWidth, '0').Select(digit => int.Parse(digit.ToString())).ToArray(); + var isReceiving = HeartrateProvider?.IsReceiving ?? false; + SetVariableValue(HeartrateVariable.Heartrate, isReceiving ? currentHeartrate.ToString("##0") : "0"); } + private static int[] toDigitArray(int num, int totalWidth) => num.ToString().PadLeft(totalWidth, '0').Select(digit => int.Parse(digit.ToString())).ToArray(); + private enum HeartrateSetting { Smoothed, From ab0d91ab853ffa27fe7a2e144f0af69725625649 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 13 Aug 2023 03:59:40 +0100 Subject: [PATCH 17/19] Ensure Teardown is called before reinitialising --- VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs b/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs index c5fa0025..7fc84362 100644 --- a/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs +++ b/VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs @@ -13,7 +13,7 @@ namespace VRCOSC.Game.Modules.Bases.Heartrate; [ModulePrefab("VRCOSC-Heartrate", "https://github.com/VolcanicArts/VRCOSC/releases/download/latest/VRCOSC-Heartrate.unitypackage")] public abstract class HeartrateModule : ChatBoxModule where T : HeartrateProvider { - private const int reconnection_delay = 500; + private const int reconnection_delay = 2000; private const int reconnection_limit = 3; protected T? HeartrateProvider; @@ -70,6 +70,7 @@ private void attemptReconnection() Log("Attempting reconnection..."); Thread.Sleep(reconnection_delay); + HeartrateProvider?.Teardown(); HeartrateProvider?.Initialise(); connectionCount++; } From 5ad07e6b5d685a086b59a69f4ce3fc0eccf712e8 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Sun, 13 Aug 2023 12:57:20 +0100 Subject: [PATCH 18/19] Remove passed reference --- .../Serialisation/V1/TimelineSerialiser.cs | 20 +++++++++---------- .../ModulePersistenceSerialiser.cs | 6 +++--- .../Serialisation/V1/ModuleSerialiser.cs | 8 ++++---- .../Serialisation/V1/RouterSerialiser.cs | 4 ++-- VRCOSC.Game/Serialisation/Serialiser.cs | 7 +++++-- .../Serialisation/V1/StartupSerialiserV1.cs | 4 ++-- .../Serialisation/V2/StartupSerialiserV2.cs | 4 ++-- 7 files changed, 28 insertions(+), 25 deletions(-) diff --git a/VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs b/VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs index 61d1054c..4f1ce2f7 100644 --- a/VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs +++ b/VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs @@ -22,7 +22,7 @@ public TimelineSerialiser(Storage storage, AppManager appManager) { } - protected override bool ExecuteAfterDeserialisation(AppManager appManager, SerialisableTimeline data) + protected override bool ExecuteAfterDeserialisation(SerialisableTimeline data) { var createdClips = new List(); @@ -30,7 +30,7 @@ protected override bool ExecuteAfterDeserialisation(AppManager appManager, Seria data.Clips.ForEach(clip => { - var newClip = appManager.ChatBoxManager.CreateClip(); + var newClip = Reference.ChatBoxManager.CreateClip(); newClip.Enabled.Value = clip.Enabled; newClip.Name.Value = clip.Name; @@ -38,7 +38,7 @@ protected override bool ExecuteAfterDeserialisation(AppManager appManager, Seria newClip.Start.Value = clip.Start; newClip.End.Value = clip.End; - var migrationList = appManager.ModuleManager.GetMigrations(); + var migrationList = Reference.ModuleManager.GetMigrations(); migrationList.ForEach(migration => { @@ -70,11 +70,11 @@ protected override bool ExecuteAfterDeserialisation(AppManager appManager, Seria migrationOccurred = true; }); - newClip.AssociatedModules.AddRange(clip.AssociatedModules.Where(serialisedModuleName => appManager.ModuleManager.DoesModuleExist(serialisedModuleName))); + newClip.AssociatedModules.AddRange(clip.AssociatedModules.Where(serialisedModuleName => Reference.ModuleManager.DoesModuleExist(serialisedModuleName))); - clip.States.Where(clipState => clipState.States.All(pair => appManager.ModuleManager.DoesModuleExist(pair.Module))).ForEach(clipState => + clip.States.Where(clipState => clipState.States.All(pair => Reference.ModuleManager.DoesModuleExist(pair.Module))).ForEach(clipState => { - if (clipState.States.All(pair => appManager.ModuleManager.IsModuleLoaded(pair.Module))) + if (clipState.States.All(pair => Reference.ModuleManager.IsModuleLoaded(pair.Module))) { var stateData = newClip.GetStateFor(clipState.States.Select(state => state.Module), clipState.States.Select(state => state.Lookup)); if (stateData is null) return; @@ -94,9 +94,9 @@ protected override bool ExecuteAfterDeserialisation(AppManager appManager, Seria } }); - clip.Events.Where(clipEvent => appManager.ModuleManager.DoesModuleExist(clipEvent.Module)).ForEach(clipEvent => + clip.Events.Where(clipEvent => Reference.ModuleManager.DoesModuleExist(clipEvent.Module)).ForEach(clipEvent => { - if (appManager.ModuleManager.IsModuleLoaded(clipEvent.Module)) + if (Reference.ModuleManager.IsModuleLoaded(clipEvent.Module)) { var eventData = newClip.GetEventFor(clipEvent.Module, clipEvent.Lookup); if (eventData is null) return; @@ -122,8 +122,8 @@ protected override bool ExecuteAfterDeserialisation(AppManager appManager, Seria createdClips.Add(newClip); }); - appManager.ChatBoxManager.Clips.ReplaceItems(createdClips); - appManager.ChatBoxManager.SetTimelineLength(TimeSpan.FromTicks(data.Ticks)); + Reference.ChatBoxManager.Clips.ReplaceItems(createdClips); + Reference.ChatBoxManager.SetTimelineLength(TimeSpan.FromTicks(data.Ticks)); return migrationOccurred; } diff --git a/VRCOSC.Game/Modules/Persistence/ModulePersistenceSerialiser.cs b/VRCOSC.Game/Modules/Persistence/ModulePersistenceSerialiser.cs index 24d6146f..5b9274a5 100644 --- a/VRCOSC.Game/Modules/Persistence/ModulePersistenceSerialiser.cs +++ b/VRCOSC.Game/Modules/Persistence/ModulePersistenceSerialiser.cs @@ -18,15 +18,15 @@ public ModulePersistenceSerialiser(Storage storage, Module reference) { } - protected override bool ExecuteAfterDeserialisation(Module reference, SerialisableModulePersistence data) + protected override bool ExecuteAfterDeserialisation(SerialisableModulePersistence data) { data.Properties.ForEach(propertyData => { - if (!reference.TryGetPersistentProperty(propertyData.Key, out var propertyInfo)) return; + if (!Reference.TryGetPersistentProperty(propertyData.Key, out var propertyInfo)) return; var targetType = propertyInfo.PropertyType; var propertyValue = JsonConvert.DeserializeObject(propertyData.Value.ToString(), targetType); - propertyInfo.SetValue(reference, propertyValue); + propertyInfo.SetValue(Reference, propertyValue); }); return false; diff --git a/VRCOSC.Game/Modules/Serialisation/V1/ModuleSerialiser.cs b/VRCOSC.Game/Modules/Serialisation/V1/ModuleSerialiser.cs index 601cfeb8..5ebbc59c 100644 --- a/VRCOSC.Game/Modules/Serialisation/V1/ModuleSerialiser.cs +++ b/VRCOSC.Game/Modules/Serialisation/V1/ModuleSerialiser.cs @@ -19,22 +19,22 @@ public ModuleSerialiser(Storage storage, Module reference) { } - protected override bool ExecuteAfterDeserialisation(Module module, SerialisableModule data) + protected override bool ExecuteAfterDeserialisation(SerialisableModule data) { - module.Enabled.Value = data.Enabled; + Reference.Enabled.Value = data.Enabled; data.Settings.ForEach(settingPair => { var (settingKey, settingValue) = settingPair; - if (module.TryGetSetting(settingKey, out var setting)) setting.DeserialiseValue(settingValue); + if (Reference.TryGetSetting(settingKey, out var setting)) setting.DeserialiseValue(settingValue); }); data.Parameters.ForEach(parameterPair => { var (parameterKey, parameterValue) = parameterPair; - if (module.TryGetParameter(parameterKey, out var parameter)) parameter.DeserialiseValue(parameterValue); + if (Reference.TryGetParameter(parameterKey, out var parameter)) parameter.DeserialiseValue(parameterValue); }); return false; diff --git a/VRCOSC.Game/Router/Serialisation/V1/RouterSerialiser.cs b/VRCOSC.Game/Router/Serialisation/V1/RouterSerialiser.cs index 7688d087..04883b7c 100644 --- a/VRCOSC.Game/Router/Serialisation/V1/RouterSerialiser.cs +++ b/VRCOSC.Game/Router/Serialisation/V1/RouterSerialiser.cs @@ -19,9 +19,9 @@ public RouterSerialiser(Storage storage, RouterManager routerManager) { } - protected override bool ExecuteAfterDeserialisation(RouterManager routerManager, SerialisableRouterManager data) + protected override bool ExecuteAfterDeserialisation(SerialisableRouterManager data) { - routerManager.Store.ReplaceItems(data.Data.Select(routerData => new RouterData + Reference.Store.ReplaceItems(data.Data.Select(routerData => new RouterData { Label = { Value = routerData.Label }, Endpoints = new OSCRouterEndpoints diff --git a/VRCOSC.Game/Serialisation/Serialiser.cs b/VRCOSC.Game/Serialisation/Serialiser.cs index 5a31b0d3..6cc934c9 100644 --- a/VRCOSC.Game/Serialisation/Serialiser.cs +++ b/VRCOSC.Game/Serialisation/Serialiser.cs @@ -17,6 +17,9 @@ public abstract class Serialiser : ISerialiser where private readonly object serialisationLock = new(); private Storage storage; + /// + /// The instance of passed in the constructor + /// protected readonly TReference Reference; protected virtual string Directory => string.Empty; @@ -87,7 +90,7 @@ public bool Deserialise(string filePathOverride = "") legacyMigrationOccurred = true; } - var reserialise = ExecuteAfterDeserialisation(Reference, data); + var reserialise = ExecuteAfterDeserialisation(data); if (legacyMigrationOccurred || reserialise) Serialise(); @@ -145,5 +148,5 @@ private void performSerialisation() stream.Write(bytes); } - protected abstract bool ExecuteAfterDeserialisation(TReference reference, TSerialisable data); + protected abstract bool ExecuteAfterDeserialisation(TSerialisable data); } diff --git a/VRCOSC.Game/Startup/Serialisation/V1/StartupSerialiserV1.cs b/VRCOSC.Game/Startup/Serialisation/V1/StartupSerialiserV1.cs index d93a9f18..17453e8e 100644 --- a/VRCOSC.Game/Startup/Serialisation/V1/StartupSerialiserV1.cs +++ b/VRCOSC.Game/Startup/Serialisation/V1/StartupSerialiserV1.cs @@ -18,9 +18,9 @@ public StartupSerialiserV1(Storage storage, StartupManager startupManager) { } - protected override bool ExecuteAfterDeserialisation(StartupManager startupManager, SerialisableStartupManagerV1 data) + protected override bool ExecuteAfterDeserialisation(SerialisableStartupManagerV1 data) { - startupManager.Instances.ReplaceItems(data.FilePaths.Select(filepath => new StartupInstance + Reference.Instances.ReplaceItems(data.FilePaths.Select(filepath => new StartupInstance { FilePath = { Value = filepath } })); diff --git a/VRCOSC.Game/Startup/Serialisation/V2/StartupSerialiserV2.cs b/VRCOSC.Game/Startup/Serialisation/V2/StartupSerialiserV2.cs index 5326f867..342f35af 100644 --- a/VRCOSC.Game/Startup/Serialisation/V2/StartupSerialiserV2.cs +++ b/VRCOSC.Game/Startup/Serialisation/V2/StartupSerialiserV2.cs @@ -18,9 +18,9 @@ public StartupSerialiserV2(Storage storage, StartupManager startupManager) { } - protected override bool ExecuteAfterDeserialisation(StartupManager startupManager, SerialisableStartupManagerV2 data) + protected override bool ExecuteAfterDeserialisation(SerialisableStartupManagerV2 data) { - startupManager.Instances.ReplaceItems(data.Instances.Select(instance => new StartupInstance + Reference.Instances.ReplaceItems(data.Instances.Select(instance => new StartupInstance { FilePath = { Value = instance.FilePath }, LaunchArguments = { Value = instance.LaunchArguments } From ecc97090253da8787f3026e081545561519264e7 Mon Sep 17 00:00:00 2001 From: VolcanicArts Date: Mon, 14 Aug 2023 13:51:40 +0100 Subject: [PATCH 19/19] Version bump --- VRCOSC.Desktop/VRCOSC.Desktop.csproj | 4 ++-- VRCOSC.Game/VRCOSC.Game.csproj | 2 +- VRCOSC.Templates/VRCOSC.Templates.csproj | 2 +- .../template-default/TemplateModule/TemplateModule.csproj | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VRCOSC.Desktop/VRCOSC.Desktop.csproj b/VRCOSC.Desktop/VRCOSC.Desktop.csproj index 78a853aa..5297b891 100644 --- a/VRCOSC.Desktop/VRCOSC.Desktop.csproj +++ b/VRCOSC.Desktop/VRCOSC.Desktop.csproj @@ -6,12 +6,12 @@ game.ico app.manifest 0.0.0 - 2023.805.0 + 2023.814.0 VRCOSC VolcanicArts VolcanicArts enable - 2023.805.0 + 2023.814.0 diff --git a/VRCOSC.Game/VRCOSC.Game.csproj b/VRCOSC.Game/VRCOSC.Game.csproj index 6015cf79..f1d58690 100644 --- a/VRCOSC.Game/VRCOSC.Game.csproj +++ b/VRCOSC.Game/VRCOSC.Game.csproj @@ -4,7 +4,7 @@ enable 11 VolcanicArts.VRCOSC.SDK - 2023.805.0 + 2023.814.0 VRCOSC SDK VolcanicArts SDK for creating custom modules with VRCOSC diff --git a/VRCOSC.Templates/VRCOSC.Templates.csproj b/VRCOSC.Templates/VRCOSC.Templates.csproj index 29873dd2..20160758 100644 --- a/VRCOSC.Templates/VRCOSC.Templates.csproj +++ b/VRCOSC.Templates/VRCOSC.Templates.csproj @@ -11,7 +11,7 @@ true NU5128 - 2023.805.0 + 2023.814.0 VolcanicArts https://github.com/VolcanicArts/VRCOSC https://github.com/VolcanicArts/VRCOSC diff --git a/VRCOSC.Templates/templates/template-default/TemplateModule/TemplateModule.csproj b/VRCOSC.Templates/templates/template-default/TemplateModule/TemplateModule.csproj index 8c66e5fe..c577ee14 100644 --- a/VRCOSC.Templates/templates/template-default/TemplateModule/TemplateModule.csproj +++ b/VRCOSC.Templates/templates/template-default/TemplateModule/TemplateModule.csproj @@ -7,7 +7,7 @@ - +