Browse Source

Entire control catalog is now loaded from precompiled xaml

xamlil-debug-info
Nikita Tsukanov 8 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> /// </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>

100
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();
const string TrampolineName = "!XamlIlPopulateTrampoline";
var trampoline = new MethodDefinition("!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());

4
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>

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) --> <!-- 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"/>

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);
}
} }
} }

19
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
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
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));

Loading…
Cancel
Save