Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: x:Shared #16644

Merged
merged 5 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Avalonia.Base/Controls/INotSharedDeferredContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Avalonia.Controls;

/// <summary>
/// Represents a not shared deferred content.
/// </summary>
public interface INotSharedDeferredContent: IDeferredContent
MrJul marked this conversation as resolved.
Show resolved Hide resolved
{
}
16 changes: 14 additions & 2 deletions src/Avalonia.Base/Controls/ResourceDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ public void AddDeferred(object key, Func<IServiceProvider?, object?> factory)
public void AddDeferred(object key, IDeferredContent deferredContent)
=> Add(key, deferredContent);

public void AddNotShared(object key, INotSharedDeferredContent notSharedDeferredContent)
MrJul marked this conversation as resolved.
Show resolved Hide resolved
=> Add(key, notSharedDeferredContent);

public void Clear()
{
if (_inner?.Count > 0)
Expand Down Expand Up @@ -236,12 +239,16 @@ public bool TryGetValue(object key, out object? value)
try
{
_lastDeferredItemKey = key;
_inner[key] = value = deferred.Build(null) switch
value = deferred.Build(null) switch
{
ITemplateResult t => t.Result,
{ } v => v,
_ => null,
};
if (deferred is not INotSharedDeferredContent)
{
_inner[key] = value;
}
}
finally
{
Expand All @@ -250,7 +257,6 @@ public bool TryGetValue(object key, out object? value)
}
return true;
}

value = null;
return false;
}
Expand Down Expand Up @@ -376,5 +382,11 @@ private sealed class DeferredItem : IDeferredContent
public DeferredItem(Func<IServiceProvider?, object?> factory) => _factory = factory;
public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider);
}

private sealed class NotSharedDeferredItem(Func<IServiceProvider?, object?> factory) : INotSharedDeferredContent
MrJul marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly Func<IServiceProvider?, object?> _factory = factory;
public object? Build(IServiceProvider? serviceProvider) => _factory(serviceProvider);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
using System.Linq;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;

namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes;

class XamlNotSharedDeferredContentNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
MrJul marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly IXamlMethod? _deferredContentCustomization;
private readonly IXamlType? _deferredContentCustomizationTypeParameter;
private readonly IXamlType _funcType;

public IXamlAstValueNode Value { get; set; }
public IXamlAstTypeReference Type { get; }

public XamlNotSharedDeferredContentNode(IXamlAstValueNode value,
IXamlMethod? deferredContentCustomization,
IXamlType? deferredContentCustomizationTypeParameter,
TransformerConfiguration config) : base(value)
{
_deferredContentCustomization = deferredContentCustomization;
_deferredContentCustomizationTypeParameter = deferredContentCustomizationTypeParameter;
Value = value;

_funcType = config.TypeSystem
.GetType("System.Func`2")
.MakeGenericType(config.TypeMappings.ServiceProvider, config.WellKnownTypes.Object);

var returnType = _deferredContentCustomization?.ReturnType ?? _funcType;
Type = new XamlAstClrTypeReference(value, returnType, false);
}

public override void VisitChildren(IXamlAstVisitor visitor)
{
Value = (IXamlAstValueNode)Value.Visit(visitor);
}

void CompileBuilder(ILEmitContext context, XamlClosureInfo xamlClosure)
{
var il = context.Emitter;
// Initialize the context
il
.Ldarg_0()
.EmitCall(xamlClosure.CreateRuntimeContextMethod)
.Stloc(context.ContextLocal);

context.Emit(Value, context.Emitter, context.Configuration.WellKnownTypes.Object);
il.Ret();

context.ExecuteAfterEmitCallbacks();
}

public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var so = context.Configuration.WellKnownTypes.Object;
var isp = context.Configuration.TypeMappings.ServiceProvider;

if (!context.TryGetItem(out XamlClosureInfo? closureInfo))
{
var closureType = context.DeclaringType.DefineSubType(
so,
"XamlClosure_" + context.Configuration.IdentifierGenerator.GenerateIdentifierPart(),
XamlVisibility.Private);

closureInfo = new XamlClosureInfo(closureType, context);
context.AddAfterEmitCallbacks(() => closureType.CreateType());
context.SetItem(closureInfo);
}

var counter = ++closureInfo.BuildMethodCounter;

var buildMethod = closureInfo.Type.DefineMethod(
so,
new[] { isp },
$"Build_{counter}",
XamlVisibility.Public,
true,
false);

var subContext = new ILEmitContext(
buildMethod.Generator,
context.Configuration,
context.EmitMappings,
context.RuntimeContext,
buildMethod.Generator.DefineLocal(context.RuntimeContext.ContextType),
closureInfo.Type,
context.File,
context.Emitters);

subContext.SetItem(closureInfo);

CompileBuilder(subContext, closureInfo);

var customization = _deferredContentCustomization;

if (_deferredContentCustomizationTypeParameter is not null)
customization = customization?.MakeGenericMethod(new[] { _deferredContentCustomizationTypeParameter });

if (customization is not null && IsFunctionPointerLike(customization.Parameters[0]))
{
// &Build
codeGen
.Ldftn(buildMethod);
}
else
{
// new Func<IServiceProvider, object>(null, &Build);
codeGen
.Ldnull()
.Ldftn(buildMethod)
.Newobj(_funcType.Constructors.First(ct =>
ct.Parameters.Count == 2 &&
ct.Parameters[0].Equals(context.Configuration.WellKnownTypes.Object)));
}

// Allow to save values from the parent context, pass own service provider, etc, etc
if (customization is not null)
{
codeGen
.Ldloc(context.ContextLocal)
.EmitCall(customization);
}

return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}

private static bool IsFunctionPointerLike(IXamlType xamlType)
=> xamlType.IsFunctionPointer // Cecil, SRE with .NET 8
|| xamlType.FullName == "System.IntPtr"; // SRE with .NET < 8 or .NET Standard

private sealed class XamlClosureInfo
{
private readonly XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> _parentContext;
private IXamlMethod? _createRuntimeContextMethod;

public IXamlTypeBuilder<IXamlILEmitter> Type { get; }

public IXamlMethod CreateRuntimeContextMethod
=> _createRuntimeContextMethod ??= BuildCreateRuntimeContextMethod();

public int BuildMethodCounter { get; set; }

public XamlClosureInfo(
IXamlTypeBuilder<IXamlILEmitter> type,
XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> parentContext)
{
Type = type;
_parentContext = parentContext;
}

private IXamlMethod BuildCreateRuntimeContextMethod()
{
var method = Type.DefineMethod(
_parentContext.RuntimeContext.ContextType,
new[] { _parentContext.Configuration.TypeMappings.ServiceProvider },
"CreateContext",
XamlVisibility.Public,
true,
false);

var context = new ILEmitContext(
method.Generator,
_parentContext.Configuration,
_parentContext.EmitMappings,
_parentContext.RuntimeContext,
method.Generator.DefineLocal(_parentContext.RuntimeContext.ContextType),
Type,
_parentContext.File,
_parentContext.Emitters);

var il = context.Emitter;

// context = new Context(arg0, ...)
il.Ldarg_0();
context.RuntimeContext.Factory(il);

if (context.Configuration.TypeMappings.RootObjectProvider is { } rootObjectProviderType)
{
// Attempt to get the root object from parent service provider
var noRoot = il.DefineLabel();
using var loc = context.GetLocalOfType(context.Configuration.WellKnownTypes.Object);
il
.Stloc(context.ContextLocal)
// if(arg == null) goto noRoot;
.Ldarg_0()
.Brfalse(noRoot)
// var loc = arg.GetService(typeof(IRootObjectProvider))
.Ldarg_0()
.Ldtype(rootObjectProviderType)
.EmitCall(context.Configuration.TypeMappings.ServiceProvider
.GetMethod(m => m.Name == "GetService"))
.Stloc(loc.Local)
// if(loc == null) goto noRoot;
.Ldloc(loc.Local)
.Brfalse(noRoot)
// loc = ((IRootObjectProvider)loc).RootObject
.Ldloc(loc.Local)
.Castclass(rootObjectProviderType)
.EmitCall(rootObjectProviderType
.GetMethod(m => m.Name == "get_RootObject"))
.Stloc(loc.Local)
// contextLocal.RootObject = loc;
.Ldloc(context.ContextLocal)
.Ldloc(loc.Local)
.Castclass(context.RuntimeContext.ContextType.GenericArguments[0])
.Stfld(context.RuntimeContext.RootObjectField!)
.MarkLabel(noRoot)
.Ldloc(context.ContextLocal);
}

il.Ret();

return method;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ void InsertBeforeMany(Type[] types, params IXamlAstTransformer[] t)

InsertBefore<AvaloniaXamlIlTransformInstanceAttachedProperties>(new AvaloniaXamlIlTransformRoutedEvent());

InsertAfter<AvaloniaXamlIlDeferredResourceTransformer>(new XSharedTransformer());

Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer());
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ private static bool ShouldBeDeferred(IXamlAstValueNode node)
return false;
}

var sharedVisitor = new NotSharedVisitor();
_ = sharedVisitor.Visit(node);
// Ingnore if Element is marked with x:Shared="false"
if (sharedVisitor.Count > 0)
{
return false;
}

// Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
// This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
// We set target scope level to 0, assuming that this resource node is a scope of itself.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ sealed class AvaloniaXamlIlWellKnownTypes
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public IXamlMethod ResourceDictionaryNotSharedAdd { get; }
public IXamlMethod ResourceDictionaryEnsureCapacity { get; }
public IXamlMethod ResourceDictionaryGetCount { get; }
public IXamlType IThemeVariantProvider { get; }
Expand All @@ -129,6 +130,7 @@ sealed class AvaloniaXamlIlWellKnownTypes
public IXamlType IReadOnlyListOfT { get; }
public IXamlType ControlTemplate { get; }
public IXamlType EventHandlerT { get; }
public IXamlMethod? NotSharedDeferredContentExecutorCustomization { get; set; }
MrJul marked this conversation as resolved.
Show resolved Hide resolved

sealed internal class InteractivityWellKnownTypes
{
Expand Down Expand Up @@ -312,6 +314,9 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.GetMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,
cfg.TypeSystem.GetType("Avalonia.Controls.IDeferredContent"));
ResourceDictionaryNotSharedAdd = ResourceDictionary.GetMethod("AddNotShared", XamlIlTypes.Void, true, XamlIlTypes.Object,
cfg.TypeSystem.GetType("Avalonia.Controls.INotSharedDeferredContent"));
MrJul marked this conversation as resolved.
Show resolved Hide resolved

ResourceDictionaryEnsureCapacity = ResourceDictionary.GetMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32);
ResourceDictionaryGetCount = ResourceDictionary.GetMethod("get_Count", XamlIlTypes.Int32, true);
IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider");
Expand All @@ -323,6 +328,9 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1");
EventHandlerT = cfg.TypeSystem.GetType("System.EventHandler`1");
Interactivity = new InteractivityWellKnownTypes(cfg);
var runtimeHelpers = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
NotSharedDeferredContentExecutorCustomization =
runtimeHelpers.FindMethod(m => m.Name == "NotSharedDeferredTransformationFactoryV0");
}
}

Expand Down
Loading
Loading