Skip to content

Commit

Permalink
Merge pull request unoplatform#14105 from unoplatform/dev/jela/hr-xam…
Browse files Browse the repository at this point in the history
…lreader

fix: Restore XamlReader HR for mobile targets
  • Loading branch information
jeromelaban authored Oct 27, 2023
2 parents 351ff7b + 3817c61 commit fecdbb9
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
<CompilerVisibleProperty Include="IsUnoHead" />
<CompilerVisibleProperty Include="UnoDisableHotRestartHelperGeneration" />
<CompilerVisibleProperty Include="RuntimeIdentifier" />
<CompilerVisibleProperty Include="UnoRuntimeIdentifier" />
<CompilerVisibleProperty Include="UnoHotReloadMode" />

<CompilerVisibleProperty Include="SolutionFileName" />
<CompilerVisibleProperty Include="LangName" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ namespace Uno.UI.RemoteControl.HotReload
partial class ClientHotReloadProcessor
{
private bool _linkerEnabled;
private HotReloadAgent _agent;
private HotReloadAgent? _agent;
private bool _metadataUpdatesEnabled;
private static ClientHotReloadProcessor? _instance;
private readonly TaskCompletionSource<bool> _hotReloadWorkloadSpaceLoaded = new();

Expand All @@ -41,6 +42,8 @@ partial void InitializeMetadataUpdater()
{
_instance = this;

_metadataUpdatesEnabled = BuildMetadataUpdatesEnabled();

_linkerEnabled = string.Equals(Environment.GetEnvironmentVariable("UNO_BOOTSTRAP_LINKER_ENABLED"), "true", StringComparison.OrdinalIgnoreCase);

if (_linkerEnabled)
Expand All @@ -60,23 +63,25 @@ partial void InitializeMetadataUpdater()
});
}


private bool MetadataUpdatesEnabled
private bool BuildMetadataUpdatesEnabled()
{
get
{
var unoRuntimeIdentifier = GetMSBuildProperty("UnoRuntimeIdentifier");
var targetFramework = GetMSBuildProperty("TargetFramework");
var buildingInsideVisualStudio = GetMSBuildProperty("BuildingInsideVisualStudio");

return
buildingInsideVisualStudio.Equals("true", StringComparison.OrdinalIgnoreCase)
&& (
// As of VS 17.8, when the debugger is not attached, mobile targets can use
// DevServer's hotreload workspace, as visual studio does not enable it on its own.
(!Debugger.IsAttached
&& (targetFramework.Contains("-android") || targetFramework.Contains("-ios"))));
}
var unoRuntimeIdentifier = GetMSBuildProperty("UnoRuntimeIdentifier");
//var targetFramework = GetMSBuildProperty("TargetFramework");
//var buildingInsideVisualStudio = GetMSBuildProperty("BuildingInsideVisualStudio");

return (_forcedHotReloadMode is HotReloadMode.MetadataUpdates or HotReloadMode.Partial)
|| unoRuntimeIdentifier.Equals("skia", StringComparison.OrdinalIgnoreCase)
// Disabled until https://github.com/dotnet/runtime/issues/93860 is fixed
//
//||
//(
// buildingInsideVisualStudio.Equals("true", StringComparison.OrdinalIgnoreCase)
// && (
// // As of VS 17.8, when the debugger is not attached, mobile targets can use
// // DevServer's hotreload workspace, as visual studio does not enable it on its own.
// (!Debugger.IsAttached
// && (targetFramework.Contains("-android") || targetFramework.Contains("-ios")))))
;
}

private string[] GetMetadataUpdateCapabilities()
Expand Down Expand Up @@ -153,7 +158,7 @@ private void AssemblyReload(AssemblyDeltaReload assemblyDeltaReload)
UpdatedTypes = ReadIntArray(changedTypesReader)
};

_agent.ApplyDeltas(new[] { delta });
_agent?.ApplyDeltas(new[] { delta });

if (this.Log().IsEnabled(LogLevel.Trace))
{
Expand Down
136 changes: 136 additions & 0 deletions src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Common.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Uno.Extensions;
using Uno.Foundation.Logging;
using Uno.UI.Extensions;
using Uno.UI.Helpers;
using Uno.UI.RemoteControl.HotReload;
using Windows.Storage.Pickers.Provider;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;

#if __IOS__
using UIKit;
#elif __MACOS__
using AppKit;
#elif __ANDROID__
using Uno.UI;
#endif

[assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(Uno.UI.RemoteControl.HotReload.ClientHotReloadProcessor))]

namespace Uno.UI.RemoteControl.HotReload
{
partial class ClientHotReloadProcessor
{
private static async IAsyncEnumerable<TMatch> EnumerateHotReloadInstances<TMatch>(
object? instance,
Func<FrameworkElement, string, Task<TMatch?>> predicate,
string? parentKey)
{

if (instance is FrameworkElement fe)
{
var instanceTypeName = (instance.GetType().GetOriginalType() ?? instance.GetType()).Name;
var instanceKey = parentKey is not null ? $"{parentKey}_{instanceTypeName}" : instanceTypeName;
var match = await predicate(fe, instanceKey);
if (match is not null)
{
yield return match;
}

var idx = 0;
foreach (var child in fe.EnumerateChildren())
{
var inner = EnumerateHotReloadInstances(child, predicate, $"{instanceKey}_[{idx}]");
idx++;
await foreach (var validElement in inner)
{
yield return validElement;
}
}
}
}

private static void SwapViews(FrameworkElement oldView, FrameworkElement newView)
{
if (_log.IsEnabled(LogLevel.Trace))
{
_log.Trace($"Swapping view {newView.GetType()}");
}

#if !WINUI
var parentAsContentControl = oldView.GetVisualTreeParent() as ContentControl;
parentAsContentControl = parentAsContentControl ?? (oldView.GetVisualTreeParent() as ContentPresenter)?.FindFirstParent<ContentControl>();
#else
var parentAsContentControl = VisualTreeHelper.GetParent(oldView) as ContentControl;
parentAsContentControl = parentAsContentControl ?? (VisualTreeHelper.GetParent(oldView) as ContentPresenter)?.FindFirstParent<ContentControl>();
#endif

if ((parentAsContentControl?.Content as FrameworkElement) == oldView)
{
parentAsContentControl.Content = newView;
}
else if (newView is Page newPage && oldView is Page oldPage)
{
// In the case of Page, swapping the actual page is not supported, so we
// need to swap the content of the page instead. This can happen if the Frame
// is using a native presenter which does not use the `Frame.Content` property.

// Clear any local context, so that the new page can inherit the value coming
// from the parent Frame. It may happen if the old page set it explicitly.

#if !WINUI
oldPage.ClearValue(Page.DataContextProperty, DependencyPropertyValuePrecedences.Local);
#else
oldPage.ClearValue(Page.DataContextProperty);
#endif

oldPage.Content = newPage;
#if !WINUI
newPage.Frame = oldPage.Frame;
#endif
}
#if !WINUI
// Currently we don't have SwapViews implementation that works with WinUI
// so skip swapping non-Page views initially for WinUI
else
{
VisualTreeHelper.SwapViews(oldView, newView);
}
#endif

if (oldView is FrameworkElement oldViewAsFE && newView is FrameworkElement newViewAsFE)
{
PropagateProperties(oldViewAsFE, newViewAsFE);
}
}

private static void PropagateProperties(FrameworkElement oldView, FrameworkElement newView)
{
if (oldView == null || newView == null)
{
return;
}

if (newView.DataContext is null
&& oldView.DataContext is not null)
{
// If the DataContext is not provided by the page itself, it may
// have been provided by an external actor. Copy the value as is
// in the DataContext of the new element.

newView.DataContext = oldView.DataContext;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,20 @@ private void InitializePartialReload()

_supportsLightweightHotReload =
buildingInsideVisualStudio.Equals("true", StringComparison.OrdinalIgnoreCase)
&& (_forcedHotReloadMode is null || _forcedHotReloadMode == HotReloadMode.Partial)
&& (
// As of VS 17.8, when the debugger is attached, mobile targets don't invoke MetadataUpdateHandlers
// and both targets are not providing updated types. We simulate parts of this process
// to determine which types have been updated, particularly those with "CreateNewOnMetadataUpdate".
(Debugger.IsAttached
&& (targetFramework.Contains("-android") || targetFramework.Contains("-ios")))
//
// Disabled until https://github.com/dotnet/runtime/issues/93860 is fixed
//
//(Debugger.IsAttached
// && (targetFramework.Contains("-android") || targetFramework.Contains("-ios")))
//||

// WebAssembly does not support sending updated types, and does not support debugger based hot reload.
|| (unoRuntimeIdentifier?.Equals("WebAssembly", StringComparison.OrdinalIgnoreCase) ?? false));
(unoRuntimeIdentifier?.Equals("WebAssembly", StringComparison.OrdinalIgnoreCase) ?? false));

if (this.Log().IsEnabled(LogLevel.Trace))
{
Expand All @@ -56,18 +61,6 @@ private void InitializePartialReload()
: new();
}

private string GetMSBuildProperty(string property, string defaultValue = "")
{
var output = defaultValue;

if (_msbuildProperties is not null && !_msbuildProperties.TryGetValue(property, out output))
{
return defaultValue;
}

return output;
}

private async Task PartialReload(FileReload fileReload)
{
if (!_supportsLightweightHotReload)
Expand Down Expand Up @@ -161,14 +154,17 @@ private async Task ObserveUpdateTypeMapping()
this.Log().Trace($"Found {newTypes.Length} updated types ({types})");
}

var actions = _agent.GetMetadataUpdateHandlerActions();
if (_agent is not null)
{
var actions = _agent.GetMetadataUpdateHandlerActions();

actions.ClearCache.ForEach(a => a(newTypes));
actions.UpdateApplication.ForEach(a => a(newTypes));
actions.ClearCache.ForEach(a => a(newTypes));
actions.UpdateApplication.ForEach(a => a(newTypes));

if (this.Log().IsEnabled(LogLevel.Trace))
{
this.Log().Trace($"ObserveUpdateTypeMapping: Invoked metadata updaters");
if (this.Log().IsEnabled(LogLevel.Trace))
{
this.Log().Trace($"ObserveUpdateTypeMapping: Invoked metadata updaters");
}
}

return;
Expand Down
Loading

0 comments on commit fecdbb9

Please sign in to comment.