71 lines
2.3 KiB
C#
71 lines
2.3 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace CursorLang.Core.Threading;
|
|
|
|
/// <summary>
|
|
/// A timer that ticks on the message loop — the agent's stand-in for
|
|
/// <c>DispatcherTimer</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <c>SetTimer</c> with a null window binds the timer to the thread rather than to a
|
|
/// window, and <c>DispatchMessage</c> calls the callback straight from the loop. The
|
|
/// upshot is the same as with a dispatcher timer: the tick arrives on the thread that
|
|
/// owns the hook and the popup, so nothing needs marshalling and nothing races.
|
|
///
|
|
/// The callback lives in a field for the reason a hook procedure does: the only
|
|
/// reference to it is held by Win32, and a collected delegate takes the process down
|
|
/// with it at the first tick.
|
|
/// </remarks>
|
|
internal sealed class MessageTimer : IDisposable
|
|
{
|
|
/// <summary>Windows will not go below this, and pretending otherwise misleads.</summary>
|
|
private const uint MinimumIntervalMilliseconds = 10;
|
|
|
|
private readonly TimerProc _callback;
|
|
|
|
private nuint _id;
|
|
|
|
internal MessageTimer() => _callback = OnTimer;
|
|
|
|
internal event EventHandler? Tick;
|
|
|
|
internal TimeSpan Interval { get; set; } = TimeSpan.FromMilliseconds(100);
|
|
|
|
internal bool IsRunning => _id != 0;
|
|
|
|
/// <summary>Starts the timer, or restarts it from zero when it is already running.</summary>
|
|
internal void Start()
|
|
{
|
|
Stop();
|
|
|
|
var milliseconds = (uint)Math.Clamp(
|
|
Math.Round(Interval.TotalMilliseconds), MinimumIntervalMilliseconds, int.MaxValue);
|
|
|
|
_id = SetTimer(IntPtr.Zero, 0, milliseconds, _callback);
|
|
}
|
|
|
|
internal void Stop()
|
|
{
|
|
if (_id == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
KillTimer(IntPtr.Zero, _id);
|
|
_id = 0;
|
|
}
|
|
|
|
public void Dispose() => Stop();
|
|
|
|
private void OnTimer(IntPtr window, uint message, nuint id, uint time) =>
|
|
Tick?.Invoke(this, EventArgs.Empty);
|
|
|
|
private delegate void TimerProc(IntPtr hWnd, uint message, nuint idEvent, uint time);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
private static extern nuint SetTimer(IntPtr hWnd, nuint nIDEvent, uint uElapse, TimerProc lpTimerFunc);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
private static extern bool KillTimer(IntPtr hWnd, nuint uIDEvent);
|
|
}
|