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