Files
Daybreak/Daybreak.API/Services/Interop/GameThreadService.cs
T
amacociangithub-actions[bot] <github-actions[bot]@users.noreply.github.com>dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Alexandru Macocian
ef513e1230 Gwca bindings (#1462)
* Bump version to 0.9.10.1

* Bump Microsoft.Web.WebView2 from 1.0.3719.77 to 1.0.3800.47 (#1442)

---
updated-dependencies:
- dependency-name: Microsoft.Web.WebView2
  dependency-version: 1.0.3800.47
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Improve mod view responsiveness (#1443)

* Improve application launcher logging (Closes #1441) (#1444)

* Damage modifier 0x248 (Closes #1438) (#1445)

* Initial commit

* Setup generator

* Progress

* Update generator

* Initial commit

* Setup generator

* Progress

* Update generator

* Fix library loading

* Move code to gwca

* Fix leave party

* Disable console on Release

* Add more exports to gwca

* Upgrade gwca

* Fixes to character selector

* Fix GetWindowHandle

* Move character selector to frame based

* Fix config spamming

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alexandru Macocian <amacocian@microsoft.com>
2026-02-24 06:37:10 -08:00

93 lines
2.7 KiB
C#

using Daybreak.API.Interop;
using Daybreak.API.Models;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Security;
namespace Daybreak.API.Services.Interop;
public sealed class GameThreadService
{
[SuppressUnmanagedCodeSecurity]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void GameThreadCallback();
// Prevent GC of delegates passed to native code
private readonly ConcurrentDictionary<GameThreadCallback, bool> prevent_GC_callbacks = [];
public Task QueueOnGameThread(Action action, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource();
var item = new WorkItem(action, taskCompletionSource, cancellationToken);
GameThreadCallback callback = null!;
callback = () =>
{
try
{
if (item.CancellationToken.IsCancellationRequested)
{
item.Cancel();
return;
}
item.Execute();
}
catch (Exception ex)
{
item.Exception(ex);
}
finally
{
this.prevent_GC_callbacks.TryRemove(callback, out _);
}
};
// CRITICAL: Keep delegate alive until callback executes
this.prevent_GC_callbacks[callback] = true;
var funcPtr = Marshal.GetFunctionPointerForDelegate(callback);
GWCA.GW.GameThread.Enqueue(funcPtr, false);
return taskCompletionSource.Task;
}
public Task<T> QueueOnGameThread<T>(Func<T> action, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<T>();
var item = new WorkItem<T>(action, taskCompletionSource, cancellationToken);
GameThreadCallback callback = null!;
callback = () =>
{
try
{
if (item.CancellationToken.IsCancellationRequested)
{
item.Cancel();
return;
}
item.Execute();
}
catch (Exception ex)
{
item.Exception(ex);
}
finally
{
// Remove from prevent-GC set after execution
this.prevent_GC_callbacks.TryRemove(callback, out _);
}
};
// CRITICAL: Keep delegate alive until callback executes
this.prevent_GC_callbacks[callback] = true;
var funcPtr = Marshal.GetFunctionPointerForDelegate(callback);
GWCA.GW.GameThread.Enqueue(funcPtr, false);
return taskCompletionSource.Task;
}
}