Browse Source

Merge pull request #10923 from AvaloniaUI/ios-dispatcher-impl

Proper implementation of DispatcherImpl for iOS
pull/10929/head
Max Katz 4 years ago
committed by GitHub
parent
commit
280640083f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 135
      src/iOS/Avalonia.iOS/DispatcherImpl.cs
  2. 55
      src/iOS/Avalonia.iOS/Interop.cs
  3. 3
      src/iOS/Avalonia.iOS/Platform.cs
  4. 31
      src/iOS/Avalonia.iOS/PlatformSettings.cs
  5. 38
      src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs

135
src/iOS/Avalonia.iOS/DispatcherImpl.cs

@ -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();
}
}

55
src/iOS/Avalonia.iOS/Interop.cs

@ -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();
}

3
src/iOS/Avalonia.iOS/Platform.cs

@ -7,6 +7,7 @@ using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia
{
@ -44,7 +45,7 @@ namespace Avalonia.iOS
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IRenderLoop>().ToSingleton<RenderLoop>()
.Bind<IRenderTimer>().ToConstant(Timer)
.Bind<IPlatformThreadingInterface>().ToConstant(new PlatformThreadingInterface())
.Bind<IDispatcherImpl>().ToConstant(DispatcherImpl.Instance)
.Bind<IKeyboardDevice>().ToConstant(keyboard);
Compositor = new Compositor(

31
src/iOS/Avalonia.iOS/PlatformSettings.cs

@ -31,24 +31,25 @@ internal class PlatformSettings : DefaultPlatformSettings
if (tintColor is not null)
{
tintColor.GetRGBA(out var red, out var green, out var blue, out var alpha);
return _lastColorValues = new PlatformColorValues
if (red != 0 && green != 0 && blue != 0 && alpha != 0)
{
ThemeVariant = themeVariant,
ContrastPreference = contrastPreference,
AccentColor1 = new Color(
(byte)(alpha * 255),
(byte)(red * 255),
(byte)(green * 255),
(byte)(blue * 255))
};
return _lastColorValues = new PlatformColorValues
{
ThemeVariant = themeVariant,
ContrastPreference = contrastPreference,
AccentColor1 = new Color(
(byte)(alpha * 255),
(byte)(red * 255),
(byte)(green * 255),
(byte)(blue * 255))
};
}
}
else
return _lastColorValues = new PlatformColorValues
{
return _lastColorValues = new PlatformColorValues
{
ThemeVariant = themeVariant, ContrastPreference = contrastPreference
};
}
ThemeVariant = themeVariant, ContrastPreference = contrastPreference
};
}
public void TraitCollectionDidChange()

38
src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs

@ -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…
Cancel
Save