Skip to content

Commit

Permalink
chore(x11): use a Timer instead of a DispatcherTimer for render callb…
Browse files Browse the repository at this point in the history
…acks
  • Loading branch information
ramezgerges committed Dec 10, 2024
1 parent ee8d7d9 commit 32885bd
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 32 deletions.
5 changes: 3 additions & 2 deletions src/Uno.UI.Runtime.Skia.X11/X11WindowWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,17 @@ public override void ExtendContentIntoTitleBar(bool extend)
_host.ExtendContentIntoTitleBar(extend);
}

private void OnWindowClosing()
private bool OnWindowClosing()
{
var closingArgs = RaiseClosing();
if (closingArgs.Cancel)
{
return;
return false;
}

// All prerequisites passed, can safely close.
Close();
return true;
}

private void OnNativeActivated(bool focused) => ActivationState = focused ? CoreWindowActivationState.PointerActivated : CoreWindowActivationState.Deactivated;
Expand Down
56 changes: 30 additions & 26 deletions src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using SkiaSharp;
using Uno.Disposables;
using Uno.UI;
using Uno.UI.Dispatching;
using Uno.UI.Xaml.Controls;

namespace Uno.WinUI.Runtime.Skia.X11;
Expand Down Expand Up @@ -73,8 +74,7 @@ internal partial class X11XamlRootHost : IXamlRootHost
private readonly ApplicationView _applicationView;
private readonly X11WindowWrapper _wrapper;
private readonly Window _window;
private readonly CompositeDisposable _disposables = new();
private readonly XamlRoot _xamlRoot;
private readonly IDisposable _windowBackgroundDisposable;

private int _synchronizedShutDownTopWindowIdleCounter;

Expand All @@ -83,14 +83,14 @@ internal partial class X11XamlRootHost : IXamlRootHost
private IX11Renderer? _renderer;

private bool _renderDirty = true;
private readonly DispatcherTimer _renderTimer;
// ReSharper disable once NotAccessedField.Local
private readonly Timer _timer; // this field is necessary to prevent the timer from being GCed

public X11Window RootX11Window => _x11Window!.Value;
public X11Window TopX11Window => _x11TopWindow!.Value;

public X11XamlRootHost(X11WindowWrapper wrapper, Window winUIWindow, XamlRoot xamlRoot, Action configureCallback, Action closingCallback, Action<bool> focusCallback, Action<bool> visibilityCallback)
public X11XamlRootHost(X11WindowWrapper wrapper, Window winUIWindow, XamlRoot xamlRoot, Action configureCallback, Func<bool> closingCallback, Action<bool> focusCallback, Action<bool> visibilityCallback)
{
_xamlRoot = xamlRoot;
_wrapper = wrapper;
_window = winUIWindow;

Expand All @@ -99,22 +99,32 @@ public X11XamlRootHost(X11WindowWrapper wrapper, Window winUIWindow, XamlRoot xa
_visibilityCallback = visibilityCallback;
_configureCallback = configureCallback;

_renderTimer = new DispatcherTimer();
_renderTimer.Interval = TimeSpan.FromSeconds(1.0 / X11ApplicationHost.RenderFrameRate); // we're on the UI thread
_renderTimer.Tick += (sender, o) =>
// running XConfigureEvent and rendering callbacks on a timer is necessary to avoid freezing in certain situations
// where we get spammed with these events. Most notably, when resizing a window by dragging an edge, we'll get spammed
// with XConfigureEvents.
_timer = new Timer(static obj =>
{
if (Interlocked.Exchange(ref _needsConfigureCallback, 0) == 1)
{
_configureCallback();
}
var @this = (X11XamlRootHost)obj!;
var needsConfigureCallback = @this._needsConfigureCallback;
var needsRender = @this._renderDirty;

if (_renderDirty)
if (needsConfigureCallback || needsRender)
{
_renderDirty = false;
_renderer?.Render();
NativeDispatcher.Main.Enqueue(() =>
{
if (needsConfigureCallback)
{
@this._needsConfigureCallback = false;
@this._configureCallback();
}
if (needsRender)
{
@this._renderDirty = false;
@this._renderer?.Render();
}
});
}
};
_renderTimer.Start();
}, this, TimeSpan.Zero, TimeSpan.FromSeconds(1.0 / X11ApplicationHost.RenderFrameRate));

_applicationView = ApplicationView.GetForWindowId(winUIWindow.AppWindow.Id);
_applicationView.PropertyChanged += OnApplicationViewPropertyChanged;
Expand Down Expand Up @@ -150,7 +160,8 @@ public X11XamlRootHost(X11WindowWrapper wrapper, Window winUIWindow, XamlRoot xa
// only start listening to events after we're done setting everything up
InitializeX11EventsThread();

RegisterForBackgroundColor();
_windowBackgroundDisposable = _window.RegisterBackgroundChangedEvent((_, _) => UpdateRendererBackground());
UpdateRendererBackground();
}

public static X11XamlRootHost? GetHostFromWindow(Window window)
Expand Down Expand Up @@ -444,7 +455,7 @@ private unsafe static X11Window CreateGLXWindow(IntPtr display, int screen, Size
}

IntPtr context = GlxInterface.glXCreateNewContext(display, bestFbc, GlxConsts.GLX_RGBA_TYPE, IntPtr.Zero, /* True */ 1);
var _1 = XLib.XSync(display, false);
_ = XLib.XSync(display, false);

XSetWindowAttributes attribs = default;
attribs.border_pixel = XLib.XBlackPixel(display, screen);
Expand Down Expand Up @@ -593,13 +604,6 @@ private void SynchronizedShutDown(X11Window x11Window)
}
}

private void RegisterForBackgroundColor()
{
UpdateRendererBackground();

_disposables.Add(_window.RegisterBackgroundChangedEvent((s, e) => UpdateRendererBackground()));
}

private void UpdateRendererBackground()
{
if (_window.Background is Microsoft.UI.Xaml.Media.SolidColorBrush brush)
Expand Down
14 changes: 10 additions & 4 deletions src/Uno.UI.Runtime.Skia.X11/X11XamlRootHost.x11events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ internal partial class X11XamlRootHost
{
private static int _threadCount;

private readonly Action _closingCallback;
private readonly Func<bool> _closingCallback;
private readonly Action<bool> _focusCallback;
private readonly Action<bool> _visibilityCallback;
private readonly Action _configureCallback;

private int _needsConfigureCallback;
private bool _needsConfigureCallback;

private X11PointerInputSource? _pointerSource;
private X11KeyboardInputSource? _keyboardSource;
Expand Down Expand Up @@ -185,7 +185,13 @@ static IEnumerable<XEvent> GetEvents(IntPtr display)
// which, according to the source code, just calls XKillClient
// https://gitlab.freedesktop.org/xorg/app/xkill/-/blob/a5f704e4cd30f03859f66bafd609a75aae27cc8c/xkill.c#L234
// In the case of xkill, we can't really do much, it's similar to a SIGKILL but for x connections
QueueAction(this, _closingCallback);
QueueAction(this, () =>
{
if (_closingCallback())
{
_windowBackgroundDisposable.Dispose();
}
});
}
else if (@event.ClientMessageEvent.message_type == X11Helper.GetAtom(x11Window.Display, X11Helper.XdndEnter) ||
@event.ClientMessageEvent.message_type == X11Helper.GetAtom(x11Window.Display, X11Helper.XdndPosition) ||
Expand All @@ -197,7 +203,7 @@ static IEnumerable<XEvent> GetEvents(IntPtr display)
}
break;
case XEventName.ConfigureNotify:
Interlocked.Exchange(ref _needsConfigureCallback, 1);
_needsConfigureCallback = true;
break;
case XEventName.FocusIn:
QueueAction(this, () => _focusCallback(true));
Expand Down

0 comments on commit 32885bd

Please sign in to comment.