committed by
GitHub
9 changed files with 357 additions and 141 deletions
@ -1,15 +1,115 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using Avalonia.Rendering.Composition.Expressions; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal class CompositionProperty |
|||
{ |
|||
private static volatile int s_NextId = 1; |
|||
public int Id { get; private set; } |
|||
private static int s_nextId = 1; |
|||
private static readonly object _lock = new(); |
|||
|
|||
public static CompositionProperty Register() => new() |
|||
private static Dictionary<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; |
|||
Name = name; |
|||
Owner = owner; |
|||
GetVariant = getVariant; |
|||
} |
|||
|
|||
public int Id { get; } |
|||
public string Name { get; } |
|||
public Type Owner { get; } |
|||
public Func<SimpleServerObject, ExpressionVariant>? GetVariant { get; } |
|||
|
|||
public static CompositionProperty<TField> Register<TOwner, TField>(string name, Func<SimpleServerObject, TField> getField, Action<SimpleServerObject, TField> setField, |
|||
Func<SimpleServerObject, ExpressionVariant>? getVariant) |
|||
{ |
|||
CompositionProperty<TField> prop; |
|||
lock (_lock) |
|||
{ |
|||
var id = s_nextId++; |
|||
prop = new CompositionProperty<TField>(id, name, typeof(TOwner), getField, setField, getVariant); |
|||
} |
|||
|
|||
s_ReadOnlyRegistry = null; |
|||
return prop; |
|||
} |
|||
|
|||
static void PopulatePropertiesForType(Type type, List<CompositionProperty> l) |
|||
{ |
|||
Id = Interlocked.Increment(ref s_NextId) |
|||
}; |
|||
Type? t = type; |
|||
while (t != null && t != typeof(object)) |
|||
{ |
|||
if (s_dynamicRegistry.TryGetValue(t, out var lst)) |
|||
l.AddRange(lst); |
|||
t = t.BaseType; |
|||
} |
|||
} |
|||
|
|||
static ReadOnlyRegistry Build() |
|||
{ |
|||
var reg = new ReadOnlyRegistry(); |
|||
foreach (var type in s_dynamicRegistry.Keys) |
|||
{ |
|||
var lst = new List<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 |
|||
{ |
|||
public Func<SimpleServerObject, T> GetField { get; } |
|||
public Action<SimpleServerObject, T> SetField { get; } |
|||
|
|||
public CompositionProperty(int id, string name, Type owner, |
|||
Func<SimpleServerObject, T> getField, |
|||
Action<SimpleServerObject, T> setField, |
|||
Func<SimpleServerObject, ExpressionVariant>? getVariant) |
|||
: base(id, name, owner, getVariant) |
|||
{ |
|||
GetField = getField; |
|||
SetField = setField; |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
internal class ServerCompositorAnimations |
|||
{ |
|||
private readonly HashSet<IServerClockItem> _clockItems = new(); |
|||
private readonly List<IServerClockItem> _clockItemsToUpdate = new(); |
|||
private readonly HashSet<ServerObjectAnimations> _dirtyAnimatedObjects = new(); |
|||
private readonly Queue<ServerObjectAnimations> _dirtyAnimatedObjectQueue = new(); |
|||
|
|||
public void AddToClock(IServerClockItem item) => |
|||
_clockItems.Add(item); |
|||
|
|||
public void RemoveFromClock(IServerClockItem item) => |
|||
_clockItems.Remove(item); |
|||
|
|||
public void Process() |
|||
{ |
|||
foreach (var animation in _clockItems) |
|||
_clockItemsToUpdate.Add(animation); |
|||
|
|||
foreach (var animation in _clockItemsToUpdate) |
|||
animation.OnTick(); |
|||
|
|||
_clockItemsToUpdate.Clear(); |
|||
|
|||
while (_dirtyAnimatedObjectQueue.Count > 0) |
|||
_dirtyAnimatedObjectQueue.Dequeue().EvaluateAnimations(); |
|||
_dirtyAnimatedObjects.Clear(); |
|||
} |
|||
|
|||
public void AddDirtyAnimatedObject(ServerObjectAnimations obj) |
|||
{ |
|||
if (_dirtyAnimatedObjects.Add(obj)) |
|||
_dirtyAnimatedObjectQueue.Enqueue(obj); |
|||
} |
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using Avalonia.Rendering.Composition.Animations; |
|||
using Avalonia.Rendering.Composition.Expressions; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server; |
|||
|
|||
class ServerObjectAnimations |
|||
{ |
|||
private readonly ServerObject _owner; |
|||
private InlineDictionary<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 RefTrackingDictionary<IAnimationInstance>? Subscribers; |
|||
|
|||
public void Invalidate() |
|||
{ |
|||
if (!IsValid) |
|||
return; |
|||
IsValid = false; |
|||
if (Subscribers != null) |
|||
foreach (var sub in Subscribers) |
|||
sub.Key.Invalidate(); |
|||
} |
|||
} |
|||
|
|||
abstract class ServerObjectAnimationInstance |
|||
{ |
|||
public ServerObjectAnimations Owner { get; } |
|||
private ExpressionVariant _cachedVariant; |
|||
public bool IsDirty { get; set; } = true; |
|||
public bool NeedsUpdate { get; set; } = true; |
|||
public IAnimationInstance Animation { get; } |
|||
|
|||
public ServerObjectAnimationInstance(ServerObjectAnimations owner, IAnimationInstance animation) |
|||
{ |
|||
Animation = animation; |
|||
Owner = owner; |
|||
} |
|||
|
|||
public ExpressionVariant GetVariant() |
|||
{ |
|||
var compositor = Owner._owner.Compositor; |
|||
if (!IsDirty) |
|||
return _cachedVariant; |
|||
|
|||
// We are setting this _before_ evaluating animation to prevent stack overflows due to potential
|
|||
// cyclic references
|
|||
IsDirty = false; |
|||
|
|||
return _cachedVariant = Animation.Evaluate(Owner._owner.Compositor.ServerNow, _cachedVariant); |
|||
} |
|||
|
|||
public abstract void UpdateTargetProperty(); |
|||
} |
|||
|
|||
class ServerObjectAnimationInstance<T> : ServerObjectAnimationInstance where T : struct |
|||
{ |
|||
private readonly CompositionProperty<T> _property; |
|||
|
|||
public ServerObjectAnimationInstance(ServerObjectAnimations owner, IAnimationInstance animation, |
|||
CompositionProperty<T> property) : base(owner, animation) |
|||
{ |
|||
_property = property; |
|||
} |
|||
|
|||
public override void UpdateTargetProperty() |
|||
{ |
|||
if (NeedsUpdate) |
|||
{ |
|||
NeedsUpdate = false; |
|||
_property.SetField(Owner._owner, GetVariant().CastOrDefault<T>()); |
|||
Owner._owner.NotifyAnimatedValueChanged(_property); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Activated() |
|||
{ |
|||
foreach(var kp in _animations) |
|||
kp.Value.Animation.Activate(); |
|||
} |
|||
|
|||
public void Deactivated() |
|||
{ |
|||
foreach(var kp in _animations) |
|||
kp.Value.Animation.Deactivate(); |
|||
} |
|||
|
|||
public void OnSetDirectValue(CompositionProperty property) |
|||
{ |
|||
if(_subscriptions.TryGetValue(property, out var subs)) |
|||
subs.Invalidate(); |
|||
} |
|||
|
|||
public void OnSetAnimatedValue<T>(CompositionProperty<T> prop, ref T field, TimeSpan committedAt, IAnimationInstance animation) where T : struct |
|||
{ |
|||
if (_owner.IsActive && _animations.TryGetValue(prop, out var oldAnimation)) |
|||
oldAnimation.Animation.Deactivate(); |
|||
_animations[prop] = new ServerObjectAnimationInstance<T>(this, animation, prop); |
|||
|
|||
animation.Initialize(committedAt, ExpressionVariant.Create(field), prop); |
|||
if(_owner.IsActive) |
|||
animation.Activate(); |
|||
|
|||
OnSetDirectValue(prop); |
|||
} |
|||
|
|||
public void RemoveAnimationForProperty(CompositionProperty property) |
|||
{ |
|||
if (_animations.TryGetAndRemoveValue(property, out var animation) && _owner.IsActive) |
|||
animation.Animation.Deactivate(); |
|||
OnSetDirectValue(property); |
|||
} |
|||
|
|||
public void SubscribeToInvalidation(CompositionProperty member, IAnimationInstance animation) |
|||
{ |
|||
if (!_subscriptions.TryGetValue(member, out var store)) |
|||
_subscriptions[member] = store = new ServerObjectSubscriptionStore(); |
|||
if (store.Subscribers == null) |
|||
store.Subscribers = new(); |
|||
store.Subscribers.AddRef(animation); |
|||
} |
|||
|
|||
public void UnsubscribeFromInvalidation(CompositionProperty member, IAnimationInstance animation) |
|||
{ |
|||
if(_subscriptions.TryGetValue(member, out var store)) |
|||
store.Subscribers?.ReleaseRef(animation); |
|||
} |
|||
|
|||
public ExpressionVariant GetPropertyForAnimation(string name) |
|||
{ |
|||
if (!_properties.TryGetValue(name, out var prop)) |
|||
return default; |
|||
|
|||
if (_subscriptions.TryGetValue(prop, out var subs)) |
|||
subs.IsValid = true; |
|||
|
|||
if (_animations.TryGetValue(prop, out var animation)) |
|||
return animation.GetVariant(); |
|||
|
|||
return prop.GetVariant?.Invoke(_owner) ?? default; |
|||
} |
|||
|
|||
public void EvaluateAnimations() |
|||
{ |
|||
foreach (var animation in _animations) |
|||
if (animation.Value.IsDirty) |
|||
animation.Value.UpdateTargetProperty(); |
|||
} |
|||
|
|||
public void NotifyAnimationInstanceInvalidated(CompositionProperty property) |
|||
{ |
|||
if (_animations.TryGetValue(property, out var instance)) |
|||
{ |
|||
instance.IsDirty = instance.NeedsUpdate = true; |
|||
_owner.Compositor.Animations.AddDirtyAnimatedObject(this); |
|||
} |
|||
else |
|||
Debug.Assert(false); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue