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 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
4 changes: 4 additions & 0 deletions build/PackageDiffIgnore.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1982,6 +1982,10 @@
<Member fullName="System.Threading.Tasks.Task`1&lt;System.Boolean&gt; Uno.UI.Helpers.TypeMappings.WaitForResume()" reason="Not really considered a public API, used by internal tooling" />
<Member fullName="System.Void Uno.UI.Helpers.TypeMappings.Resume(System.Boolean updateLayout)" reason="Not really considered a public API, used by internal tooling" />
<!-- END TypeMappings -->

<!-- BEGIN DiagnosticsOverlay -->
<Member fullName="System.Void Uno.Diagnostics.UI.DiagnosticsOverlay.Add(System.String id, System.String name, Microsoft.UI.Xaml.UIElement preview, System.Func`1&lt;Microsoft.UI.Xaml.UIElement&gt; details)" reason="Recently public, nobody uses it yet." />
<!-- END DiagnosticsOverlay -->
</Methods>
</IgnoreSet>

Expand Down
23 changes: 20 additions & 3 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 @@ -35,15 +35,17 @@ public static void Register(IDiagnosticView view, DiagnosticViewRegistrationMode
}
}

internal record DiagnosticViewRegistration(DiagnosticViewRegistrationMode Mode, IDiagnosticView View);
internal sealed record DiagnosticViewRegistration(
DiagnosticViewRegistrationMode Mode,
IDiagnosticView View);

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 +57,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,
}
2 changes: 2 additions & 0 deletions src/Uno.Foundation/Diagnostics/IDiagnosticView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public interface IDiagnosticView
/// </summary>
string Name { get; }

DiagnosticViewRegistrationPosition Position => DiagnosticViewRegistrationPosition.Normal;

/// <summary>
/// Gets a visual element of the diagnostic, usually a value or an icon.
/// </summary>
Expand Down
63 changes: 60 additions & 3 deletions 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 Expand Up @@ -223,7 +280,6 @@ public void RegisterPreProcessor(IRemoteControlPreProcessor preprocessor)

_status.Report(ConnectionState.Connecting);


const string lastEndpointKey = "__UNO__" + nameof(RemoteControlClient) + "__last_endpoint";
var preferred = ApplicationData.Current.LocalSettings.Values.TryGetValue(lastEndpointKey, out var lastValue) && lastValue is string lastEp
? _serverAddresses.FirstOrDefault(srv => srv.endpoint.Equals(lastEp, StringComparison.OrdinalIgnoreCase)).endpoint
Expand Down Expand Up @@ -429,7 +485,8 @@ private async Task<Connection> Connect(Uri serverUri, int delay, CancellationTok
{
if (this.Log().IsEnabled(LogLevel.Trace))
{
this.Log().Trace($"Connecting to [{serverUri}] failed: {e.Message}");
var innerMessage = e.InnerException is { } ie ? $" ({ie.Message})" : "";
this.Log().Trace($"Connecting to [{serverUri}] failed: {e.Message}{innerMessage}");
}

return new(this, serverUri, watch, null);
Expand Down
21 changes: 20 additions & 1 deletion src/Uno.UI.RemoteControl/RemoteControlStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,27 @@ public record RemoteControlStatus(
ImmutableHashSet<RemoteControlStatus.MissingProcessor> MissingRequiredProcessors,
(long Count, ImmutableHashSet<Type> Types) InvalidFrames)
{
public bool IsAllGood => State == ConnectionState.Connected && IsVersionValid == true && MissingRequiredProcessors.IsEmpty && KeepAlive.State == KeepAliveState.Ok && InvalidFrames.Count == 0;

/// <summary>
/// A boolean indicating if everything is fine with the connection and the handshaking succeeded.
/// </summary>
public bool IsAllGood =>
State == ConnectionState.Connected
#if !DEBUG
// For debug builds, it's annoying to have the version mismatch preventing the connection
// Only Uno devs should get this issue, let's not block them.
&& IsVersionValid == true
#endif
&& MissingRequiredProcessors.IsEmpty
&& KeepAlive.State == KeepAliveState.Ok
&& InvalidFrames.Count == 0;

/// <summary>
/// If the connection is problematic, meaning that the connection is not in a good state.
/// </summary>
/// <remarks>
/// It's just a negation of <see cref="IsAllGood"/>.
/// </remarks>
public bool IsProblematic => !IsAllGood;

public (Classification kind, string message) GetSummary()
Expand Down
9 changes: 5 additions & 4 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 @@ -316,8 +317,8 @@ public void Show(string viewId)
/// Add a UI diagnostic element to this overlay.
/// </summary>
/// <remarks>This will also make this overlay visible (cf. <see cref="Show(bool?)"/>).</remarks>
public void Add(string id, string name, UIElement preview, Func<UIElement>? details = null)
=> Add(new DiagnosticView(id, name, _ => preview, (_, ct) => new(details?.Invoke())));
public void Add(string id, string name, UIElement preview, Func<UIElement>? details = null, DiagnosticViewRegistrationPosition position = default)
=> Add(new DiagnosticView(id, name, _ => preview, (_, ct) => new(details?.Invoke()), position));

/// <summary>
/// Add a UI diagnostic element to this overlay.
Expand Down Expand Up @@ -464,8 +465,8 @@ private void EnqueueUpdate(bool forceUpdate = false)
.Where(ShouldMaterialize)
.Select(reg => reg.View)
.Concat(_localRegistrations)
.Distinct()
.ToList();
.OrderBy(r => (int)r.Position)
.Distinct();

foreach (var view in viewsThatShouldBeMaterialized)
{
Expand Down
44 changes: 31 additions & 13 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);
var provider = new DiagnosticView<TView>(typeof(TView).Name, friendlyName, () => new TView(), position: position);
DiagnosticViewRegistry.Register(provider, mode);
return provider;
}

Expand All @@ -43,10 +48,15 @@ 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);
var provider = new DiagnosticView<TView>(typeof(TView).Name, friendlyName, factory, position: position);
DiagnosticViewRegistry.Register(provider, mode);
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);
? new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, _ => new TView(), update, position: position)
: new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, _ => new TView(), update, (ctx, state, ct) => new(details(state)), position: position);
DiagnosticViewRegistry.Register(provider, mode);
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);
? new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, factory, update, position: position)
: new DiagnosticView<TView, TState>(typeof(TView).Name, friendlyName, factory, update, (ctx, state, ct) => new(details(state)), position: position);
DiagnosticViewRegistry.Register(provider, mode);
return provider;
}
}
5 changes: 4 additions & 1 deletion src/Uno.UI/Diagnostics/DiagnosticView.TView.TState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public class DiagnosticView<TView, TState>(
string name,
Func<IDiagnosticViewContext, TView> factory,
Action<TView, TState> update,
Func<IDiagnosticViewContext, TState, CancellationToken, ValueTask<object?>>? details = null)
Func<IDiagnosticViewContext, TState, CancellationToken, ValueTask<object?>>? details = null,
DiagnosticViewRegistrationPosition position = default)
: IDiagnosticView
where TView : FrameworkElement
{
Expand All @@ -37,6 +38,8 @@ public void Update(TState status)
/// <inheritdoc />
string IDiagnosticView.Name => name;

DiagnosticViewRegistrationPosition IDiagnosticView.Position => position;

/// <inheritdoc />
object IDiagnosticView.GetElement(IDiagnosticViewContext context)
=> _elementsManager.GetView(context);
Expand Down
10 changes: 7 additions & 3 deletions src/Uno.UI/Diagnostics/DiagnosticView.TView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ public class DiagnosticView<TView>(
string id,
string name,
Func<IDiagnosticViewContext, TView> factory,
Func<IDiagnosticViewContext, CancellationToken, ValueTask<object?>>? details = null)
Func<IDiagnosticViewContext, CancellationToken, ValueTask<object?>>? details = null,
DiagnosticViewRegistrationPosition position = default)
: IDiagnosticView
where TView : UIElement
{
public DiagnosticView(
string id,
string name,
Func<TView> preview,
Func<CancellationToken, ValueTask<object?>>? details = null)
: this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct))
Func<CancellationToken, ValueTask<object?>>? details = null,
DiagnosticViewRegistrationPosition position = default)
: this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct), position)
{
}

Expand All @@ -33,6 +35,8 @@ public DiagnosticView(
/// <inheritdoc />
string IDiagnosticView.Name => name;

DiagnosticViewRegistrationPosition IDiagnosticView.Position => position;

/// <inheritdoc />
object IDiagnosticView.GetElement(IDiagnosticViewContext context) => factory(context);

Expand Down
10 changes: 6 additions & 4 deletions src/Uno.UI/Diagnostics/DiagnosticView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ public partial class DiagnosticView(
string id,
string name,
Func<IDiagnosticViewContext, UIElement> factory,
Func<IDiagnosticViewContext, CancellationToken, ValueTask<object?>>? details = null)
: DiagnosticView<UIElement>(id, name, factory, details)
Func<IDiagnosticViewContext, CancellationToken, ValueTask<object?>>? details = null,
DiagnosticViewRegistrationPosition position = default)
: DiagnosticView<UIElement>(id, name, factory, details, position)
{
public DiagnosticView(
string id,
string name,
Func<UIElement> preview,
Func<CancellationToken, ValueTask<object?>>? details = null)
: this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct))
Func<CancellationToken, ValueTask<object?>>? details = null,
DiagnosticViewRegistrationPosition position = default)
: this(id, name, _ => preview(), async (_, ct) => details is null ? null : await details(ct), position)
{
}
}
4 changes: 3 additions & 1 deletion src/Uno.UI/Diagnostics/DiagnosticViewManager.TView.TState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ namespace Uno.Diagnostics.UI;
/// <typeparam name="TState">Type of the state used to update the <typeparamref name="TView"/>.</typeparam>
/// <param name="factory">Factory to create an instance of the <typeparamref name="TView"/>.</param>
/// <param name="update">Delegate to use to update the <typeparamref name="TView"/> on <see cref="NotifyChanged"/>.</param>
internal class DiagnosticViewManager<TView, TState>(Func<IDiagnosticViewContext, TView> factory, Action<IDiagnosticViewContext, TView, TState> update)
internal class DiagnosticViewManager<TView, TState>(
Func<IDiagnosticViewContext, TView> factory,
Action<IDiagnosticViewContext, TView, TState> update)
where TView : FrameworkElement
{
private event EventHandler<TState>? _changed;
Expand Down
Loading