Browse Source

Entire control catalog is now loaded from precompiled xaml

xamlil-debug-info
Nikita Tsukanov 7 years ago
parent
commit
dbf6c3a148
  1. 10
      src/Avalonia.Base/Platform/IAssetLoader.cs
  2. 100
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  3. 4
      src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
  4. 4
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  5. 7
      src/Avalonia.Themes.Default/DefaultTheme.xaml.cs
  6. 19
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  7. 7
      src/Shared/PlatformSupport/AssetLoader.cs
  8. 5
      tests/Avalonia.UnitTests/MockAssetLoader.cs

10
src/Avalonia.Base/Platform/IAssetLoader.cs

@ -61,6 +61,16 @@ namespace Avalonia.Platform
/// </exception>
(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>
/// Gets all assets of a folder and subfolders that match specified uri.
/// </summary>

100
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -72,12 +72,38 @@ namespace Avalonia.Build.Tasks
asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors()
.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)
{
var typeDef = new TypeDefinition("CompiledAvaloniaXaml", group.Name,
var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name,
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
{
ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)}
@ -87,6 +113,8 @@ namespace Avalonia.Build.Tasks
foreach (var res in group.Resources.Where(CheckXamlName))
{
if(res.Name.Contains("DefaultTheme"))
Console.WriteLine();
try
{
// StreamReader is needed here to handle BOM
@ -114,15 +142,18 @@ namespace Avalonia.Build.Tasks
compiler.Compile(parsed, builder, contextClass,
populateName, buildName,
"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()
.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);
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
classTypeDefinition.Methods.Add(trampoline);
@ -143,6 +174,16 @@ namespace Avalonia.Build.Tasks
&& i[c].OpCode == OpCodes.Call)
{
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
&& op.Name == "Load"
&& op.Parameters.Count == 1
@ -158,9 +199,10 @@ namespace Avalonia.Build.Tasks
if (!foundXamlLoader)
{
var ctors = classTypeDefinition.GetConstructors().ToList();
var ctors = classTypeDefinition.GetConstructors()
.Where(c => !c.IsStatic).ToList();
// 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 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)
{
@ -191,11 +266,9 @@ namespace Avalonia.Build.Tasks
}
res.Remove();
}
return true;
}
if (emres.Resources.Count(CheckXamlName) != 0)
if (!CompileGroup(emres))
return new CompileResult(false);
@ -205,7 +278,10 @@ namespace Avalonia.Build.Tasks
return new CompileResult(false);
avares.Save();
}
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
var ms = new MemoryStream();
asm.Write(ms);
return new CompileResult(true, ms.ToArray());

4
src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj

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

4
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) -->
<StyleInclude Source="resm:Avalonia.Themes.Default.ToolTip.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.DataValidationErrors.xaml?assembly=Avalonia.Themes.Default"/>

7
src/Avalonia.Themes.Default/DefaultTheme.xaml.cs

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

19
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -68,8 +68,8 @@ namespace Avalonia.Markup.Xaml
{
throw new InvalidOperationException(
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
}
foreach (var uri in GetUrisFor(assetLocator, type))
{
if (assetLocator.Exists(uri))
@ -116,6 +116,19 @@ namespace Avalonia.Markup.Xaml
"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);
using (var stream = asset.stream)
{
@ -130,7 +143,7 @@ namespace Avalonia.Markup.Xaml
}
}
}
/// <summary>
/// Loads XAML from a string.
/// </summary>

7
src/Shared/PlatformSupport/AssetLoader.cs

@ -97,6 +97,13 @@ namespace Avalonia.Shared.PlatformSupport
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>
/// Gets all assets of a folder and subfolders that match specified uri.
/// </summary>

5
tests/Avalonia.UnitTests/MockAssetLoader.cs

@ -32,6 +32,11 @@ namespace Avalonia.UnitTests
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)
{
return _assets.Keys.Where(x => x.AbsolutePath.Contains(uri.AbsolutePath));

Loading…
Cancel
Save