committed by
GitHub
27 changed files with 676 additions and 758 deletions
@ -0,0 +1,135 @@ |
|||||
|
#nullable enable |
||||
|
|
||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.Threading; |
||||
|
using Avalonia.Threading; |
||||
|
using CoreFoundation; |
||||
|
using Foundation; |
||||
|
using ObjCRuntime; |
||||
|
using CFIndex = System.IntPtr; |
||||
|
|
||||
|
namespace Avalonia.iOS; |
||||
|
|
||||
|
internal class DispatcherImpl : IDispatcherImplWithExplicitBackgroundProcessing |
||||
|
{ |
||||
|
// CFRunLoopTimerSetNextFireDate docs recommend to "create a repeating timer with an initial
|
||||
|
// firing time in the distant future (or the initial firing time) and a very large repeat
|
||||
|
// interval—on the order of decades or more"
|
||||
|
private const double DistantFutureInterval = (double)50 * 365 * 24 * 3600; |
||||
|
internal static readonly DispatcherImpl Instance = new(); |
||||
|
|
||||
|
private readonly Stopwatch _clock = Stopwatch.StartNew(); |
||||
|
private readonly Action _checkSignaledAction; |
||||
|
private readonly Action _wakeUpLoopAction; |
||||
|
private readonly IntPtr _timer; |
||||
|
private Thread? _loopThread; |
||||
|
private bool _backgroundProcessingRequested, _signaled; |
||||
|
|
||||
|
private DispatcherImpl() |
||||
|
{ |
||||
|
_checkSignaledAction = CheckSignaled; |
||||
|
_wakeUpLoopAction = () => |
||||
|
{ |
||||
|
// This is needed to wakeup the loop if we are called from inside of BeforeWait hook
|
||||
|
}; |
||||
|
|
||||
|
var observer = Interop.CFRunLoopObserverCreate(IntPtr.Zero, |
||||
|
Interop.CFOptionFlags.kCFRunLoopAfterWaiting | Interop.CFOptionFlags.kCFRunLoopBeforeSources | |
||||
|
Interop.CFOptionFlags.kCFRunLoopBeforeWaiting, |
||||
|
true, 0, ObserverCallback, IntPtr.Zero); |
||||
|
Interop.CFRunLoopAddObserver(Interop.CFRunLoopGetMain(), observer, Interop.kCFRunLoopCommonModes); |
||||
|
|
||||
|
_timer = Interop.CFRunLoopTimerCreate(IntPtr.Zero, |
||||
|
Interop.CFAbsoluteTimeGetCurrent() + DistantFutureInterval, |
||||
|
DistantFutureInterval, 0, 0, TimerCallback, IntPtr.Zero); |
||||
|
Interop.CFRunLoopAddTimer(Interop.CFRunLoopGetMain(), _timer, Interop.kCFRunLoopCommonModes); |
||||
|
} |
||||
|
|
||||
|
public event Action? Signaled; |
||||
|
public event Action? Timer; |
||||
|
public event Action? ReadyForBackgroundProcessing; |
||||
|
|
||||
|
public bool CurrentThreadIsLoopThread |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (_loopThread != null) |
||||
|
return Thread.CurrentThread == _loopThread; |
||||
|
if (!NSThread.IsMain) |
||||
|
return false; |
||||
|
_loopThread = Thread.CurrentThread; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Signal() |
||||
|
{ |
||||
|
lock (this) |
||||
|
{ |
||||
|
if (_signaled) |
||||
|
return; |
||||
|
_signaled = true; |
||||
|
|
||||
|
DispatchQueue.MainQueue.DispatchAsync(_checkSignaledAction); |
||||
|
CFRunLoop.Main.WakeUp(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void UpdateTimer(long? dueTimeInMs) |
||||
|
{ |
||||
|
var ms = dueTimeInMs == null ? -1 : (int)Math.Min(int.MaxValue - 10, Math.Max(1, dueTimeInMs.Value - Now)); |
||||
|
var interval = ms < 0 ? DistantFutureInterval : ((double)ms / 1000); |
||||
|
Interop.CFRunLoopTimerSetTolerance(_timer, 0); |
||||
|
Interop.CFRunLoopTimerSetNextFireDate(_timer, Interop.CFAbsoluteTimeGetCurrent() + interval); |
||||
|
} |
||||
|
|
||||
|
public long Now => _clock.ElapsedMilliseconds; |
||||
|
|
||||
|
public void RequestBackgroundProcessing() |
||||
|
{ |
||||
|
if (_backgroundProcessingRequested) |
||||
|
return; |
||||
|
_backgroundProcessingRequested = true; |
||||
|
DispatchQueue.MainQueue.DispatchAsync(_wakeUpLoopAction); |
||||
|
} |
||||
|
|
||||
|
private void CheckSignaled() |
||||
|
{ |
||||
|
bool signaled; |
||||
|
lock (this) |
||||
|
{ |
||||
|
signaled = _signaled; |
||||
|
_signaled = false; |
||||
|
} |
||||
|
|
||||
|
if (signaled) |
||||
|
{ |
||||
|
Signaled?.Invoke(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[MonoPInvokeCallback(typeof(Interop.CFRunLoopObserverCallback))] |
||||
|
private static void ObserverCallback(IntPtr observer, Interop.CFOptionFlags activity, IntPtr info) |
||||
|
{ |
||||
|
if (activity == Interop.CFOptionFlags.kCFRunLoopBeforeWaiting) |
||||
|
{ |
||||
|
bool triggerProcessing; |
||||
|
lock (Instance) |
||||
|
{ |
||||
|
triggerProcessing = Instance._backgroundProcessingRequested; |
||||
|
Instance._backgroundProcessingRequested = false; |
||||
|
} |
||||
|
|
||||
|
if (triggerProcessing) Instance.ReadyForBackgroundProcessing?.Invoke(); |
||||
|
} |
||||
|
|
||||
|
Instance.CheckSignaled(); |
||||
|
} |
||||
|
|
||||
|
[MonoPInvokeCallback(typeof(Interop.CFRunLoopTimerCallback))] |
||||
|
private static void TimerCallback(IntPtr timer, IntPtr info) |
||||
|
{ |
||||
|
Instance.Timer?.Invoke(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using CoreFoundation; |
||||
|
using Foundation; |
||||
|
using ObjCRuntime; |
||||
|
|
||||
|
namespace Avalonia.iOS; |
||||
|
|
||||
|
// TODO: use LibraryImport in NET7
|
||||
|
internal class Interop |
||||
|
{ |
||||
|
internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; |
||||
|
internal static NativeHandle kCFRunLoopCommonModes = CFString.CreateNative("kCFRunLoopCommonModes"); |
||||
|
|
||||
|
[Flags] |
||||
|
internal enum CFOptionFlags : ulong |
||||
|
{ |
||||
|
kCFRunLoopBeforeSources = (1UL << 2), |
||||
|
kCFRunLoopAfterWaiting = (1UL << 6), |
||||
|
kCFRunLoopBeforeWaiting = (1UL << 5) |
||||
|
} |
||||
|
|
||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)] |
||||
|
internal delegate void CFRunLoopObserverCallback(IntPtr observer, CFOptionFlags activity, IntPtr info); |
||||
|
|
||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)] |
||||
|
internal delegate void CFRunLoopTimerCallback(IntPtr timer, IntPtr info); |
||||
|
|
||||
|
[DllImport(CoreFoundationLibrary)] |
||||
|
internal static extern IntPtr CFRunLoopGetMain(); |
||||
|
|
||||
|
[DllImport(CoreFoundationLibrary)] |
||||
|
internal static extern IntPtr CFRunLoopObserverCreate(IntPtr allocator, CFOptionFlags activities, |
||||
|
bool repeats, int index, CFRunLoopObserverCallback callout, IntPtr context); |
||||
|
|
||||
|
[DllImport(CoreFoundationLibrary)] |
||||
|
internal static extern IntPtr CFRunLoopAddObserver(IntPtr loop, IntPtr observer, IntPtr mode); |
||||
|
|
||||
|
[DllImport(CoreFoundationLibrary)] |
||||
|
internal static extern IntPtr CFRunLoopTimerCreate(IntPtr allocator, double firstDate, double interval, |
||||
|
CFOptionFlags flags, int order, CFRunLoopTimerCallback callout, IntPtr context); |
||||
|
|
||||
|
[DllImport(CoreFoundationLibrary)] |
||||
|
internal static extern void CFRunLoopTimerSetTolerance(IntPtr timer, double tolerance); |
||||
|
|
||||
|
[DllImport(CoreFoundationLibrary)] |
||||
|
internal static extern void CFRunLoopTimerSetNextFireDate(IntPtr timer, double fireDate); |
||||
|
|
||||
|
[DllImport(CoreFoundationLibrary)] |
||||
|
internal static extern void CFRunLoopAddTimer(IntPtr loop, IntPtr timer, IntPtr mode); |
||||
|
|
||||
|
[DllImport(CoreFoundationLibrary)] |
||||
|
internal static extern double CFAbsoluteTimeGetCurrent(); |
||||
|
} |
||||
@ -1,38 +0,0 @@ |
|||||
using System; |
|
||||
using System.Threading; |
|
||||
using Avalonia.Platform; |
|
||||
using Avalonia.Threading; |
|
||||
using CoreFoundation; |
|
||||
using Foundation; |
|
||||
|
|
||||
namespace Avalonia.iOS |
|
||||
{ |
|
||||
class PlatformThreadingInterface : IPlatformThreadingInterface |
|
||||
{ |
|
||||
private bool _signaled; |
|
||||
public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); |
|
||||
public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; |
|
||||
|
|
||||
public event Action<DispatcherPriority?> Signaled; |
|
||||
|
|
||||
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) |
|
||||
=> NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick()); |
|
||||
|
|
||||
public void Signal(DispatcherPriority prio) |
|
||||
{ |
|
||||
lock (this) |
|
||||
{ |
|
||||
if(_signaled) |
|
||||
return; |
|
||||
_signaled = true; |
|
||||
} |
|
||||
|
|
||||
DispatchQueue.MainQueue.DispatchAsync(() => |
|
||||
{ |
|
||||
lock (this) |
|
||||
_signaled = false; |
|
||||
Signaled?.Invoke(null); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue