Browse Source

CompositionAnimation fixes (#20936)

* fix: ExpressionVariant types

* fix: CompositionProperty is not correctly registered

* fix(ExpressionVariant): remove Scalar

* refactor: use switch expressions

* fix: "this.Target" is not tracked

* fix: unit tests

* fix: remove registry

* remove unused code

* fix

* fix: notify other animations when update

* add unit tests

* fix: IsValid

* fix

* fix: requeue target when another animation invalidated during evaluation

* fix: GetProperty fails in aot

* fix
pull/21059/head
Betta_Fish 2 months ago
committed by GitHub
parent
commit
bb3f0f331e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs
  2. 8
      src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs
  3. 15
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  4. 14
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs
  5. 555
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs
  6. 65
      src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs
  7. 9
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs
  8. 2
      src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
  9. 11
      src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs
  10. 2
      src/tools/DevGenerators/CompositionGenerator/Generator.cs
  11. 6
      tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationParserTests.cs
  12. 133
      tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs

4
src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs

@ -30,7 +30,9 @@ internal abstract class AnimationInstanceBase : IAnimationInstance
_trackedObjects = new ();
foreach (var t in trackedObjects)
{
var obj = Parameters.GetObjectParameter(t.name);
var obj = (t.name == ExpressionKeywords.Target)
? TargetObject
: Parameters.GetObjectParameter(t.name);
if (obj is ServerObject tracked)
{
var off = tracked.GetCompositionProperty(t.member);

8
src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs

@ -62,12 +62,10 @@ namespace Avalonia.Rendering.Composition.Expressions
var arg = arguments[c].Type;
if (parameter != arg)
{
var canCast = (parameter == VariantType.Double && arg == VariantType.Scalar)
|| (parameter == VariantType.Vector3D && arg == VariantType.Vector3)
var canCast = (parameter == VariantType.Vector3D && arg == VariantType.Vector3)
|| (parameter == VariantType.Vector && arg == VariantType.Vector2)
|| (anyCast && (
(arg == VariantType.Double && parameter == VariantType.Scalar)
|| (arg == VariantType.Vector3D && parameter == VariantType.Vector3)
(arg == VariantType.Vector3D && parameter == VariantType.Vector3)
|| (arg == VariantType.Vector && parameter == VariantType.Vector2)
));
if (!canCast)
@ -112,7 +110,7 @@ namespace Avalonia.Rendering.Composition.Expressions
static readonly Dictionary<Type, VariantType> TypeMap = new Dictionary<Type, VariantType>
{
[typeof(bool)] = VariantType.Boolean,
[typeof(float)] = VariantType.Scalar,
[typeof(float)] = VariantType.Double,
[typeof(double)] = VariantType.Double,
[typeof(Vector2)] = VariantType.Vector2,
[typeof(Vector)] = VariantType.Vector,

15
src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs

@ -104,6 +104,17 @@ namespace Avalonia.Rendering.Composition.Expressions
False
}
internal static class ExpressionKeywords
{
public const string StartingValue = "this.startingvalue";
public const string CurrentValue = "this.currentvalue";
public const string FinalValue = "this.finalvalue";
public const string Pi = "pi";
public const string True = "true";
public const string False = "false";
public const string Target = "this.target";
}
internal class ConditionalExpression : Expression
{
public Expression Condition { get; }
@ -202,6 +213,10 @@ namespace Avalonia.Rendering.Composition.Expressions
Target.CollectReferences(references);
if (Target is ParameterExpression pe)
references.Add((pe.Name, Member));
else if (Target is KeywordExpression { Keyword : ExpressionKeyword.Target })
{
references.Add((ExpressionKeywords.Target, Member));
}
}
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)

14
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs

@ -25,19 +25,19 @@ namespace Avalonia.Rendering.Composition.Expressions
{
// We can parse keywords, parameter names and constants
expr = null;
if (parser.TryParseKeywordLowerCase("this.startingvalue"))
if (parser.TryParseKeywordLowerCase(ExpressionKeywords.StartingValue))
expr = new KeywordExpression(ExpressionKeyword.StartingValue);
else if(parser.TryParseKeywordLowerCase("this.currentvalue"))
else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.CurrentValue))
expr = new KeywordExpression(ExpressionKeyword.CurrentValue);
else if(parser.TryParseKeywordLowerCase("this.finalvalue"))
else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.FinalValue))
expr = new KeywordExpression(ExpressionKeyword.FinalValue);
else if(parser.TryParseKeywordLowerCase("pi"))
else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.Pi))
expr = new KeywordExpression(ExpressionKeyword.Pi);
else if(parser.TryParseKeywordLowerCase("true"))
else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.True))
expr = new KeywordExpression(ExpressionKeyword.True);
else if(parser.TryParseKeywordLowerCase("false"))
else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.False))
expr = new KeywordExpression(ExpressionKeyword.False);
else if (parser.TryParseKeywordLowerCase("this.target"))
else if (parser.TryParseKeywordLowerCase(ExpressionKeywords.Target))
expr = new KeywordExpression(ExpressionKeyword.Target);
if (expr != null)

555
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs

@ -10,7 +10,6 @@ namespace Avalonia.Rendering.Composition.Expressions
{
Invalid,
Boolean,
Scalar,
Double,
Vector2,
Vector3,
@ -33,7 +32,6 @@ namespace Avalonia.Rendering.Composition.Expressions
[FieldOffset(0)] public VariantType Type;
[FieldOffset(4)] public bool Boolean;
[FieldOffset(4)] public float Scalar;
[FieldOffset(4)] public double Double;
[FieldOffset(4)] public Vector2 Vector2;
[FieldOffset(4)] public Vector3 Vector3;
@ -45,191 +43,194 @@ namespace Avalonia.Rendering.Composition.Expressions
[FieldOffset(4)] public Matrix4x4 Matrix4x4;
[FieldOffset(4)] public Quaternion Quaternion;
[FieldOffset(4)] public Color Color;
public ExpressionVariant GetProperty(string property)
{
if (Type == VariantType.Vector2)
{
if (ReferenceEquals(property, "X"))
if (IsMatch(property, "X"))
return Vector2.X;
if (ReferenceEquals(property, "Y"))
if (IsMatch(property, "Y"))
return Vector2.Y;
return default;
}
if (Type == VariantType.Vector)
{
if (ReferenceEquals(property, "X"))
if (IsMatch(property, "X"))
return Vector.X;
if (ReferenceEquals(property, "Y"))
if (IsMatch(property, "Y"))
return Vector.Y;
return default;
}
if (Type == VariantType.Vector3)
{
if (ReferenceEquals(property, "X"))
if (IsMatch(property, "X"))
return Vector3.X;
if (ReferenceEquals(property, "Y"))
if (IsMatch(property, "Y"))
return Vector3.Y;
if (ReferenceEquals(property, "Z"))
if (IsMatch(property, "Z"))
return Vector3.Z;
if(ReferenceEquals(property, "XY"))
if (IsMatch(property, "XY"))
return new Vector2(Vector3.X, Vector3.Y);
if(ReferenceEquals(property, "YX"))
if (IsMatch(property, "YX"))
return new Vector2(Vector3.Y, Vector3.X);
if(ReferenceEquals(property, "XZ"))
if (IsMatch(property, "XZ"))
return new Vector2(Vector3.X, Vector3.Z);
if(ReferenceEquals(property, "ZX"))
if (IsMatch(property, "ZX"))
return new Vector2(Vector3.Z, Vector3.X);
if(ReferenceEquals(property, "YZ"))
if (IsMatch(property, "YZ"))
return new Vector2(Vector3.Y, Vector3.Z);
if(ReferenceEquals(property, "ZY"))
if (IsMatch(property, "ZY"))
return new Vector2(Vector3.Z, Vector3.Y);
return default;
}
if (Type == VariantType.Vector3D)
{
if (ReferenceEquals(property, "X"))
if (IsMatch(property, "X"))
return Vector3D.X;
if (ReferenceEquals(property, "Y"))
if (IsMatch(property, "Y"))
return Vector3D.Y;
if (ReferenceEquals(property, "Z"))
if (IsMatch(property, "Z"))
return Vector3D.Z;
if(ReferenceEquals(property, "XY"))
if (IsMatch(property, "XY"))
return new Vector(Vector3D.X, Vector3D.Y);
if(ReferenceEquals(property, "YX"))
if (IsMatch(property, "YX"))
return new Vector(Vector3D.Y, Vector3D.X);
if(ReferenceEquals(property, "XZ"))
if (IsMatch(property, "XZ"))
return new Vector(Vector3D.X, Vector3D.Z);
if(ReferenceEquals(property, "ZX"))
if (IsMatch(property, "ZX"))
return new Vector(Vector3D.Z, Vector3D.X);
if(ReferenceEquals(property, "YZ"))
if (IsMatch(property, "YZ"))
return new Vector(Vector3D.Y, Vector3D.Z);
if(ReferenceEquals(property, "ZY"))
if (IsMatch(property, "ZY"))
return new Vector(Vector3D.Z, Vector3D.Y);
return default;
}
if (Type == VariantType.Vector4)
{
if (ReferenceEquals(property, "X"))
if (IsMatch(property, "X"))
return Vector4.X;
if (ReferenceEquals(property, "Y"))
if (IsMatch(property, "Y"))
return Vector4.Y;
if (ReferenceEquals(property, "Z"))
if (IsMatch(property, "Z"))
return Vector4.Z;
if (ReferenceEquals(property, "W"))
if (IsMatch(property, "W"))
return Vector4.W;
return default;
}
if (Type == VariantType.Matrix3x2)
{
if (ReferenceEquals(property, "M11"))
if (IsMatch(property, "M11"))
return Matrix3x2.M11;
if (ReferenceEquals(property, "M12"))
if (IsMatch(property, "M12"))
return Matrix3x2.M12;
if (ReferenceEquals(property, "M21"))
if (IsMatch(property, "M21"))
return Matrix3x2.M21;
if (ReferenceEquals(property, "M22"))
if (IsMatch(property, "M22"))
return Matrix3x2.M22;
if (ReferenceEquals(property, "M31"))
if (IsMatch(property, "M31"))
return Matrix3x2.M31;
if (ReferenceEquals(property, "M32"))
if (IsMatch(property, "M32"))
return Matrix3x2.M32;
return default;
}
if (Type == VariantType.AvaloniaMatrix)
{
if (ReferenceEquals(property, "M11"))
if (IsMatch(property, "M11"))
return AvaloniaMatrix.M11;
if (ReferenceEquals(property, "M12"))
if (IsMatch(property, "M12"))
return AvaloniaMatrix.M12;
if (ReferenceEquals(property, "M13"))
if (IsMatch(property, "M13"))
return AvaloniaMatrix.M13;
if (ReferenceEquals(property, "M21"))
if (IsMatch(property, "M21"))
return AvaloniaMatrix.M21;
if (ReferenceEquals(property, "M22"))
if (IsMatch(property, "M22"))
return AvaloniaMatrix.M22;
if (ReferenceEquals(property, "M23"))
if (IsMatch(property, "M23"))
return AvaloniaMatrix.M23;
if (ReferenceEquals(property, "M31"))
if (IsMatch(property, "M31"))
return AvaloniaMatrix.M31;
if (ReferenceEquals(property, "M32"))
if (IsMatch(property, "M32"))
return AvaloniaMatrix.M32;
if (ReferenceEquals(property, "M33"))
if (IsMatch(property, "M33"))
return AvaloniaMatrix.M33;
return default;
}
if (Type == VariantType.Matrix4x4)
{
if (ReferenceEquals(property, "M11"))
if (IsMatch(property, "M11"))
return Matrix4x4.M11;
if (ReferenceEquals(property, "M12"))
if (IsMatch(property, "M12"))
return Matrix4x4.M12;
if (ReferenceEquals(property, "M13"))
if (IsMatch(property, "M13"))
return Matrix4x4.M13;
if (ReferenceEquals(property, "M14"))
if (IsMatch(property, "M14"))
return Matrix4x4.M14;
if (ReferenceEquals(property, "M21"))
if (IsMatch(property, "M21"))
return Matrix4x4.M21;
if (ReferenceEquals(property, "M22"))
if (IsMatch(property, "M22"))
return Matrix4x4.M22;
if (ReferenceEquals(property, "M23"))
if (IsMatch(property, "M23"))
return Matrix4x4.M23;
if (ReferenceEquals(property, "M24"))
if (IsMatch(property, "M24"))
return Matrix4x4.M24;
if (ReferenceEquals(property, "M31"))
if (IsMatch(property, "M31"))
return Matrix4x4.M31;
if (ReferenceEquals(property, "M32"))
if (IsMatch(property, "M32"))
return Matrix4x4.M32;
if (ReferenceEquals(property, "M33"))
if (IsMatch(property, "M33"))
return Matrix4x4.M33;
if (ReferenceEquals(property, "M34"))
if (IsMatch(property, "M34"))
return Matrix4x4.M34;
if (ReferenceEquals(property, "M41"))
if (IsMatch(property, "M41"))
return Matrix4x4.M41;
if (ReferenceEquals(property, "M42"))
if (IsMatch(property, "M42"))
return Matrix4x4.M42;
if (ReferenceEquals(property, "M43"))
if (IsMatch(property, "M43"))
return Matrix4x4.M43;
if (ReferenceEquals(property, "M44"))
if (IsMatch(property, "M44"))
return Matrix4x4.M44;
return default;
}
if (Type == VariantType.Quaternion)
{
if (ReferenceEquals(property, "X"))
if (IsMatch(property, "X"))
return Quaternion.X;
if (ReferenceEquals(property, "Y"))
if (IsMatch(property, "Y"))
return Quaternion.Y;
if (ReferenceEquals(property, "Z"))
if (IsMatch(property, "Z"))
return Quaternion.Z;
if (ReferenceEquals(property, "W"))
if (IsMatch(property, "W"))
return Quaternion.W;
return default;
}
if (Type == VariantType.Color)
{
if (ReferenceEquals(property, "A"))
if (IsMatch(property, "A"))
return Color.A;
if (ReferenceEquals(property, "R"))
if (IsMatch(property, "R"))
return Color.R;
if (ReferenceEquals(property, "G"))
if (IsMatch(property, "G"))
return Color.G;
if (ReferenceEquals(property, "B"))
if (IsMatch(property, "B"))
return Color.B;
return default;
}
return default;
static bool IsMatch(string propertyName, string memberName) =>
string.Equals(propertyName, memberName, StringComparison.Ordinal);
}
public static implicit operator ExpressionVariant(bool value) =>
@ -238,13 +239,6 @@ namespace Avalonia.Rendering.Composition.Expressions
Type = VariantType.Boolean,
Boolean = value
};
public static implicit operator ExpressionVariant(float scalar) =>
new ExpressionVariant
{
Type = VariantType.Scalar,
Scalar = scalar
};
public static implicit operator ExpressionVariant(double d) =>
new ExpressionVariant
@ -300,7 +294,7 @@ namespace Avalonia.Rendering.Composition.Expressions
public static implicit operator ExpressionVariant(Matrix value) =>
new ExpressionVariant
{
Type = VariantType.Matrix3x2,
Type = VariantType.AvaloniaMatrix,
AvaloniaMatrix = value
};
@ -330,15 +324,12 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type != right.Type || left.Type == VariantType.Invalid)
return default;
if (left.Type == VariantType.Scalar)
return left.Scalar + right.Scalar;
if (left.Type == VariantType.Double)
return left.Double + right.Double;
if (left.Type == VariantType.Vector2)
return left.Vector2 + right.Vector2;
if (left.Type == VariantType.Vector)
return left.Vector + right.Vector;
@ -350,16 +341,16 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector4)
return left.Vector4 + right.Vector4;
if (left.Type == VariantType.Matrix3x2)
return left.Matrix3x2 + right.Matrix3x2;
if (left.Type == VariantType.Matrix4x4)
return left.Matrix4x4 + right.Matrix4x4;
if (left.Type == VariantType.Quaternion)
return left.Quaternion + right.Quaternion;
return default;
}
@ -368,15 +359,12 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type != right.Type || left.Type == VariantType.Invalid)
return default;
if (left.Type == VariantType.Scalar)
return left.Scalar - right.Scalar;
if (left.Type == VariantType.Double)
return left.Double - right.Double;
if (left.Type == VariantType.Vector2)
return left.Vector2 - right.Vector2;
if (left.Type == VariantType.Vector)
return left.Vector - right.Vector;
@ -388,13 +376,13 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector4)
return left.Vector4 - right.Vector4;
if (left.Type == VariantType.Matrix3x2)
return left.Matrix3x2 - right.Matrix3x2;
if (left.Type == VariantType.Matrix4x4)
return left.Matrix4x4 - right.Matrix4x4;
if (left.Type == VariantType.Quaternion)
return left.Quaternion - right.Quaternion;
@ -403,34 +391,31 @@ namespace Avalonia.Rendering.Composition.Expressions
public static ExpressionVariant operator -(ExpressionVariant left)
{
if (left.Type == VariantType.Scalar)
return -left.Scalar;
if (left.Type == VariantType.Double)
return -left.Double;
if (left.Type == VariantType.Vector2)
return -left.Vector2;
if (left.Type == VariantType.Vector)
return -left.Vector;
if (left.Type == VariantType.Vector3)
return -left.Vector3;
if (left.Type == VariantType.Vector3D)
return -left.Vector3D;
if (left.Type == VariantType.Vector4)
return -left.Vector4;
if (left.Type == VariantType.Matrix3x2)
return -left.Matrix3x2;
if (left.Type == VariantType.AvaloniaMatrix)
return -left.AvaloniaMatrix;
if (left.Type == VariantType.Matrix4x4)
return -left.Matrix4x4;
@ -445,9 +430,6 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Invalid || right.Type == VariantType.Invalid)
return default;
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar * right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double * right.Double;
@ -457,53 +439,50 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector && right.Type == VariantType.Vector)
return Vector.Multiply(left.Vector, right.Vector);
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar)
return left.Vector2 * right.Scalar;
if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar)
return left.Vector * right.Scalar;
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Double)
return left.Vector2 * (float)right.Double;
if (left.Type == VariantType.Vector && right.Type == VariantType.Double)
return left.Vector * right.Double;
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3)
return left.Vector3 * right.Vector3;
if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D)
return Vector3D.Multiply(left.Vector3D, right.Vector3D);
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar)
return left.Vector3 * right.Scalar;
if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar)
return Vector3D.Multiply(left.Vector3D, right.Scalar);
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Double)
return left.Vector3 * (float)right.Double;
if (left.Type == VariantType.Vector3D && right.Type == VariantType.Double)
return Vector3D.Multiply(left.Vector3D, right.Double);
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4)
return left.Vector4 * right.Vector4;
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar)
return left.Vector4 * right.Scalar;
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Double)
return left.Vector4 * (float)right.Double;
if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Matrix3x2)
return left.Matrix3x2 * right.Matrix3x2;
if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Scalar)
return left.Matrix3x2 * right.Scalar;
if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Double)
return left.Matrix3x2 * (float)right.Double;
if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.AvaloniaMatrix)
return left.AvaloniaMatrix * right.AvaloniaMatrix;
if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Matrix4x4)
return left.Matrix4x4 * right.Matrix4x4;
if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Scalar)
return left.Matrix4x4 * right.Scalar;
if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Double)
return left.Matrix4x4 * (float)right.Double;
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion)
return left.Quaternion * right.Quaternion;
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Scalar)
return left.Quaternion * right.Scalar;
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Double)
return left.Quaternion * (float)right.Double;
return default;
}
@ -513,9 +492,6 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Invalid || right.Type == VariantType.Invalid)
return default;
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar / right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double / right.Double;
@ -525,14 +501,11 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector && right.Type == VariantType.Vector)
return Vector.Divide(left.Vector, right.Vector);
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar)
return left.Vector2 / right.Scalar;
if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar)
return left.Vector / right.Scalar;
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Double)
return left.Vector2 / (float)right.Double;
if (left.Type == VariantType.Vector && right.Type == VariantType.Double)
return left.Vector / right.Scalar;
return left.Vector / right.Double;
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3)
return left.Vector3 / right.Vector3;
@ -540,21 +513,18 @@ namespace Avalonia.Rendering.Composition.Expressions
if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D)
return Vector3D.Divide(left.Vector3D, right.Vector3D);
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar)
return left.Vector3 / right.Scalar;
if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar)
return Avalonia.Vector3D.Divide(left.Vector3D, right.Scalar);
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Double)
return left.Vector3 / (float)right.Double;
if (left.Type == VariantType.Vector3D && right.Type == VariantType.Double)
return Avalonia.Vector3D.Divide(left.Vector3D, right.Double);
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4)
return left.Vector4 / right.Vector4;
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar)
return left.Vector4 / right.Scalar;
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Double)
return left.Vector4 / (float)right.Double;
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion)
return left.Quaternion / right.Quaternion;
@ -564,11 +534,7 @@ namespace Avalonia.Rendering.Composition.Expressions
public ExpressionVariant EqualsTo(ExpressionVariant right)
{
if (Type != right.Type || Type == VariantType.Invalid)
return default;
if (Type == VariantType.Scalar)
return Scalar == right.Scalar;
return default;
if (Type == VariantType.Double)
return Double == right.Double;
@ -623,8 +589,6 @@ namespace Avalonia.Rendering.Composition.Expressions
public static ExpressionVariant operator %(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar % right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double % right.Double;
return default;
@ -632,18 +596,13 @@ namespace Avalonia.Rendering.Composition.Expressions
public static ExpressionVariant operator <(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar < right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double < right.Double;
return default;
}
public static ExpressionVariant operator >(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar > right.Scalar;
{
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double > right.Double;
return default;
@ -659,207 +618,89 @@ namespace Avalonia.Rendering.Composition.Expressions
public ExpressionVariant Or(ExpressionVariant right)
{
if (Type == VariantType.Boolean && right.Type == VariantType.Boolean)
return Boolean && right.Boolean;
return Boolean || right.Boolean;
return default;
}
public bool TryCast<T>(out T res) where T : struct
{
if (typeof(T) == typeof(bool))
{
if (Type == VariantType.Boolean)
{
res = (T) (object) Boolean;
return true;
}
}
if (typeof(T) == typeof(float))
switch (default(T))
{
if (Type == VariantType.Scalar)
{
res = (T) (object) Scalar;
return true;
}
if (Type == VariantType.Double)
{
res = (T)(object)Scalar;
case bool when Type is VariantType.Boolean:
res = (T)(object)Boolean;
return true;
}
}
if (typeof(T) == typeof(double))
{
if (Type == VariantType.Double)
{
res = (T) (object) Double;
return true;
}
if (Type == VariantType.Scalar)
{
case float when Type is VariantType.Double:
res = (T)(object)(float)Double;
return true;
}
}
if (typeof(T) == typeof(Vector2))
{
if (Type == VariantType.Vector2)
{
res = (T) (object) Vector2;
case double when Type is VariantType.Double:
res = (T)(object)Double;
return true;
}
if (Type == VariantType.Vector)
{
res = (T) (object) Vector.ToVector2();
case System.Numerics.Vector2 when Type is VariantType.Vector2:
res = (T)(object)Vector2;
return true;
}
}
if (typeof(T) == typeof(Vector))
{
if (Type == VariantType.Vector)
{
res = (T) (object) Vector;
case System.Numerics.Vector2 when Type is VariantType.Vector:
res = (T)(object)Vector.ToVector2();
return true;
}
if (Type == VariantType.Vector2)
{
case Avalonia.Vector when Type is VariantType.Vector:
res = (T)(object)Vector;
return true;
case Avalonia.Vector when Type is VariantType.Vector2:
res = (T)(object)new Vector(Vector2);
return true;
}
}
if (typeof(T) == typeof(Vector3))
{
if (Type == VariantType.Vector3)
{
res = (T) (object) Vector3;
case System.Numerics.Vector3 when Type is VariantType.Vector3:
res = (T)(object)Vector3;
return true;
}
if (Type == VariantType.Vector3D)
{
res = (T) (object) Vector3D.ToVector3();
case System.Numerics.Vector3 when Type is VariantType.Vector3D:
res = (T)(object)Vector3D.ToVector3();
return true;
}
}
if (typeof(T) == typeof(Vector3D))
{
if (Type == VariantType.Vector3D)
{
res = (T) (object) Vector3D;
case Avalonia.Vector3D when Type is VariantType.Vector3D:
res = (T)(object)Vector3D;
return true;
}
if (Type == VariantType.Vector3)
{
case Avalonia.Vector3D when Type is VariantType.Vector3:
res = (T)(object)new Vector3D(Vector3);
return true;
}
}
if (typeof(T) == typeof(Vector4))
{
if (Type == VariantType.Vector4)
{
res = (T) (object) Vector4;
case System.Numerics.Vector4 when Type is VariantType.Vector4:
res = (T)(object)Vector4;
return true;
}
}
if (typeof(T) == typeof(Matrix3x2))
{
if (Type == VariantType.Matrix3x2)
{
res = (T) (object) Matrix3x2;
case System.Numerics.Matrix3x2 when Type is VariantType.Matrix3x2:
res = (T)(object)Matrix3x2;
return true;
}
}
if (typeof(T) == typeof(Matrix))
{
if (Type == VariantType.AvaloniaMatrix)
{
res = (T) (object) Matrix3x2;
case Avalonia.Matrix when Type is VariantType.AvaloniaMatrix:
res = (T)(object)AvaloniaMatrix;
return true;
}
}
if (typeof(T) == typeof(Matrix4x4))
{
if (Type == VariantType.Matrix4x4)
{
res = (T) (object) Matrix4x4;
case System.Numerics.Matrix4x4 when Type is VariantType.Matrix4x4:
res = (T)(object)Matrix4x4;
return true;
}
}
if (typeof(T) == typeof(Quaternion))
{
if (Type == VariantType.Quaternion)
{
res = (T) (object) Quaternion;
case System.Numerics.Quaternion when Type is VariantType.Quaternion:
res = (T)(object)Quaternion;
return true;
}
}
if (typeof(T) == typeof(Avalonia.Media.Color))
{
if (Type == VariantType.Color)
{
res = (T) (object) Color;
case Avalonia.Media.Color when Type is VariantType.Color:
res = (T)(object)Color;
return true;
}
default:
res = default;
return false;
}
res = default;
return false;
}
public static ExpressionVariant Create<T>(T v) where T : struct
{
if (typeof(T) == typeof(bool))
return (bool) (object) v;
if (typeof(T) == typeof(float))
return (float) (object) v;
if (typeof(T) == typeof(Vector2))
return (Vector2) (object) v;
if (typeof(T) == typeof(Vector))
return (Vector) (object) v;
if (typeof(T) == typeof(Vector3))
return (Vector3) (object) v;
if (typeof(T) == typeof(Vector3D))
return (Vector3D) (object) v;
if (typeof(T) == typeof(Vector4))
return (Vector4) (object) v;
if (typeof(T) == typeof(Matrix3x2))
return (Matrix3x2) (object) v;
if (typeof(T) == typeof(Matrix))
return (Matrix) (object) v;
if (typeof(T) == typeof(Matrix4x4))
return (Matrix4x4) (object) v;
if (typeof(T) == typeof(Quaternion))
return (Quaternion) (object) v;
if (typeof(T) == typeof(Avalonia.Media.Color))
return (Avalonia.Media.Color) (object) v;
throw new ArgumentException("Invalid variant type: " + typeof(T));
}
=> default(T) switch
{
bool => (bool)(object)v,
float => (float)(object)v,
double => (double)(object)v,
System.Numerics.Vector2 => (Vector2)(object)v,
Avalonia.Vector => (Vector)(object)v,
System.Numerics.Vector3 => (Vector3)(object)v,
Avalonia.Vector3D => (Vector3D)(object)v,
System.Numerics.Vector4 => (Vector4)(object)v,
System.Numerics.Matrix3x2 => (Matrix3x2)(object)v,
Avalonia.Matrix => (Matrix)(object)v,
System.Numerics.Matrix4x4 => (Matrix4x4)(object)v,
System.Numerics.Quaternion => (Quaternion)(object)v,
Avalonia.Media.Color => (Avalonia.Media.Color)(object)v,
_ => throw new ArgumentException("Invalid variant type: " + typeof(T))
};
public T CastOrDefault<T>() where T : struct
{
@ -869,35 +710,23 @@ namespace Avalonia.Rendering.Composition.Expressions
public override string ToString()
{
if (Type == VariantType.Boolean)
return Boolean.ToString();
if (Type == VariantType.Scalar)
return Scalar.ToString(CultureInfo.InvariantCulture);
if (Type == VariantType.Double)
return Double.ToString(CultureInfo.InvariantCulture);
if (Type == VariantType.Vector2)
return Vector2.ToString();
if (Type == VariantType.Vector)
return Vector.ToString();
if (Type == VariantType.Vector3)
return Vector3.ToString();
if (Type == VariantType.Vector3D)
return Vector3D.ToString();
if (Type == VariantType.Vector4)
return Vector4.ToString();
if (Type == VariantType.Quaternion)
return Quaternion.ToString();
if (Type == VariantType.Matrix3x2)
return Matrix3x2.ToString();
if (Type == VariantType.AvaloniaMatrix)
return AvaloniaMatrix.ToString();
if (Type == VariantType.Matrix4x4)
return Matrix4x4.ToString();
if (Type == VariantType.Color)
return Color.ToString();
if (Type == VariantType.Invalid)
return "Invalid";
return "Unknown";
return Type switch
{
VariantType.Boolean => Boolean.ToString(),
VariantType.Double => Double.ToString(CultureInfo.InvariantCulture),
VariantType.Vector2 => Vector2.ToString(),
VariantType.Vector => Vector.ToString(),
VariantType.Vector3 => Vector3.ToString(),
VariantType.Vector3D => Vector3D.ToString(),
VariantType.Vector4 => Vector4.ToString(),
VariantType.Quaternion => Quaternion.ToString(),
VariantType.Matrix3x2 => Matrix3x2.ToString(),
VariantType.AvaloniaMatrix => AvaloniaMatrix.ToString(),
VariantType.Matrix4x4 => Matrix4x4.ToString(),
VariantType.Color => Color.ToString(),
VariantType.Invalid => "Invalid",
_ => "Unknown"
};
}
}

65
src/Avalonia.Base/Rendering/Composition/Server/CompositionProperty.cs

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Server;
@ -11,15 +8,6 @@ internal class CompositionProperty
private static int s_nextId = 1;
private static readonly object _lock = new();
private static Dictionary<Type, List<CompositionProperty>> s_dynamicRegistry = new();
class ReadOnlyRegistry : Dictionary<Type, IReadOnlyDictionary<string, CompositionProperty>>
{
}
private static volatile ReadOnlyRegistry? s_ReadOnlyRegistry;
public CompositionProperty(int id, string name, Type owner, Func<SimpleServerObject, ExpressionVariant>? getVariant)
{
Id = id;
@ -43,59 +31,8 @@ internal class CompositionProperty
prop = new CompositionProperty<TField>(id, name, typeof(TOwner), getField, setField, getVariant);
}
s_ReadOnlyRegistry = null;
return prop;
}
static void PopulatePropertiesForType(Type type, List<CompositionProperty> l)
{
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<CompositionProperty>();
PopulatePropertiesForType(type, lst);
reg[type] = lst.ToDictionary(x => x.Name);
}
return reg;
}
public static IReadOnlyDictionary<string, CompositionProperty>? 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<T> : CompositionProperty
@ -112,4 +49,4 @@ internal class CompositionProperty<T> : CompositionProperty
GetField = getField;
SetField = setField;
}
}
}

9
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositorAnimations.cs

@ -26,7 +26,12 @@ internal class ServerCompositorAnimations
_clockItemsToUpdate.Clear();
while (_dirtyAnimatedObjectQueue.Count > 0)
_dirtyAnimatedObjectQueue.Dequeue().EvaluateAnimations();
{
var animation = _dirtyAnimatedObjectQueue.Dequeue();
_dirtyAnimatedObjects.Remove(animation);
animation.EvaluateAnimations();
}
_dirtyAnimatedObjects.Clear();
}
@ -37,4 +42,4 @@ internal class ServerCompositorAnimations
if (_dirtyAnimatedObjects.Add(obj))
_dirtyAnimatedObjectQueue.Enqueue(obj);
}
}
}

2
src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs

@ -71,7 +71,7 @@ namespace Avalonia.Rendering.Composition.Server
ExpressionVariant IExpressionObject.GetProperty(string name)
{
if (_animations == null)
return CompositionProperty.Find(this.GetType(), name)?.GetVariant?.Invoke(this) ?? default;
return GetCompositionProperty(name)?.GetVariant?.Invoke(this) ?? default;
return _animations.GetPropertyForAnimation(name);
}

11
src/Avalonia.Base/Rendering/Composition/Server/ServerObjectAnimations.cs

@ -12,18 +12,15 @@ class ServerObjectAnimations
private readonly ServerObject _owner;
private InlineDictionary<CompositionProperty, ServerObjectSubscriptionStore> _subscriptions;
private InlineDictionary<CompositionProperty, ServerObjectAnimationInstance> _animations;
private readonly IReadOnlyDictionary<string, CompositionProperty> _properties;
public ServerObjectAnimations(ServerObject owner)
{
_owner = owner;
_properties = CompositionProperty.TryGetPropertiesForType(owner.GetType()) ??
new Dictionary<string, CompositionProperty>();
}
private class ServerObjectSubscriptionStore
{
public bool IsValid;
public bool IsValid = true;
public RefTrackingDictionary<IAnimationInstance>? Subscribers;
public void Invalidate()
@ -84,6 +81,7 @@ class ServerObjectAnimations
NeedsUpdate = false;
_property.SetField(Owner._owner, GetVariant().CastOrDefault<T>());
Owner._owner.NotifyAnimatedValueChanged(_property);
Owner.OnSetDirectValue(_property);
}
}
}
@ -143,7 +141,8 @@ class ServerObjectAnimations
public ExpressionVariant GetPropertyForAnimation(string name)
{
if (!_properties.TryGetValue(name, out var prop))
var prop = _owner.GetCompositionProperty(name);
if (prop is null)
return default;
if (_subscriptions.TryGetValue(prop, out var subs))
@ -172,4 +171,4 @@ class ServerObjectAnimations
else
Debug.Assert(false);
}
}
}

2
src/tools/DevGenerators/CompositionGenerator/Generator.cs

@ -426,6 +426,7 @@ return;
{
"bool",
"float",
"double",
"Vector2",
"Vector3",
"Vector4",
@ -435,6 +436,7 @@ return;
"Quaternion",
"Color",
"Avalonia.Media.Color",
"Vector",
"Vector3D"
};

6
tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationParserTests.cs

@ -28,12 +28,10 @@ public class CompositionAnimationParserTests
};
var res = expr.Evaluate(ref ctx);
double doubleRes;
if (res.Type == VariantType.Scalar)
doubleRes = res.Scalar;
else if (res.Type == VariantType.Double)
if (res.Type == VariantType.Double)
doubleRes = res.Double;
else
throw new Exception("Invalid result type: " + res.Type);
Assert.Equal(value, doubleRes);
}
}
}

133
tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs

@ -1,10 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Base.UnitTests.Rendering;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Expressions;
@ -81,7 +80,7 @@ public class CompositionAnimationTests : ScopedTestBase
public void Post(Action action, DispatcherPriority priority = default) => throw new NotSupportedException();
}
[AnimationDataProvider]
[Theory]
public void GenericCheck(AnimationData data)
@ -100,11 +99,11 @@ public class CompositionAnimationTests : ScopedTestBase
foreach (var check in data.Checks)
{
currentValue = instance.Evaluate(TimeSpan.FromSeconds(check.time), currentValue);
Assert.Equal(check.value, currentValue.Scalar);
Assert.Equal(check.value, currentValue.Double);
}
}
public class AnimationData
{
public AnimationData(string name)
@ -112,7 +111,7 @@ public class CompositionAnimationTests : ScopedTestBase
Name = name;
}
public string Name { get; }
public string Name { get; }
public List<(float key, float value)> Frames { get; set; } = new();
public List<(float time, float value)> Checks { get; set; } = new();
public float StartingValue { get; set; }
@ -122,4 +121,124 @@ public class CompositionAnimationTests : ScopedTestBase
return Name;
}
}
[Theory]
[InlineData("Color")]
[InlineData("Offset")]
public void GetCompositionProperty_ReturnsRegisteredProperties(string propName)
{
using var scope = AvaloniaLocator.EnterScope();
var compositor = new Compositor(RenderLoop.FromTimer(new CompositorTestServices.ManualRenderTimer()), null);
var target = compositor.CreateSolidColorVisual();
var property = target.Server.GetCompositionProperty(propName);
Assert.NotNull(property);
Assert.Equal(propName, property.Name);
Assert.NotNull(property.GetVariant);
}
[Fact]
public void ExpressionAnimation_Operations_WorksCorrectly()
{
using var scope = AvaloniaLocator.EnterScope();
var compositor = new Compositor(RenderLoop.FromTimer(new CompositorTestServices.ManualRenderTimer()), null);
var target = compositor.CreateSolidColorVisual();
target.Server.Offset = new Vector3D(100, 200, 0);
var ani = compositor.CreateExpressionAnimation("this.Target.Offset.X * 0.5 + 10");
var instance = ani.CreateInstance(target.Server, null);
instance.Initialize(TimeSpan.Zero, ExpressionVariant.Create(0f),
ServerCompositionVisual.s_IdOfRotationAngleProperty);
var result = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f));
Assert.Equal(VariantType.Double, result.Type);
Assert.Equal(60.0, result.Double);
}
[Fact]
public void ExpressionAnimation_Tracks_ReferenceParameter()
{
using var scope = AvaloniaLocator.EnterScope();
var compositor = new Compositor(RenderLoop.FromTimer(new CompositorTestServices.ManualRenderTimer()), null);
var target = compositor.CreateSolidColorVisual();
var obj = compositor.CreateSolidColorVisual();
obj.Server.Offset = new Vector3D(100, 200, 0);
var ani = compositor.CreateExpressionAnimation("obj.Offset.X * 0.5 + 10");
ani.SetReferenceParameter("obj", obj);
var instance = ani.CreateInstance(target.Server, null);
target.Server.Activate();
// Invoke OnSetAnimatedValue manually to create ServerObjectAnimationInstance.
target.Server.GetOrCreateAnimations();
var tmp = 0f;
target.Server.Animations!.OnSetAnimatedValue(ServerCompositionVisual.s_IdOfRotationAngleProperty, ref tmp, TimeSpan.Zero, instance);
var initialResult = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f));
Assert.Equal(60.0, initialResult.Double);
obj.Server.Offset = new Vector3D(200, 300, 0);
var updatedResult = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f));
Assert.Equal(110.0, updatedResult.Double);
}
[Fact]
public void ExpressionAnimation_Tracks_Target()
{
using var scope = AvaloniaLocator.EnterScope();
var compositor = new Compositor(RenderLoop.FromTimer(new CompositorTestServices.ManualRenderTimer()), null);
var target = compositor.CreateSolidColorVisual();
target.Server.Offset = new Vector3D(100, 200, 0);
var ani = compositor.CreateExpressionAnimation("this.Target.Offset.X * 0.5 + 10");
var instance = ani.CreateInstance(target.Server, null);
target.Server.Activate();
// Invoke OnSetAnimatedValue manually to create ServerObjectAnimationInstance.
target.Server.GetOrCreateAnimations();
var tmp = 0f;
target.Server.Animations!.OnSetAnimatedValue(ServerCompositionVisual.s_IdOfRotationAngleProperty, ref tmp, TimeSpan.Zero, instance);
var initialResult = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f));
Assert.Equal(60, initialResult.Double);
target.Server.Offset = new Vector3D(200, 300, 0);
var updatedResult = instance.Evaluate(TimeSpan.Zero, ExpressionVariant.Create(0f));
Assert.Equal(110.0, updatedResult.Double);
}
[Fact]
public void ExpressionAnimation_Requeues_Target_When_Another_Animation_Is_Invalidated_During_Evaluation()
{
using var services = new CompositorTestServices();
var border = new Border
{
Width = 10,
Height = 10
};
services.TopLevel.Content = border;
services.RunJobs();
var visual = ElementComposition.GetElementVisual(border)!;
var opacityAnimation = visual.Compositor.CreateExpressionAnimation("this.Target.RotationAngle * 0.1");
var rotationAnimation = visual.Compositor.CreateExpressionAnimation("this.Target.Offset.X * 0.5");
visual.StartAnimation("Opacity", opacityAnimation);
visual.StartAnimation("RotationAngle", rotationAnimation);
services.RunJobs();
visual.Offset = new Vector3D(100, 0, 0);
services.RunJobs();
Assert.Equal(50, visual.Server.RotationAngle);
Assert.Equal(5, visual.Server.Opacity);
}
}

Loading…
Cancel
Save