From e79d62a66b2c9f0bb45ddbf83bc17e47e3b1cc56 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 26 Mar 2024 02:35:59 +0500 Subject: [PATCH] Don't involve the animation system in every single property read (#15110) --- .../Animations/AnimationInstanceBase.cs | 6 +- .../Animations/KeyFrameAnimationInstance.cs | 6 +- .../Composition/Server/CompositionProperty.cs | 110 ++++++++++- .../Composition/Server/ServerCompositor.cs | 19 +- .../Server/ServerCompositorAnimations.cs | 38 ++++ .../Server/ServerCustomCompositionVisual.cs | 8 +- .../Composition/Server/ServerObject.cs | 107 ++--------- .../Server/ServerObjectAnimations.cs | 175 ++++++++++++++++++ .../CompositionGenerator/Generator.cs | 29 +-- 9 files changed, 357 insertions(+), 141 deletions(-) create mode 100644 src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs create mode 100644 src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs index 64aa1736f8..8a25f23e59 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs @@ -61,14 +61,14 @@ internal abstract class AnimationInstanceBase : IAnimationInstance { if (_trackedObjects != null) foreach (var tracked in _trackedObjects) - tracked.obj.SubscribeToInvalidation(tracked.member, this); + tracked.obj.GetOrCreateAnimations().SubscribeToInvalidation(tracked.member, this); } public virtual void Deactivate() { if (_trackedObjects != null) foreach (var tracked in _trackedObjects) - tracked.obj.UnsubscribeFromInvalidation(tracked.member, this); + tracked.obj.Animations?.UnsubscribeFromInvalidation(tracked.member, this); } public void Invalidate() @@ -76,7 +76,7 @@ internal abstract class AnimationInstanceBase : IAnimationInstance if (_invalidated) return; _invalidated = true; - TargetObject.NotifyAnimatedValueChanged(Property); + TargetObject.Animations?.NotifyAnimationInstanceInvalidated(Property); } public void OnTick() => Invalidate(); diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs index f29bb71ede..aacb0579e7 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs @@ -74,7 +74,7 @@ namespace Avalonia.Rendering.Composition.Animations && elapsed > _totalDuration) { // Active check? - TargetObject.Compositor.RemoveFromClock(this); + TargetObject.Compositor.Animations.RemoveFromClock(this); _finished = true; } return res; @@ -177,13 +177,13 @@ namespace Avalonia.Rendering.Composition.Animations { return; } - TargetObject.Compositor.AddToClock(this); + TargetObject.Compositor.Animations.AddToClock(this); base.Activate(); } public override void Deactivate() { - TargetObject.Compositor.RemoveFromClock(this); + TargetObject.Compositor.Animations.RemoveFromClock(this); base.Deactivate(); } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs index c6ff722778..180c717803 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs @@ -1,15 +1,115 @@ +using System; using System.Collections.Generic; +using System.Linq; using System.Threading; +using Avalonia.Rendering.Composition.Expressions; namespace Avalonia.Rendering.Composition.Server; internal class CompositionProperty { - private static volatile int s_NextId = 1; - public int Id { get; private set; } + private static int s_nextId = 1; + private static readonly object _lock = new(); - public static CompositionProperty Register() => new() + private static Dictionary> s_dynamicRegistry = new(); + + class ReadOnlyRegistry : Dictionary> + { + + } + + private static volatile ReadOnlyRegistry? s_ReadOnlyRegistry; + + public CompositionProperty(int id, string name, Type owner, Func? getVariant) + { + Id = id; + Name = name; + Owner = owner; + GetVariant = getVariant; + } + + public int Id { get; } + public string Name { get; } + public Type Owner { get; } + public Func? GetVariant { get; } + + public static CompositionProperty Register(string name, Func getField, Action setField, + Func? getVariant) + { + CompositionProperty prop; + lock (_lock) + { + var id = s_nextId++; + prop = new CompositionProperty(id, name, typeof(TOwner), getField, setField, getVariant); + } + + s_ReadOnlyRegistry = null; + return prop; + } + + static void PopulatePropertiesForType(Type type, List l) { - Id = Interlocked.Increment(ref s_NextId) - }; + Type? t = type; + while (t != null && t != typeof(object)) + { + if (s_dynamicRegistry.TryGetValue(t, out var lst)) + l.AddRange(lst); + t = t.BaseType; + } + } + + static ReadOnlyRegistry Build() + { + var reg = new ReadOnlyRegistry(); + foreach (var type in s_dynamicRegistry.Keys) + { + var lst = new List(); + PopulatePropertiesForType(type, lst); + reg[type] = lst.ToDictionary(x => x.Name); + } + + return reg; + } + + public static IReadOnlyDictionary? TryGetPropertiesForType(Type t) + { + GetRegistry().TryGetValue(t, out var rv); + return rv; + } + + public static CompositionProperty? Find(Type owner, string name) + { + if (TryGetPropertiesForType(owner)?.TryGetValue(name, out var prop) == true) + return prop; + return null; + } + + static ReadOnlyRegistry GetRegistry() + { + var reg = s_ReadOnlyRegistry; + if (reg != null) + return reg; + lock (_lock) + { + // ReSharper disable once NonAtomicCompoundOperator + // This is the only line ever that would set the field to a not-null value, and we are inside of a lock + return s_ReadOnlyRegistry ??= Build(); + } + } } + +internal class CompositionProperty : CompositionProperty +{ + public Func GetField { get; } + public Action SetField { get; } + + public CompositionProperty(int id, string name, Type owner, + Func getField, + Action setField, + Func? getVariant) + : base(id, name, owner, getVariant) + { + GetField = getField; + SetField = setField; + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index a471fc765b..196bd88409 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -27,8 +27,6 @@ namespace Avalonia.Rendering.Composition.Server public Stopwatch Clock { get; } = Stopwatch.StartNew(); public TimeSpan ServerNow { get; private set; } private readonly List _activeTargets = new(); - private readonly HashSet _clockItems = new(); - private readonly List _clockItemsToUpdate = new(); internal BatchStreamObjectPool BatchObjectPool; internal BatchStreamMemoryPool BatchMemoryPool; private readonly object _lock = new object(); @@ -39,12 +37,14 @@ namespace Avalonia.Rendering.Composition.Server internal static readonly object RenderThreadJobsStartMarker = new(); internal static readonly object RenderThreadJobsEndMarker = new(); public CompositionOptions Options { get; } + public ServerCompositorAnimations Animations { get; } public ServerCompositor(IRenderLoop renderLoop, IPlatformGraphics? platformGraphics, CompositionOptions options, BatchStreamObjectPool batchObjectPool, BatchStreamMemoryPool batchMemoryPool) { Options = options; + Animations = new(); _renderLoop = renderLoop; RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics); RenderInterface.ContextDisposed += RT_OnContextDisposed; @@ -210,14 +210,9 @@ namespace Avalonia.Rendering.Composition.Server UpdateServerTime(); ApplyPendingBatches(); NotifyBatchesProcessed(); - - foreach(var animation in _clockItems) - _clockItemsToUpdate.Add(animation); - foreach (var animation in _clockItemsToUpdate) - animation.OnTick(); - - _clockItemsToUpdate.Clear(); + Animations.Process(); + ApplyEnqueuedRenderResourceChanges(); @@ -244,12 +239,6 @@ namespace Avalonia.Rendering.Composition.Server _activeTargets.Remove(target); } - public void AddToClock(IServerClockItem item) => - _clockItems.Add(item); - - public void RemoveFromClock(IServerClockItem item) => - _clockItems.Remove(item); - public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { using (RenderInterface.EnsureCurrent()) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs new file mode 100644 index 0000000000..1f2c7dedb8 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace Avalonia.Rendering.Composition.Server; + +internal class ServerCompositorAnimations +{ + private readonly HashSet _clockItems = new(); + private readonly List _clockItemsToUpdate = new(); + private readonly HashSet _dirtyAnimatedObjects = new(); + private readonly Queue _dirtyAnimatedObjectQueue = new(); + + public void AddToClock(IServerClockItem item) => + _clockItems.Add(item); + + public void RemoveFromClock(IServerClockItem item) => + _clockItems.Remove(item); + + public void Process() + { + foreach (var animation in _clockItems) + _clockItemsToUpdate.Add(animation); + + foreach (var animation in _clockItemsToUpdate) + animation.OnTick(); + + _clockItemsToUpdate.Clear(); + + while (_dirtyAnimatedObjectQueue.Count > 0) + _dirtyAnimatedObjectQueue.Dequeue().EvaluateAnimations(); + _dirtyAnimatedObjects.Clear(); + } + + public void AddDirtyAnimatedObject(ServerObjectAnimations obj) + { + if (_dirtyAnimatedObjects.Add(obj)) + _dirtyAnimatedObjectQueue.Enqueue(obj); + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs index 483c4b26d0..773812773e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs @@ -38,7 +38,7 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer _wantsNextAnimationFrameAfterTick = false; _handler.OnAnimationFrameUpdate(); if (!_wantsNextAnimationFrameAfterTick) - Compositor.RemoveFromClock(this); + Compositor.Animations.RemoveFromClock(this); } public override Rect OwnContentBounds => _handler.GetRenderBounds(); @@ -46,13 +46,13 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer protected override void OnAttachedToRoot(ServerCompositionTarget target) { if (_wantsNextAnimationFrameAfterTick) - Compositor.AddToClock(this); + Compositor.Animations.AddToClock(this); base.OnAttachedToRoot(target); } protected override void OnDetachedFromRoot(ServerCompositionTarget target) { - Compositor.RemoveFromClock(this); + Compositor.Animations.RemoveFromClock(this); base.OnDetachedFromRoot(target); } @@ -67,7 +67,7 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer { _wantsNextAnimationFrameAfterTick = true; if (Root != null) - Compositor.AddToClock(this); + Compositor.Animations.AddToClock(this); } protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs index 4cfc14312b..c845321cc9 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs @@ -16,37 +16,15 @@ namespace Avalonia.Rendering.Composition.Server internal abstract class ServerObject : SimpleServerObject, IExpressionObject { private uint _activationCount; + private ServerObjectAnimations? _animations; + public ServerObjectAnimations? Animations => _animations; + public ServerObjectAnimations GetOrCreateAnimations() => _animations ??= new(this); public bool IsActive => _activationCount != 0; - private InlineDictionary _subscriptions; - private InlineDictionary _animations; - - private class ServerObjectSubscriptionStore - { - public bool IsValid; - public RefTrackingDictionary? Subscribers; - - public void Invalidate() - { - if (!IsValid) - return; - IsValid = false; - if (Subscribers != null) - foreach (var sub in Subscribers) - sub.Key.Invalidate(); - } - } public ServerObject(ServerCompositor compositor) : base(compositor) { } - public virtual ExpressionVariant GetPropertyForAnimation(string name) - { - return default; - } - - ExpressionVariant IExpressionObject.GetProperty(string name) => GetPropertyForAnimation(name); - public void Activate() { _activationCount++; @@ -65,92 +43,37 @@ namespace Avalonia.Rendering.Composition.Server Deactivated(); } - protected void Activated() - { - foreach(var kp in _animations) - kp.Value.Activate(); - } - - protected void Deactivated() - { - foreach(var kp in _animations) - kp.Value.Deactivate(); - } + private void Activated() => _animations?.Activated(); - void InvalidateSubscriptions(CompositionProperty property) - { - if(_subscriptions.TryGetValue(property, out var subs)) - subs.Invalidate(); - } + private void Deactivated() => _animations?.Deactivated(); protected new void SetValue(CompositionProperty prop, ref T field, T value) { field = value; - InvalidateSubscriptions(prop); - } - - protected new T GetValue(CompositionProperty prop, ref T field) - { - if (_subscriptions.TryGetValue(prop, out var subs)) - subs.IsValid = true; - return field; + _animations?.OnSetDirectValue(prop); } - protected void SetAnimatedValue(CompositionProperty prop, ref T field, + protected void SetAnimatedValue(CompositionProperty prop, ref T field, TimeSpan committedAt, IAnimationInstance animation) where T : struct { - if (IsActive && _animations.TryGetValue(prop, out var oldAnimation)) - oldAnimation.Deactivate(); - _animations[prop] = animation; - - animation.Initialize(committedAt, ExpressionVariant.Create(field), prop); - if(IsActive) - animation.Activate(); - - InvalidateSubscriptions(prop); + GetOrCreateAnimations().OnSetAnimatedValue(prop, ref field, committedAt, animation); } protected void SetAnimatedValue(CompositionProperty property, out T field, T value) { - if (_animations.TryGetAndRemoveValue(property, out var animation) && IsActive) - animation.Deactivate(); field = value; - InvalidateSubscriptions(property); + _animations?.RemoveAnimationForProperty(property); } - protected T GetAnimatedValue(CompositionProperty property, ref T field) where T : struct - { - if (_subscriptions.TryGetValue(property, out var subscriptions)) - subscriptions.IsValid = true; - - if (_animations.TryGetValue(property, out var animation)) - field = animation.Evaluate(Compositor.ServerNow, ExpressionVariant.Create(field)) - .CastOrDefault(); - - return field; - } + public virtual void NotifyAnimatedValueChanged(CompositionProperty prop) => ValuesInvalidated(); - public virtual void NotifyAnimatedValueChanged(CompositionProperty prop) - { - InvalidateSubscriptions(prop); - ValuesInvalidated(); - } - - public void SubscribeToInvalidation(CompositionProperty member, IAnimationInstance animation) + public virtual CompositionProperty? GetCompositionProperty(string fieldName) => null; + ExpressionVariant IExpressionObject.GetProperty(string name) { - if (!_subscriptions.TryGetValue(member, out var store)) - _subscriptions[member] = store = new ServerObjectSubscriptionStore(); - if (store.Subscribers == null) - store.Subscribers = new(); - store.Subscribers.AddRef(animation); - } + if (_animations == null) + return CompositionProperty.Find(this.GetType(), name)?.GetVariant?.Invoke(this) ?? default; - public void UnsubscribeFromInvalidation(CompositionProperty member, IAnimationInstance animation) - { - if(_subscriptions.TryGetValue(member, out var store)) - store.Subscribers?.ReleaseRef(animation); + return _animations.GetPropertyForAnimation(name); } - - public virtual CompositionProperty? GetCompositionProperty(string fieldName) => null; } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs new file mode 100644 index 0000000000..1d213781c7 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Avalonia.Rendering.Composition.Animations; +using Avalonia.Rendering.Composition.Expressions; +using Avalonia.Utilities; + +namespace Avalonia.Rendering.Composition.Server; + +class ServerObjectAnimations +{ + private readonly ServerObject _owner; + private InlineDictionary _subscriptions; + private InlineDictionary _animations; + private readonly IReadOnlyDictionary _properties; + + public ServerObjectAnimations(ServerObject owner) + { + _owner = owner; + _properties = CompositionProperty.TryGetPropertiesForType(owner.GetType()) ?? + new Dictionary(); + } + + private class ServerObjectSubscriptionStore + { + public bool IsValid; + public RefTrackingDictionary? Subscribers; + + public void Invalidate() + { + if (!IsValid) + return; + IsValid = false; + if (Subscribers != null) + foreach (var sub in Subscribers) + sub.Key.Invalidate(); + } + } + + abstract class ServerObjectAnimationInstance + { + public ServerObjectAnimations Owner { get; } + private ExpressionVariant _cachedVariant; + public bool IsDirty { get; set; } = true; + public bool NeedsUpdate { get; set; } = true; + public IAnimationInstance Animation { get; } + + public ServerObjectAnimationInstance(ServerObjectAnimations owner, IAnimationInstance animation) + { + Animation = animation; + Owner = owner; + } + + public ExpressionVariant GetVariant() + { + var compositor = Owner._owner.Compositor; + if (!IsDirty) + return _cachedVariant; + + // We are setting this _before_ evaluating animation to prevent stack overflows due to potential + // cyclic references + IsDirty = false; + + return _cachedVariant = Animation.Evaluate(Owner._owner.Compositor.ServerNow, _cachedVariant); + } + + public abstract void UpdateTargetProperty(); + } + + class ServerObjectAnimationInstance : ServerObjectAnimationInstance where T : struct + { + private readonly CompositionProperty _property; + + public ServerObjectAnimationInstance(ServerObjectAnimations owner, IAnimationInstance animation, + CompositionProperty property) : base(owner, animation) + { + _property = property; + } + + public override void UpdateTargetProperty() + { + if (NeedsUpdate) + { + NeedsUpdate = false; + _property.SetField(Owner._owner, GetVariant().CastOrDefault()); + Owner._owner.NotifyAnimatedValueChanged(_property); + } + } + } + + public void Activated() + { + foreach(var kp in _animations) + kp.Value.Animation.Activate(); + } + + public void Deactivated() + { + foreach(var kp in _animations) + kp.Value.Animation.Deactivate(); + } + + public void OnSetDirectValue(CompositionProperty property) + { + if(_subscriptions.TryGetValue(property, out var subs)) + subs.Invalidate(); + } + + public void OnSetAnimatedValue(CompositionProperty prop, ref T field, TimeSpan committedAt, IAnimationInstance animation) where T : struct + { + if (_owner.IsActive && _animations.TryGetValue(prop, out var oldAnimation)) + oldAnimation.Animation.Deactivate(); + _animations[prop] = new ServerObjectAnimationInstance(this, animation, prop); + + animation.Initialize(committedAt, ExpressionVariant.Create(field), prop); + if(_owner.IsActive) + animation.Activate(); + + OnSetDirectValue(prop); + } + + public void RemoveAnimationForProperty(CompositionProperty property) + { + if (_animations.TryGetAndRemoveValue(property, out var animation) && _owner.IsActive) + animation.Animation.Deactivate(); + OnSetDirectValue(property); + } + + public void SubscribeToInvalidation(CompositionProperty member, IAnimationInstance animation) + { + if (!_subscriptions.TryGetValue(member, out var store)) + _subscriptions[member] = store = new ServerObjectSubscriptionStore(); + if (store.Subscribers == null) + store.Subscribers = new(); + store.Subscribers.AddRef(animation); + } + + public void UnsubscribeFromInvalidation(CompositionProperty member, IAnimationInstance animation) + { + if(_subscriptions.TryGetValue(member, out var store)) + store.Subscribers?.ReleaseRef(animation); + } + + public ExpressionVariant GetPropertyForAnimation(string name) + { + if (!_properties.TryGetValue(name, out var prop)) + return default; + + if (_subscriptions.TryGetValue(prop, out var subs)) + subs.IsValid = true; + + if (_animations.TryGetValue(prop, out var animation)) + return animation.GetVariant(); + + return prop.GetVariant?.Invoke(_owner) ?? default; + } + + public void EvaluateAnimations() + { + foreach (var animation in _animations) + if (animation.Value.IsDirty) + animation.Value.UpdateTargetProperty(); + } + + public void NotifyAnimationInstanceInvalidated(CompositionProperty property) + { + if (_animations.TryGetValue(property, out var instance)) + { + instance.IsDirty = instance.NeedsUpdate = true; + _owner.Compositor.Animations.AddDirtyAnimatedObject(this); + } + else + Debug.Assert(false); + } +} \ No newline at end of file diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.cs index 2a49980f96..2075295ca2 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.cs @@ -166,15 +166,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator .AddModifiers(SyntaxKind.PublicKeyword) .AddAccessorListAccessors( AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithExpressionBody( - ArrowExpressionClause( - InvocationExpression(IdentifierName("GetAnimatedValue"), - ArgumentList(SeparatedList(new[] - { - Argument(IdentifierName(CompositionPropertyField(prop))), - Argument(null, Token(SyntaxKind.RefKeyword), - IdentifierName(fieldName)) - } - ))))).WithSemicolonToken(Semicolon()), + ArrowExpressionClause(IdentifierName(fieldName))).WithSemicolonToken(Semicolon()), AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) .WithExpressionBody(ArrowExpressionClause( ParseExpression($"SetAnimatedValue({CompositionPropertyField(prop)}, out {PropertyBackingFieldName(prop)}, value)"))) @@ -187,13 +179,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator .AddModifiers(SyntaxKind.PublicKeyword) .AddAccessorListAccessors( AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, - Block(ReturnStatement( - InvocationExpression(IdentifierName("GetValue"), - ArgumentList(SeparatedList(new[]{ - Argument(IdentifierName(CompositionPropertyField(prop))), - Argument(null, Token(SyntaxKind.RefKeyword), IdentifierName(fieldName)) - } - )))))), + Block(ReturnStatement(IdentifierName(fieldName)))), AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, Block( ParseStatement("var changed = false;"), @@ -235,8 +221,13 @@ namespace Avalonia.SourceGenerator.CompositionGenerator serverGetPropertyBody = ApplyGetProperty(serverGetPropertyBody, prop); serverGetCompositionPropertyBody = ApplyGetProperty(serverGetCompositionPropertyBody, prop, CompositionPropertyField(prop)); - server = server.AddMembers(DeclareField("CompositionProperty", CompositionPropertyField(prop), - EqualsValueClause(ParseExpression("CompositionProperty.Register()")), + + string compositionPropertyVariantGetter = "null"; + if(VariantPropertyTypes.Contains(prop.Type)) + compositionPropertyVariantGetter = $"obj => (({serverName})obj).{fieldName}"; + server = server.AddMembers(DeclareField($"CompositionProperty<{serverPropertyType}>", CompositionPropertyField(prop), + EqualsValueClause(ParseExpression( + $"CompositionProperty.Register<{serverName}, {serverPropertyType}>(\"{prop.Name}\", obj => (({serverName})obj).{fieldName}, (obj, v) => (({serverName})obj).{fieldName} = v, {compositionPropertyVariantGetter})")), SyntaxKind.InternalKeyword, SyntaxKind.StaticKeyword)); if (prop.DefaultValue != null) @@ -282,7 +273,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator if (!cl.ServerOnly) { - server = WithGetPropertyForAnimation(server, serverGetPropertyBody); + //server = WithGetPropertyForAnimation(server, serverGetPropertyBody); server = WithGetCompositionProperty(server, serverGetCompositionPropertyBody); }