From bdca389f2e403cf4d3999895df066bcca0b5281d Mon Sep 17 00:00:00 2001 From: art0007i Date: Thu, 2 Oct 2025 03:35:32 +0200 Subject: [PATCH] fix native libs add SdlEvent callback add OpenWindow function --- Plugin/ImGuiInstance.cs | 70 ++++++++++++++++++++++++++++++---- Plugin/ResoniteImGuiLib.csproj | 2 +- Plugin/SDL3/SDL3Window.cs | 44 ++++++++++++++------- README.md | 2 + thunderstore.toml | 6 +-- 5 files changed, 100 insertions(+), 24 deletions(-) diff --git a/Plugin/ImGuiInstance.cs b/Plugin/ImGuiInstance.cs index dc5d272..30239b3 100644 --- a/Plugin/ImGuiInstance.cs +++ b/Plugin/ImGuiInstance.cs @@ -15,7 +15,14 @@ public class ImGuiInstance } public string Name => _name; + /// + /// Called whenever ImGui is being rendered. You can use ImGui functions inside here. + /// public event Action? Layout; + /// + /// Called whenever the window is processing an event. Returning false means that the event will not be processed further. + /// + public event Func? SdlEvent; private string _name; private ConfigEntry _entry; @@ -26,6 +33,14 @@ public class ImGuiInstance internal static ConcurrentQueue newInstances = new(); internal static ConcurrentQueue<(string, Action)> windowRequests = new(); + /// + /// Tries to open the window. If it's already open, nothing happens. This function may be expensive to call rapidly. + /// + public void OpenWindow() + { + newInstances.Enqueue(this); + } + /// /// Gets a reference to the SDL3Window inside of a callback. /// The callback will be called inside the SDL3 Thread. @@ -74,7 +89,7 @@ public class ImGuiInstance if (IsSDL3Running) return; - _sdl3Thread = new Thread(() => RunSDL3()) + _sdl3Thread = new Thread(RunSDL3) { Name = $"Resonite ImGui SDL3", Priority = ThreadPriority.Highest, @@ -93,16 +108,21 @@ public class ImGuiInstance { while (newInstances.TryDequeue(out var request)) { + if (windows.ContainsKey(request.Name)) continue; + SDL3Window app = new SDL3Window("ImGuiContext: " + request.Name, request._entry.Value.x, request._entry.Value.y, request._entry.Value.z, request._entry.Value.w); app.WindowRectModified += (rect) => request._entry.Value = rect; // TODO: maybe listen to config changes to resize window // request._entry.SettingChanged += (_, _) => { }; - app.RenderCallback = request.Layout!; - windows[request.Name] = app; - var winId = SDL.GetWindowID(app.Window); - if (!windowsById.TryAdd(winId, app)) + app.RenderCallback = request.Layout; + app.SdlEventReceived = request.SdlEvent; + if (!windows.TryAdd(request.Name, app)) { - Plugin.Log.LogError($"Failed to add window with id {(uint) app.Window}, name {request.Name}!"); + Plugin.Log.LogError($"Failed to add window with name {request.Name}!"); + } + if (!windowsById.TryAdd(app.SdlWindowId, app)) + { + Plugin.Log.LogError($"Failed to add window with id {app.SdlWindowId}, name {request.Name}!"); } } for (int i = 0; i < windowRequests.Count; i++) @@ -119,7 +139,6 @@ public class ImGuiInstance } } } - while (SDL.PollEvent(out SDL.Event ev)) { if (windowsById.TryGetValue(ev.Window.WindowID, out var window)) @@ -128,10 +147,47 @@ public class ImGuiInstance } } + var removed = new HashSet<(string, uint)>(); foreach (var (key, window) in windows) { + if(window.Disposed) + { + removed.Add((key, window.SdlWindowId)); + continue; + } + window.SetContext(); + + if (Plugin.CancellationToken.IsCancellationRequested) + { + window.ForceDispose(); + continue; + } + + if (window.ShouldClose) + { + removed.Add((key, window.SdlWindowId)); + window.Dispose(); + continue; + } + window.RunOneFrame(); } + foreach (var (k, k2) in removed) + { + var b = windows.Remove(k); + var b2 = windowsById.Remove(k2); + Plugin.Log.LogDebug($"Removed window {k}, success codes: {b}|{b2}"); + } + if (Plugin.CancellationToken.IsCancellationRequested) + { + break; + } + + // Prevent busy looping when nothing is happening. + if (windows.Count == 0) + { + Thread.Sleep(1000); + } } } catch (Exception e) diff --git a/Plugin/ResoniteImGuiLib.csproj b/Plugin/ResoniteImGuiLib.csproj index 13c02e3..bf36d01 100644 --- a/Plugin/ResoniteImGuiLib.csproj +++ b/Plugin/ResoniteImGuiLib.csproj @@ -1,7 +1,7 @@ - 2.0.0 + 2.1.0 art0007i net9.0 https://github.com/art0007i/ResoniteImGuiLib diff --git a/Plugin/SDL3/SDL3Window.cs b/Plugin/SDL3/SDL3Window.cs index 0b61ce0..3f8e9f3 100644 --- a/Plugin/SDL3/SDL3Window.cs +++ b/Plugin/SDL3/SDL3Window.cs @@ -1,5 +1,4 @@ -using CodeGenerationConfig; -using Elements.Core; +using Elements.Core; using ImGuiNET; using SDL3; using System.Diagnostics; @@ -12,12 +11,14 @@ public class SDL3Window : IDisposable public readonly IntPtr Device; public readonly ImGuiSDL3 Platform; public readonly ImGuiSDL3Renderer Renderer; + public readonly uint SdlWindowId; internal readonly Queue EventQueue = new(); + internal Action? RenderCallback { get; set; } + internal Func? SdlEventReceived { get; set; } public event Action? WindowRectModified; public color? ClearColor = null; - public Action? RenderCallback { get; set; } private readonly Stopwatch _timer = Stopwatch.StartNew(); private TimeSpan _time = TimeSpan.Zero; @@ -57,9 +58,11 @@ public class SDL3Window : IDisposable Platform = new ImGuiSDL3(Window, Device); Renderer = new ImGuiSDL3Renderer(Device); + SdlWindowId = SDL.GetWindowID(Window); //ImGuiManager.TriggerImGuiRecreated(); } + public bool Disposed => _disposed; private bool _disposed; ~SDL3Window() @@ -68,6 +71,11 @@ public class SDL3Window : IDisposable } public void Dispose() + { + Dispose(false); + } + + public void ForceDispose() { Dispose(true); GC.SuppressFinalize(this); @@ -102,8 +110,8 @@ public class SDL3Window : IDisposable if (_imGuiContext != IntPtr.Zero) { - ImGui.SetCurrentContext(_imGuiContext); - ImGui.DestroyContext(); + ImGui.SetCurrentContext(IntPtr.Zero); + ImGui.DestroyContext(_imGuiContext); _imGuiContext = IntPtr.Zero; } @@ -118,12 +126,15 @@ public class SDL3Window : IDisposable } } + public void SetContext() + { + ImGui.SetCurrentContext(_imGuiContext); + } + public void RunOneFrame() { if (_disposed) return; - ImGui.SetCurrentContext(_imGuiContext); - ImGui.GetIO().DeltaTime = (float) (_timer.Elapsed - _time).TotalSeconds; _time = _timer.Elapsed; @@ -136,14 +147,10 @@ public class SDL3Window : IDisposable Render(); - if (_shouldClose || Plugin.CancellationToken.IsCancellationRequested) - { - Dispose(); - } ImGui.SetCurrentContext(IntPtr.Zero); } - private bool _shouldClose; + public bool ShouldClose; private void PollEvents() { @@ -154,6 +161,17 @@ public class SDL3Window : IDisposable while (EventQueue.TryDequeue(out var ev)) { + var skip = false; + foreach (var func in Delegate.EnumerateInvocationList(SdlEventReceived)) + { + if (!func(ev)) + { + skip = true; + break; + } + } + if (skip) continue; + Platform.ProcessEvent(ev); switch ((SDL.EventType) ev.Type) @@ -168,7 +186,7 @@ public class SDL3Window : IDisposable case SDL.EventType.Terminating: case SDL.EventType.WindowCloseRequested: case SDL.EventType.Quit: - _shouldClose = true; + ShouldClose = true; break; case SDL.EventType.WindowResized: SetupScreenClipRect(); diff --git a/README.md b/README.md index 783ff4e..82d6e1b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ A [Resonite](https://resonite.com/) library that allows modders to create ImGui windows. +Example usage can be found here: https://github.com/art0007i/ImGuiExample + ## Installation (Manual) 1. Install [BepisLoader](https://github.com/ResoniteModding/BepisLoader) for Resonite. 2. Download the latest release ZIP file (e.g., `art0007i-ResoniteImGuiLib-2.0.0.zip`) from the [Releases](https://github.com/art0007i/ResoniteImGuiLib/releases) page. diff --git a/thunderstore.toml b/thunderstore.toml index 2737c4c..71417a8 100644 --- a/thunderstore.toml +++ b/thunderstore.toml @@ -10,7 +10,7 @@ description = "Library that allows modders to create ImGui windows." websiteUrl = "https://github.com/art0007i/ResoniteImGuiLib" containsNsfwContent = false -[package.dependencies] +[package.dependencies] ResoniteModding-BepisLoader = "1.4.1" ResoniteModding-BepisResoniteWrapper = "1.0.0" @@ -33,11 +33,11 @@ target = "plugins/ResoniteImGuiLib/" [[build.copy]] source = "./Plugin/bin/Release/runtimes/linux-x64/native/libcimgui.so" -target = "plugins/ResoniteImGuiLib/native/" +target = "plugins/ResoniteImGuiLib/" [[build.copy]] source = "./Plugin/bin/Release/runtimes/win-x64/native/cimgui.dll" -target = "plugins/ResoniteImGuiLib/native/" +target = "plugins/ResoniteImGuiLib/" [[build.copy]] source = "./CHANGELOG.md"