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

DiagnosticView Ordering #18645

Merged
merged 6 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
45 changes: 40 additions & 5 deletions src/Uno.Foundation/Diagnostics/DiagnosticViewRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal static class DiagnosticViewRegistry
{
internal static EventHandler<IImmutableList<DiagnosticViewRegistration>>? Added;

private static ImmutableArray<DiagnosticViewRegistration> _registrations = ImmutableArray<DiagnosticViewRegistration>.Empty;
private static ImmutableArray<DiagnosticViewRegistration> _registrations = [];

/// <summary>
/// Gets the list of registered diagnostic providers.
Expand All @@ -24,26 +24,46 @@ internal static class DiagnosticViewRegistry
/// </summary>
/// <param name="view">A diagnostic view to display.</param>
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
public static void Register(IDiagnosticView view, DiagnosticViewRegistrationMode mode = default)
public static void Register(IDiagnosticView view, DiagnosticViewRegistrationMode mode = default, DiagnosticViewRegistrationPosition position = default)
{
ImmutableInterlocked.Update(
ref _registrations,
static (providers, provider) => providers.Add(provider),
new DiagnosticViewRegistration(mode, view));
new DiagnosticViewRegistration(mode, position, view));

Added?.Invoke(null, _registrations);
}
}

internal record DiagnosticViewRegistration(DiagnosticViewRegistrationMode Mode, IDiagnosticView View);
internal sealed record DiagnosticViewRegistration(
DiagnosticViewRegistrationMode Mode,
DiagnosticViewRegistrationPosition Position,
IDiagnosticView View) : IComparable<DiagnosticViewRegistration>
{
public int CompareTo(DiagnosticViewRegistration? other)
{
if (other is null)
{
return 1;
}

if (Position == other.Position)
{
// If the position is the same, we compare the view id to ensure a stable order.
return string.Compare(View.Id, other.View.Id, StringComparison.Ordinal);
}

return (int)Position - (int)other.Position;
}
}

public enum DiagnosticViewRegistrationMode
{
/// <summary>
/// Diagnostic is being display on at least one window.
/// I.e. only the main/first opened but move to the next one if the current window is closed.
/// </summary>
One,
One, // Default

/// <summary>
/// Diagnostic is being rendered as overlay on each window.
Expand All @@ -55,3 +75,18 @@ public enum DiagnosticViewRegistrationMode
/// </summary>
OnDemand
}

public enum DiagnosticViewRegistrationPosition
{
Normal = 0, // Default

/// <summary>
/// Register as the first diagnostic view, ensuring it is displayed first.
/// </summary>
First = -1,

/// <summary>
/// Register as the last diagnostic view, ensuring it is displayed last.
/// </summary>
Last = 1,
}
59 changes: 58 additions & 1 deletion src/Uno.UI.RemoteControl/RemoteControlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,63 @@ public partial class RemoteControlClient : IRemoteControlClient
public delegate void RemoteControlClientEventEventHandler(object sender, ClientEventEventArgs args);
public delegate void SendMessageFailedEventHandler(object sender, SendMessageFailedEventArgs args);

public static RemoteControlClient? Instance { get; private set; }
public static RemoteControlClient? Instance
{
get => _instance;
private set
{
_instance = value;

if (value is { })
{
while (Interlocked.Exchange(ref _waitingList, null) is { } waitingList)
{
foreach (var action in waitingList)
{
action(value);
}
}
}
}
}

private static IReadOnlyCollection<Action<RemoteControlClient>>? _waitingList;

/// <summary>
/// Add a callback to be called when the Instance is available.
/// </summary>
/// <remarks>
/// Will be called synchronously if the instance is already available, no need to check for it before.
/// </remarks>
public static void OnRemoteControlClientAvailable(Action<RemoteControlClient> action)
{
if (Instance is { })
{
action(Instance);
}
else
{
// Thread-safe way to add the action to a waiting list for the client to be available
while (true)
{
var waitingList = _waitingList;
IReadOnlyCollection<Action<RemoteControlClient>> newList = waitingList is null
? [action]
: [.. waitingList, action];

if (Instance is { } i) // Last chance to avoid the waiting list
{
action(i);
break;
}

if (ReferenceEquals(Interlocked.CompareExchange(ref _waitingList, newList, waitingList), waitingList))
{
break;
}
}
}
}

public static RemoteControlClient Initialize(Type appType)
=> Instance = new RemoteControlClient(appType);
Expand All @@ -59,6 +115,7 @@ internal static RemoteControlClient Initialize(Type appType, ServerEndpointAttri

private readonly StatusSink _status;
private static readonly TimeSpan _keepAliveInterval = TimeSpan.FromSeconds(30);
private static RemoteControlClient? _instance;
private readonly (string endpoint, int port)[]? _serverAddresses;
private readonly Dictionary<string, IClientProcessor> _processors = new();
private readonly List<IRemoteControlPreProcessor> _preprocessors = new();
Expand Down
7 changes: 4 additions & 3 deletions src/Uno.UI.Toolkit/Diagnostics/DiagnosticsOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#if WINUI || HAS_UNO_WINUI
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -462,10 +463,10 @@ private void EnqueueUpdate(bool forceUpdate = false)
var viewsThatShouldBeMaterialized = DiagnosticViewRegistry
.Registrations
.Where(ShouldMaterialize)
.Order() // See DiagnosticViewRegistration.CompareTo
.Select(reg => reg.View)
.Concat(_localRegistrations)
.Distinct()
.ToList();
.Concat(_localRegistrations) // They are at the end of the list.
.Distinct();

foreach (var view in viewsThatShouldBeMaterialized)
{
Expand Down
34 changes: 26 additions & 8 deletions src/Uno.UI/Diagnostics/DiagnosticView.Factories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ partial class DiagnosticView
/// </remarks>
/// <typeparam name="TView">Type of the control.</typeparam>
/// <param name="friendlyName">The user-friendly name of the diagnostics view.</param>
public static DiagnosticView<TView> Register<TView>(string friendlyName)
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
/// <param name="position">Defines where the item should be placed in the overlay.</param>
public static DiagnosticView<TView> Register<TView>(
string friendlyName,
DiagnosticViewRegistrationMode mode = default,
DiagnosticViewRegistrationPosition position = default)
where TView : UIElement, new()
{
var provider = new DiagnosticView<TView>(typeof(TView).Name, friendlyName, () => new TView());
DiagnosticViewRegistry.Register(provider);
DiagnosticViewRegistry.Register(provider, mode, position);
return provider;
}

Expand All @@ -43,11 +48,16 @@ public static DiagnosticView<TView> Register<TView>(string friendlyName)
/// <param name="friendlyName">The user-friendly name of the diagnostics view.</param>
/// <param name="factory">Factory to create an instance of the control.</param>
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
public static DiagnosticView<TView> Register<TView>(string friendlyName, Func<TView> factory, DiagnosticViewRegistrationMode mode = default)
/// <param name="position">Defines where the item should be placed in the overlay.</param>
public static DiagnosticView<TView> Register<TView>(
string friendlyName,
Func<TView> factory,
DiagnosticViewRegistrationMode mode = default,
DiagnosticViewRegistrationPosition position = default)
where TView : UIElement
{
var provider = new DiagnosticView<TView>(typeof(TView).Name, friendlyName, factory);
DiagnosticViewRegistry.Register(provider, mode);
DiagnosticViewRegistry.Register(provider, mode, position);
return provider;
}

Expand All @@ -62,17 +72,21 @@ public static DiagnosticView<TView> Register<TView>(string friendlyName, Func<TV
/// <param name="friendlyName">The user-friendly name of the diagnostics view.</param>
/// <param name="update">Delegate to use to update the <typeparamref name="TView"/> when the <typeparamref name="TState"/> is being updated.</param>
/// <param name="details">Optional delegate used to show more details about the diagnostic info when user taps on the view.</param>
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
/// <param name="position">Defines where the item should be placed in the overlay.</param>
/// <returns>A diagnostic view helper class which can be used to push updates of the state (cf. <see cref="DiagnosticView{TView,TState}.Update"/>).</returns>
public static DiagnosticView<TView, TState> Register<TView, TState>(
string friendlyName,
Action<TView, TState> update,
Func<TState, object?>? details = null)
Func<TState, object?>? details = null,
DiagnosticViewRegistrationMode mode = default,
DiagnosticViewRegistrationPosition position = default)
where TView : FrameworkElement, new()
{
var provider = details is null
? new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, _ => new TView(), update)
: new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, _ => new TView(), update, (ctx, state, ct) => new(details(state)));
DiagnosticViewRegistry.Register(provider);
DiagnosticViewRegistry.Register(provider, mode, position);
return provider;
}

Expand All @@ -88,18 +102,22 @@ public static DiagnosticView<TView, TState> Register<TView, TState>(
/// <param name="factory">Factory to create an instance of the generic element.</param>
/// <param name="update">Delegate to use to update the <typeparamref name="TView"/> when the <typeparamref name="TState"/> is being updated.</param>
/// <param name="details">Optional delegate used to show more details about the diagnostic info when user taps on the view.</param>
/// <param name="mode">Defines when the registered diagnostic view should be displayed.</param>
/// <param name="position">Defines where the item should be placed in the overlay.</param>
/// <returns>A diagnostic view helper class which can be used to push updates of the state (cf. <see cref="DiagnosticView{TView,TState}.Update"/>).</returns>
public static DiagnosticView<TView, TState> Register<TView, TState>(
string friendlyName,
Func<IDiagnosticViewContext, TView> factory,
Action<TView, TState> update,
Func<TState, object?>? details = null)
Func<TState, object?>? details = null,
DiagnosticViewRegistrationMode mode = default,
DiagnosticViewRegistrationPosition position = default)
where TView : FrameworkElement
{
var provider = details is null
? new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, factory, update)
: new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, factory, update, (ctx, state, ct) => new(details(state)));
DiagnosticViewRegistry.Register(provider);
DiagnosticViewRegistry.Register(provider, mode, position);
return provider;
}
}
Loading