@ -1,8 +1,11 @@
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
using System.Reflection ;
using System.Runtime.CompilerServices ;
using System.Runtime.InteropServices ;
using Avalonia.Controls ;
using Avalonia.Controls.Templates ;
using Avalonia.Data ;
@ -15,6 +18,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public static class XamlIlRuntimeHelpers
{
[ThreadStatic] private static List < IResourceNode > ? s_resourceNodeBuffer ;
[ThreadStatic] private static LastParentStack ? s_lastParentStack ;
public static Func < IServiceProvider , object > DeferredTransformationFactoryV1 ( Func < IServiceProvider , object > builder ,
IServiceProvider provider )
{
@ -24,36 +30,193 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public static Func < IServiceProvider , object > DeferredTransformationFactoryV2 < T > ( Func < IServiceProvider , object > builder ,
IServiceProvider provider )
{
var resourceNodes = provider . GetRequiredService < IAvaloniaXamlIlParentStackProvider > ( ) . Parents
. OfType < IResourceNode > ( ) . ToList ( ) ;
var resourceNodes = AsResourceNodes ( provider . GetRequiredService < IAvaloniaXamlIlParentStackProvider > ( ) ) ;
var rootObject = provider . GetRequiredService < IRootObjectProvider > ( ) . RootObject ;
var parentScope = provider . GetService < INameScope > ( ) ;
return new DelegateDeferredContent < T > ( resourceNodes , rootObject , parentScope , builder ) . Build ;
}
// The builder is typed as IntPtr instead of delegate*<IServiceProvider, object> because Reflection.Emit has
// trouble with generic methods containing function pointers. See https://github.com/dotnet/runtime/issues/100020
public static unsafe IDeferredContent DeferredTransformationFactoryV3 < T > (
/*delegate*<IServiceProvider, object>*/ IntPtr builder ,
IServiceProvider provider )
{
var resourceNodes = AsResourceNodes ( provider . GetRequiredService < IAvaloniaXamlIlParentStackProvider > ( ) ) ;
var rootObject = provider . GetRequiredService < IRootObjectProvider > ( ) . RootObject ;
var parentScope = provider . GetService < INameScope > ( ) ;
return sp = >
var typedBuilder = ( delegate * < IServiceProvider , object > ) builder ;
return new PointerDeferredContent < T > ( resourceNodes , rootObject , parentScope , typedBuilder ) ;
}
private static IResourceNode [ ] AsResourceNodes ( IAvaloniaXamlIlParentStackProvider provider )
{
var buffer = s_resourceNodeBuffer ? ? = new List < IResourceNode > ( 8 ) ;
buffer . Clear ( ) ;
if ( provider is IAvaloniaXamlIlEagerParentStackProvider eagerProvider )
{
var scope = parentScope ! = null ? new ChildNameScope ( parentScope ) : ( INameScope ) new NameScope ( ) ;
var obj = builder ( new DeferredParentServiceProvider ( sp , resourceNodes , rootObject , scope ) ) ;
scope . Complete ( ) ;
var enumerator = new EagerParentStackEnumerator ( eagerProvider ) ;
while ( enumerator . TryGetNextOfType < IResourceNode > ( ) is { } node )
buffer . Add ( node ) ;
}
else
{
foreach ( var item in provider . Parents )
{
if ( item is IResourceNode node )
buffer . Add ( node ) ;
}
}
var lastParentStack = s_lastParentStack ;
if ( lastParentStack is null
| | ! lastParentStack . IsEquivalentTo ( provider , buffer , out var resourceNodes ) )
{
resourceNodes = buffer . ToArray ( ) ;
if ( lastParentStack is null )
{
lastParentStack = new LastParentStack ( ) ;
s_lastParentStack = lastParentStack ;
}
lastParentStack . Set ( provider , resourceNodes ) ;
}
if ( typeof ( T ) = = typeof ( Control ) )
return new TemplateResult < Control > ( ( Control ) obj , scope ) ;
buffer . Clear ( ) ;
return resourceNodes ;
}
// Parent resource nodes are often the same (e.g. most values in a ResourceDictionary), cache the last ones.
private sealed class LastParentStack
{
private readonly WeakReference < IAvaloniaXamlIlParentStackProvider ? > _ parentStackProvider = new ( null ) ;
private readonly WeakReference < IResourceNode [ ] ? > _ resourceNodes = new ( null ) ;
public void Set ( IAvaloniaXamlIlParentStackProvider parentStackProvider , IResourceNode [ ] resourceNodes )
{
_ parentStackProvider . SetTarget ( parentStackProvider ) ;
_ resourceNodes . SetTarget ( resourceNodes ) ;
}
public bool IsEquivalentTo (
IAvaloniaXamlIlParentStackProvider parentStackProvider ,
List < IResourceNode > resourceNodes ,
[NotNullWhen(true)] out IResourceNode [ ] ? cachedResourceNodes )
{
if ( ! _ parentStackProvider . TryGetTarget ( out var lastParentStackProvider )
| | ! _ resourceNodes . TryGetTarget ( out var lastResourceNodes )
| | parentStackProvider ! = lastParentStackProvider
| | resourceNodes . Count ! = lastResourceNodes . Length )
{
cachedResourceNodes = null ;
return false ;
}
#if NET6_0_OR_GREATER
if ( ! CollectionsMarshal . AsSpan ( resourceNodes ) . SequenceEqual ( lastResourceNodes ) )
{
cachedResourceNodes = null ;
return false ;
}
#else
for ( var i = 0 ; i < lastResourceNodes . Length ; + + i )
{
if ( lastResourceNodes [ i ] ! = resourceNodes [ i ] )
{
cachedResourceNodes = null ;
return false ;
}
}
#endif
cachedResourceNodes = lastResourceNodes ;
return true ;
}
}
private abstract class DeferredContent < T > : IDeferredContent
{
private readonly INameScope ? _ parentNameScope ;
private readonly object _ rootObject ;
private readonly IResourceNode [ ] _ parentResourceNodes ;
protected DeferredContent (
IResourceNode [ ] parentResourceNodes ,
object rootObject ,
INameScope ? parentNameScope )
{
_ parentNameScope = parentNameScope ;
_ parentResourceNodes = parentResourceNodes ;
_ rootObject = rootObject ;
}
public object Build ( IServiceProvider ? serviceProvider )
{
INameScope scope = _ parentNameScope is null ? new NameScope ( ) : new ChildNameScope ( _ parentNameScope ) ;
var obj = InvokeBuilder ( new DeferredParentServiceProvider ( serviceProvider , _ parentResourceNodes , _ rootObject , scope ) ) ;
scope . Complete ( ) ;
return new TemplateResult < T > ( ( T ) obj , scope ) ;
} ;
}
protected abstract object InvokeBuilder ( IServiceProvider serviceProvider ) ;
}
private sealed unsafe class PointerDeferredContent < T > : DeferredContent < T >
{
private readonly delegate * < IServiceProvider , object > _ builder ;
public PointerDeferredContent (
IResourceNode [ ] parentResourceNodes ,
object rootObject ,
INameScope ? parentNameScope ,
delegate * < IServiceProvider , object > builder )
: base ( parentResourceNodes , rootObject , parentNameScope )
= > _ builder = builder ;
protected override object InvokeBuilder ( IServiceProvider serviceProvider )
= > _ builder ( serviceProvider ) ;
}
private class DeferredParentServiceProvider :
IAvaloniaXamlIlParentStackProvider ,
private sealed class DelegateDeferredContent < T > : DeferredContent < T >
{
private readonly Func < IServiceProvider , object > _ builder ;
public DelegateDeferredContent (
IResourceNode [ ] parentResourceNodes ,
object rootObject ,
INameScope ? parentNameScope ,
Func < IServiceProvider , object > builder )
: base ( parentResourceNodes , rootObject , parentNameScope )
= > _ builder = builder ;
protected override object InvokeBuilder ( IServiceProvider serviceProvider )
= > _ builder ( serviceProvider ) ;
}
private sealed class DeferredParentServiceProvider :
IAvaloniaXamlIlEagerParentStackProvider ,
IServiceProvider ,
IRootObjectProvider ,
IAvaloniaXamlIlControlTemplateProvider
{
private readonly IServiceProvider ? _ parentProvider ;
private readonly List < IResourceNode > ? _ parentResourceNodes ;
private readonly IResourceNode [ ] _ parentResourceNodes ;
private readonly INameScope _ nameScope ;
private IRuntimePlatform ? _ runtimePlatform ;
private Optional < IAvaloniaXamlIlEagerParentStackProvider ? > _ parentStackProvider ;
public DeferredParentServiceProvider ( IServiceProvider ? parentProvider , List < IResourceNode > ? parentResourceNodes ,
object rootObject , INameScope nameScope )
public DeferredParentServiceProvider (
IServiceProvider ? parentProvider ,
IResourceNode [ ] parentResourceNodes ,
object rootObject ,
INameScope nameScope )
{
_ parentProvider = parentProvider ;
_ parentResourceNodes = parentResourceNodes ;
@ -61,16 +224,35 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
RootObject = rootObject ;
}
public IEnumerable < object > Parents = > GetParents ( ) ;
public object RootObject { get ; }
public object IntermediateRootObject
= > RootObject ;
IEnumerable < object > GetParents ( )
public IEnumerable < object > Parents
= > _ parentResourceNodes ;
public IReadOnlyList < object > DirectParents
= > _ parentResourceNodes ;
public IAvaloniaXamlIlEagerParentStackProvider ? ParentProvider
{
if ( _ parentResourceNodes = = null )
yield break ;
foreach ( var p in _ parentResourceNodes )
yield return p ;
get
{
if ( ! _ parentStackProvider . HasValue )
{
_ parentStackProvider =
new Optional < IAvaloniaXamlIlEagerParentStackProvider ? > ( GetParentStackProviderFromServices ( ) ) ;
}
return _ parentStackProvider . GetValueOrDefault ( ) ;
}
}
private IAvaloniaXamlIlEagerParentStackProvider ? GetParentStackProviderFromServices ( )
= > _ parentProvider ? . GetService < IAvaloniaXamlIlParentStackProvider > ( )
as IAvaloniaXamlIlEagerParentStackProvider ;
public object? GetService ( Type serviceType )
{
if ( serviceType = = typeof ( INameScope ) )
@ -82,19 +264,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
if ( serviceType = = typeof ( IAvaloniaXamlIlControlTemplateProvider ) )
return this ;
if ( serviceType = = typeof ( IRuntimePlatform ) )
{
if ( _ runtimePlatform = = null )
_ runtimePlatform = AvaloniaLocator . Current . GetService < IRuntimePlatform > ( ) ;
return _ runtimePlatform ;
}
return _ runtimePlatform ? ? = AvaloniaLocator . Current . GetService < IRuntimePlatform > ( ) ;
return _ parentProvider ? . GetService ( serviceType ) ;
}
public object RootObject { get ; }
public object IntermediateRootObject = > RootObject ;
}
public static void ApplyNonMatchingMarkupExtensionV1 ( object target , object property , IServiceProvider prov ,
object value )
{
@ -119,7 +293,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public static IServiceProvider CreateInnerServiceProviderV1 ( IServiceProvider compiled )
= > new InnerServiceProvider ( compiled ) ;
private class InnerServiceProvider : IServiceProvider
private sealed class InnerServiceProvider : IServiceProvider
{
private readonly IServiceProvider _ compiledProvider ;
private XamlTypeResolver ? _ resolver ;
@ -137,7 +311,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
}
}
private class XamlTypeResolver : IXamlTypeResolver
private sealed class XamlTypeResolver : IXamlTypeResolver
{
private readonly IAvaloniaXamlIlXmlNamespaceInfoProvider _ nsInfo ;
@ -182,11 +356,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
}
#line default
private class RootServiceProvider : IServiceProvider
private sealed class RootServiceProvider : IServiceProvider
{
private readonly INameScope _ nameScope ;
private readonly IServiceProvider ? _ parentServiceProvider ;
private readonly IRuntimePlatform ? _ runtimePlatform ;
private IAvaloniaXamlIlParentStackProvider ? _ parentStackProvider ;
public RootServiceProvider ( INameScope nameScope , IServiceProvider ? parentServiceProvider )
{
@ -200,26 +375,98 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
if ( serviceType = = typeof ( INameScope ) )
return _ nameScope ;
if ( serviceType = = typeof ( IAvaloniaXamlIlParentStackProvider ) )
return _ parentServiceProvider ? . GetService < IAvaloniaXamlIlParentStackProvider > ( )
? ? DefaultAvaloniaXamlIlParentStackProvider . Instance ;
return _ parentStackProvider ? ? = CreateParentStackProvider ( ) ;
if ( serviceType = = typeof ( IRuntimePlatform ) )
return _ runtimePlatform ? ? throw new KeyNotFoundException ( $"{nameof(IRuntimePlatform)} was not registered" ) ;
return null ;
}
private class DefaultAvaloniaXamlIlParentStackProvider : IAvaloniaXamlIlParentStackProvider
private IAvaloniaXamlIlParentStackProvider CreateParentStackProvider ( )
= > _ parentServiceProvider ? . GetService < IAvaloniaXamlIlParentStackProvider > ( )
? ? GetParentStackProviderForApplication ( Application . Current ) ;
private static IAvaloniaXamlIlEagerParentStackProvider GetParentStackProviderForApplication ( Application ? application )
= > application is null ?
EmptyAvaloniaXamlIlParentStackProvider . Instance :
ApplicationAvaloniaXamlIlParentStackProvider . GetForApplication ( application ) ;
private sealed class ApplicationAvaloniaXamlIlParentStackProvider :
IAvaloniaXamlIlEagerParentStackProvider ,
IReadOnlyList < object >
{
public static DefaultAvaloniaXamlIlParentStackProvider Instance { get ; } = new ( ) ;
private static ApplicationAvaloniaXamlIlParentStackProvider ? s_lastProvider ;
private static Application ? s_lastApplication ;
public static ApplicationAvaloniaXamlIlParentStackProvider GetForApplication ( Application application )
{
if ( application ! = s_lastApplication )
{
s_lastProvider = new ApplicationAvaloniaXamlIlParentStackProvider ( application ) ;
s_lastApplication = application ;
}
return s_lastProvider ! ;
}
private readonly Application _ application ;
private IEnumerable < object > ? _ parents ;
public ApplicationAvaloniaXamlIlParentStackProvider ( Application application )
= > _ application = application ;
public IEnumerable < object > Parents
= > _ parents ? ? = new object [ ] { _ application } ;
public IReadOnlyList < object > DirectParents
= > this ;
public int Count
= > 1 ;
public IEnumerator < object > GetEnumerator ( )
= > Parents . GetEnumerator ( ) ;
IEnumerator IEnumerable . GetEnumerator ( )
= > GetEnumerator ( ) ;
public object this [ int index ]
{
get
{
if ( Application . Current ! = null )
yield return Application . Current ;
if ( index ! = 0 )
ThrowArgumentOutOfRangeException ( ) ;
return _ application ;
}
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
[SuppressMessage("ReSharper", "NotResolvedInText")]
private static void ThrowArgumentOutOfRangeException ( )
= > throw new ArgumentOutOfRangeException ( "index" ) ;
public IAvaloniaXamlIlEagerParentStackProvider ? ParentProvider
= > null ;
}
private sealed class EmptyAvaloniaXamlIlParentStackProvider : IAvaloniaXamlIlEagerParentStackProvider
{
public static EmptyAvaloniaXamlIlParentStackProvider Instance { get ; } = new ( ) ;
private EmptyAvaloniaXamlIlParentStackProvider ( )
{
}
public IEnumerable < object > Parents
= > Array . Empty < object > ( ) ;
public IReadOnlyList < object > DirectParents
= > Array . Empty < object > ( ) ;
public IAvaloniaXamlIlEagerParentStackProvider ? ParentProvider
= > null ;
}
}
}