Skip to content

Commit

Permalink
Fix iOS Dispatcher (AvaloniaUI#13942)
Browse files Browse the repository at this point in the history
* Replace kCFRunLoopCommonModes with kCFRunLoopDefaultMode

* Replace delegates with function pointers in iOS backend

* Update DispatcherImpl.cs
  • Loading branch information
maxkatz6 authored Dec 21, 2023
1 parent 06a3727 commit 447b9b9
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 35 deletions.
1 change: 1 addition & 0 deletions src/iOS/Avalonia.iOS/Avalonia.iOS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<TargetFramework>net7.0-ios16.0</TargetFramework>
<SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
Expand Down
57 changes: 33 additions & 24 deletions src/iOS/Avalonia.iOS/DispatcherImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Threading;
using CoreFoundation;
using Foundation;
using ObjCRuntime;
using CFIndex = System.IntPtr;

namespace Avalonia.iOS;

Expand All @@ -20,30 +19,28 @@ internal class DispatcherImpl : IDispatcherImplWithExplicitBackgroundProcessing
internal static readonly DispatcherImpl Instance = new();

private readonly Stopwatch _clock = Stopwatch.StartNew();
private readonly Action _checkSignaledAction;
private readonly Action _wakeUpLoopAction;
private readonly object _sync = new();
private readonly IntPtr _timer;
private readonly IntPtr _mainLoop;
private readonly IntPtr _mainQueue;
private Thread? _loopThread;
private bool _backgroundProcessingRequested, _signaled;

private DispatcherImpl()
private unsafe DispatcherImpl()
{
_checkSignaledAction = CheckSignaled;
_wakeUpLoopAction = () =>
{
// This is needed to wakeup the loop if we are called from inside of BeforeWait hook
};
_mainLoop = Interop.CFRunLoopGetMain();
_mainQueue = DispatchQueue.MainQueue.Handle.Handle;

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);
1, 0, &ObserverCallback, IntPtr.Zero);
Interop.CFRunLoopAddObserver(_mainLoop, observer, Interop.kCFRunLoopDefaultMode);

_timer = Interop.CFRunLoopTimerCreate(IntPtr.Zero,
Interop.CFAbsoluteTimeGetCurrent() + DistantFutureInterval,
DistantFutureInterval, 0, 0, TimerCallback, IntPtr.Zero);
Interop.CFRunLoopAddTimer(Interop.CFRunLoopGetMain(), _timer, Interop.kCFRunLoopCommonModes);
DistantFutureInterval, 0, 0, &TimerCallback, IntPtr.Zero);
Interop.CFRunLoopAddTimer(_mainLoop, _timer, Interop.kCFRunLoopDefaultMode);
}

public event Action? Signaled;
Expand All @@ -63,16 +60,16 @@ public bool CurrentThreadIsLoopThread
}
}

public void Signal()
public unsafe void Signal()
{
lock (this)
lock (_sync)
{
if (_signaled)
return;
_signaled = true;

DispatchQueue.MainQueue.DispatchAsync(_checkSignaledAction);
CFRunLoop.Main.WakeUp();
Interop.dispatch_async_f(_mainQueue, IntPtr.Zero, &CheckSignaled);
Interop.CFRunLoopWakeUp(_mainLoop);
}
}

Expand All @@ -86,18 +83,18 @@ public void UpdateTimer(long? dueTimeInMs)

public long Now => _clock.ElapsedMilliseconds;

public void RequestBackgroundProcessing()
public unsafe void RequestBackgroundProcessing()
{
if (_backgroundProcessingRequested)
return;
_backgroundProcessingRequested = true;
DispatchQueue.MainQueue.DispatchAsync(_wakeUpLoopAction);
Interop.dispatch_async_f(_mainQueue, IntPtr.Zero, &WakeUpCallback);
}

private void CheckSignaled()
{
bool signaled;
lock (this)
lock (_sync)
{
signaled = _signaled;
_signaled = false;
Expand All @@ -109,13 +106,25 @@ private void CheckSignaled()
}
}

[MonoPInvokeCallback(typeof(Interop.CFRunLoopObserverCallback))]
[UnmanagedCallersOnly]
private static void CheckSignaled(IntPtr context)
{
Instance.CheckSignaled();
}

[UnmanagedCallersOnly]
private static void WakeUpCallback(IntPtr context)
{

}

[UnmanagedCallersOnly]
private static void ObserverCallback(IntPtr observer, Interop.CFOptionFlags activity, IntPtr info)
{
if (activity == Interop.CFOptionFlags.kCFRunLoopBeforeWaiting)
{
bool triggerProcessing;
lock (Instance)
lock (Instance._sync)
{
triggerProcessing = Instance._backgroundProcessingRequested;
Instance._backgroundProcessingRequested = false;
Expand All @@ -127,7 +136,7 @@ private static void ObserverCallback(IntPtr observer, Interop.CFOptionFlags acti
Instance.CheckSignaled();
}

[MonoPInvokeCallback(typeof(Interop.CFRunLoopTimerCallback))]
[UnmanagedCallersOnly]
private static void TimerCallback(IntPtr timer, IntPtr info)
{
Instance.Timer?.Invoke();
Expand Down
25 changes: 14 additions & 11 deletions src/iOS/Avalonia.iOS/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

namespace Avalonia.iOS;

// TODO: use LibraryImport in NET7
internal class Interop
internal unsafe class Interop
{
internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
internal static NativeHandle kCFRunLoopCommonModes = CFString.CreateNative("kCFRunLoopCommonModes");
internal const string libcLibrary = "/usr/lib/libc.dylib";
internal static NativeHandle kCFRunLoopDefaultMode = CFString.CreateNative("kCFRunLoopDefaultMode");

[Flags]
internal enum CFOptionFlags : ulong
Expand All @@ -20,26 +20,29 @@ internal enum CFOptionFlags : ulong
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(libcLibrary)]
internal static extern void dispatch_async_f(IntPtr queue, IntPtr context, delegate* unmanaged<IntPtr, void> dispatch);

[DllImport(CoreFoundationLibrary)]
internal static extern IntPtr CFRunLoopGetMain();

[DllImport(CoreFoundationLibrary)]
internal static extern IntPtr CFRunLoopGetCurrent();

[DllImport (CoreFoundationLibrary)]
internal static extern void CFRunLoopWakeUp(IntPtr rl);

[DllImport(CoreFoundationLibrary)]
internal static extern IntPtr CFRunLoopObserverCreate(IntPtr allocator, CFOptionFlags activities,
bool repeats, int index, CFRunLoopObserverCallback callout, IntPtr context);
int repeats, int index, delegate* unmanaged<IntPtr, CFOptionFlags, IntPtr, void> 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);
CFOptionFlags flags, int order, delegate* unmanaged<IntPtr, IntPtr, void> callout, IntPtr context);

[DllImport(CoreFoundationLibrary)]
internal static extern void CFRunLoopTimerSetTolerance(IntPtr timer, double tolerance);
Expand Down

0 comments on commit 447b9b9

Please sign in to comment.