Browse Source

Entire control catalog is now loaded from precompiled xaml

Nikita Tsukanov 6 years ago
parent
commit
dbf6c3a148

+ 10 - 0
src/Avalonia.Base/Platform/IAssetLoader.cs

@@ -61,6 +61,16 @@ namespace Avalonia.Platform
         /// </exception>
         /// </exception>
         (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null);
         (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null);
 
 
+        /// <summary>
+        /// Extracts assembly information from URI
+        /// </summary>
+        /// <param name="uri">The URI.</param>
+        /// <param name="baseUri">
+        /// A base URI to use if <paramref name="uri"/> is relative.
+        /// </param>
+        /// <returns>Assembly associated with the Uri</returns>
+        Assembly GetAssembly(Uri uri, Uri baseUri = null);
+
         /// <summary>
         /// <summary>
         /// Gets all assets of a folder and subfolders that match specified uri.
         /// Gets all assets of a folder and subfolders that match specified uri.
         /// </summary>
         /// </summary>

+ 88 - 12
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -72,12 +72,38 @@ namespace Avalonia.Build.Tasks
                 asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors()
                 asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors()
                     .First(c => c.Parameters.Count == 1));
                     .First(c => c.Parameters.Count == 1));
 
 
+            
+            var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader",
+                TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+
+
+            loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
+            {
+                ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
+            });
+
+
+            var loaderDispatcherMethod = new MethodDefinition("TryLoad",
+                MethodAttributes.Static | MethodAttributes.Public,
+                asm.MainModule.TypeSystem.Object)
+            {
+                Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)}
+            };
+            loaderDispatcherDef.Methods.Add(loaderDispatcherMethod);
+            asm.MainModule.Types.Add(loaderDispatcherDef);
+
+            var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First(
+                m =>
+                    m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 2 &&
+                    m.ReturnType.FullName == "System.Boolean"
+                    && m.Parameters[0].ParameterType.FullName == "System.String"
+                    && m.Parameters[1].ParameterType.FullName == "System.String"));
+            
             bool CompileGroup(IResourceGroup group)
             bool CompileGroup(IResourceGroup group)
             {
             {
-                var typeDef = new TypeDefinition("CompiledAvaloniaXaml", group.Name,
+                var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name,
                     TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
                     TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
 
 
-
                 typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
                 typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
                 {
                 {
                     ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
                     ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
@@ -87,6 +113,8 @@ namespace Avalonia.Build.Tasks
                 
                 
                 foreach (var res in group.Resources.Where(CheckXamlName))
                 foreach (var res in group.Resources.Where(CheckXamlName))
                 {
                 {
+                    if(res.Name.Contains("DefaultTheme"))
+                        Console.WriteLine();
                     try
                     try
                     {
                     {
                         // StreamReader is needed here to handle BOM
                         // StreamReader is needed here to handle BOM
@@ -114,15 +142,18 @@ namespace Avalonia.Build.Tasks
                         compiler.Compile(parsed, builder, contextClass,
                         compiler.Compile(parsed, builder, contextClass,
                             populateName, buildName,
                             populateName, buildName,
                             "NamespaceInfo:" + res.Name, res.Uri);
                             "NamespaceInfo:" + res.Name, res.Uri);
+
+                        var classTypeDefinition =
+                            classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
                         
                         
-                        if (classType != null)
+                        if (classTypeDefinition != null)
                         {
                         {
                             var compiledPopulateMethod = typeSystem.GetTypeReference(builder).Resolve()
                             var compiledPopulateMethod = typeSystem.GetTypeReference(builder).Resolve()
                                 .Methods.First(m => m.Name == populateName);
                                 .Methods.First(m => m.Name == populateName);
-                            var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve();
-                            
-                            
-                            var trampoline = new MethodDefinition("!XamlIlPopulateTrampoline",
+
+
+                            const string TrampolineName = "!XamlIlPopulateTrampoline";
+                            var trampoline = new MethodDefinition(TrampolineName,
                                 MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
                                 MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
                             trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
                             trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
                             classTypeDefinition.Methods.Add(trampoline);
                             classTypeDefinition.Methods.Add(trampoline);
@@ -143,6 +174,16 @@ namespace Avalonia.Build.Tasks
                                         && i[c].OpCode == OpCodes.Call)
                                         && i[c].OpCode == OpCodes.Call)
                                     {
                                     {
                                         var op = i[c].Operand as MethodReference;
                                         var op = i[c].Operand as MethodReference;
+                                        
+                                        // TODO: Throw an error
+                                        // This usually happens when same XAML resource was added twice for some weird reason
+                                        // We currently support it for dual-named default theme resource
+                                        if (op != null
+                                            && op.Name == TrampolineName)
+                                        {
+                                            foundXamlLoader = true;
+                                            break;
+                                        }
                                         if (op != null
                                         if (op != null
                                             && op.Name == "Load"
                                             && op.Name == "Load"
                                             && op.Parameters.Count == 1
                                             && op.Parameters.Count == 1
@@ -158,9 +199,10 @@ namespace Avalonia.Build.Tasks
 
 
                             if (!foundXamlLoader)
                             if (!foundXamlLoader)
                             {
                             {
-                                var ctors = classTypeDefinition.GetConstructors().ToList();
+                                var ctors = classTypeDefinition.GetConstructors()
+                                    .Where(c => !c.IsStatic).ToList();
                                 // We can inject xaml loader into default constructor
                                 // We can inject xaml loader into default constructor
-                                if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode!=OpCodes.Nop) == 3)
+                                if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3)
                                 {
                                 {
                                     var i = ctors[0].Body.Instructions;
                                     var i = ctors[0].Body.Instructions;
                                     var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
                                     var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
@@ -175,6 +217,39 @@ namespace Avalonia.Build.Tasks
                             }
                             }
 
 
                         }
                         }
+
+                        if (buildName != null || classTypeDefinition != null)
+                        {
+                            var compiledBuildMethod = buildName == null ?
+                                null :
+                                typeSystem.GetTypeReference(builder).Resolve()
+                                    .Methods.First(m => m.Name == buildName);
+                            var parameterlessConstructor = compiledBuildMethod != null ?
+                                null :
+                                classTypeDefinition.GetConstructors().FirstOrDefault(c =>
+                                    c.IsPublic && !c.IsStatic && !c.HasParameters);
+
+                            if (compiledBuildMethod != null || parameterlessConstructor != null)
+                            {
+                                var i = loaderDispatcherMethod.Body.Instructions;
+                                var nop = Instruction.Create(OpCodes.Nop);
+                                i.Add(Instruction.Create(OpCodes.Ldarg_0));
+                                i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri));
+                                i.Add(Instruction.Create(OpCodes.Call, stringEquals));
+                                i.Add(Instruction.Create(OpCodes.Brfalse, nop));
+                                if (parameterlessConstructor != null)
+                                    i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor));
+                                else
+                                {
+                                    i.Add(Instruction.Create(OpCodes.Ldnull));
+                                    i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
+                                }
+
+                                i.Add(Instruction.Create(OpCodes.Ret));
+                                i.Add(nop);
+                            }
+                        }
+
                     }
                     }
                     catch (Exception e)
                     catch (Exception e)
                     {
                     {
@@ -191,11 +266,9 @@ namespace Avalonia.Build.Tasks
                     }
                     }
                     res.Remove();
                     res.Remove();
                 }
                 }
-
                 return true;
                 return true;
             }
             }
             
             
-            
             if (emres.Resources.Count(CheckXamlName) != 0)
             if (emres.Resources.Count(CheckXamlName) != 0)
                 if (!CompileGroup(emres))
                 if (!CompileGroup(emres))
                     return new CompileResult(false);
                     return new CompileResult(false);
@@ -205,7 +278,10 @@ namespace Avalonia.Build.Tasks
                     return new CompileResult(false);
                     return new CompileResult(false);
                 avares.Save();
                 avares.Save();
             }
             }
-
+            
+            loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
+            loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
+            
             var ms = new MemoryStream();
             var ms = new MemoryStream();
             asm.Write(ms);
             asm.Write(ms);
             return new CompileResult(true, ms.ToArray());
             return new CompileResult(true, ms.ToArray());

+ 3 - 1
src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
     <TargetFramework>netstandard2.0</TargetFramework>
   </PropertyGroup>
   </PropertyGroup>
@@ -11,10 +11,12 @@
     <ProjectReference Include="..\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj" />
     <ProjectReference Include="..\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
     <ProjectReference Include="..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />    
     <ProjectReference Include="..\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />    
     <ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
     <ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
   </ItemGroup>
   </ItemGroup>
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\EmbedXaml.props" />
   <Import Project="..\..\build\EmbedXaml.props" />
   <Import Project="..\..\build\JetBrains.Annotations.props" />
   <Import Project="..\..\build\JetBrains.Annotations.props" />
+  <Import Project="..\..\build\BuildTargets.targets" />
 </Project>
 </Project>

+ 3 - 1
src/Avalonia.Themes.Default/DefaultTheme.xaml

@@ -1,4 +1,6 @@
-<Styles xmlns="https://github.com/avaloniaui">
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Avalonia.Themes.Default.DefaultTheme">
   <!-- Define ToolTip first so its styles can be overriden by other controls (e.g. TextBox) -->
   <!-- Define ToolTip first so its styles can be overriden by other controls (e.g. TextBox) -->
   <StyleInclude Source="resm:Avalonia.Themes.Default.ToolTip.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ToolTip.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.DataValidationErrors.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.DataValidationErrors.xaml?assembly=Avalonia.Themes.Default"/>

+ 0 - 7
src/Avalonia.Themes.Default/DefaultTheme.xaml.cs

@@ -11,12 +11,5 @@ namespace Avalonia.Themes.Default
     /// </summary>
     /// </summary>
     public class DefaultTheme : Styles
     public class DefaultTheme : Styles
     {
     {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="DefaultTheme"/> class.
-        /// </summary>
-        public DefaultTheme()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
     }
     }
 }
 }

+ 16 - 3
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@@ -68,8 +68,8 @@ namespace Avalonia.Markup.Xaml
             {
             {
                 throw new InvalidOperationException(
                 throw new InvalidOperationException(
                     "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
                     "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
-            }
-
+            }           
+            
             foreach (var uri in GetUrisFor(assetLocator, type))
             foreach (var uri in GetUrisFor(assetLocator, type))
             {
             {
                 if (assetLocator.Exists(uri))
                 if (assetLocator.Exists(uri))
@@ -116,6 +116,19 @@ namespace Avalonia.Markup.Xaml
                     "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
                     "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
             }
             }
 
 
+            var compiledLoader = assetLocator.GetAssembly(uri, baseUri)
+                ?.GetType("CompiledAvaloniaXaml.!XamlLoader")
+                ?.GetMethod("TryLoad", new[] {typeof(string)});
+            if (compiledLoader != null)
+            {
+                var uriString = (!uri.IsAbsoluteUri && baseUri != null ? new Uri(baseUri, uri) : uri)
+                    .ToString();
+                var compiledResult = compiledLoader.Invoke(null, new object[] {uriString});
+                if (compiledResult != null)
+                    return compiledResult;
+            }
+            
+            
             var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
             var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
             using (var stream = asset.stream)
             using (var stream = asset.stream)
             {
             {
@@ -130,7 +143,7 @@ namespace Avalonia.Markup.Xaml
                 }
                 }
             }
             }
         }
         }
-
+        
         /// <summary>
         /// <summary>
         /// Loads XAML from a string.
         /// Loads XAML from a string.
         /// </summary>
         /// </summary>

+ 7 - 0
src/Shared/PlatformSupport/AssetLoader.cs

@@ -97,6 +97,13 @@ namespace Avalonia.Shared.PlatformSupport
             return (asset.GetStream(), asset.Assembly);
             return (asset.GetStream(), asset.Assembly);
         }
         }
 
 
+        public Assembly GetAssembly(Uri uri, Uri baseUri)
+        {
+            if (!uri.IsAbsoluteUri && baseUri != null)
+                uri = new Uri(baseUri, uri);
+            return GetAssembly(uri).Assembly;
+        }
+
         /// <summary>
         /// <summary>
         /// Gets all assets of a folder and subfolders that match specified uri.
         /// Gets all assets of a folder and subfolders that match specified uri.
         /// </summary>
         /// </summary>

+ 5 - 0
tests/Avalonia.UnitTests/MockAssetLoader.cs

@@ -32,6 +32,11 @@ namespace Avalonia.UnitTests
             return (Open(uri, baseUri), (Assembly)null);
             return (Open(uri, baseUri), (Assembly)null);
         }
         }
 
 
+        public Assembly GetAssembly(Uri uri, Uri baseUri = null)
+        {
+            throw new NotImplementedException();
+        }
+
         public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
         public IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri)
         {
         {
             return _assets.Keys.Where(x => x.AbsolutePath.Contains(uri.AbsolutePath));
             return _assets.Keys.Where(x => x.AbsolutePath.Contains(uri.AbsolutePath));