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 (); _trackedObjects = new ();
foreach (var t in trackedObjects) 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) if (obj is ServerObject tracked)
{ {
var off = tracked.GetCompositionProperty(t.member); 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; var arg = arguments[c].Type;
if (parameter != arg) if (parameter != arg)
{ {
var canCast = (parameter == VariantType.Double && arg == VariantType.Scalar) var canCast = (parameter == VariantType.Vector3D && arg == VariantType.Vector3)
|| (parameter == VariantType.Vector3D && arg == VariantType.Vector3)
|| (parameter == VariantType.Vector && arg == VariantType.Vector2) || (parameter == VariantType.Vector && arg == VariantType.Vector2)
|| (anyCast && ( || (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) || (arg == VariantType.Vector && parameter == VariantType.Vector2)
)); ));
if (!canCast) if (!canCast)
@ -112,7 +110,7 @@ namespace Avalonia.Rendering.Composition.Expressions
static readonly Dictionary<Type, VariantType> TypeMap = new Dictionary<Type, VariantType> static readonly Dictionary<Type, VariantType> TypeMap = new Dictionary<Type, VariantType>
{ {
[typeof(bool)] = VariantType.Boolean, [typeof(bool)] = VariantType.Boolean,
[typeof(float)] = VariantType.Scalar, [typeof(float)] = VariantType.Double,
[typeof(double)] = VariantType.Double, [typeof(double)] = VariantType.Double,
[typeof(Vector2)] = VariantType.Vector2, [typeof(Vector2)] = VariantType.Vector2,
[typeof(Vector)] = VariantType.Vector, [typeof(Vector)] = VariantType.Vector,

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

@ -104,6 +104,17 @@ namespace Avalonia.Rendering.Composition.Expressions
False 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 internal class ConditionalExpression : Expression
{ {
public Expression Condition { get; } public Expression Condition { get; }
@ -202,6 +213,10 @@ namespace Avalonia.Rendering.Composition.Expressions
Target.CollectReferences(references); Target.CollectReferences(references);
if (Target is ParameterExpression pe) if (Target is ParameterExpression pe)
references.Add((pe.Name, Member)); 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) 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 // We can parse keywords, parameter names and constants
expr = null; expr = null;
if (parser.TryParseKeywordLowerCase("this.startingvalue")) if (parser.TryParseKeywordLowerCase(ExpressionKeywords.StartingValue))
expr = new KeywordExpression(ExpressionKeyword.StartingValue); expr = new KeywordExpression(ExpressionKeyword.StartingValue);
else if(parser.TryParseKeywordLowerCase("this.currentvalue")) else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.CurrentValue))
expr = new KeywordExpression(ExpressionKeyword.CurrentValue); expr = new KeywordExpression(ExpressionKeyword.CurrentValue);
else if(parser.TryParseKeywordLowerCase("this.finalvalue")) else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.FinalValue))
expr = new KeywordExpression(ExpressionKeyword.FinalValue); expr = new KeywordExpression(ExpressionKeyword.FinalValue);
else if(parser.TryParseKeywordLowerCase("pi")) else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.Pi))
expr = new KeywordExpression(ExpressionKeyword.Pi); expr = new KeywordExpression(ExpressionKeyword.Pi);
else if(parser.TryParseKeywordLowerCase("true")) else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.True))
expr = new KeywordExpression(ExpressionKeyword.True); expr = new KeywordExpression(ExpressionKeyword.True);
else if(parser.TryParseKeywordLowerCase("false")) else if(parser.TryParseKeywordLowerCase(ExpressionKeywords.False))
expr = new KeywordExpression(ExpressionKeyword.False); expr = new KeywordExpression(ExpressionKeyword.False);
else if (parser.TryParseKeywordLowerCase("this.target")) else if (parser.TryParseKeywordLowerCase(ExpressionKeywords.Target))
expr = new KeywordExpression(ExpressionKeyword.Target); expr = new KeywordExpression(ExpressionKeyword.Target);
if (expr != null) if (expr != null)

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

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

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

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

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

@ -26,7 +26,12 @@ internal class ServerCompositorAnimations
_clockItemsToUpdate.Clear(); _clockItemsToUpdate.Clear();
while (_dirtyAnimatedObjectQueue.Count > 0) while (_dirtyAnimatedObjectQueue.Count > 0)
_dirtyAnimatedObjectQueue.Dequeue().EvaluateAnimations(); {
var animation = _dirtyAnimatedObjectQueue.Dequeue();
_dirtyAnimatedObjects.Remove(animation);
animation.EvaluateAnimations();
}
_dirtyAnimatedObjects.Clear(); _dirtyAnimatedObjects.Clear();
} }
@ -37,4 +42,4 @@ internal class ServerCompositorAnimations
if (_dirtyAnimatedObjects.Add(obj)) if (_dirtyAnimatedObjects.Add(obj))
_dirtyAnimatedObjectQueue.Enqueue(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) ExpressionVariant IExpressionObject.GetProperty(string name)
{ {
if (_animations == null) if (_animations == null)
return CompositionProperty.Find(this.GetType(), name)?.GetVariant?.Invoke(this) ?? default; return GetCompositionProperty(name)?.GetVariant?.Invoke(this) ?? default;
return _animations.GetPropertyForAnimation(name); return _animations.GetPropertyForAnimation(name);
} }

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

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

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

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

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

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

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

@ -1,10 +1,9 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Base.UnitTests.Rendering; using Avalonia.Controls;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
@ -81,7 +80,7 @@ public class CompositionAnimationTests : ScopedTestBase
public void Post(Action action, DispatcherPriority priority = default) => throw new NotSupportedException(); public void Post(Action action, DispatcherPriority priority = default) => throw new NotSupportedException();
} }
[AnimationDataProvider] [AnimationDataProvider]
[Theory] [Theory]
public void GenericCheck(AnimationData data) public void GenericCheck(AnimationData data)
@ -100,11 +99,11 @@ public class CompositionAnimationTests : ScopedTestBase
foreach (var check in data.Checks) foreach (var check in data.Checks)
{ {
currentValue = instance.Evaluate(TimeSpan.FromSeconds(check.time), currentValue); currentValue = instance.Evaluate(TimeSpan.FromSeconds(check.time), currentValue);
Assert.Equal(check.value, currentValue.Scalar); Assert.Equal(check.value, currentValue.Double);
} }
} }
public class AnimationData public class AnimationData
{ {
public AnimationData(string name) public AnimationData(string name)
@ -112,7 +111,7 @@ public class CompositionAnimationTests : ScopedTestBase
Name = name; Name = name;
} }
public string Name { get; } public string Name { get; }
public List<(float key, float value)> Frames { get; set; } = new(); public List<(float key, float value)> Frames { get; set; } = new();
public List<(float time, float value)> Checks { get; set; } = new(); public List<(float time, float value)> Checks { get; set; } = new();
public float StartingValue { get; set; } public float StartingValue { get; set; }
@ -122,4 +121,124 @@ public class CompositionAnimationTests : ScopedTestBase
return Name; 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