diff --git a/Browser.cs b/Browser.cs index 97a8bcc6..6c9ef386 100644 --- a/Browser.cs +++ b/Browser.cs @@ -6,13 +6,14 @@ using CefSharp; using CefSharp.OffScreen; using SharpDX.Direct3D11; -using System; using System.Drawing; +using System.Threading; namespace VRCX { public class Browser : ChromiumWebBrowser { + private readonly ReaderWriterLockSlim m_Lock = new ReaderWriterLockSlim(); private Texture2D m_Texture; public Browser(Texture2D texture, string address) @@ -24,38 +25,46 @@ namespace VRCX m_Texture = texture; Size = new Size(texture.Description.Width, texture.Description.Height); RenderHandler.Dispose(); - RenderHandler = new RenderHandler(this); + RenderHandler = new RenderHandler(this, m_Lock); var options = new BindingOptions() { CamelCaseJavascriptNames = false }; JavascriptObjectRepository.Register("VRCX", new VRCX(), true, options); JavascriptObjectRepository.Register("VRCXStorage", new VRCXStorage(), false, options); + JavascriptObjectRepository.Register("SQLite", new SQLite(), true, options); + } + + public new void Dispose() + { + RenderHandler.Dispose(); + RenderHandler = null; + base.Dispose(); } public void Render() { - var handler = (RenderHandler)RenderHandler; - lock (handler.BufferLock) + m_Lock.EnterReadLock(); + try { - if (handler.Buffer.IsAllocated) + var H = (RenderHandler)RenderHandler; + if (H.Buffer.IsAllocated) { var context = m_Texture.Device.ImmediateContext; var box = context.MapSubresource(m_Texture, 0, MapMode.WriteDiscard, MapFlags.None); - if (box.DataPointer != IntPtr.Zero) + if (box.IsEmpty) { - var width = handler.Width; - var height = handler.Height; - if (box.RowPitch == width * 4) + if (box.RowPitch == H.Width * 4) { - WinApi.CopyMemory(box.DataPointer, handler.Buffer.AddrOfPinnedObject(), (uint)handler.BufferSize); + WinApi.CopyMemory(box.DataPointer, H.Buffer.AddrOfPinnedObject(), (uint)H.BufferSize); } else { var dest = box.DataPointer; - var src = handler.Buffer.AddrOfPinnedObject(); + var src = H.Buffer.AddrOfPinnedObject(); var pitch = box.RowPitch; - var length = width * 4; + var length = H.Width * 4; + var height = H.Height; for (var i = 0; i < height; ++i) { WinApi.CopyMemory(dest, src, (uint)length); @@ -67,6 +76,10 @@ namespace VRCX context.UnmapSubresource(m_Texture, 0); } } + finally + { + m_Lock.ExitReadLock(); + } } } } \ No newline at end of file diff --git a/CpuMonitor.cs b/CpuMonitor.cs index 8c5ec9fe..658393c1 100644 --- a/CpuMonitor.cs +++ b/CpuMonitor.cs @@ -13,36 +13,32 @@ namespace VRCX public static float CpuUsage { get; private set; } private static Thread m_Thread; - public static void Start() + public static void Init() { - if (m_Thread == null) + m_Thread = new Thread(() => { - m_Thread = new Thread(() => + PerformanceCounter counter = null; + try { - PerformanceCounter cpuCounter = null; - try - { - cpuCounter = new PerformanceCounter("Processor Information", "% Processor Utility", "_Total", true); - } - catch - { - } - try - { - if (cpuCounter == null) - { - cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", true); - } - } - catch + counter = new PerformanceCounter("Processor Information", "% Processor Utility", "_Total", true); + } + catch + { + } + try + { + if (counter == null) { + counter = new PerformanceCounter("Processor", "% Processor Time", "_Total", true); } + } + catch + { + } + if (counter != null) + { while (m_Thread != null) { - if (cpuCounter != null) - { - CpuUsage = cpuCounter.NextValue(); - } try { Thread.Sleep(1000); @@ -51,31 +47,23 @@ namespace VRCX { // ThreadInterruptedException } + CpuUsage = counter.NextValue(); } - if (cpuCounter != null) - { - cpuCounter.Dispose(); - } - }); - m_Thread.Start(); - } + counter.Dispose(); + } + }) + { + IsBackground = true + }; + m_Thread.Start(); } - public static void Stop() + public static void Exit() { - var thread = m_Thread; - if (thread != null) - { - m_Thread = null; - try - { - thread.Interrupt(); - thread.Join(); - } - catch - { - } - } + var T = m_Thread; + m_Thread = null; + T.Interrupt(); + T.Join(); } } } \ No newline at end of file diff --git a/Discord.cs b/Discord.cs index f963aaf5..09475805 100644 --- a/Discord.cs +++ b/Discord.cs @@ -4,38 +4,80 @@ // For a copy, see . using DiscordRPC; +using System.Threading; namespace VRCX { public class Discord { - private static RichPresence m_Presence = new RichPresence(); - private static DiscordRpcClient m_Client; + private static readonly ReaderWriterLockSlim m_Lock = new ReaderWriterLockSlim(); + private static readonly RichPresence m_Presence = new RichPresence(); + private static Thread m_Thread; private static bool m_Active; - public static void Update() + public static void Init() { - if (m_Client != null) + m_Thread = new Thread(() => { - m_Client.Invoke(); - } - if (m_Active) - { - if (m_Client == null) + DiscordRpcClient client = null; + while (m_Thread != null) { - m_Client = new DiscordRpcClient("525953831020920832"); - m_Client.Initialize(); + try + { + Thread.Sleep(1000); + } + catch + { + // ThreadInterruptedException + } + if (client != null) + { + m_Lock.EnterReadLock(); + try + { + client.SetPresence(m_Presence); + } + finally + { + m_Lock.ExitReadLock(); + } + client.Invoke(); + } + if (m_Active) + { + if (client == null) + { + client = new DiscordRpcClient("525953831020920832"); + if (!client.Initialize()) + { + client.Dispose(); + client = null; + } + } + } + else if (client != null) + { + client.Dispose(); + client = null; + } } - lock (m_Presence) + if (client != null) { - m_Client.SetPresence(m_Presence); + client.Dispose(); } - } - else if (m_Client != null) + }) { - m_Client.Dispose(); - m_Client = null; - } + IsBackground = true + }; + m_Thread.Start(); + } + + public static void Exit() + { + var T = m_Thread; + m_Thread = null; + T.Interrupt(); + T.Join(); } public void SetActive(bool active) @@ -45,16 +87,22 @@ namespace VRCX public void SetText(string details, string state) { - lock (m_Presence) + m_Lock.EnterWriteLock(); + try { m_Presence.Details = details; m_Presence.State = state; } + finally + { + m_Lock.ExitWriteLock(); + } } public void SetAssets(string largeKey, string largeText, string smallKey, string smallText) { - lock (m_Presence) + m_Lock.EnterWriteLock(); + try { if (string.IsNullOrEmpty(largeKey) && string.IsNullOrEmpty(smallKey)) @@ -73,6 +121,10 @@ namespace VRCX m_Presence.Assets.SmallImageText = smallText; } } + finally + { + m_Lock.ExitWriteLock(); + } } // JSB Sucks @@ -83,7 +135,8 @@ namespace VRCX public static void SetTimestamps(ulong startUnixMilliseconds, ulong endUnixMilliseconds) { - lock (m_Presence) + m_Lock.EnterWriteLock(); + try { if (startUnixMilliseconds == 0) { @@ -106,6 +159,10 @@ namespace VRCX } } } + finally + { + m_Lock.ExitWriteLock(); + } } } } \ No newline at end of file diff --git a/LogWatcher.cs b/LogWatcher.cs index c6ee91fe..332390f3 100644 --- a/LogWatcher.cs +++ b/LogWatcher.cs @@ -5,176 +5,194 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; using System.Threading; namespace VRCX { - public class VRCX_LogWatcher + public class LogWatcherFile { + public long Length; + public long Position; + } + + public class LogWatcher + { + private static readonly ReaderWriterLockSlim m_Lock = new ReaderWriterLockSlim(); private static List m_GameLog = new List(); private static Thread m_Thread; private static bool m_Reset; - public static void Start() + // NOTE + // FileSystemWatcher() is unreliable + + public static void Init() { - if (m_Thread == null) + m_Thread = new Thread(() => { - m_Thread = new Thread(() => + var D = new Dictionary(); + var di = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat"); + while (m_Thread != null) { - var lastPosition = 0L; - var firstLine = string.Empty; - while (m_Thread != null) + try { - if (m_Reset) + Thread.Sleep(1000); + } + catch + { + // ThreadInterruptedException + } + if (m_Reset) + { + m_Reset = false; + D.Clear(); + m_Lock.EnterWriteLock(); + try { - m_Reset = false; - firstLine = string.Empty; - lastPosition = 0; + m_GameLog.Clear(); } - var info = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat"); - if (info != null && - info.Exists) + finally { - var files = info.GetFiles("output_log_*.txt", SearchOption.TopDirectoryOnly); - if (files != null && - files.Length >= 1) + m_Lock.ExitWriteLock(); + } + } + var S = new HashSet(D.Keys); + di.Refresh(); + if (di.Exists) + { + var files = di.GetFiles("output_log_*.txt", SearchOption.TopDirectoryOnly); + Array.Sort(files, (A, B) => A.CreationTime.CompareTo(B.CreationTime)); + var bias = DateTime.Now.AddMinutes(-5d); + foreach (var fi in files) + { + if (bias.CompareTo(fi.LastWriteTime) <= 0) { - Array.Sort(files, (A, B) => B.LastWriteTime.CompareTo(A.LastWriteTime)); - if (firstLine == string.Empty) + fi.Refresh(); + } + if (D.TryGetValue(fi.Name, out LogWatcherFile F)) + { + S.Remove(fi.Name); + if (F.Length == fi.Length) { - for (var i = files.Length - 1; i >= 1; --i) + continue; + } + } + else + { + F = new LogWatcherFile(); + D.Add(fi.Name, F); + } + F.Length = fi.Length; + Parse(fi, ref F.Position); + } + } + foreach (var key in S) + { + D.Remove(key); + } + } + }) + { + IsBackground = true + }; + m_Thread.Start(); + } + + public static void Exit() + { + var T = m_Thread; + m_Thread = null; + T.Interrupt(); + T.Join(); + } + + public static void Parse(FileInfo info, ref long position) + { + try + { + using (var stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var reader = new StreamReader(stream, Encoding.UTF8)) + { + stream.Position = position; + var s = string.Empty; + while ((s = reader.ReadLine()) != null) + { + if (s.Length > 35) + { + var c = s[35]; + if (c == 'R') + { + // 2019.07.31 22:26:24 Log - [RoomManager] Joining wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd:6974~private(usr_4f76a584-9d4b-46f6-8209-8305eb683661)~nonce(0000000000000000000000000000000000000000000000000000000000000000) + // 2019.07.31 22:26:24 Log - [RoomManager] Joining or Creating Room: VRChat Home + if (s.Length > 56 && + string.Compare(s, 34, "[RoomManager] Joining ", 0, "[RoomManager] Joining ".Length, StringComparison.Ordinal) == 0 && + string.Compare(s, 56, "or ", 0, "or ".Length, StringComparison.Ordinal) != 0) + { + var item = new[] { - using (var stream = files[i].Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (var reader = new StreamReader(stream, Encoding.UTF8)) - { - var line = string.Empty; - while ((line = reader.ReadLine()) != null) - { - if (line.Length > 32 && - line[31] == '-') - { - ParseLine(line); - } - } - } + ConvertLogTimeToISO8601(s), + "Location", + s.Substring(56) + }; + m_Lock.EnterWriteLock(); + try + { + m_GameLog.Add(item); + } + finally + { + m_Lock.ExitWriteLock(); } } - using (var stream = files[0].Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (var reader = new StreamReader(stream, Encoding.UTF8)) + } + else if (c == 'N') + { + // 2019.07.31 22:41:18 Log - [NetworkManager] OnPlayerJoined pypy + if (s.Length > 66 && + string.Compare(s, 34, "[NetworkManager] OnPlayerJoined ", 0, "[NetworkManager] OnPlayerJoined ".Length, StringComparison.Ordinal) == 0) { - var line = reader.ReadLine(); - if (line != null) + var item = new[] { - if (string.Equals(firstLine, line)) - { - stream.Position = lastPosition; - } - else - { - firstLine = line; - } - do - { - lastPosition = stream.Position; - ParseLine(line); - } - while ((line = reader.ReadLine()) != null); + ConvertLogTimeToISO8601(s), + "OnPlayerJoined", + s.Substring(66) + }; + m_Lock.EnterWriteLock(); + try + { + m_GameLog.Add(item); + } + finally + { + m_Lock.ExitWriteLock(); + } + } + // 2019.07.31 22:29:31 Log - [NetworkManager] OnPlayerLeft pypy + else if (s.Length > 64 && + string.Compare(s, 34, "[NetworkManager] OnPlayerLeft ", 0, "[NetworkManager] OnPlayerLeft ".Length, StringComparison.Ordinal) == 0) + { + var item = new[] + { + ConvertLogTimeToISO8601(s), + "OnPlayerLeft", + s.Substring(64) + }; + m_Lock.EnterWriteLock(); + try + { + m_GameLog.Add(item); + } + finally + { + m_Lock.ExitWriteLock(); } } } } - try - { - Thread.Sleep(3000); - } - catch - { - // ThreadInterruptedException - } - } - }); - m_Thread.Start(); - } - } - - public static void Stop() - { - var thread = m_Thread; - if (thread != null) - { - m_Thread = null; - try - { - thread.Interrupt(); - thread.Join(); - } - catch - { - } - } - } - - private static string ConvertLogTimeToISO8601(string s) - { - // 2019.07.31 22:26:24 - var dt = new DateTime( - int.Parse(s.Substring(0, 4)), - int.Parse(s.Substring(5, 2)), - int.Parse(s.Substring(8, 2)), - int.Parse(s.Substring(11, 2)), - int.Parse(s.Substring(14, 2)), - int.Parse(s.Substring(17, 2)), - DateTimeKind.Local - ).ToUniversalTime(); - return $"{dt:yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'}"; - } - - private static void ParseLine(string line) - { - try - { - // 2019.07.31 22:26:24 Log - [RoomManager] Joining wrld_4432ea9b-729c-46e3-8eaf-846aa0a37fdd:6974~private(usr_4f76a584-9d4b-46f6-8209-8305eb683661)~nonce(0000000000000000000000000000000000000000000000000000000000000000) - // 2019.07.31 22:26:24 Log - [RoomManager] Joining or Creating Room: VRChat Home - if (string.Compare(line, 34, "[RoomManager] Joining ", 0, "[RoomManager] Joining ".Length) == 0 && - string.Compare(line, 56, "or ", 0, "or ".Length) != 0) - { - lock (m_GameLog) - { - m_GameLog.Add(new[] - { - ConvertLogTimeToISO8601(line), - "Location", - line.Substring(56) - }); - } - } - // 2019.07.31 22:41:18 Log - [NetworkManager] OnPlayerJoined pypy - else if (string.Compare(line, 34, "[NetworkManager] OnPlayerJoined ", 0, "[NetworkManager] OnPlayerJoined ".Length) == 0) - { - lock (m_GameLog) - { - m_GameLog.Add(new[] - { - ConvertLogTimeToISO8601(line), - "OnPlayerJoined", - line.Substring(66) - }); - } - } - // 2019.07.31 22:29:31 Log - [NetworkManager] OnPlayerLeft pypy - else if (string.Compare(line, 34, "[NetworkManager] OnPlayerLeft ", 0, "[NetworkManager] OnPlayerLeft ".Length) == 0) - { - lock (m_GameLog) - { - m_GameLog.Add(new[] - { - ConvertLogTimeToISO8601(line), - "OnPlayerLeft", - line.Substring(64) - }); } + position = stream.Position; } } catch @@ -182,31 +200,54 @@ namespace VRCX } } + private static string ConvertLogTimeToISO8601(string s) + { + // 2019.07.31 22:26:24 + if (!DateTime.TryParseExact(s.Substring(0, 19), + "yyyy.MM.dd HH:mm:ss", + CultureInfo.InvariantCulture, + DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal, + out DateTime dt)) + { + dt = DateTime.UtcNow; + } + return $"{dt:yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'}"; + } + public void Reset() { - lock (m_GameLog) + if (m_Thread != null) { m_Reset = true; - m_GameLog.Clear(); + m_Thread.Interrupt(); } - m_Thread.Interrupt(); } - public string[][] GetLogs() + public string[][] Get() { - lock (m_GameLog) + m_Lock.EnterUpgradeableReadLock(); + try { + if (m_Reset || + m_GameLog.Count == 0) + { + return new string[][] { }; + } var array = m_GameLog.ToArray(); - m_GameLog.Clear(); + m_Lock.EnterWriteLock(); + try + { + m_GameLog.Clear(); + } + finally + { + m_Lock.ExitWriteLock(); + } return array; } - } - - public bool HasLog() - { - lock (m_GameLog) + finally { - return m_GameLog.Count > 0; + m_Lock.ExitUpgradeableReadLock(); } } } diff --git a/MainForm.Designer.cs b/MainForm.Designer.cs index 54ad0648..0bb2b8a2 100644 --- a/MainForm.Designer.cs +++ b/MainForm.Designer.cs @@ -33,16 +33,8 @@ namespace VRCX /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - this.timer = new System.Windows.Forms.Timer(this.components); this.SuspendLayout(); // - // timer - // - this.timer.Enabled = true; - this.timer.Interval = 1000; - this.timer.Tick += new System.EventHandler(this.timer_Tick); - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); @@ -56,7 +48,5 @@ namespace VRCX } #endregion - - private System.Windows.Forms.Timer timer; } } \ No newline at end of file diff --git a/MainForm.cs b/MainForm.cs index cc1bf708..89af721d 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -43,7 +43,8 @@ namespace VRCX }; Browser.JavascriptObjectRepository.Register("VRCX", new VRCX(), true, options); Browser.JavascriptObjectRepository.Register("VRCXStorage", new VRCXStorage(), false, options); - Browser.JavascriptObjectRepository.Register("LogWatcher", new VRCX_LogWatcher(), true, options); + Browser.JavascriptObjectRepository.Register("SQLite", new SQLite(), true, options); + Browser.JavascriptObjectRepository.Register("LogWatcher", new LogWatcher(), true, options); Browser.JavascriptObjectRepository.Register("Discord", new Discord(), true, options); Browser.IsBrowserInitializedChanged += (A, B) => { @@ -51,10 +52,5 @@ namespace VRCX }; Controls.Add(Browser); } - - private void timer_Tick(object sender, System.EventArgs e) - { - Discord.Update(); - } } } \ No newline at end of file diff --git a/MainForm.resx b/MainForm.resx index e22c5ac6..1af7de15 100644 --- a/MainForm.resx +++ b/MainForm.resx @@ -117,7 +117,4 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 17, 17 - \ No newline at end of file diff --git a/Program.cs b/Program.cs index 58ea7ad6..e030a100 100644 --- a/Program.cs +++ b/Program.cs @@ -53,15 +53,19 @@ namespace VRCX { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - CpuMonitor.Start(); VRCXStorage.Load(); - VRCXVR.Setup(); - VRCX_LogWatcher.Start(); + SQLite.Init(); + CpuMonitor.Init(); + Discord.Init(); + LogWatcher.Init(); + VRCXVR.Init(); Application.Run(new MainForm()); - VRCX_LogWatcher.Stop(); - VRCXVR.Stop(); + VRCXVR.Exit(); + LogWatcher.Exit(); + Discord.Exit(); + CpuMonitor.Exit(); + SQLite.Exit(); VRCXStorage.Save(); - CpuMonitor.Stop(); Cef.Shutdown(); } } diff --git a/RenderHandler.cs b/RenderHandler.cs index 9051d063..d50eca05 100644 --- a/RenderHandler.cs +++ b/RenderHandler.cs @@ -5,6 +5,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using CefSharp; using CefSharp.Enums; using CefSharp.OffScreen; @@ -15,26 +16,32 @@ namespace VRCX public class RenderHandler : IRenderHandler { private ChromiumWebBrowser m_Browser; - public readonly object BufferLock = new object(); + private ReaderWriterLockSlim m_Lock; public int BufferSize { get; private set; } public GCHandle Buffer { get; private set; } public int Width { get; private set; } public int Height { get; private set; } - public RenderHandler(ChromiumWebBrowser browser) + public RenderHandler(ChromiumWebBrowser browser, ReaderWriterLockSlim @lock) { m_Browser = browser; + m_Lock = @lock; } public void Dispose() { - lock (BufferLock) + m_Lock.EnterWriteLock(); + try { if (Buffer.IsAllocated) { Buffer.Free(); } } + finally + { + m_Lock.ExitWriteLock(); + } m_Browser = null; } @@ -64,23 +71,36 @@ namespace VRCX { if (type == PaintElementType.View) { - lock (BufferLock) + m_Lock.EnterUpgradeableReadLock(); + try { if (!Buffer.IsAllocated || width != Width || height != Height) { - Width = width; - Height = height; - BufferSize = width * height * 4; - if (Buffer.IsAllocated) + m_Lock.EnterWriteLock(); + try { - Buffer.Free(); + Width = width; + Height = height; + BufferSize = width * height * 4; + if (Buffer.IsAllocated) + { + Buffer.Free(); + } + Buffer = GCHandle.Alloc(new byte[BufferSize], GCHandleType.Pinned); + } + finally + { + m_Lock.ExitWriteLock(); } - Buffer = GCHandle.Alloc(new byte[BufferSize], GCHandleType.Pinned); } WinApi.CopyMemory(Buffer.AddrOfPinnedObject(), buffer, (uint)BufferSize); } + finally + { + m_Lock.ExitUpgradeableReadLock(); + } } } diff --git a/SQLite.cs b/SQLite.cs new file mode 100644 index 00000000..229e5e15 --- /dev/null +++ b/SQLite.cs @@ -0,0 +1,91 @@ +using CefSharp; +using System.Collections.Generic; +using System.Data; +using System.Data.SQLite; +using System.Threading; +using System.Windows.Forms; + +namespace VRCX +{ + public class SQLite + { + private static readonly ReaderWriterLockSlim m_Lock = new ReaderWriterLockSlim(); + private static SQLiteConnection m_Connection; + + public static void Init() + { + m_Connection = new SQLiteConnection($"Data Source={Application.StartupPath}/VRCX.sqlite;Version=3"); + m_Connection.Open(); + } + + public static void Exit() + { + m_Connection.Close(); + m_Connection.Dispose(); + } + + public void Execute(string sql, IDictionary param = null) + { + m_Lock.EnterWriteLock(); + try + { + if (m_Connection.State != ConnectionState.Open) + { + m_Connection.Close(); + m_Connection.Open(); + } + using (var C = new SQLiteCommand(sql, m_Connection)) + { + if (param != null) + { + foreach (var prop in param) + { + C.Parameters.Add(new SQLiteParameter("@" + prop.Key, prop.Value)); + } + } + C.ExecuteNonQuery(); + } + } + finally + { + m_Lock.ExitWriteLock(); + } + } + + public void ExecuteQuery(IJavascriptCallback callback, string sql, IDictionary param = null) + { + m_Lock.EnterReadLock(); + try + { + if (m_Connection.State != ConnectionState.Open) + { + m_Connection.Close(); + m_Connection.Open(); + } + using (var C = new SQLiteCommand(sql, m_Connection)) + { + if (param != null) + { + foreach (var prop in param) + { + C.Parameters.Add(new SQLiteParameter("@" + prop.Key, prop.Value)); + } + } + using (var R = C.ExecuteReader()) + { + while (R.Read()) + { + var row = new object[R.FieldCount]; + R.GetValues(row); + callback.ExecuteAsync(row); + } + } + } + } + finally + { + m_Lock.ExitReadLock(); + } + } + } +} \ No newline at end of file diff --git a/VRCX.cs b/VRCX.cs index b4e84fef..b332073e 100644 --- a/VRCX.cs +++ b/VRCX.cs @@ -69,12 +69,12 @@ namespace VRCX public void StartVR() { - VRCXVR.Start(); + VRCXVR.SetActive(true); } public void StopVR() { - VRCXVR.Stop(); + VRCXVR.SetActive(false); } public void RefreshVR() diff --git a/VRCX.csproj b/VRCX.csproj index 4cf50a76..43a458cd 100644 --- a/VRCX.csproj +++ b/VRCX.csproj @@ -110,6 +110,9 @@ + + packages\System.Data.SQLite.Core.1.0.111.0\lib\net451\System.Data.SQLite.dll + @@ -125,6 +128,7 @@ + Form @@ -204,8 +208,10 @@ + + \ No newline at end of file diff --git a/VRCXStorage.cs b/VRCXStorage.cs index 456c5094..ca415356 100644 --- a/VRCXStorage.cs +++ b/VRCXStorage.cs @@ -4,71 +4,114 @@ // For a copy, see . using System.Collections.Generic; +using System.Threading; using System.Windows.Forms; namespace VRCX { public class VRCXStorage { + private static readonly ReaderWriterLockSlim m_Lock = new ReaderWriterLockSlim(); private static Dictionary m_Storage = new Dictionary(); private static bool m_Dirty; public static void Load() { - JsonSerializer.Deserialize(Application.StartupPath + "/VRCX.json", ref m_Storage); + m_Lock.EnterWriteLock(); + try + { + JsonSerializer.Deserialize(Application.StartupPath + "/VRCX.json", ref m_Storage); + m_Dirty = false; + } + finally + { + m_Lock.ExitWriteLock(); + } } public static void Save() { - JsonSerializer.Serialize(Application.StartupPath + "/VRCX.json", m_Storage); - } - - public void Clear() - { - lock (m_Storage) + m_Lock.EnterReadLock(); + try { - m_Dirty = true; - m_Storage.Clear(); + if (m_Dirty) + { + JsonSerializer.Serialize(Application.StartupPath + "/VRCX.json", m_Storage); + m_Dirty = false; + } + } + finally + { + m_Lock.ExitReadLock(); } } public void Flush() { - lock (m_Storage) + Save(); + } + + public void Clear() + { + m_Lock.EnterWriteLock(); + try { - if (m_Dirty) + if (m_Storage.Count > 0) { - m_Dirty = false; - Save(); + m_Dirty = true; } + m_Storage.Clear(); + } + finally + { + m_Lock.ExitWriteLock(); } } public bool Remove(string key) { - lock (m_Storage) + m_Lock.EnterWriteLock(); + try { - m_Dirty = true; - return m_Storage.Remove(key); + var result = m_Storage.Remove(key); + if (result) + { + m_Dirty = true; + } + return result; + } + finally + { + m_Lock.ExitWriteLock(); } } public string Get(string key) { - lock (m_Storage) + m_Lock.EnterReadLock(); + try { return m_Storage.TryGetValue(key, out string value) ? value : string.Empty; } + finally + { + m_Lock.ExitReadLock(); + } } public void Set(string key, string value) { - lock (m_Storage) + m_Lock.EnterWriteLock(); + try { - m_Dirty = true; m_Storage[key] = value; + m_Dirty = true; + } + finally + { + m_Lock.ExitWriteLock(); } } } diff --git a/VRCXVR.cs b/VRCXVR.cs index cf3e2e83..8d7b4363 100644 --- a/VRCXVR.cs +++ b/VRCXVR.cs @@ -21,7 +21,7 @@ namespace VRCX { public static class VRCXVR { - private static readonly object m_LockObject = new object(); + private static readonly ReaderWriterLockSlim m_Lock = new ReaderWriterLockSlim(); private static List m_Devices = new List(); private static Thread m_Thread; private static Device m_Device; @@ -29,6 +29,7 @@ namespace VRCX private static Texture2D m_Texture2; private static Browser m_Browser1; private static Browser m_Browser2; + private static bool m_Active; private static float[] m_Rotation = { 0f, 0f, 0f }; private static float[] m_Translation = { 0f, 0f, 0f }; private static float[] m_L_Translation = { -7f / 100f, -5f / 100f, 6f / 100f }; @@ -38,7 +39,7 @@ namespace VRCX // NOTE // 메모리 릭 때문에 미리 생성해놓고 계속 사용함 - public static void Setup() + public static void Init() { m_Device = new Device(DriverType.Hardware, DeviceCreationFlags.SingleThreaded | DeviceCreationFlags.BgraSupport); m_Texture1 = new Texture2D(m_Device, new Texture2DDescription() @@ -67,38 +68,139 @@ namespace VRCX }); m_Browser1 = new Browser(m_Texture1, Application.StartupPath + "/html/vr.html?1"); m_Browser2 = new Browser(m_Texture2, Application.StartupPath + "/html/vr.html?2"); - } - - public static void Start() - { - lock (m_LockObject) + m_Thread = new Thread(() => { - if (m_Thread == null) + var active = false; + var e = new VREvent_t(); + var nextInit = DateTime.MinValue; + var nextDeviceUpdate = DateTime.MinValue; + var nextOverlay = DateTime.MinValue; + var overlayIndex = OpenVR.k_unTrackedDeviceIndexInvalid; + var overlayVisible1 = false; + var overlayVisible2 = false; + var dashboardHandle = 0UL; + var overlayHandle1 = 0UL; + var overlayHandle2 = 0UL; + while (m_Thread != null) { - m_Thread = new Thread(ThreadProc); - m_Thread.Start(); - } - } - } - - public static void Stop() - { - lock (m_LockObject) - { - var thread = m_Thread; - if (thread != null) - { - m_Thread = null; try { - thread.Interrupt(); - thread.Join(); + Thread.Sleep(10); } catch { + // ThreadInterruptedException + } + if (m_Active) + { + m_Browser1.Render(); + m_Browser2.Render(); + var system = OpenVR.System; + if (system == null) + { + if (DateTime.Now.CompareTo(nextInit) <= 0) + { + continue; + } + var _err = EVRInitError.None; + system = OpenVR.Init(ref _err, EVRApplicationType.VRApplication_Overlay); + nextInit = DateTime.Now.AddSeconds(5); + if (system == null) + { + continue; + } + active = true; + } + while (system.PollNextEvent(ref e, (uint)Marshal.SizeOf(e))) + { + var type = (EVREventType)e.eventType; + if (type == EVREventType.VREvent_Quit) + { + active = false; + OpenVR.Shutdown(); + nextInit = DateTime.Now.AddSeconds(10); + system = null; + break; + } + } + if (system != null) + { + if (DateTime.Now.CompareTo(nextDeviceUpdate) >= 0) + { + overlayIndex = OpenVR.k_unTrackedDeviceIndexInvalid; + UpdateDevices(system, ref overlayIndex); + if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid) + { + nextOverlay = DateTime.Now.AddSeconds(10); + } + nextDeviceUpdate = DateTime.Now.AddSeconds(0.1); + } + var overlay = OpenVR.Overlay; + if (overlay != null) + { + var dashboardVisible = overlay.IsDashboardVisible(); + var err = ProcessDashboard(overlay, ref dashboardHandle, dashboardVisible); + if (err != EVROverlayError.None && + dashboardHandle != 0) + { + overlay.DestroyOverlay(dashboardHandle); + dashboardHandle = 0; + } + err = ProcessOverlay1(overlay, ref overlayHandle1, ref overlayVisible1, dashboardVisible, overlayIndex, nextOverlay); + if (err != EVROverlayError.None && + overlayHandle1 != 0) + { + overlay.DestroyOverlay(overlayHandle1); + overlayHandle1 = 0; + } + err = ProcessOverlay2(overlay, ref overlayHandle2, ref overlayVisible2, dashboardVisible); + if (err != EVROverlayError.None && + overlayHandle2 != 0) + { + overlay.DestroyOverlay(overlayHandle2); + overlayHandle2 = 0; + } + } + } + } + else if (active) + { + active = false; + OpenVR.Shutdown(); + m_Lock.EnterWriteLock(); + try + { + m_Devices.Clear(); + } + finally + { + m_Lock.ExitWriteLock(); + } } } - } + }) + { + IsBackground = true + }; + m_Thread.Start(); + } + + public static void Exit() + { + var T = m_Thread; + m_Thread = null; + T.Interrupt(); + T.Join(); + m_Browser2.Dispose(); + m_Browser1.Dispose(); + m_Texture2.Dispose(); + m_Texture1.Dispose(); + m_Device.Dispose(); + } + + public static void SetActive(bool active) + { + m_Active = active; } public static void Refresh() @@ -109,86 +211,105 @@ namespace VRCX public static string[][] GetDevices() { - lock (m_Devices) + m_Lock.EnterReadLock(); + try { return m_Devices.ToArray(); } + finally + { + m_Lock.ExitReadLock(); + } } - private static void UpdateDevices(CVRSystem system, ref uint trackingIndex) + private static void UpdateDevices(CVRSystem system, ref uint overlayIndex) { - lock (m_Devices) + m_Lock.EnterWriteLock(); + try { m_Devices.Clear(); - var sb = new StringBuilder(256); - var state = new VRControllerState_t(); - for (var i = 0u; i < OpenVR.k_unMaxTrackedDeviceCount; ++i) + } + finally + { + m_Lock.ExitWriteLock(); + } + var sb = new StringBuilder(256); + var state = new VRControllerState_t(); + for (var i = 0u; i < OpenVR.k_unMaxTrackedDeviceCount; ++i) + { + var devClass = system.GetTrackedDeviceClass(i); + if (devClass == ETrackedDeviceClass.Controller || + devClass == ETrackedDeviceClass.GenericTracker) { - var devClass = system.GetTrackedDeviceClass(i); - if (devClass == ETrackedDeviceClass.Controller || - devClass == ETrackedDeviceClass.GenericTracker) + var err = ETrackedPropertyError.TrackedProp_Success; + var batteryPercentage = system.GetFloatTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float, ref err); + if (err != ETrackedPropertyError.TrackedProp_Success) { - var err = ETrackedPropertyError.TrackedProp_Success; - var batteryPercentage = system.GetFloatTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float, ref err); - if (err != ETrackedPropertyError.TrackedProp_Success) - { - batteryPercentage = 1f; - } - sb.Clear(); - system.GetStringTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_TrackingSystemName_String, sb, (uint)sb.Capacity, ref err); - var isOculus = sb.ToString().IndexOf("oculus", StringComparison.OrdinalIgnoreCase) >= 0; - // Oculus : B/Y, Bit 1, Mask 2 - // Oculus : A/X, Bit 7, Mask 128 - // Vive : Menu, Bit 1, Mask 2, - // Vive : Grip, Bit 2, Mask 4 - var role = system.GetControllerRoleForTrackedDeviceIndex(i); - if (role == ETrackedControllerRole.LeftHand || - role == ETrackedControllerRole.RightHand) - { - if (system.GetControllerState(i, ref state, (uint)Marshal.SizeOf(state)) && - (state.ulButtonPressed & (isOculus ? 2u : 4u)) != 0) - { - if (role == ETrackedControllerRole.LeftHand) - { - Array.Copy(m_L_Translation, m_Translation, 3); - Array.Copy(m_L_Rotation, m_Rotation, 3); - } - else - { - Array.Copy(m_R_Translation, m_Translation, 3); - Array.Copy(m_R_Rotation, m_Rotation, 3); - } - trackingIndex = i; - } - } - var type = string.Empty; - if (devClass == ETrackedDeviceClass.Controller) + batteryPercentage = 1f; + } + sb.Clear(); + system.GetStringTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_TrackingSystemName_String, sb, (uint)sb.Capacity, ref err); + var isOculus = sb.ToString().IndexOf("oculus", StringComparison.OrdinalIgnoreCase) >= 0; + // Oculus : B/Y, Bit 1, Mask 2 + // Oculus : A/X, Bit 7, Mask 128 + // Vive : Menu, Bit 1, Mask 2, + // Vive : Grip, Bit 2, Mask 4 + var role = system.GetControllerRoleForTrackedDeviceIndex(i); + if (role == ETrackedControllerRole.LeftHand || + role == ETrackedControllerRole.RightHand) + { + if (system.GetControllerState(i, ref state, (uint)Marshal.SizeOf(state)) && + (state.ulButtonPressed & (isOculus ? 2u : 4u)) != 0) { if (role == ETrackedControllerRole.LeftHand) { - type = "leftController"; - } - else if (role == ETrackedControllerRole.RightHand) - { - type = "rightController"; + Array.Copy(m_L_Translation, m_Translation, 3); + Array.Copy(m_L_Rotation, m_Rotation, 3); } else { - type = "controller"; + Array.Copy(m_R_Translation, m_Translation, 3); + Array.Copy(m_R_Rotation, m_Rotation, 3); } + overlayIndex = i; } - else if (devClass == ETrackedDeviceClass.GenericTracker) + } + var type = string.Empty; + if (devClass == ETrackedDeviceClass.Controller) + { + if (role == ETrackedControllerRole.LeftHand) { - type = "tracker"; + type = "leftController"; } - m_Devices.Add(new[] + else if (role == ETrackedControllerRole.RightHand) { - type, - system.IsTrackedDeviceConnected(i) - ? "connected" - : "disconnected", - (batteryPercentage * 100).ToString() - }); + type = "rightController"; + } + else + { + type = "controller"; + } + } + else if (devClass == ETrackedDeviceClass.GenericTracker) + { + type = "tracker"; + } + var item = new[] + { + type, + system.IsTrackedDeviceConnected(i) + ? "connected" + : "disconnected", + (batteryPercentage * 100).ToString() + }; + m_Lock.EnterWriteLock(); + try + { + m_Devices.Add(item); + } + finally + { + m_Lock.ExitWriteLock(); } } } @@ -267,7 +388,7 @@ namespace VRCX return err; } - private static EVROverlayError ProcessOverlay1(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible, uint trackingIndex, DateTime nextRender) + private static EVROverlayError ProcessOverlay1(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible, uint overlayIndex, DateTime nextOverlay) { var err = EVROverlayError.None; @@ -304,7 +425,7 @@ namespace VRCX } } - if (trackingIndex != OpenVR.k_unTrackedDeviceIndexInvalid) + if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid) { // http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices // Scaling-Rotation-Translation @@ -328,7 +449,7 @@ namespace VRCX m10 = m.M33, m11 = m.M43, }; - err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, trackingIndex, ref hm34); + err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, overlayIndex, ref hm34); if (err != EVROverlayError.None) { return err; @@ -336,7 +457,7 @@ namespace VRCX } if (!dashboardVisible && - DateTime.Now.CompareTo(nextRender) <= 0) + DateTime.Now.CompareTo(nextOverlay) <= 0) { var texture = new Texture_t { @@ -462,123 +583,5 @@ namespace VRCX return err; } - - private static void ThreadProc() - { - var e = new VREvent_t(); - var nextOpenVRInit = DateTime.MinValue; - var nextDeviceInfoUpdate = DateTime.MinValue; - var nextRender = DateTime.MinValue; - var trackingIndex = OpenVR.k_unTrackedDeviceIndexInvalid; - var overlayVisible1 = false; - var overlayVisible2 = false; - var dashboardHandle = 0UL; - var overlayHandle1 = 0UL; - var overlayHandle2 = 0UL; - - while (m_Thread != null) - { - m_Browser1.Render(); - m_Browser2.Render(); - - try - { - Thread.Sleep(10); - } - catch - { - // ThreadInterruptedException - } - - var system = OpenVR.System; - - if (system == null) - { - if (DateTime.Now.CompareTo(nextOpenVRInit) < 0) - { - continue; - } - var _err = EVRInitError.None; - system = OpenVR.Init(ref _err, EVRApplicationType.VRApplication_Overlay); - nextOpenVRInit = DateTime.Now.AddSeconds(5); - if (system == null) - { - continue; - } - } - - while (system.PollNextEvent(ref e, (uint)Marshal.SizeOf(e))) - { - var type = (EVREventType)e.eventType; - if (type == EVREventType.VREvent_Quit) - { - OpenVR.Shutdown(); - // VRChat이 실행 중일 때만 켜는 옵션이 생겨서 시간을 줄임 - nextOpenVRInit = DateTime.Now.AddSeconds(10); - system = null; - break; - } - } - - if (system == null) - { - continue; - } - - if (DateTime.Now.CompareTo(nextDeviceInfoUpdate) >= 0) - { - trackingIndex = OpenVR.k_unTrackedDeviceIndexInvalid; - UpdateDevices(system, ref trackingIndex); - if (trackingIndex != OpenVR.k_unTrackedDeviceIndexInvalid) - { - nextRender = DateTime.Now.AddSeconds(10); - } - nextDeviceInfoUpdate = DateTime.Now.AddSeconds(0.1); - } - - var overlay = OpenVR.Overlay; - - if (overlay == null) - { - continue; - } - - var dashboardVisible = overlay.IsDashboardVisible(); - - var err = ProcessDashboard(overlay, ref dashboardHandle, dashboardVisible); - - if (err != EVROverlayError.None && - dashboardHandle != 0) - { - overlay.DestroyOverlay(dashboardHandle); - dashboardHandle = 0; - } - - err = ProcessOverlay1(overlay, ref overlayHandle1, ref overlayVisible1, dashboardVisible, trackingIndex, nextRender); - - if (err != EVROverlayError.None && - overlayHandle1 != 0) - { - overlay.DestroyOverlay(overlayHandle1); - overlayHandle1 = 0; - } - - err = ProcessOverlay2(overlay, ref overlayHandle2, ref overlayVisible2, dashboardVisible); - - if (err != EVROverlayError.None && - overlayHandle2 != 0) - { - overlay.DestroyOverlay(overlayHandle2); - overlayHandle2 = 0; - } - } - - lock (m_Devices) - { - m_Devices.Clear(); - } - - OpenVR.Shutdown(); - } } } \ No newline at end of file diff --git a/VRForm.cs b/VRForm.cs index 831f8cba..79b895bb 100644 --- a/VRForm.cs +++ b/VRForm.cs @@ -44,8 +44,10 @@ namespace VRCX }; Browser1.JavascriptObjectRepository.Register("VRCX", new VRCX(), true, options); Browser1.JavascriptObjectRepository.Register("VRCXStorage", new VRCXStorage(), false, options); + Browser1.JavascriptObjectRepository.Register("SQLite", new SQLite(), true, options); Browser2.JavascriptObjectRepository.Register("VRCX", new VRCX(), true, options); Browser2.JavascriptObjectRepository.Register("VRCXStorage", new VRCXStorage(), false, options); + Browser2.JavascriptObjectRepository.Register("SQLite", new SQLite(), true, options); Browser1.IsBrowserInitializedChanged += (A, B) => { // Browser1.ShowDevTools(); @@ -60,9 +62,9 @@ namespace VRCX private void button_refresh_Click(object sender, System.EventArgs e) { - VRCXVR.Refresh(); Browser1.ExecuteScriptAsync("location.reload()"); Browser2.ExecuteScriptAsync("location.reload()"); + VRCXVR.Refresh(); } private void button_devtools_Click(object sender, System.EventArgs e) diff --git a/html/.eslintrc.js b/html/.eslintrc.js index ea1ac5a4..10c8537d 100644 --- a/html/.eslintrc.js +++ b/html/.eslintrc.js @@ -9,6 +9,7 @@ module.exports = { 'CefSharp': 'readonly', 'VRCX': 'readonly', 'VRCXStorage': 'readonly', + 'SQLite': 'readonly', 'LogWatcher': 'readonly', 'Discord': 'readonly', 'Noty': 'readonly', diff --git a/html/app.js b/html/app.js index b4d9bb17..eb709e1f 100644 --- a/html/app.js +++ b/html/app.js @@ -7,6 +7,7 @@ if (window.CefSharp) { Promise.all([ CefSharp.BindObjectAsync('VRCX'), CefSharp.BindObjectAsync('VRCXStorage'), + CefSharp.BindObjectAsync('SQLite'), CefSharp.BindObjectAsync('LogWatcher'), CefSharp.BindObjectAsync('Discord') ]).catch(() => { @@ -3490,6 +3491,8 @@ if (window.CefSharp) { var ref; var i; var j; + // FIXME + // 여러 개 켠다면 gameLogTable의 데이터가 시간순이 아닐 수도 있음 i = this.gameLogTable.data.length; j = 0; while (j < 25) { @@ -4460,31 +4463,27 @@ if (window.CefSharp) { }; $app.methods.refreshGameLog = function () { - LogWatcher.HasLog().then((result) => { - if (result) { - LogWatcher.GetLogs().then((logs) => { - logs.forEach((log) => { - var ctx = { - created_at: log[0], - type: log[1], - data: log[2] - }; - this.gameLogTable.data.push(ctx); - if (ctx.type === 'Location') { - this.lastLocation = ctx.data; - } - }); - this.sweepGameLog(); - this.updateSharedFeed(); - // sweepGameLog로 기록이 삭제되면 - // 아무 것도 없는데 알림이 떠서 이상함 - if (this.gameLogTable.length) { - this.notifyMenu('gameLog'); + LogWatcher.Get().then((logs) => { + if (logs.length) { + logs.forEach((log) => { + var ctx = { + created_at: log[0], + type: log[1], + data: log[2] + }; + this.gameLogTable.data.push(ctx); + if (ctx.type === 'Location') { + this.lastLocation = ctx.data; } }); - } else { - this.updateSharedFeed(); + this.sweepGameLog(); + // sweepGameLog로 기록이 삭제되면 + // 아무 것도 없는데 알림이 떠서 이상함 + if (this.gameLogTable.length) { + this.notifyMenu('gameLog'); + } } + this.updateSharedFeed(); }); }; diff --git a/html/vr.js b/html/vr.js index fbd60d47..a5187bbf 100644 --- a/html/vr.js +++ b/html/vr.js @@ -6,7 +6,8 @@ if (window.CefSharp) { Promise.all([ CefSharp.BindObjectAsync('VRCX'), - CefSharp.BindObjectAsync('VRCXStorage') + CefSharp.BindObjectAsync('VRCXStorage'), + CefSharp.BindObjectAsync('SQLite') ]).catch(() => { location = 'https://github.com/pypy-vrc/vrcx'; }).then(() => { diff --git a/packages.config b/packages.config index 332a1bab..250c38bc 100644 --- a/packages.config +++ b/packages.config @@ -14,4 +14,5 @@ + \ No newline at end of file