From 617d2e161d29bb57ed1ca82b0c2c0d05cb28c505 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Thu, 16 Oct 2025 13:19:40 +1100 Subject: [PATCH] Fix VRCX.json being slow to save --- Dotnet/Cef/MainForm.cs | 2 +- Dotnet/VRCXStorage.cs | 124 +++++++++------------------------ Dotnet/WebApi.cs | 2 +- src/service/jsonStorage.js | 9 --- src/stores/settings/general.js | 4 +- src/stores/vrcx.js | 3 +- 6 files changed, 37 insertions(+), 107 deletions(-) diff --git a/Dotnet/Cef/MainForm.cs b/Dotnet/Cef/MainForm.cs index d1ed4e49..57568b57 100644 --- a/Dotnet/Cef/MainForm.cs +++ b/Dotnet/Cef/MainForm.cs @@ -204,7 +204,7 @@ namespace VRCX VRCXStorage.Instance.Set("VRCX_SizeWidth", LastSizeWidth.ToString()); VRCXStorage.Instance.Set("VRCX_SizeHeight", LastSizeHeight.ToString()); VRCXStorage.Instance.Set("VRCX_WindowState", ((int)LastWindowStateToRestore).ToString()); - VRCXStorage.Instance.Flush(); + VRCXStorage.Instance.Save(); } private void MainForm_FormClosed(object sender, FormClosedEventArgs e) diff --git a/Dotnet/VRCXStorage.cs b/Dotnet/VRCXStorage.cs index 2b3a2973..cafd84ed 100644 --- a/Dotnet/VRCXStorage.cs +++ b/Dotnet/VRCXStorage.cs @@ -1,10 +1,5 @@ -// Copyright(c) 2019-2025 pypy, Natsumi and individual contributors. -// All rights reserved. -// -// This work is licensed under the terms of the MIT license. -// For a copy, see . - using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Text.Json; @@ -15,127 +10,72 @@ namespace VRCX public class VRCXStorage { public static readonly VRCXStorage Instance; - private static readonly ReaderWriterLockSlim m_Lock = new ReaderWriterLockSlim(); - private static Dictionary m_Storage = new Dictionary(); - private static readonly string m_JsonPath = Path.Join(Program.AppDataDirectory, "VRCX.json"); - private static bool m_Dirty; + + private static ConcurrentDictionary _storage = new ConcurrentDictionary(); + private static readonly string JsonPath = Path.Join(Program.AppDataDirectory, "VRCX.json"); + + private static readonly TimeSpan SaveDebounce = TimeSpan.FromMilliseconds(500); + private static readonly Timer SaveTimer; + private static readonly Lock SaveLock = new Lock(); static VRCXStorage() { Instance = new VRCXStorage(); + SaveTimer = new Timer(_ => Instance.Save(), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); } public void Load() { - m_Lock.EnterWriteLock(); - try - { - JsonFileSerializer.Deserialize(m_JsonPath, ref m_Storage); - m_Dirty = false; - } - finally - { - m_Lock.ExitWriteLock(); - } + var tmp = new Dictionary(); + JsonFileSerializer.Deserialize(JsonPath, ref tmp); + _storage = new ConcurrentDictionary(tmp); } public void Save() { - m_Lock.EnterReadLock(); - try + lock (SaveLock) { - if (m_Dirty) - { - JsonFileSerializer.Serialize(m_JsonPath, m_Storage); - m_Dirty = false; - } + var snapshot = new Dictionary(_storage); + JsonFileSerializer.Serialize(JsonPath, snapshot); } - finally - { - m_Lock.ExitReadLock(); - } - } - - public void Flush() - { - Save(); } public void Clear() { - m_Lock.EnterWriteLock(); - try + if (!_storage.IsEmpty) { - if (m_Storage.Count > 0) - { - m_Storage.Clear(); - m_Dirty = true; - } - } - finally - { - m_Lock.ExitWriteLock(); + _storage.Clear(); + ScheduleSave(); } } public bool Remove(string key) { - m_Lock.EnterWriteLock(); - try - { - var result = m_Storage.Remove(key); - if (result) - { - m_Dirty = true; - } - return result; - } - finally - { - m_Lock.ExitWriteLock(); - } + var result = _storage.TryRemove(key, out _); + if (result) + ScheduleSave(); + return result; } public string Get(string key) { - m_Lock.EnterReadLock(); - try - { - return m_Storage.TryGetValue(key, out string value) - ? value - : string.Empty; - } - finally - { - m_Lock.ExitReadLock(); - } + return _storage.TryGetValue(key, out var value) ? value : string.Empty; } public void Set(string key, string value) { - m_Lock.EnterWriteLock(); - try - { - m_Storage[key] = value; - m_Dirty = true; - } - finally - { - m_Lock.ExitWriteLock(); - } + _storage[key] = value; + ScheduleSave(); } public string GetAll() { - m_Lock.EnterReadLock(); - try - { - return JsonSerializer.Serialize(m_Storage); - } - finally - { - m_Lock.ExitReadLock(); - } + return JsonSerializer.Serialize(new Dictionary(_storage)); + } + + private static void ScheduleSave() + { + SaveTimer.Change(SaveDebounce, Timeout.InfiniteTimeSpan); } } -} +} \ No newline at end of file diff --git a/Dotnet/WebApi.cs b/Dotnet/WebApi.cs index c8461bca..c5ac2c52 100644 --- a/Dotnet/WebApi.cs +++ b/Dotnet/WebApi.cs @@ -94,7 +94,7 @@ namespace VRCX catch (UriFormatException) { VRCXStorage.Instance.Set("VRCX_ProxyServer", string.Empty); - VRCXStorage.Instance.Flush(); + VRCXStorage.Instance.Save(); const string message = "The proxy server URI you used is invalid.\nVRCX will close, please correct the proxy URI."; #if !LINUX System.Windows.Forms.MessageBox.Show(message, "Invalid Proxy URI", MessageBoxButtons.OK, MessageBoxIcon.Error); diff --git a/src/service/jsonStorage.js b/src/service/jsonStorage.js index 264ff9d6..0851d3fd 100644 --- a/src/service/jsonStorage.js +++ b/src/service/jsonStorage.js @@ -1,5 +1,3 @@ -import * as workerTimers from 'worker-timers'; - let VRCXStorage = {}; export default class { @@ -40,12 +38,5 @@ export default class { VRCXStorage.SetObject = function (key, value) { this.Set(key, JSON.stringify(value)); }; - - workerTimers.setInterval( - () => { - VRCXStorage.Flush(); - }, - 5 * 60 * 1000 - ); } } diff --git a/src/stores/settings/general.js b/src/stores/settings/general.js index b262a811..b28d42ad 100644 --- a/src/stores/settings/general.js +++ b/src/stores/settings/general.js @@ -248,7 +248,7 @@ export const useGeneralSettingsStore = defineStore('GeneralSettings', () => { 'VRCX_ProxyServer', vrcxStore.proxyServer ); - await VRCXStorage.Flush(); + await VRCXStorage.Save(); await new Promise((resolve) => { workerTimers.setTimeout(resolve, 100); }); @@ -263,7 +263,7 @@ export const useGeneralSettingsStore = defineStore('GeneralSettings', () => { 'VRCX_ProxyServer', vrcxStore.proxyServer ); - await VRCXStorage.Flush(); + await VRCXStorage.Save(); await new Promise((resolve) => { workerTimers.setTimeout(resolve, 100); }); diff --git a/src/stores/vrcx.js b/src/stores/vrcx.js index 9e022d46..9f5eafac 100644 --- a/src/stores/vrcx.js +++ b/src/stores/vrcx.js @@ -90,7 +90,7 @@ export const useVrcxStore = defineStore('Vrcx', () => { }); window.electron.onWindowStateChange((event, newState) => { - state.windowState = newState.windowState; + state.windowState = newState.toString(); debounce(saveVRCXWindowOption, 300)(); }); @@ -364,7 +364,6 @@ export const useVrcxStore = defineStore('Vrcx', () => { VRCXStorage.Set('VRCX_SizeWidth', state.sizeWidth.toString()); VRCXStorage.Set('VRCX_SizeHeight', state.sizeHeight.toString()); VRCXStorage.Set('VRCX_WindowState', state.windowState); - VRCXStorage.Flush(); } }