From 6d3ec36749e93c7452ff9803138db1d97aa55cf9 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Thu, 21 Oct 2021 17:06:08 -0400 Subject: [PATCH] feat: Add mixed C#/XAML hot reload support --- .../Content/Uno.UI.SourceGenerators.props | 2 + .../XamlGenerator/GenerationInfo.cs | 26 ++++ .../XamlGenerator/GenerationInfoManager.cs | 60 ++++++++ .../XamlGenerator/GenerationRunFileInfo.cs | 34 +++++ .../XamlGenerator/XamlCodeGeneration.cs | 5 +- .../XamlGenerator/XamlCodeGenerator.cs | 6 +- .../XamlGenerator/XamlFileGenerator.cs | 130 +++++++++++------- ...amlLazyApplyBlockIIndentedStringBuilder.cs | 7 +- .../HotReload/ClientHotReloadProcessor.cs | 4 +- src/Uno.UI/AssemblyInfo.cs | 5 +- .../DataBinding/BindingPropertyHelper.cs | 11 ++ src/Uno.UI/DebugHelper.cs | 3 +- .../3.0.0.0/Windows.UI.Xaml/Application.cs | 11 +- .../Helpers/MetadataUpdateHandlerAttribute.cs | 19 +++ .../RuntimeTypeMetadataUpdateHandler.cs | 25 ++++ src/Uno.UI/UI/Xaml/Application.Components.cs | 33 +++++ src/Uno.UI/UI/Xaml/Application.cs | 1 + src/Uno.UI/UI/Xaml/ApplicationHelper.cs | 8 +- .../Xaml/Markup/Reader/XamlObjectBuilder.cs | 92 ++++++++----- .../UI/Xaml/Markup/Reader/XamlTypeResolver.cs | 69 +++++----- src/Uno.UI/UI/Xaml/Markup/XamlReader.cs | 11 +- 21 files changed, 429 insertions(+), 133 deletions(-) create mode 100644 src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationInfo.cs create mode 100644 src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationInfoManager.cs create mode 100644 src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationRunFileInfo.cs create mode 100644 src/Uno.UI/Helpers/MetadataUpdateHandlerAttribute.cs create mode 100644 src/Uno.UI/RuntimeTypeMetadataUpdateHandler.cs create mode 100644 src/Uno.UI/UI/Xaml/Application.Components.cs diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props b/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props index c85d32503a20..83ebd9cc1f96 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props @@ -369,6 +369,8 @@ + + diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationInfo.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationInfo.cs new file mode 100644 index 000000000000..4b6bce6b0e80 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationInfo.cs @@ -0,0 +1,26 @@ +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Uno.UI.SourceGenerators.XamlGenerator +{ + internal class GenerationRunInfo + { + private ConcurrentDictionary _fileInfo = new ConcurrentDictionary(); + + internal GenerationRunInfo(GenerationRunInfoManager manager, int index) + { + Index = index; + Manager = manager; + } + + internal GenerationRunInfoManager Manager { get; } + + internal int Index { get; } + + internal GenerationRunFileInfo GetRunFileInfo(string fileId) + => _fileInfo.GetOrAdd(fileId, f => new GenerationRunFileInfo(this, f)); + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationInfoManager.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationInfoManager.cs new file mode 100644 index 000000000000..99428d60762c --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationInfoManager.cs @@ -0,0 +1,60 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Uno.Roslyn; + +#if NETFRAMEWORK +using Uno.SourceGeneration; +#endif + +namespace Uno.UI.SourceGenerators.XamlGenerator +{ + internal class GenerationRunInfoManager + { + private List _runs = new List(); + private DateTime _previousWriteTime; + + internal GenerationRunInfoManager() + { + } + + public IEnumerable PreviousRuns + => _runs.AsEnumerable(); + + internal GenerationRunInfo CreateRun() + { + var runInfo = new GenerationRunInfo(this, _runs.Count); + + _runs.Add(runInfo); + + return runInfo; + } + + internal void Update(GeneratorExecutionContext context) + { + var intermediateOutputPath = context.GetMSBuildPropertyValue("IntermediateOutputPath"); + + if (intermediateOutputPath != null) + { + var runFilePath = Path.Combine(intermediateOutputPath, "build-time-generator.touch"); + + if (File.Exists(runFilePath)) + { + var lastWriteTime = new FileInfo(runFilePath).LastWriteTime; + + if(lastWriteTime > _previousWriteTime) + { + _previousWriteTime = lastWriteTime; + + // Clear the existing runs if a full build has been started + _runs.Clear(); + } + } + } + } + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationRunFileInfo.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationRunFileInfo.cs new file mode 100644 index 000000000000..e8db6ba96547 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/GenerationRunFileInfo.cs @@ -0,0 +1,34 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace Uno.UI.SourceGenerators.XamlGenerator +{ + internal class GenerationRunFileInfo + { + private string _fileId; + private Dictionary _appliedTypes = new Dictionary(); + + public GenerationRunFileInfo(GenerationRunInfo runInfo,string fileId) + { + _fileId = fileId; + RunInfo = runInfo; + } + + internal GenerationRunInfo RunInfo { get; } + + internal string? ComponentCode { get; set; } + + internal IReadOnlyDictionary AppliedTypes => _appliedTypes; + + internal void SetAppliedTypes(Dictionary appliedTypes) + { + foreach(var type in appliedTypes) + { + _appliedTypes.Add(type.Key, type.Value); + } + } + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs index c868107de330..fe3613d11b1c 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs @@ -248,7 +248,7 @@ private string GetSourceLink(MSBuildItem projectItemInstance) return link; } - public KeyValuePair[] Generate() + public KeyValuePair[] Generate(GenerationRunInfo generationRunInfo) { var stopwatch = Stopwatch.StartNew(); @@ -315,7 +315,8 @@ public KeyValuePair[] Generate() isUnoAssembly: IsUnoAssembly, isLazyVisualStateManagerEnabled: _isLazyVisualStateManagerEnabled, generatorContext: _generatorContext, - xamlResourcesTrimming: _xamlResourcesTrimming + xamlResourcesTrimming: _xamlResourcesTrimming, + generationRunFileInfo: generationRunInfo.GetRunFileInfo(file.UniqueID) ) .GenerateFile() ) diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs index 1564c2579408..738f1de07773 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs @@ -21,6 +21,8 @@ namespace Uno.UI.SourceGenerators.XamlGenerator [Generator] public class XamlCodeGenerator : ISourceGenerator { + private readonly GenerationRunInfoManager _generationRunInfoManager = new GenerationRunInfoManager(); + public void Initialize(GeneratorInitializationContext context) { DependenciesInitializer.Init(); @@ -36,8 +38,10 @@ public void Execute(GeneratorExecutionContext context) if (PlatformHelper.IsValidPlatform(context)) { + _generationRunInfoManager.Update(context); + var gen = new XamlCodeGeneration(context); - var genereratedTrees = gen.Generate(); + var genereratedTrees = gen.Generate(_generationRunInfoManager.CreateRun()); foreach (var tree in genereratedTrees) { diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs index 66dfeadce1b9..57eeb07153ad 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs @@ -117,7 +117,15 @@ internal partial class XamlFileGenerator // visual tree. See https://github.com/unoplatform/uno/issues/61 private readonly bool _skipUserControlsInVisualTree; - private readonly List _xamlAppliedTypes = new List(); + /// + /// Holds information about multiple generator runs + /// + private readonly GenerationRunFileInfo _generationRunFileInfo; + + /// + /// Information about types used in .Apply() scenarios + /// + private readonly Dictionary _xamlAppliedTypes = new Dictionary(); private readonly INamedTypeSymbol _elementStubSymbol; private readonly INamedTypeSymbol _contentPresenterSymbol; @@ -218,7 +226,8 @@ public XamlFileGenerator( bool isUnoAssembly, bool isLazyVisualStateManagerEnabled, GeneratorExecutionContext generatorContext, - bool xamlResourcesTrimming) + bool xamlResourcesTrimming, + GenerationRunFileInfo generationRunFileInfo) { _fileDefinition = file; _targetPath = targetPath; @@ -239,6 +248,7 @@ public XamlFileGenerator( _isLazyVisualStateManagerEnabled = isLazyVisualStateManagerEnabled; _generatorContext = generatorContext; _xamlResourcesTrimming = xamlResourcesTrimming; + _generationRunFileInfo = generationRunFileInfo; InitCaches(); @@ -411,60 +421,49 @@ private string InnerGenerateFile() using (writer.BlockInvariant("partial class {0} : {1}", _className.className, controlBaseType.ToDisplayString())) { - var isDirectUserControlChild = _skipUserControlsInVisualTree && IsUserControl(topLevelControl.Type, checkInheritance: false); - using (Scope(_className.ns, _className.className)) { - using (writer.BlockInvariant(BuildControlInitializerDeclaration(topLevelControl))) + if (_generationRunFileInfo.RunInfo.Index == 0) { - if (IsApplication(topLevelControl.Type)) - { - BuildApplicationInitializerBody(writer, topLevelControl); - } - else + var componentBuilder = new IndentedStringBuilder(); + + using (componentBuilder.Indent(writer.CurrentLevel)) { - BuildGenericControlInitializerBody(writer, topLevelControl, isDirectUserControlChild); - BuildNamedResources(writer, _namedResources); - } + BuildInitializeComponent(componentBuilder, topLevelControl, controlBaseType, false); - BuildCompiledBindingsInitializer(writer, _className.className, controlBaseType); + _generationRunFileInfo.SetAppliedTypes(_xamlAppliedTypes); - if (isDirectUserControlChild) - { - writer.AppendLineInvariant("return content;"); - } - } + TryBuildElementStubHolders(componentBuilder); - if (isDirectUserControlChild) - { - using (writer.BlockInvariant("public {0}(bool skipsInitializeComponents)", _className.className)) - { + BuildPartials(componentBuilder, isStatic: false); + + BuildBackingFields(componentBuilder); + + BuildChildSubclasses(componentBuilder); + + BuildComponentFields(componentBuilder); + + BuildCompiledBindings(componentBuilder, _className.className); + + _generationRunFileInfo.ComponentCode = componentBuilder.ToString(); } - using (writer.BlockInvariant("private void InitializeComponent()")) + writer.AppendLineInvariant("{0}", componentBuilder.ToString()); + } + else + { + if (_generationRunFileInfo.RunInfo.Manager.PreviousRuns.FirstOrDefault(r => r.GetRunFileInfo(_fileUniqueId)?.ComponentCode != null) is var runFileInfo) { - writer.AppendLineInvariant("Content = (_View)GetContent();"); + var generationRunFileInfo = runFileInfo.GetRunFileInfo(_fileUniqueId); - BuildCompiledBindingsInitializer(writer, _className.className, controlBaseType); + writer.AppendLineInvariant("{0}", generationRunFileInfo.ComponentCode); - if (_isDebug) + foreach(var type in generationRunFileInfo.AppliedTypes) { - writer.AppendLineInvariant($"global::Uno.UI.FrameworkElementHelper.SetBaseUri(this, \"file:///{_fileDefinition.FilePath.Replace("\\", "/")}\");"); + _xamlAppliedTypes.Add(type.Key, type.Value); } } } - - TryBuildElementStubHolders(writer); - - BuildPartials(writer, isStatic: false); - - BuildBackingFields(writer); - - BuildChildSubclasses(writer); - - BuildComponentFields(writer); - - BuildCompiledBindings(writer, _className.className); } } } @@ -476,6 +475,41 @@ private string InnerGenerateFile() return writer.ToString(); } + private void BuildInitializeComponent(IndentedStringBuilder writer, XamlObjectDefinition topLevelControl, INamedTypeSymbol controlBaseType, bool isDirectUserControlChild) + { + using (writer.BlockInvariant($"private void InitializeComponent()")) + { + if (IsApplication(topLevelControl.Type)) + { + BuildApplicationInitializerBody(writer, topLevelControl); + } + else + { + if (_isDebug) + { + // Insert hot reload support + writer.AppendLineInvariant($"var __resourceLocator = new global::System.Uri(\"file:///{_fileDefinition.FilePath.Replace("\\", "/")}\");"); + + using (writer.BlockInvariant($"if(global::Uno.UI.ApplicationHelper.IsLoadableComponent(__resourceLocator))")) + { + writer.AppendLineInvariant($"global::Windows.UI.Xaml.Application.LoadComponent(this, __resourceLocator);"); + writer.AppendLineInvariant($"return;"); + } + } + + BuildGenericControlInitializerBody(writer, topLevelControl, isDirectUserControlChild); + BuildNamedResources(writer, _namedResources); + } + + BuildCompiledBindingsInitializer(writer, _className.className, controlBaseType); + + if (isDirectUserControlChild) + { + writer.AppendLineInvariant("return content;"); + } + } + } + private void BuildXamlApplyBlocks(IndentedStringBuilder writer) { TryAnnotateWithGeneratorSource(writer); @@ -483,13 +517,13 @@ private void BuildXamlApplyBlocks(IndentedStringBuilder writer) { using (writer.BlockInvariant("static class {0}XamlApplyExtensions", _fileUniqueId)) { - foreach (var typeInfo in _xamlAppliedTypes.Select((type, i) => new { type, Index = i })) + foreach (var typeInfo in _xamlAppliedTypes) { - writer.AppendLineInvariant($"public delegate void XamlApplyHandler{typeInfo.Index}({GetGlobalizedTypeName(typeInfo.type.ToString())} instance);"); + writer.AppendLineInvariant($"public delegate void XamlApplyHandler{typeInfo.Value}({GetGlobalizedTypeName(typeInfo.Key.ToString())} instance);"); writer.AppendLineInvariant($"[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]"); using (writer.BlockInvariant( - $"public static {GetGlobalizedTypeName(typeInfo.type.ToString())} {_fileUniqueId}_XamlApply(this {GetGlobalizedTypeName(typeInfo.type.ToString())} instance, XamlApplyHandler{typeInfo.Index} handler)" + $"public static {GetGlobalizedTypeName(typeInfo.Key.ToString())} {_fileUniqueId}_XamlApply(this {GetGlobalizedTypeName(typeInfo.Key.ToString())} instance, XamlApplyHandler{typeInfo.Value} handler)" )) { writer.AppendLineInvariant($"handler(instance);"); @@ -3672,15 +3706,13 @@ private XamlLazyApplyBlockIIndentedStringBuilder CreateApplyBlock(IIndentedStrin if (appliedType != null) { - int typeIndex = _xamlAppliedTypes.IndexOf(appliedType); - - if (typeIndex == -1) + if (!_xamlAppliedTypes.TryGetValue(appliedType, out var appliedTypeIndex)) { - _xamlAppliedTypes.Add(appliedType); - typeIndex = _xamlAppliedTypes.Count - 1; + appliedTypeIndex = _xamlAppliedTypes.Count; + _xamlAppliedTypes.Add(appliedType, _xamlAppliedTypes.Count); } - delegateType = $"{_fileUniqueId}XamlApplyExtensions.XamlApplyHandler{typeIndex}"; + delegateType = $"{_fileUniqueId}XamlApplyExtensions.XamlApplyHandler{appliedTypeIndex}"; } return new XamlLazyApplyBlockIIndentedStringBuilder(writer, closureName, appliedType != null ? _fileUniqueId : null, delegateType); diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlLazyApplyBlockIIndentedStringBuilder.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlLazyApplyBlockIIndentedStringBuilder.cs index c39638954335..3ed9ec88f0b5 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlLazyApplyBlockIIndentedStringBuilder.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlLazyApplyBlockIIndentedStringBuilder.cs @@ -12,7 +12,7 @@ namespace Uno.UI.SourceGenerators.XamlGenerator { - internal class XamlLazyApplyBlockIIndentedStringBuilder : IIndentedStringBuilder, IDisposable + internal class XamlLazyApplyBlockIIndentedStringBuilder : IIndentedStringBuilder, IDisposable { private bool _applyOpened; private readonly string _closureName; @@ -20,13 +20,15 @@ internal class XamlLazyApplyBlockIIndentedStringBuilder : IIndentedStringBuilder private IDisposable? _applyDisposable; private readonly string? _applyPrefix; private readonly string? _delegateType; + private readonly IDisposable? _parentDisposable; - public XamlLazyApplyBlockIIndentedStringBuilder(IIndentedStringBuilder source, string closureName, string? applyPrefix, string? delegateType) + public XamlLazyApplyBlockIIndentedStringBuilder(IIndentedStringBuilder source, string closureName, string? applyPrefix, string? delegateType, IDisposable? parentDisposable = null) { _closureName = closureName; _source = source; _applyPrefix = applyPrefix; _delegateType = delegateType; + _parentDisposable = parentDisposable; } private void TryWriteApply() @@ -102,6 +104,7 @@ public IDisposable Indent(int count = 1) public void Dispose() { _applyDisposable?.Dispose(); + _parentDisposable?.Dispose(); } public override string ToString() => _source.ToString(); diff --git a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs index d2f20dcfdb89..19b4dc3332b2 100644 --- a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs +++ b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs @@ -82,7 +82,9 @@ private async Task ReloadFile(FileReload fileReload) var uri = new Uri("file:///" + fileReload.FilePath.Replace("\\", "/")); - foreach (var instance in EnumerateInstances(Window.Current.Content, uri)) + Application.RegisterComponent(uri, fileReload.Content); + + foreach (var instance in EnumerateInstances(Window.Current.Content, uri)) { switch (instance) { diff --git a/src/Uno.UI/AssemblyInfo.cs b/src/Uno.UI/AssemblyInfo.cs index 1146c09457f5..91e03502943e 100644 --- a/src/Uno.UI/AssemblyInfo.cs +++ b/src/Uno.UI/AssemblyInfo.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System; +using System.Reflection; using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Uno.UI.Foldable")] @@ -20,3 +21,5 @@ [assembly: InternalsVisibleTo("Uno.UI.FluentTheme.v2")] [assembly: AssemblyMetadata("IsTrimmable", "True")] + +[assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(Uno.UI.RuntimeTypeMetadataUpdateHandler))] diff --git a/src/Uno.UI/DataBinding/BindingPropertyHelper.cs b/src/Uno.UI/DataBinding/BindingPropertyHelper.cs index 8fe96bb87cd4..f532d4effaeb 100644 --- a/src/Uno.UI/DataBinding/BindingPropertyHelper.cs +++ b/src/Uno.UI/DataBinding/BindingPropertyHelper.cs @@ -62,6 +62,17 @@ static BindingPropertyHelper() Conversion = new DefaultConversionExtensions(); } + internal static void ClearCaches() + { + _getValueGetter.Clear(); + _getValueSetter.Clear(); + _getPrecedenceSpecificValueGetter.Clear(); + _getSubstituteValueGetter.Clear(); + _getValueUnsetter.Clear(); + _isEvent.Clear(); + _getPropertyType.Clear(); + } + private static Func DefaultInvokerBuilder(MethodInfo method) { return (instance, args) => method.Invoke(instance, args); diff --git a/src/Uno.UI/DebugHelper.cs b/src/Uno.UI/DebugHelper.cs index f917acb1236f..7f504dce853e 100644 --- a/src/Uno.UI/DebugHelper.cs +++ b/src/Uno.UI/DebugHelper.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Text; diff --git a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml/Application.cs b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml/Application.cs index 22704c2f77c5..3fdfdc16115c 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml/Application.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml/Application.cs @@ -25,7 +25,7 @@ public partial class Application global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Application", "ApplicationHighContrastAdjustment Application.HighContrastAdjustment"); } } - #endif +#endif // Skipping already declared property Current // Skipping already declared method Windows.UI.Xaml.Application.Application() // Forced skipping of method Windows.UI.Xaml.Application.Application() @@ -53,7 +53,7 @@ public partial class Application // Forced skipping of method Windows.UI.Xaml.Application.HighContrastAdjustment.set // Skipping already declared method Windows.UI.Xaml.Application.OnActivated(Windows.ApplicationModel.Activation.IActivatedEventArgs) // Skipping already declared method Windows.UI.Xaml.Application.OnLaunched(Windows.ApplicationModel.Activation.LaunchActivatedEventArgs) - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] protected virtual void OnFileActivated( global::Windows.ApplicationModel.Activation.FileActivatedEventArgs args) { @@ -107,13 +107,6 @@ protected virtual void OnBackgroundActivated( global::Windows.ApplicationModel.A // Skipping already declared method Windows.UI.Xaml.Application.Start(Windows.UI.Xaml.ApplicationInitializationCallback) #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public static void LoadComponent( object component, global::System.Uri resourceLocator) - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Application", "void Application.LoadComponent(object component, Uri resourceLocator)"); - } - #endif - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] public static void LoadComponent( object component, global::System.Uri resourceLocator, global::Windows.UI.Xaml.Controls.Primitives.ComponentResourceLocation componentResourceLocation) { global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Application", "void Application.LoadComponent(object component, Uri resourceLocator, ComponentResourceLocation componentResourceLocation)"); diff --git a/src/Uno.UI/Helpers/MetadataUpdateHandlerAttribute.cs b/src/Uno.UI/Helpers/MetadataUpdateHandlerAttribute.cs new file mode 100644 index 000000000000..5e418555a80c --- /dev/null +++ b/src/Uno.UI/Helpers/MetadataUpdateHandlerAttribute.cs @@ -0,0 +1,19 @@ +#if !NET6_0_OR_GREATER +namespace System.Reflection.Metadata +{ + /// + /// Compatibility attribute for original added in .NET 6 + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class MetadataUpdateHandlerAttribute : Attribute + { + public Type HandlerType { get; } + + public MetadataUpdateHandlerAttribute(Type handlerType) + { + HandlerType = handlerType; + } + } + +} +#endif diff --git a/src/Uno.UI/RuntimeTypeMetadataUpdateHandler.cs b/src/Uno.UI/RuntimeTypeMetadataUpdateHandler.cs new file mode 100644 index 000000000000..f6892fb8b3ac --- /dev/null +++ b/src/Uno.UI/RuntimeTypeMetadataUpdateHandler.cs @@ -0,0 +1,25 @@ +using System; +using Uno.UI.Xaml; +using Windows.UI.Xaml; + +namespace Uno.UI +{ + /// + /// Metadata update handler used to reset caches when changes are applied + /// by the hot reload engine. + /// + internal class RuntimeTypeMetadataUpdateHandler + { + public static void ClearCache(Type[] types) + { + Windows.UI.Xaml.DependencyProperty.ClearRegistry(); + DataBinding.BindingPropertyHelper.ClearCaches(); + } + + public static void UpdateApplication(Type[] types) + { + // Keep empty, both methods (ClearCache, UpdateApplication) are + // invoked in sequence. + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Application.Components.cs b/src/Uno.UI/UI/Xaml/Application.Components.cs new file mode 100644 index 000000000000..98db7017a391 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Application.Components.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using Windows.UI.Xaml.Markup; + +namespace Windows.UI.Xaml +{ + public partial class Application + { + private Dictionary _loadableComponents; + + internal bool IsLoadableComponent(Uri resource) + { + EnsureLoadableComponents(); + + return _loadableComponents.ContainsKey(resource.OriginalString); + } + + public static void LoadComponent(object component, Uri resourceLocator) + { + if(Current._loadableComponents.TryGetValue(resourceLocator.OriginalString, out var document)) + { + XamlReader.LoadUsingComponent(document, component); + } + } + + internal static void RegisterComponent(Uri resourceLocator, string xaml) + { + Current._loadableComponents[resourceLocator.OriginalString] = xaml; + } + + private void EnsureLoadableComponents() => _loadableComponents ??= new Dictionary(); + } +} diff --git a/src/Uno.UI/UI/Xaml/Application.cs b/src/Uno.UI/UI/Xaml/Application.cs index 6eef23f5c712..f8be7e3d9358 100644 --- a/src/Uno.UI/UI/Xaml/Application.cs +++ b/src/Uno.UI/UI/Xaml/Application.cs @@ -38,6 +38,7 @@ #else using View = Windows.UI.Xaml.UIElement; using ViewGroup = Windows.UI.Xaml.UIElement; +using System.Collections.Generic; #endif namespace Windows.UI.Xaml diff --git a/src/Uno.UI/UI/Xaml/ApplicationHelper.cs b/src/Uno.UI/UI/Xaml/ApplicationHelper.cs index 8f98a9ef46f0..58c372735361 100644 --- a/src/Uno.UI/UI/Xaml/ApplicationHelper.cs +++ b/src/Uno.UI/UI/Xaml/ApplicationHelper.cs @@ -1,4 +1,5 @@ -using Windows.UI.Xaml; +using System; +using Windows.UI.Xaml; namespace Uno.UI { @@ -34,5 +35,10 @@ public static string RequestedCustomTheme Application.UpdateRequestedThemesForResources(); } } + + public static bool IsLoadableComponent(Uri resource) + { + return Application.Current.IsLoadableComponent(resource); + } } } diff --git a/src/Uno.UI/UI/Xaml/Markup/Reader/XamlObjectBuilder.cs b/src/Uno.UI/UI/Xaml/Markup/Reader/XamlObjectBuilder.cs index 6342e6e2d87d..c44f9f31c1db 100644 --- a/src/Uno.UI/UI/Xaml/Markup/Reader/XamlObjectBuilder.cs +++ b/src/Uno.UI/UI/Xaml/Markup/Reader/XamlObjectBuilder.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -57,18 +59,18 @@ public XamlObjectBuilder(XamlFileDefinition xamlFileDefinition) TypeResolver = new XamlTypeResolver(_fileDefinition); } - internal object Build() + internal object? Build(object? component = null) { var topLevelControl = _fileDefinition.Objects.First(); - var instance = LoadObject(topLevelControl); + var instance = LoadObject(topLevelControl, component); ApplyPostActions(instance); return instance; } - private object LoadObject(XamlObjectDefinition control) + private object? LoadObject(XamlObjectDefinition? control, object? component = null) { if (control == null) { @@ -84,17 +86,24 @@ private object LoadObject(XamlObjectDefinition control) } var type = TypeResolver.FindType(control.Type); + var classMember = control.Members.FirstOrDefault(m => m.Member.Name == "Class" && m.Member.PreferredXamlNamespace == XamlConstants.XamlXmlNamespace); if (type == null) { throw new InvalidOperationException($"Unable to find type {control.Type}"); } + var unknownContent = control.Members.Where(m => m.Member.Name == "_UnknownContent").FirstOrDefault(); + var unknownContentValue = unknownContent?.Value; + var initializationMember = control.Members.Where(m => m.Member.Name == "_Initialization").FirstOrDefault(); + + var isBrush = type == typeof(Media.Brush); + if (type.Is()) { - Func<_View> builder = () => + Func<_View?> builder = () => { - var contentOwner = control.Members.FirstOrDefault(m => m.Member.Name == "_UnknownContent"); + var contentOwner = unknownContent; return LoadObject(contentOwner?.Objects.FirstOrDefault()) as _View; }; @@ -103,9 +112,9 @@ private object LoadObject(XamlObjectDefinition control) } else if (type.Is()) { - var contentOwner = control.Members.FirstOrDefault(m => m.Member.Name == "_UnknownContent"); + var contentOwner = unknownContent; - var rd = Activator.CreateInstance(type) as ResourceDictionary; + var rd = (ResourceDictionary)Activator.CreateInstance(type); foreach (var xamlObjectDefinition in contentOwner.Objects) { var key = xamlObjectDefinition.Members.FirstOrDefault(m => m.Member.Name == "Key")?.Value; @@ -120,25 +129,24 @@ private object LoadObject(XamlObjectDefinition control) return rd; } - else if (type.IsPrimitive && control.Members.Where(m => m.Member.Name == "_Initialization").FirstOrDefault()?.Value is string primitiveValue) + else if (type.IsPrimitive && initializationMember?.Value is string primitiveValue) { return Convert.ChangeType(primitiveValue, type, CultureInfo.InvariantCulture); } - else if (type == typeof(string) && control.Members.Where(m => m.Member.Name == "_Initialization").FirstOrDefault()?.Value is string stringValue) + else if (type == typeof(string) && unknownContentValue is string stringValue) { return stringValue; } - else if ( - _genericConvertibles.Contains(type) - && control.Members.Where(m => m.Member.Name == "_UnknownContent").FirstOrDefault()?.Value is string otherContentValue) + else if (isBrush) { - return XamlBindingHelper.ConvertValue(type, otherContentValue); + return XamlBindingHelper.ConvertValue(typeof(Media.Brush), unknownContentValue); } else { - var instance = Activator.CreateInstance(type); + var classType = TypeResolver.FindType(classMember?.Value?.ToString()); + var instance = component ?? Activator.CreateInstance(type); - IDisposable TryProcessStyle() + IDisposable? TryProcessStyle() { if (instance is Style style) { @@ -180,8 +188,10 @@ IDisposable TryProcessStyle() } } - private string RewriteAttachedPropertyPath(string value) + private string RewriteAttachedPropertyPath(string? value) { + value ??= ""; + if (value.Contains("(")) { foreach (var ns in _fileDefinition.Namespaces) @@ -330,7 +340,10 @@ private void ProcessSpan(XamlObjectDefinition control, Span span, XamlMemberDefi { foreach (var node in member.Objects) { - span.Inlines.Add((Documents.Inline)LoadObject(node)); + if (LoadObject(node) is Inline inline) + { + span.Inlines.Add(inline); + } } } @@ -377,7 +390,10 @@ private void ProcessTextBlock(XamlObjectDefinition control, TextBlock instance, { foreach (var node in member.Objects) { - instance.Inlines.Add((Documents.Inline)LoadObject(node)); + if (LoadObject(node) is Inline inline) + { + instance.Inlines.Add(inline); + } } } } @@ -432,7 +448,7 @@ private void ProcessMemberElements(object instance, XamlMemberDefinition member, var resourceTargetType = GetResourceTargetType(child); if ( - item.GetType() == typeof(Style) + item?.GetType() == typeof(Style) && resourceTargetType == null ) { @@ -533,13 +549,14 @@ private void ProcessStaticResourceMarkupNode(object instance, XamlMemberDefiniti } } - private static object ResolveStaticResource(object instance, string keyName) + private static object? ResolveStaticResource(object? instance, string? keyName) { var staticResource = (instance as FrameworkElement) - .Flatten(i => (i.Parent as FrameworkElement)) + .Flatten(i => (i?.Parent as FrameworkElement)) .Select(fe => { - if (fe.Resources.TryGetValue(keyName, out var resource, shouldCheckSystem: false)) + if (fe != null + && fe.Resources.TryGetValue(keyName, out var resource, shouldCheckSystem: false)) { return resource; } @@ -595,7 +612,7 @@ private void ProcessBindingMarkupNode(object instance, XamlMemberDefinition memb } } - private Binding BuildBindingExpression(object instance, XamlMemberDefinition member) + private Binding BuildBindingExpression(object? instance, XamlMemberDefinition member) { var bindingNode = member.Objects.FirstOrDefault(o => o.Type.Name == "Binding"); var templateBindingNode = member.Objects.FirstOrDefault(o => o.Type.Name == "TemplateBinding"); @@ -607,7 +624,12 @@ private Binding BuildBindingExpression(object instance, XamlMemberDefinition mem binding.RelativeSource = RelativeSource.TemplatedParent; } - foreach (var bindingProperty in (bindingNode ?? templateBindingNode).Members) + if(bindingNode == null && templateBindingNode == null) + { + throw new InvalidOperationException("Unable to find Binding or TemplateBinding node"); + } + + foreach (var bindingProperty in (bindingNode ?? templateBindingNode)!.Members) { switch (bindingProperty.Member.Name) { @@ -619,7 +641,11 @@ private Binding BuildBindingExpression(object instance, XamlMemberDefinition mem case nameof(Binding.ElementName): var subject = new ElementNameSubject(); binding.ElementName = subject; - AddElementName(bindingProperty.Value?.ToString(), subject); + + if (bindingProperty.Value != null) + { + AddElementName(bindingProperty.Value.ToString(), subject); + } break; case nameof(Binding.TargetNullValue): @@ -644,7 +670,7 @@ private Binding BuildBindingExpression(object instance, XamlMemberDefinition mem case nameof(Binding.RelativeSource): if (bindingProperty.Objects.First() is XamlObjectDefinition relativeSource && relativeSource.Type.Name == "RelativeSource") { - string relativeSourceValue = relativeSource.Members.FirstOrDefault()?.Value?.ToString()?.ToLowerInvariant(); + var relativeSourceValue = relativeSource.Members.FirstOrDefault()?.Value?.ToString()?.ToLowerInvariant(); switch (relativeSourceValue) { case "templatedparent": @@ -679,11 +705,11 @@ private Binding BuildBindingExpression(object instance, XamlMemberDefinition mem { if (converterResource.Type.Name == "StaticResource") { - string staticResourceName = converterResource.Members.FirstOrDefault()?.Value?.ToString(); + var staticResourceName = converterResource.Members.FirstOrDefault()?.Value?.ToString(); void ResolveResource() { - object staticResource = ResolveStaticResource(instance, staticResourceName); + var staticResource = ResolveStaticResource(instance, staticResourceName); if (staticResource != null) { @@ -740,7 +766,7 @@ private void AddCollectionItems(object collectionInstance, IEnumerable + private object? GetResourceKey(XamlObjectDefinition child) => child.Members.FirstOrDefault(m => string.Equals(m.Member.Name, "Name", StringComparison.OrdinalIgnoreCase) || string.Equals(m.Member.Name, "Key", StringComparison.OrdinalIgnoreCase) @@ -775,7 +801,7 @@ private PropertyInfo GetMemberProperty(XamlObjectDefinition control, XamlMemberD } } - private object BuildLiteralValue(XamlMemberDefinition member, Type propertyType = null) + private object? BuildLiteralValue(XamlMemberDefinition member, Type? propertyType = null) { if (member.Objects.None()) { @@ -820,12 +846,12 @@ private object BuildLiteralValue(XamlMemberDefinition member, Type propertyType } } - private object BuildLiteralValue(Type propertyType, string memberValue) + private object? BuildLiteralValue(Type propertyType, string? memberValue) { return Uno.UI.DataBinding.BindingPropertyHelper.Convert(() => propertyType, memberValue); } - private void ApplyPostActions(object instance) + private void ApplyPostActions(object? instance) { if (instance is FrameworkElement fe) { diff --git a/src/Uno.UI/UI/Xaml/Markup/Reader/XamlTypeResolver.cs b/src/Uno.UI/UI/Xaml/Markup/Reader/XamlTypeResolver.cs index ddbee3b95106..fda38210192a 100644 --- a/src/Uno.UI/UI/Xaml/Markup/Reader/XamlTypeResolver.cs +++ b/src/Uno.UI/UI/Xaml/Markup/Reader/XamlTypeResolver.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; @@ -13,20 +14,16 @@ namespace Windows.UI.Xaml.Markup.Reader { - internal class XamlTypeResolver - { - private readonly static Assembly[] _lookupAssemblies = new[]{ - typeof(FrameworkElement).Assembly, - typeof(Color).Assembly, - typeof(Size).Assembly, - }; - - private readonly Func _findType; + internal class XamlTypeResolver + { + private readonly static Assembly _frameworkElementAssembly = typeof(FrameworkElement).Assembly; + + private readonly Func _findType; private readonly Func _isAttachedProperty; private readonly XamlFileDefinition FileDefinition; - private readonly Func _findPropertyTypeByName; - private readonly Func _findPropertyTypeByXamlMember; - private readonly Func _findContentProperty; + private readonly Func _findPropertyTypeByName; + private readonly Func _findPropertyTypeByXamlMember; + private readonly Func _findContentProperty; public static ImmutableDictionary KnownNamespaces { get; } = new Dictionary @@ -49,9 +46,9 @@ public XamlTypeResolver(XamlFileDefinition definition) _findType = SourceFindType; _findType = _findType.AsMemoized(); _isAttachedProperty = Funcs.Create(SourceIsAttachedProperty).AsLockedMemoized(); - _findPropertyTypeByXamlMember = Funcs.Create(SourceFindPropertyType).AsLockedMemoized(); - _findPropertyTypeByName = Funcs.Create(SourceFindPropertyType).AsLockedMemoized(); - _findContentProperty = Funcs.Create(SourceFindContentProperty).AsLockedMemoized(); + _findPropertyTypeByXamlMember = Funcs.Create(SourceFindPropertyType).AsLockedMemoized(); + _findPropertyTypeByName = Funcs.Create(SourceFindPropertyType).AsLockedMemoized(); + _findContentProperty = Funcs.Create(SourceFindContentProperty).AsLockedMemoized(); } public bool IsAttachedProperty(XamlMemberDefinition member) @@ -106,17 +103,22 @@ public bool IsInitializedCollection(PropertyInfo property) return false; } - public PropertyInfo GetPropertyByName(XamlType declaringType, string propertyName) + public PropertyInfo? GetPropertyByName(XamlType declaringType, string propertyName) => GetPropertyByName(FindType(declaringType), propertyName); - public PropertyInfo FindContentProperty(Type elementType) + public PropertyInfo? FindContentProperty(Type elementType) => _findContentProperty(elementType); - private static PropertyInfo GetPropertyByName(Type type, string propertyName) + private static PropertyInfo? GetPropertyByName(Type? type, string propertyName) => type?.GetProperties().FirstOrDefault(p => p.Name == propertyName); - private PropertyInfo SourceFindContentProperty(Type elementType) + private PropertyInfo? SourceFindContentProperty(Type? elementType) { + if(elementType == null) + { + return null; + } + var data = elementType .GetCustomAttributes() .FirstOrDefault(); @@ -141,7 +143,7 @@ private PropertyInfo SourceFindContentProperty(Type elementType) /// /// Returns true if the property has an accessible public setter and has a parameterless constructor /// - public bool IsNewableProperty(PropertyInfo property, out Type newableType) + public bool IsNewableProperty(PropertyInfo property, out Type? newableType) { var namedType = property.PropertyType as Type; @@ -153,7 +155,7 @@ public bool IsNewableProperty(PropertyInfo property, out Type newableType) return isNewable; } - public DependencyProperty FindDependencyProperty(XamlMemberDefinition member) + public DependencyProperty? FindDependencyProperty(XamlMemberDefinition member) { var propertyOwner = FindType(member.Member.DeclaringType); @@ -169,7 +171,7 @@ public DependencyProperty FindDependencyProperty(XamlMemberDefinition member) } } - public DependencyProperty FindDependencyProperty(Type propertyOwner, string propertyName) + public DependencyProperty? FindDependencyProperty(Type propertyOwner, string propertyName) { var propertyDependencyPropertyQuery = GetAllProperties(propertyOwner) .Where(p => p.Name == propertyName + "Property") @@ -259,7 +261,7 @@ public bool IsType(XamlType xamlType, string typeName) return false; } - public Type FindType(string name) + public Type? FindType(string? name) { if (name.IsNullOrWhiteSpace()) { @@ -269,7 +271,7 @@ public Type FindType(string name) return _findType(name); } - public Type FindType(XamlType type) + public Type? FindType(XamlType? type) { if (type != null) { @@ -288,7 +290,7 @@ public Type FindType(XamlType type) } } - var fullName = isKnownNamespace ? ns.Prefix + ":" + type.Name : type.Name; + var fullName = isKnownNamespace ? ns?.Prefix + ":" + type.Name : type.Name; return _findType(fullName); } @@ -298,8 +300,13 @@ public Type FindType(XamlType type) } } - private Type SourceFindType(string name) + private Type? SourceFindType(string? name) { + if(name == null) + { + return null; + } + var originalName = name; if (name.Contains(":")) @@ -401,9 +408,9 @@ private static bool SourceIsAttachedProperty(Type type, string name) } while (true); } - public Type FindPropertyType(XamlMember xamlMember) => _findPropertyTypeByXamlMember(xamlMember); + public Type? FindPropertyType(XamlMember xamlMember) => _findPropertyTypeByXamlMember(xamlMember); - private Type SourceFindPropertyType(XamlMember xamlMember) + private Type? SourceFindPropertyType(XamlMember xamlMember) { // Search for the type the clr namespaces registered with the xml namespace if (xamlMember.DeclaringType != null) @@ -426,12 +433,12 @@ private Type SourceFindPropertyType(XamlMember xamlMember) var type = FindType(xamlMember.DeclaringType); // If not, try to find the closest match using the name only. - return FindPropertyType(type.SelectOrDefault(t => t.FullName, "$$unknown"), xamlMember.Name); + return FindPropertyType(type?.FullName ?? "$$unknown", xamlMember.Name); } - public Type FindPropertyType(string ownerType, string propertyName) => _findPropertyTypeByName(ownerType, propertyName); + public Type? FindPropertyType(string ownerType, string propertyName) => _findPropertyTypeByName(ownerType, propertyName); - private Type SourceFindPropertyType(string ownerType, string propertyName) + private Type? SourceFindPropertyType(string ownerType, string propertyName) { var type = FindType(ownerType); diff --git a/src/Uno.UI/UI/Xaml/Markup/XamlReader.cs b/src/Uno.UI/UI/Xaml/Markup/XamlReader.cs index 452e53437f77..af14c743acec 100644 --- a/src/Uno.UI/UI/Xaml/Markup/XamlReader.cs +++ b/src/Uno.UI/UI/Xaml/Markup/XamlReader.cs @@ -19,5 +19,14 @@ public static object LoadWithInitialTemplateValidation(string xaml) { return Load(xaml); } - } + + internal static void LoadUsingComponent(string xaml, object component) + { + var r = new XamlStringParser(); + + var builder = new XamlObjectBuilder(r.Parse(xaml)); + + builder.Build(component); + } + } }