Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix iOS Dispatcher #13942

Merged
merged 4 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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