Browse Source

Switched to byte-stream based transport

pull/8105/head
Nikita Tsukanov 4 years ago
parent
commit
4991d4f370
  1. 5
      src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs
  2. 5
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  3. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
  4. 2
      src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
  5. 2
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  6. 16
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  7. 4
      src/Avalonia.Base/Rendering/Composition/CompositionEasingFunction.cs
  8. 57
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  9. 12
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  10. 2
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  11. 50
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  12. 13
      src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs
  13. 56
      src/Avalonia.Base/Rendering/Composition/CustomDrawVisual.cs
  14. 10
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  15. 4
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  16. 23
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  17. 9
      src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs
  18. 31
      src/Avalonia.Base/Rendering/Composition/Server/ServerCustomDrawVisual.cs
  19. 24
      src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs
  20. 28
      src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
  21. 31
      src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs
  22. 7
      src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs
  23. 27
      src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs
  24. 24
      src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
  25. 9
      src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs
  26. 82
      src/Avalonia.Base/Rendering/Composition/Transport/Change.cs
  27. 36
      src/Avalonia.Base/Rendering/Composition/Transport/ChangeSet.cs
  28. 42
      src/Avalonia.Base/Rendering/Composition/Transport/ChangeSetPool.cs
  29. 11
      src/Avalonia.Base/Rendering/Composition/Transport/CompositionTargetChanges.cs
  30. 20
      src/Avalonia.Base/Rendering/Composition/Transport/CustomDrawVisualChanges.cs
  31. 48
      src/Avalonia.Base/Rendering/Composition/Transport/DrawListVisualChanges.cs
  32. 19
      src/Avalonia.Base/Rendering/Composition/Transport/ListChange.cs
  33. 25
      src/Avalonia.Base/Rendering/Composition/Transport/ListChangeSet.cs
  34. 55
      src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs
  35. 16
      src/Avalonia.Base/Rendering/Composition/Transport/VisualChanges.cs
  36. 35
      src/Avalonia.Base/Rendering/Composition/Visual.cs
  37. 1
      src/Avalonia.Base/Rendering/Composition/VisualCollection.cs
  38. 7
      src/Avalonia.Base/composition-schema.xml
  39. 6
      src/Avalonia.SourceGenerator/CompositionGenerator/Config.cs
  40. 9
      src/Avalonia.SourceGenerator/CompositionGenerator/Extensions.cs
  41. 2
      src/Avalonia.SourceGenerator/CompositionGenerator/Generator.KeyFrameAnimation.cs
  42. 12
      src/Avalonia.SourceGenerator/CompositionGenerator/Generator.ListProxy.cs
  43. 413
      src/Avalonia.SourceGenerator/CompositionGenerator/Generator.cs
  44. 2
      src/Avalonia.X11/X11Platform.cs
  45. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  46. 45
      tests/Avalonia.Base.UnitTests/Composition/BatchStreamTests.cs

5
src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs

@ -1,3 +1,4 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
@ -81,7 +82,7 @@ namespace Avalonia.Rendering.Composition.Animations
public bool IsAnimation => _animation != null;
public void SetAnimation(ServerObject target, ChangeSet cs, IAnimationInstance animation, int storeOffset)
public void SetAnimation(ServerObject target, TimeSpan commitedAt, IAnimationInstance animation, int storeOffset)
{
_direct = default;
if (_animation != null)
@ -91,7 +92,7 @@ namespace Avalonia.Rendering.Composition.Animations
}
_animation = animation;
_animation.Initialize(cs.Batch.CommitedAt, ExpressionVariant.Create(LastAnimated), storeOffset);
_animation.Initialize(commitedAt, ExpressionVariant.Create(LastAnimated), storeOffset);
if (target.IsActive)
_animation.Activate();

5
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs

@ -18,8 +18,6 @@ namespace Avalonia.Rendering.Composition.Animations
_propertySet = new CompositionPropertySet(compositor);
}
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException();
public void ClearAllParameters() => _propertySet.ClearAll();
public void ClearParameter(string key) => _propertySet.Clear(key);
@ -52,8 +50,7 @@ namespace Avalonia.Rendering.Composition.Animations
internal abstract IAnimationInstance CreateInstance(ServerObject targetObject,
ExpressionVariant? finalValue);
internal PropertySetSnapshot CreateSnapshot(bool server)
=> _propertySet.Snapshot(server);
internal PropertySetSnapshot CreateSnapshot() => _propertySet.Snapshot();
void ICompositionAnimationBase.InternalOnly()
{

2
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs

@ -20,7 +20,5 @@ namespace Avalonia.Rendering.Composition.Animations
public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!)
{
}
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException();
}
}

2
src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs

@ -29,6 +29,6 @@ namespace Avalonia.Rendering.Composition.Animations
internal override IAnimationInstance CreateInstance(
ServerObject targetObject, ExpressionVariant? finalValue)
=> new ExpressionAnimationInstance(ParsedExpression,
targetObject, finalValue, CreateSnapshot(true));
targetObject, finalValue, CreateSnapshot());
}
}

2
src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs

@ -15,8 +15,6 @@ namespace Avalonia.Rendering.Composition.Animations
_innerface = _inner;
}
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException();
public IEnumerator<KeyValuePair<string, ICompositionAnimationBase>> GetEnumerator() => _inner.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) _inner).GetEnumerator();

16
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@ -9,7 +9,7 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
{
public Visual Visual { get; }
private new DrawListVisualChanges Changes => (DrawListVisualChanges)base.Changes;
private bool _drawListChanged;
private CompositionDrawList? _drawList;
public CompositionDrawList? DrawList
{
@ -18,11 +18,21 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
{
_drawList?.Dispose();
_drawList = value;
Changes.DrawCommands = value?.Clone();
_drawListChanged = true;
RegisterForSerialization();
}
}
private protected override IChangeSetPool ChangeSetPool => DrawListVisualChanges.Pool;
private protected override void SerializeChangesCore(BatchStreamWriter writer)
{
writer.Write((byte)(_drawListChanged ? 1 : 0));
if (_drawListChanged)
{
writer.WriteObject(DrawList?.Clone());
_drawListChanged = false;
}
base.SerializeChangesCore(writer);
}
internal CompositionDrawListVisual(Compositor compositor, ServerCompositionDrawListVisual server, Visual visual) : base(compositor, server)
{

4
src/Avalonia.Base/Rendering/Composition/CompositionEasingFunction.cs

@ -10,9 +10,7 @@ namespace Avalonia.Rendering.Composition
internal CompositionEasingFunction(Compositor compositor) : base(compositor, null!)
{
}
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException();
internal abstract IEasingFunction Snapshot();
}

57
src/Avalonia.Base/Rendering/Composition/CompositionObject.cs

@ -6,7 +6,7 @@ using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition
{
public abstract class CompositionObject : IDisposable, IExpressionObject
public abstract class CompositionObject : IDisposable
{
public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
internal CompositionObject(Compositor compositor, ServerObject server)
@ -18,46 +18,17 @@ namespace Avalonia.Rendering.Composition
public Compositor Compositor { get; }
internal ServerObject Server { get; }
public bool IsDisposed { get; private set; }
private ChangeSet? _changes;
private bool _registeredForSerialization;
private static void ThrowInvalidOperation() =>
throw new InvalidOperationException("There is no server-side counterpart for this object");
private protected ChangeSet Changes
{
get
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (Server == null) ThrowInvalidOperation();
var currentBatch = Compositor.CurrentBatch;
if (_changes != null && _changes.Batch != currentBatch)
_changes = null;
if (_changes == null)
{
_changes = ChangeSetPool.Get(Server!, currentBatch);
currentBatch.Changes!.Add(_changes);
Compositor.QueueImplicitBatchCommit();
}
return _changes;
}
}
private protected abstract IChangeSetPool ChangeSetPool { get; }
public void Dispose()
{
Changes.Dispose = true;
//Changes.Dispose = true;
IsDisposed = true;
}
internal virtual ExpressionVariant GetPropertyForAnimation(string name)
{
return default;
}
ExpressionVariant IExpressionObject.GetProperty(string name) => GetPropertyForAnimation(name);
public void StartAnimation(string propertyName, CompositionAnimation animation)
=> StartAnimation(propertyName, animation, null);
@ -121,5 +92,27 @@ namespace Avalonia.Rendering.Composition
throw new ArgumentException();
}
protected void RegisterForSerialization()
{
if (Server == null)
throw new InvalidOperationException("The object doesn't have an associated server counterpart");
if(_registeredForSerialization)
return;
_registeredForSerialization = true;
Compositor.RegisterForSerialization(this);
}
internal void SerializeChanges(BatchStreamWriter writer)
{
_registeredForSerialization = false;
SerializeChangesCore(writer);
}
private protected virtual void SerializeChangesCore(BatchStreamWriter writer)
{
}
}
}

12
src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs

@ -16,8 +16,6 @@ namespace Avalonia.Rendering.Composition
{
}
private protected override IChangeSetPool ChangeSetPool => throw new NotSupportedException();
internal void Set(string key, ExpressionVariant value)
{
_objects.Remove(key);
@ -104,10 +102,10 @@ namespace Avalonia.Rendering.Composition
_variants.Remove(key);
}
internal PropertySetSnapshot Snapshot(bool server) =>
SnapshotCore(server, 1);
internal PropertySetSnapshot Snapshot() =>
SnapshotCore(1);
private PropertySetSnapshot SnapshotCore(bool server, int allowedNestingLevel)
private PropertySetSnapshot SnapshotCore(int allowedNestingLevel)
{
var dic = new Dictionary<string, PropertySetSnapshot.Value>(_objects.Count + _variants.Count);
foreach (var o in _objects)
@ -116,12 +114,12 @@ namespace Avalonia.Rendering.Composition
{
if (allowedNestingLevel <= 0)
throw new InvalidOperationException("PropertySet depth limit reached");
dic[o.Key] = new PropertySetSnapshot.Value(ps.SnapshotCore(server, allowedNestingLevel - 1));
dic[o.Key] = new PropertySetSnapshot.Value(ps.SnapshotCore(allowedNestingLevel - 1));
}
else if (o.Value.Server == null)
throw new InvalidOperationException($"Object of type {o.Value.GetType()} is not allowed");
else
dic[o.Key] = new PropertySetSnapshot.Value(server ? (IExpressionObject) o.Value.Server : o.Value);
dic[o.Key] = new PropertySetSnapshot.Value(o.Value.Server);
}
foreach (var v in _variants)

2
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -102,6 +102,6 @@ namespace Avalonia.Rendering.Composition
return false;
}
public void RequestRedraw() => Changes.RedrawRequested.Value = true;
public void RequestRedraw() => RegisterForSerialization();
}
}

50
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -16,18 +16,17 @@ namespace Avalonia.Rendering.Composition
public partial class Compositor
{
private ServerCompositor _server;
private Batch _currentBatch;
private bool _implicitBatchCommitQueued;
private Action _implicitBatchCommit;
internal Batch CurrentBatch => _currentBatch;
private BatchStreamObjectPool<object?> _batchObjectPool = new();
private BatchStreamMemoryPool _batchMemoryPool = new();
private List<CompositionObject> _objectsForSerialization = new();
internal ServerCompositor Server => _server;
internal CompositionEasingFunction DefaultEasing { get; }
private Compositor(ServerCompositor server)
public Compositor(IRenderLoop loop)
{
_server = server;
_currentBatch = new Batch();
_server = new ServerCompositor(loop, _batchObjectPool, _batchMemoryPool);
_implicitBatchCommit = ImplicitBatchCommit;
DefaultEasing = new CubicBezierEasingFunction(this,
new Vector2(0.25f, 0.1f), new Vector2(0.25f, 1f));
@ -40,18 +39,27 @@ namespace Avalonia.Rendering.Composition
public Task RequestCommitAsync()
{
var batch = CurrentBatch;
_currentBatch = new Batch();
var batch = new Batch();
using (var writer = new BatchStreamWriter(batch.Changes, _batchMemoryPool, _batchObjectPool))
{
foreach (var obj in _objectsForSerialization)
{
writer.WriteObject(obj.Server);
obj.SerializeChanges(writer);
#if DEBUG_COMPOSITOR_SERIALIZATION
writer.Write(BatchStreamDebugMarkers.ObjectEndMagic);
writer.WriteObject(BatchStreamDebugMarkers.ObjectEndMarker);
#endif
}
_objectsForSerialization.Clear();
}
batch.CommitedAt = Server.Clock.Elapsed;
_server.EnqueueBatch(batch);
return batch.Completed;
}
public static Compositor Create(IRenderLoop timer)
{
return new Compositor(new ServerCompositor(timer));
}
public void Dispose()
{
@ -122,12 +130,8 @@ namespace Avalonia.Rendering.Composition
public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this);
public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this);
internal CustomDrawVisual<T> CreateCustomDrawVisual<T>(ICustomDrawVisualRenderer<T> renderer,
ICustomDrawVisualHitTest<T>? hitTest = null) where T : IEquatable<T> =>
new CustomDrawVisual<T>(this, renderer, hitTest);
public void QueueImplicitBatchCommit()
private void QueueImplicitBatchCommit()
{
if(_implicitBatchCommitQueued)
return;
@ -140,5 +144,11 @@ namespace Avalonia.Rendering.Composition
_implicitBatchCommitQueued = false;
RequestCommitAsync();
}
internal void RegisterForSerialization(CompositionObject compositionObject)
{
_objectsForSerialization.Add(compositionObject);
QueueImplicitBatchCommit();
}
}
}

13
src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs

@ -2,19 +2,20 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition
{
public class CompositionContainerVisual : CompositionVisual
public partial class CompositionContainerVisual : CompositionVisual
{
public CompositionVisualCollection Children { get; }
internal CompositionContainerVisual(Compositor compositor, ServerCompositionContainerVisual server) : base(compositor, server)
public CompositionVisualCollection Children { get; private set; } = null!;
partial void InitializeDefaultsExtra()
{
Children = new CompositionVisualCollection(this, server.Children);
Children = new CompositionVisualCollection(this, Server.Children);
}
private protected override void OnRootChanged()
private protected override void OnRootChangedCore()
{
foreach (var ch in Children)
ch.Root = Root;
base.OnRootChanged();
base.OnRootChangedCore();
}
}
}

56
src/Avalonia.Base/Rendering/Composition/CustomDrawVisual.cs

@ -1,56 +0,0 @@
using System;
using System.Numerics;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition
{
internal class CustomDrawVisual<TData> : CompositionContainerVisual where TData : IEquatable<TData>
{
private readonly ICustomDrawVisualHitTest<TData>? _hitTest;
internal CustomDrawVisual(Compositor compositor, ICustomDrawVisualRenderer<TData> renderer,
ICustomDrawVisualHitTest<TData>? hitTest) : base(compositor,
new ServerCustomDrawVisual<TData>(compositor.Server, renderer))
{
_hitTest = hitTest;
}
private TData? _data;
static bool Eq(TData? left, TData? right)
{
if (left == null && right == null)
return true;
if (left == null)
return false;
return left.Equals(right);
}
public TData? Data
{
get => _data;
set
{
if (!Eq(_data, value))
{
((CustomDrawVisualChanges<TData?>) Changes).Data.Value = value;
_data = value;
}
}
}
private protected override IChangeSetPool ChangeSetPool => CustomDrawVisualChanges<TData>.Pool;
}
public interface ICustomDrawVisualRenderer<TData>
{
void Render(IDrawingContextImpl canvas, TData? data);
}
public interface ICustomDrawVisualHitTest<TData>
{
bool HitTest(TData data, Vector2 vector2);
}
}

10
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@ -1,3 +1,4 @@
using System;
using System.Numerics;
using Avalonia.Collections.Pooled;
using Avalonia.Platform;
@ -35,16 +36,15 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
}
}
protected override void ApplyCore(ChangeSet changes)
protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt)
{
var ch = (DrawListVisualChanges)changes;
if (ch.DrawCommandsIsSet)
if (reader.Read<byte>() == 1)
{
_renderCommands?.Dispose();
_renderCommands = ch.AcquireDrawCommands();
_renderCommands = reader.ReadObject<CompositionDrawList?>();
_contentBounds = null;
}
base.ApplyCore(changes);
base.DeserializeChangesCore(reader, commitedAt);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)

4
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -42,8 +42,8 @@ namespace Avalonia.Rendering.Composition.Server
else
_compositor.RemoveCompositionTarget(this);
}
partial void ApplyChangesExtra(CompositionTargetChanges c)
partial void DeserializeChangesExtra(BatchStreamReader c)
{
_redrawRequested = true;
}

23
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@ -17,10 +17,14 @@ namespace Avalonia.Rendering.Composition.Server
private List<ServerCompositionTarget> _activeTargets = new();
private HashSet<IAnimationInstance> _activeAnimations = new();
private List<IAnimationInstance> _animationsToUpdate = new();
private BatchStreamObjectPool<object?> _batchObjectPool;
private BatchStreamMemoryPool _batchMemoryPool;
public ServerCompositor(IRenderLoop renderLoop)
public ServerCompositor(IRenderLoop renderLoop, BatchStreamObjectPool<object?> batchObjectPool, BatchStreamMemoryPool batchMemoryPool)
{
_renderLoop = renderLoop;
_batchObjectPool = batchObjectPool;
_batchMemoryPool = batchMemoryPool;
_renderLoop.Add(this);
}
@ -45,14 +49,21 @@ namespace Avalonia.Rendering.Composition.Server
batch = _batches.Dequeue();
}
foreach (var change in batch.Changes)
using (var stream = new BatchStreamReader(batch.Changes, _batchMemoryPool, _batchObjectPool))
{
if (change.Dispose)
while (!stream.IsObjectEof)
{
//TODO
var target = (ServerObject)stream.ReadObject()!;
target.DeserializeChanges(stream, batch);
#if DEBUG_COMPOSITOR_SERIALIZATION
if (stream.ReadObject() != BatchStreamDebugMarkers.ObjectEndMarker)
throw new InvalidOperationException(
$"Object {target.GetType()} failed to deserialize properly on object stream");
if(stream.Read<Guid>() != BatchStreamDebugMarkers.ObjectEndMagic)
throw new InvalidOperationException(
$"Object {target.GetType()} failed to deserialize properly on data stream");
#endif
}
change.Target!.Apply(change);
change.Reset();
}
_reusableToCompleteList.Add(batch);

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

@ -3,10 +3,9 @@ using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server
{
internal class ServerCompositionContainerVisual : ServerCompositionVisual
internal partial class ServerCompositionContainerVisual : ServerCompositionVisual
{
public ServerCompositionVisualCollection Children { get; }
public ServerCompositionVisualCollection Children { get; private set; } = null!;
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
@ -28,9 +27,9 @@ namespace Avalonia.Rendering.Composition.Server
child.Update(root, GlobalTransformMatrix);
}
public ServerCompositionContainerVisual(ServerCompositor compositor) : base(compositor)
partial void Initialize()
{
Children = new ServerCompositionVisualCollection(compositor);
Children = new ServerCompositionVisualCollection(Compositor);
}
}
}

31
src/Avalonia.Base/Rendering/Composition/Server/ServerCustomDrawVisual.cs

@ -1,31 +0,0 @@
using System.Numerics;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server
{
class ServerCustomDrawVisual<TData> : ServerCompositionContainerVisual
{
private readonly ICustomDrawVisualRenderer<TData> _renderer;
private TData? _data;
public ServerCustomDrawVisual(ServerCompositor compositor, ICustomDrawVisualRenderer<TData> renderer) : base(compositor)
{
_renderer = renderer;
}
protected override void ApplyCore(ChangeSet changes)
{
var c = (CustomDrawVisualChanges<TData>) changes;
if (c.Data.IsSet)
_data = c.Data.Value;
base.ApplyCore(changes);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
_renderer.Render(canvas, _data);
base.RenderCore(canvas, transform);
}
}
}

24
src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs

@ -7,25 +7,19 @@ namespace Avalonia.Rendering.Composition.Server
class ServerList<T> : ServerObject where T : ServerObject
{
public List<T> List { get; } = new List<T>();
protected override void ApplyCore(ChangeSet changes)
protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt)
{
var c = (ListChangeSet<T>) changes;
if (c.HasListChanges)
if (reader.Read<byte>() == 1)
{
foreach (var lc in c.ListChanges)
{
if(lc.Action == ListChangeAction.Clear)
List.Clear();
if(lc.Action == ListChangeAction.RemoveAt)
List.RemoveAt(lc.Index);
if(lc.Action == ListChangeAction.InsertAt)
List.Insert(lc.Index, lc.Added!);
if (lc.Action == ListChangeAction.ReplaceAt)
List[lc.Index] = lc.Added!;
}
List.Clear();
var count = reader.Read<int>();
for (var c = 0; c < count; c++)
List.Add(reader.ReadObject<T>());
}
base.DeserializeChangesCore(reader, commitedAt);
}
public override long LastChangedBy
{
get

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

@ -23,18 +23,6 @@ namespace Avalonia.Rendering.Composition.Server
Compositor = compositor;
}
protected virtual void ApplyCore(ChangeSet changes)
{
}
public void Apply(ChangeSet changes)
{
ApplyCore(changes);
ValuesInvalidated();
ItselfLastChangedBy = changes.Batch!.SequenceId;
}
public virtual ExpressionVariant GetPropertyForAnimation(string name)
{
return default;
@ -81,6 +69,10 @@ namespace Avalonia.Rendering.Composition.Server
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref ServerObjectSubscriptionStore GetStoreFromOffset(int offset)
{
#if DEBUG
if (offset == 0)
throw new InvalidOperationException();
#endif
return ref Unsafe.As<uint, ServerObjectSubscriptionStore>(ref Unsafe.AddByteOffset(ref _activationCount,
new IntPtr(offset)));
}
@ -112,5 +104,17 @@ namespace Avalonia.Rendering.Composition.Server
}
public virtual int? GetFieldOffset(string fieldName) => null;
protected virtual void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt)
{
}
public void DeserializeChanges(BatchStreamReader reader, Batch batch)
{
DeserializeChangesCore(reader, batch.CommitedAt);
ValuesInvalidated();
ItselfLastChangedBy = batch.SequenceId;
}
}
}

31
src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs

@ -7,7 +7,6 @@ namespace Avalonia.Rendering.Composition.Server
unsafe partial class ServerCompositionVisual : ServerObject
{
private bool _isDirty;
private ServerCompositionTarget? _root;
private bool _isBackface;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
@ -119,28 +118,24 @@ namespace Avalonia.Rendering.Composition.Server
public bool Visible;
}
partial void ApplyChangesExtra(CompositionVisualChanges c)
partial void DeserializeChangesExtra(BatchStreamReader c)
{
if (c.Parent.IsSet)
Parent = c.Parent.Value;
if (c.Root.IsSet)
Root = c.Root.Value;
ValuesInvalidated();
}
public ServerCompositionTarget? Root
partial void OnRootChanging()
{
get => _root;
private set
{
if(_root != null)
Deactivate();
_root = value;
if (_root != null)
Activate();
}
if(Root != null)
Deactivate();
}
partial void OnRootChanged()
{
if (Root != null)
Activate();
}
protected override void ValuesInvalidated()
{
_isDirty = true;
@ -149,8 +144,6 @@ namespace Avalonia.Rendering.Composition.Server
else
Root?.Invalidate();
}
public ServerCompositionVisual? Parent { get; private set; }
public bool IsVisibleInFrame { get; set; }
public Rect TransformedBounds { get; set; }
public virtual Rect ContentBounds => new Rect(0, 0, Size.X, Size.Y);

7
src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs

@ -9,23 +9,22 @@ namespace Avalonia.Rendering.Composition.Transport
internal class Batch
{
private static long _nextSequenceId = 1;
private static ConcurrentBag<List<ChangeSet>> _pool = new ConcurrentBag<List<ChangeSet>>();
private static ConcurrentBag<BatchStreamData> _pool = new();
public long SequenceId { get; }
public Batch()
{
SequenceId = Interlocked.Increment(ref _nextSequenceId);
if (!_pool.TryTake(out var lst))
lst = new List<ChangeSet>();
lst = new BatchStreamData();
Changes = lst;
}
private TaskCompletionSource<int> _tcs = new TaskCompletionSource<int>();
public List<ChangeSet> Changes { get; private set; }
public BatchStreamData Changes { get; private set; }
public TimeSpan CommitedAt { get; set; }
public void Complete()
{
Changes.Clear();
_pool.Add(Changes);
Changes = null!;

27
src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs

@ -2,13 +2,14 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport;
internal class BatchStreamData
{
public Queue<BatchStreamSegment<ServerObject[]>> Objects { get; } = new();
public Queue<BatchStreamSegment<object?[]>> Objects { get; } = new();
public Queue<BatchStreamSegment<IntPtr>> Structs { get; } = new();
}
@ -22,12 +23,12 @@ internal class BatchStreamWriter : IDisposable
{
private readonly BatchStreamData _output;
private readonly BatchStreamMemoryPool _memoryPool;
private readonly BatchStreamObjectPool<ServerObject> _objectPool;
private readonly BatchStreamObjectPool<object?> _objectPool;
private BatchStreamSegment<ServerObject[]?> _currentObjectSegment;
private BatchStreamSegment<object?[]?> _currentObjectSegment;
private BatchStreamSegment<IntPtr> _currentDataSegment;
public BatchStreamWriter(BatchStreamData output, BatchStreamMemoryPool memoryPool, BatchStreamObjectPool<ServerObject> objectPool)
public BatchStreamWriter(BatchStreamData output, BatchStreamMemoryPool memoryPool, BatchStreamObjectPool<object?> objectPool)
{
_output = output;
_memoryPool = memoryPool;
@ -69,7 +70,7 @@ internal class BatchStreamWriter : IDisposable
_currentDataSegment.ElementCount += size;
}
public void Write(ServerObject item)
public void WriteObject(object? item)
{
if (_currentObjectSegment.Data == null ||
_currentObjectSegment.ElementCount >= _currentObjectSegment.Data.Length)
@ -89,15 +90,15 @@ internal class BatchStreamReader : IDisposable
{
private readonly BatchStreamData _input;
private readonly BatchStreamMemoryPool _memoryPool;
private readonly BatchStreamObjectPool<ServerObject> _objectPool;
private readonly BatchStreamObjectPool<object?> _objectPool;
private BatchStreamSegment<ServerObject[]?> _currentObjectSegment;
private BatchStreamSegment<object?[]?> _currentObjectSegment;
private BatchStreamSegment<IntPtr> _currentDataSegment;
private int _memoryOffset, _objectOffset;
public BatchStreamReader(BatchStreamData _input, BatchStreamMemoryPool memoryPool, BatchStreamObjectPool<ServerObject> objectPool)
public BatchStreamReader(BatchStreamData input, BatchStreamMemoryPool memoryPool, BatchStreamObjectPool<object?> objectPool)
{
this._input = _input;
_input = input;
_memoryPool = memoryPool;
_objectPool = objectPool;
}
@ -116,7 +117,7 @@ internal class BatchStreamReader : IDisposable
if (_memoryOffset + size > _currentDataSegment.ElementCount)
throw new InvalidOperationException("Attempted to read more memory then left in the current segment");
var rv = *(T*)((byte*)_currentDataSegment.Data + size);
var rv = *(T*)((byte*)_currentDataSegment.Data + _memoryOffset);
_memoryOffset += size;
if (_memoryOffset == _currentDataSegment.ElementCount)
{
@ -127,7 +128,9 @@ internal class BatchStreamReader : IDisposable
return rv;
}
public ServerObject ReadObject()
public T ReadObject<T>() where T : class? => (T)ReadObject()!;
public object? ReadObject()
{
if (_currentObjectSegment.Data == null)
{
@ -148,6 +151,8 @@ internal class BatchStreamReader : IDisposable
return rv;
}
public bool IsObjectEof => _currentObjectSegment.Data == null && _input.Objects.Count == 0;
public bool IsStructEof => _currentDataSegment.Data == IntPtr.Zero && _input.Structs.Count == 0;
public void Dispose()

24
src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs

@ -18,27 +18,31 @@ internal abstract class BatchStreamPoolBase<T> : IDisposable
readonly int[] _usageStatistics = new int[10];
int _usageStatisticsSlot;
public BatchStreamPoolBase(bool needsFinalize = false)
public BatchStreamPoolBase(bool needsFinalize, Action<Func<bool>>? startTimer = null)
{
if(!needsFinalize)
GC.SuppressFinalize(needsFinalize);
var updateRef = new WeakReference<BatchStreamPoolBase<T>>(this);
StartUpdateTimer(updateRef);
StartUpdateTimer(startTimer, updateRef);
}
static void StartUpdateTimer(WeakReference<BatchStreamPoolBase<T>> updateRef)
static void StartUpdateTimer(Action<Func<bool>>? startTimer, WeakReference<BatchStreamPoolBase<T>> updateRef)
{
DispatcherTimer.Run(() =>
Func<bool> timerProc = () =>
{
if (updateRef.TryGetTarget(out var target))
{
target.UpdateStatistics();
return true;
}
return false;
}, TimeSpan.FromSeconds(1));
return false;
};
if (startTimer != null)
startTimer(timerProc);
else
DispatcherTimer.Run(timerProc, TimeSpan.FromSeconds(1));
}
private void UpdateStatistics()
@ -50,7 +54,7 @@ internal abstract class BatchStreamPoolBase<T> : IDisposable
while (recentlyUsedPooledSlots < _pool.Count)
DestroyItem(_pool.Pop());
_usageStatistics[_usage] = 0;
_usageStatistics[_usageStatisticsSlot] = 0;
_usageStatisticsSlot = (_usageStatisticsSlot + 1) % _usageStatistics.Length;
}
}
@ -109,11 +113,11 @@ internal abstract class BatchStreamPoolBase<T> : IDisposable
}
}
internal sealed class BatchStreamObjectPool<T> : BatchStreamPoolBase<T[]> where T : class
internal sealed class BatchStreamObjectPool<T> : BatchStreamPoolBase<T[]> where T : class?
{
private readonly int _arraySize;
public BatchStreamObjectPool(int arraySize = 1024)
public BatchStreamObjectPool(int arraySize = 1024, Action<Func<bool>>? startTimer = null) : base(false, startTimer)
{
_arraySize = arraySize;
}
@ -133,7 +137,7 @@ internal sealed class BatchStreamMemoryPool : BatchStreamPoolBase<IntPtr>
{
public int BufferSize { get; }
public BatchStreamMemoryPool(int bufferSize = 16384)
public BatchStreamMemoryPool(int bufferSize = 16384, Action<Func<bool>>? startTimer = null) : base(true, startTimer)
{
BufferSize = bufferSize;
}

9
src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamDebugMarker.cs

@ -0,0 +1,9 @@
using System;
namespace Avalonia.Rendering.Composition.Transport;
internal class BatchStreamDebugMarkers
{
public static object ObjectEndMarker = new object();
public static Guid ObjectEndMagic = Guid.NewGuid();
}

82
src/Avalonia.Base/Rendering/Composition/Transport/Change.cs

@ -1,82 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Rendering.Composition.Animations;
namespace Avalonia.Rendering.Composition.Transport
{
struct Change<T>
{
private T? _value;
public bool IsSet { get; private set; }
public T? Value
{
get
{
if(!IsSet)
throw new InvalidOperationException();
return _value;
}
set
{
IsSet = true;
_value = value;
}
}
public void Reset()
{
_value = default;
IsSet = false;
}
}
struct AnimatedChange<T>
{
private T? _value;
private IAnimationInstance? _animation;
public bool IsValue { get; private set; }
public bool IsAnimation { get; private set; }
public T Value
{
get
{
if(!IsValue)
throw new InvalidOperationException();
return _value!;
}
set
{
IsAnimation = false;
_animation = null;
IsValue = true;
_value = value;
}
}
public IAnimationInstance Animation
{
get
{
if(!IsAnimation)
throw new InvalidOperationException();
return _animation!;
}
set
{
IsValue = false;
_value = default;
IsAnimation = true;
_animation = value;
}
}
public void Reset()
{
this = default;
}
}
}

36
src/Avalonia.Base/Rendering/Composition/Transport/ChangeSet.cs

@ -1,36 +0,0 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
internal abstract class ChangeSet
{
private readonly IChangeSetPool _pool;
public Batch Batch = null!;
public ServerObject? Target;
public bool Dispose;
public ChangeSet(IChangeSetPool pool)
{
_pool = pool;
}
public virtual void Reset()
{
Batch = null!;
Target = null;
Dispose = false;
}
public void Return()
{
_pool.Return(this);
}
}
internal class CompositionObjectChanges : ChangeSet
{
public CompositionObjectChanges(IChangeSetPool pool) : base(pool)
{
}
}
}

42
src/Avalonia.Base/Rendering/Composition/Transport/ChangeSetPool.cs

@ -1,42 +0,0 @@
using System;
using System.Collections.Concurrent;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
interface IChangeSetPool
{
void Return(ChangeSet changes);
ChangeSet Get(ServerObject target, Batch batch);
}
class ChangeSetPool<T> : IChangeSetPool where T : ChangeSet
{
private readonly Func<IChangeSetPool, T> _factory;
private readonly ConcurrentBag<T> _pool = new ConcurrentBag<T>();
public ChangeSetPool(Func<IChangeSetPool, T> factory)
{
_factory = factory;
}
public void Return(T changes)
{
changes.Reset();
_pool.Add(changes);
}
void IChangeSetPool.Return(ChangeSet changes) => Return((T) changes);
ChangeSet IChangeSetPool.Get(ServerObject target, Batch batch) => Get(target, batch);
public T Get(ServerObject target, Batch batch)
{
if (!_pool.TryTake(out var res))
res = _factory(this);
res.Target = target;
res.Batch = batch;
res.Dispose = false;
return res;
}
}
}

11
src/Avalonia.Base/Rendering/Composition/Transport/CompositionTargetChanges.cs

@ -1,11 +0,0 @@
namespace Avalonia.Rendering.Composition.Transport;
partial class CompositionTargetChanges
{
public Change<bool> RedrawRequested;
partial void ResetExtra()
{
RedrawRequested.Reset();
}
}

20
src/Avalonia.Base/Rendering/Composition/Transport/CustomDrawVisualChanges.cs

@ -1,20 +0,0 @@
namespace Avalonia.Rendering.Composition.Transport
{
class CustomDrawVisualChanges<TData> : CompositionVisualChanges
{
public CustomDrawVisualChanges(IChangeSetPool pool) : base(pool)
{
}
public Change<TData> Data;
public override void Reset()
{
Data.Reset();
base.Reset();
}
public new static ChangeSetPool<CustomDrawVisualChanges<TData>> Pool { get; } =
new ChangeSetPool<CustomDrawVisualChanges<TData>>(pool => new CustomDrawVisualChanges<TData>(pool));
}
}

48
src/Avalonia.Base/Rendering/Composition/Transport/DrawListVisualChanges.cs

@ -1,48 +0,0 @@
using System.Collections.Generic;
using Avalonia.Collections.Pooled;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Transport;
internal class DrawListVisualChanges : CompositionVisualChanges
{
private CompositionDrawList? _drawCommands;
public DrawListVisualChanges(IChangeSetPool pool) : base(pool)
{
}
public CompositionDrawList? DrawCommands
{
get => _drawCommands;
set
{
_drawCommands?.Dispose();
_drawCommands = value;
DrawCommandsIsSet = true;
}
}
public bool DrawCommandsIsSet { get; private set; }
public CompositionDrawList? AcquireDrawCommands()
{
var rv = _drawCommands;
_drawCommands = null;
DrawCommandsIsSet = false;
return rv;
}
public override void Reset()
{
_drawCommands?.Dispose();
_drawCommands = null;
DrawCommandsIsSet = false;
base.Reset();
}
public new static ChangeSetPool<DrawListVisualChanges> Pool { get; } =
new ChangeSetPool<DrawListVisualChanges>(pool => new(pool));
}

19
src/Avalonia.Base/Rendering/Composition/Transport/ListChange.cs

@ -1,19 +0,0 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
internal class ListChange<T> where T : ServerObject
{
public int Index;
public ListChangeAction Action;
public T? Added;
}
internal enum ListChangeAction
{
InsertAt,
RemoveAt,
Clear,
ReplaceAt
}
}

25
src/Avalonia.Base/Rendering/Composition/Transport/ListChangeSet.cs

@ -1,25 +0,0 @@
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
class ListChangeSet<T> : ChangeSet where T : ServerObject
{
private List<ListChange<T>>? _listChanges;
public List<ListChange<T>> ListChanges => _listChanges ??= new List<ListChange<T>>();
public bool HasListChanges => _listChanges != null;
public override void Reset()
{
_listChanges?.Clear();
base.Reset();
}
public ListChangeSet(IChangeSetPool pool) : base(pool)
{
}
public static readonly ChangeSetPool<ListChangeSet<T>> Pool =
new ChangeSetPool<ListChangeSet<T>>(pool => new ListChangeSet<T>(pool));
}
}

55
src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs

@ -8,19 +8,21 @@ namespace Avalonia.Rendering.Composition.Transport
where TServer : ServerObject
where TClient : CompositionObject
{
private readonly IGetChanges _parent;
private readonly List<TClient> _list = new List<TClient>();
private readonly IRegisterForSerialization _parent;
private bool _changed;
public interface IGetChanges
public interface IRegisterForSerialization
{
ListChangeSet<TServer> GetChanges();
void RegisterForSerialization();
}
public ServerListProxyHelper(IGetChanges parent)
public ServerListProxyHelper(IRegisterForSerialization parent)
{
_parent = parent;
}
private readonly List<TClient> _list = new List<TClient>();
IEnumerator<TClient> IEnumerable<TClient>.GetEnumerator() => GetEnumerator();
public List<TClient>.Enumerator GetEnumerator() => _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
@ -30,10 +32,8 @@ namespace Avalonia.Rendering.Composition.Transport
public void Clear()
{
_list.Clear();
_parent.GetChanges().ListChanges.Add(new ListChange<TServer>
{
Action = ListChangeAction.Clear
});
_changed = true;
_parent.RegisterForSerialization();
}
public bool Contains(TClient item) => _list.Contains(item);
@ -56,22 +56,15 @@ namespace Avalonia.Rendering.Composition.Transport
public void Insert(int index, TClient item)
{
_list.Insert(index, item);
_parent.GetChanges().ListChanges.Add(new ListChange<TServer>
{
Action = ListChangeAction.InsertAt,
Index = index,
Added = (TServer) item.Server
});
_changed = true;
_parent.RegisterForSerialization();
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
_parent.GetChanges().ListChanges.Add(new ListChange<TServer>
{
Action = ListChangeAction.RemoveAt,
Index = index
});
_changed = true;
_parent.RegisterForSerialization();
}
public TClient this[int index]
@ -80,13 +73,21 @@ namespace Avalonia.Rendering.Composition.Transport
set
{
_list[index] = value;
_parent.GetChanges().ListChanges.Add(new ListChange<TServer>
{
Action = ListChangeAction.ReplaceAt,
Index = index,
Added = (TServer) value.Server
});
_changed = true;
_parent.RegisterForSerialization();
}
}
public void Serialize(BatchStreamWriter writer)
{
writer.Write((byte)(_changed ? 1 : 0));
if (_changed)
{
writer.Write(_list.Count);
foreach (var el in _list)
writer.WriteObject(el.Server);
}
_changed = false;
}
}
}

16
src/Avalonia.Base/Rendering/Composition/Transport/VisualChanges.cs

@ -1,16 +0,0 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
partial class CompositionVisualChanges
{
public Change<ServerCompositionVisual> Parent;
public Change<ServerCompositionTarget> Root;
partial void ResetExtra()
{
Parent.Reset();
Root.Reset();
}
}
}

35
src/Avalonia.Base/Rendering/Composition/Visual.cs

@ -4,39 +4,14 @@ namespace Avalonia.Rendering.Composition
{
public abstract partial class CompositionVisual
{
private CompositionVisual? _parent;
private CompositionTarget? _root;
public CompositionVisual? Parent
{
get => _parent;
internal set
{
if (_parent == value)
return;
_parent = value;
Changes.Parent.Value = value?.Server;
Root = _parent?.Root;
}
}
// TODO: hide behind private-ish API
public CompositionTarget? Root
private protected virtual void OnRootChangedCore()
{
get => _root;
internal set
{
var changed = _root != value;
_root = value;
Changes.Root.Value = value?.Server;
if (changed)
OnRootChanged();
}
}
private protected virtual void OnRootChanged()
{
}
partial void OnRootChanged() => OnRootChangedCore();
partial void OnParentChanged() => Root = Parent?.Root;
internal Matrix4x4? TryGetServerTransform()
{

1
src/Avalonia.Base/Rendering/Composition/VisualCollection.cs

@ -64,6 +64,7 @@ namespace Avalonia.Rendering.Composition
{
if (item.Parent != null)
throw new InvalidOperationException("Visual already has a parent");
item.Parent = item;
}
}
}

7
src/Avalonia.Base/composition-schema.xml

@ -4,10 +4,12 @@
<Using>Avalonia.Rendering.Composition.Server</Using>
<Using>Avalonia.Rendering.Composition.Transport</Using>
<Using>Avalonia.Rendering.Composition.Animations</Using>
<Manual Name="CompositionContainerVisual" />
<Manual Name="Avalonia.Platform.IGeometryImpl" Passthrough="true"/>
<Manual Name="ICompositionSurface" ServerName="ServerCompositionSurface" />
<Object Name="CompositionVisual" Abstract="true">
<Property Name="Root" Type="CompositionTarget?" InternalSet="true" />
<Property Name="Parent" Type="CompositionVisual?" InternalSet="true" />
<Property Name="Visible" Type="bool" Animated="true" DefaultValue="true"/>
<Property Name="Opacity" Type="float" Animated="true" DefaultValue="1"/>
<Property Name="Clip" Type="Avalonia.Platform.IGeometryImpl?" />
@ -21,6 +23,7 @@
<Property Name="Scale" Type="Vector3" DefaultValue="new Vector3(1, 1, 1)" Animated="true"/>
<Property Name="TransformMatrix" Type="Matrix4x4" DefaultValue="Matrix4x4.Identity" Animated="true"/>
</Object>
<Object Name="CompositionContainerVisual" Inherits="CompositionVisual"/>
<List Name="CompositionVisualCollection" ItemType="CompositionVisual" CustomCtor="true"/>
<Object Name="CompositionTarget" CustomServerCtor="true">
<Property Name="Root" Type="CompositionVisual?"/>

6
src/Avalonia.SourceGenerator/CompositionGenerator/Config.cs

@ -35,6 +35,10 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public bool Passthrough { get; set; }
[XmlAttribute]
public string ServerName { get; set; }
}
@ -104,6 +108,8 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
public string DefaultValue { get; set; }
[XmlAttribute]
public bool Animated { get; set; }
[XmlAttribute]
public bool InternalSet { get; set; }
}
public class GAnimationType

9
src/Avalonia.SourceGenerator/CompositionGenerator/Extensions.cs

@ -41,6 +41,15 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static EnumDeclarationSyntax AddModifiers(this EnumDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static string WithLowerFirst(this string s)
{

2
src/Avalonia.SourceGenerator/CompositionGenerator/Generator.KeyFrameAnimation.cs

@ -25,7 +25,7 @@ namespace Avalonia.Rendering.Composition
internal override IAnimationInstance CreateInstance(Avalonia.Rendering.Composition.Server.ServerObject targetObject, ExpressionVariant? finalValue)
{{
return new KeyFrameAnimationInstance<{a.Type}>({name}Interpolator.Instance, _keyFrames.Snapshot(), CreateSnapshot(true),
return new KeyFrameAnimationInstance<{a.Type}>({name}Interpolator.Instance, _keyFrames.Snapshot(), CreateSnapshot(),
finalValue?.CastOrDefault<{a.Type}>(), targetObject,
DelayBehavior, DelayTime, Direction, Duration, IterationBehavior,
IterationCount, StopBehavior);

12
src/Avalonia.SourceGenerator/CompositionGenerator/Generator.ListProxy.cs

@ -11,9 +11,7 @@ class Template
{
private ServerListProxyHelper<ItemTypeName, ServerItemTypeName> _list = null!;
ListChangeSet<ServerItemTypeName>
ServerListProxyHelper<ItemTypeName, ServerItemTypeName>.IGetChanges.
GetChanges() => Changes;
void ServerListProxyHelper<ItemTypeName, ServerItemTypeName>.IRegisterForSerialization.RegisterForSerialization() => RegisterForSerialization();
public List<ItemTypeName>.Enumerator GetEnumerator() => _list.GetEnumerator();
@ -87,7 +85,11 @@ class Template
partial void OnBeforeReplace(ItemTypeName oldItem, ItemTypeName newItem);
partial void OnReplace(ItemTypeName oldItem, ItemTypeName newItem);
partial void OnClear();
}
private protected override void SerializeChangesCore(BatchStreamWriter writer)
{{
_list.Serialize(writer);
base.SerializeChangesCore(writer);
}}
";
private ClassDeclarationSyntax AppendListProxy(GList list, ClassDeclarationSyntax cl)
@ -97,7 +99,7 @@ class Template
var serverItemType = ServerName(itemType);
cl = cl.AddBaseListTypes(SimpleBaseType(
ParseTypeName("ServerListProxyHelper<" + itemType + ", " + serverItemType + ">.IGetChanges")),
ParseTypeName("ServerListProxyHelper<" + itemType + ", " + serverItemType + ">.IRegisterForSerialization")),
SimpleBaseType(ParseTypeName("IList<" + itemType + ">"))
);
var code = ListProxyTemplate.Replace("ListTypeName", list.Name)

413
src/Avalonia.SourceGenerator/CompositionGenerator/Generator.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
@ -38,7 +39,12 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
string ServerName(string c) => c != null ? ("Server" + c) : "ServerObject";
string ChangesName(string c) => c != null ? (c + "Changes") : "ChangeSet";
string ChangedFieldsTypeName(GClass c) => c.Name + "ChangedFields";
string ChangedFieldsFieldName(GClass c) => "_changedFieldsOf" + c.Name;
string PropertyBackingFieldName(GProperty prop) => "_" + prop.Name.WithLowerFirst();
string ServerPropertyOffsetFieldName(GProperty prop) => "s_OffsetOf" + PropertyBackingFieldName(prop);
string PropertyPendingAnimationFieldName(GProperty prop) => "_pendingAnimationFor" + prop.Name;
void GenerateClass(GClass cl)
{
var list = cl as GList;
@ -68,36 +74,13 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
.WithBaseType(serverBase);
string changesName = ChangesName(cl.Name);
var changesBase = ChangesName(cl.ChangesBase ?? cl.Inherits);
string changedFieldsTypeName = ChangedFieldsTypeName(cl);
string changedFieldsName = ChangedFieldsFieldName(cl);
if (list != null)
changesBase = "ListChangeSet<" + ServerName(list.ItemType) + ">";
var changeSetPoolType = "ChangeSetPool<" + changesName + ">";
var transport = ClassDeclaration(changesName)
.AddModifiers(SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword)
.WithBaseType(changesBase)
.AddMembers(DeclareField(changeSetPoolType, "Pool",
EqualsValueClause(
ParseExpression($"new {changeSetPoolType}(pool => new {changesName}(pool))")
),
SyntaxKind.PublicKeyword,
SyntaxKind.StaticKeyword, SyntaxKind.ReadOnlyKeyword))
.AddMembers(ParseMemberDeclaration($"public {changesName}(IChangeSetPool pool) : base(pool){{}}"));
client = client
.AddMembers(
PropertyDeclaration(ParseTypeName("IChangeSetPool"), "ChangeSetPool")
.AddModifiers(SyntaxKind.PrivateKeyword, SyntaxKind.ProtectedKeyword,
SyntaxKind.OverrideKeyword)
.WithExpressionBody(
ArrowExpressionClause(MemberAccess(changesName, "Pool")))
.WithSemicolonToken(Semicolon()))
.AddMembers(PropertyDeclaration(ParseTypeName(changesName), "Changes")
.AddModifiers(SyntaxKind.PrivateKeyword, SyntaxKind.NewKeyword)
.WithExpressionBody(ArrowExpressionClause(CastExpression(ParseTypeName(changesName),
MemberAccess(BaseExpression(), "Changes"))))
.WithSemicolonToken(Semicolon()));
if (cl.Properties.Count > 0)
client = client
.AddMembers(DeclareField(changedFieldsTypeName, changedFieldsName));
if (!cl.CustomCtor)
{
@ -105,7 +88,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
.AddModifiers(SyntaxKind.InternalKeyword, SyntaxKind.NewKeyword)
.AddAccessorListAccessors(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(Semicolon())));
client = client.AddMembers(
client = client.AddMembers(
ConstructorDeclaration(cl.Name)
.AddModifiers(SyntaxKind.InternalKeyword)
.WithParameterList(ParameterList(SeparatedList(new[]
@ -141,20 +124,20 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
ArgumentList(SeparatedList(new[]
{
Argument(IdentifierName("compositor")),
})))).WithBody(Block()));
})))).WithBody(Block(ParseStatement("Initialize();"))));
}
server = server.AddMembers(
MethodDeclaration(ParseTypeName("void"), "Initialize")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
var changesVarName = "c";
var changesVar = IdentifierName(changesVarName);
server = server.AddMembers(
MethodDeclaration(ParseTypeName("void"), "ApplyChangesExtra")
.AddParameterListParameters(Parameter(Identifier("c")).WithType(ParseTypeName(changesName)))
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
transport = transport.AddMembers(
MethodDeclaration(ParseTypeName("void"), "ResetExtra")
MethodDeclaration(ParseTypeName("void"), "DeserializeChangesExtra")
.AddParameterListParameters(Parameter(Identifier("c")).WithType(ParseTypeName("BatchStreamReader")))
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
var applyMethodBody = Block(
@ -168,7 +151,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
ExpressionStatement(InvocationExpression(IdentifierName("ApplyChangesExtra"))
.AddArgumentListArguments(Argument(IdentifierName("c"))))
);
var uninitializedObjectName = "dummy";
var serverStaticCtorBody = cl.Abstract
? Block()
@ -179,72 +162,51 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
ParseStatement("InitializeFieldOffsets(dummy);")
);
var initializeFieldOffsetsBody = cl.ServerBase == null
var initializeFieldOffsetsBody = cl.Inherits == null
? Block()
: Block(ParseStatement($"{cl.ServerBase}.InitializeFieldOffsets(dummy);"));
: Block(ParseStatement($"Server{cl.Inherits}.InitializeFieldOffsets(dummy);"));
var resetBody = Block();
var startAnimationBody = Block();
var getPropertyBody = Block();
var serverGetPropertyBody = Block();
var serverGetFieldOffsetBody = Block();
var activatedBody = Block(ParseStatement("base.Activated();"));
var deactivatedBody = Block(ParseStatement("base.Deactivated();"));
var serializeMethodBody = SerializeChangesPrologue(cl);
var deserializeMethodBody = DeserializeChangesPrologue(cl);
var defaultsMethodBody = Block();
var defaultsMethodBody = Block(ParseStatement("InitializeDefaultsExtra();"));
foreach (var prop in cl.Properties)
{
var fieldName = "_" + prop.Name.WithLowerFirst();
var fieldOffsetName = "s_OffsetOf" + fieldName;
var fieldName = PropertyBackingFieldName(prop);
var animatedFieldName = PropertyPendingAnimationFieldName(prop);
var fieldOffsetName = ServerPropertyOffsetFieldName(prop);
var propType = ParseTypeName(prop.Type);
var filteredPropertyType = prop.Type.TrimEnd('?');
var isObject = _objects.Contains(filteredPropertyType);
var isNullable = prop.Type.EndsWith("?");
client = client
.AddMembers(DeclareField(prop.Type, fieldName))
.AddMembers(PropertyDeclaration(propType, prop.Name)
.AddModifiers(SyntaxKind.PublicKeyword)
.AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration,
Block(ReturnStatement(IdentifierName(fieldName)))),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration,
Block(
ParseStatement("var changed = false;"),
IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression,
IdentifierName(fieldName),
IdentifierName("value")),
Block(
ParseStatement("On" + prop.Name + "Changing();"),
ParseStatement("changed = true;"),
GeneratePropertySetterAssignment(prop, fieldName, isObject, isNullable))
),
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(fieldName), IdentifierName("value"))),
ParseStatement($"if(changed) On" + prop.Name + "Changed();")
))
))
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changed")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()))
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changing")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
bool isPassthrough = false;
if (prop.Animated)
client = client.AddMembers(DeclareField("IAnimationInstance?", animatedFieldName));
client = GenerateClientProperty(client, cl, prop, propType, isObject, isNullable);
var animatedServer = prop.Animated;
var serverPropertyType = ((isObject ? "Server" : "") + prop.Type);
if (_manuals.TryGetValue(filteredPropertyType, out var manual) && manual.ServerName != null)
serverPropertyType = manual.ServerName + (isNullable ? "?" : "");
transport = transport
.AddMembers(DeclareField((animatedServer ? "Animated" : "") + "Change<" + serverPropertyType + ">",
prop.Name, SyntaxKind.PublicKeyword));
if (_manuals.TryGetValue(filteredPropertyType, out var manual))
{
if (manual.Passthrough)
{
isPassthrough = true;
serverPropertyType = prop.Type;
}
if (manual.ServerName != null)
serverPropertyType = manual.ServerName + (isNullable ? "?" : "");
}
if (animatedServer)
server = server.AddMembers(
DeclareField("ServerAnimatedValueStore<" + serverPropertyType + ">", fieldName),
@ -301,7 +263,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
ArgumentList(SeparatedList(new[]
{
Argument(IdentifierName("this")),
Argument(changesVar),
Argument(ParseExpression("c.Batch.CommitedAt")),
Argument(MemberAccess(changesVar, prop.Name, "Animation")),
Argument(IdentifierName(fieldOffsetName))
})))))
@ -316,16 +278,18 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
resetBody = resetBody.AddStatements(
ExpressionStatement(InvocationExpression(MemberAccess(prop.Name, "Reset"))));
serializeMethodBody = ApplySerializeField(serializeMethodBody,cl, prop, isObject, isPassthrough);
deserializeMethodBody = ApplyDeserializeField(deserializeMethodBody,cl, prop, serverPropertyType, isObject);
if (animatedServer)
{
startAnimationBody = ApplyStartAnimation(startAnimationBody, prop, fieldName);
startAnimationBody = ApplyStartAnimation(startAnimationBody, cl, prop);
activatedBody = activatedBody.AddStatements(ParseStatement($"{fieldName}.Activate(this);"));
deactivatedBody = deactivatedBody.AddStatements(ParseStatement($"{fieldName}.Deactivate(this);"));
}
getPropertyBody = ApplyGetProperty(getPropertyBody, prop);
serverGetPropertyBody = ApplyGetProperty(serverGetPropertyBody, prop);
serverGetFieldOffsetBody = ApplyGetProperty(serverGetFieldOffsetBody, prop, fieldOffsetName);
@ -345,55 +309,6 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
}
}
if (cl is GBrush brush && !cl.Abstract)
{
var brushName = brush.Name.StripPrefix("Composition");
/*
server = server.AddMembers(
MethodDeclaration(ParseTypeName("ICbBrush"), "CreateBackendBrush")
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword)
.WithExpressionBody(ArrowExpressionClause(
InvocationExpression(MemberAccess("Compositor", "Backend", "Create" + brushName))
)).WithSemicolonToken(Semicolon())
);
if (!brush.CustomUpdate)
server = server.AddMembers(
MethodDeclaration(ParseTypeName("void"), "UpdateBackendBrush")
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword)
.AddParameterListParameters(Parameter(Identifier("brush"))
.WithType(ParseTypeName("ICbBrush")))
.AddBodyStatements(
ExpressionStatement(
InvocationExpression(
MemberAccess(
ParenthesizedExpression(
CastExpression(ParseTypeName("ICb" + brushName), IdentifierName("brush"))), "Update"),
ArgumentList(SeparatedList(cl.Properties.Select(x =>
{
if(x.Type.TrimEnd('?') == "ICompositionSurface")
return Argument(
ConditionalAccessExpression(IdentifierName(x.Name),
MemberBindingExpression(IdentifierName("BackendSurface")))
);
if (_brushes.Contains(x.Type))
return Argument(
ConditionalAccessExpression(IdentifierName(x.Name),
MemberBindingExpression(IdentifierName("Brush")))
);
return Argument(IdentifierName(x.Name));
}))))
)));
*/
}
server = server.AddMembers(
MethodDeclaration(ParseTypeName("void"), "ApplyCore")
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword)
.AddParameterListParameters(
Parameter(Identifier("changes")).WithType(ParseTypeName("ChangeSet")))
.WithBody(applyMethodBody));
server = server.AddMembers(ConstructorDeclaration(serverName)
.WithModifiers(TokenList(Token(SyntaxKind.StaticKeyword)))
.WithBody(serverStaticCtorBody));
@ -408,26 +323,32 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
$"protected override void Activated(){{}}")!).WithBody(activatedBody))
.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration(
$"protected override void Deactivated(){{}}")!).WithBody(deactivatedBody));
if (cl.Properties.Count > 0)
server = server.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration(
$"protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt){{}}")
!)
.WithBody(deserializeMethodBody));
client = client.AddMembers(
MethodDeclaration(ParseTypeName("void"), "InitializeDefaults").WithBody(defaultsMethodBody));
transport = transport.AddMembers(MethodDeclaration(ParseTypeName("void"), "Reset")
.AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.OverrideKeyword)
.WithBody(resetBody.AddStatements(
ExpressionStatement(InvocationExpression(IdentifierName("ResetExtra"))),
ExpressionStatement(InvocationExpression(MemberAccess("base", "Reset"))))));
MethodDeclaration(ParseTypeName("void"), "InitializeDefaults").WithBody(defaultsMethodBody))
.AddMembers(
MethodDeclaration(ParseTypeName("void"), "InitializeDefaultsExtra")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
if (cl.Properties.Count > 0)
client = client.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration(
$"private protected override void SerializeChangesCore(BatchStreamWriter writer){{}}")!)
.WithBody(serializeMethodBody));
if (list != null)
client = AppendListProxy(list, client);
if (startAnimationBody.Statements.Count != 0)
client = WithStartAnimation(client, startAnimationBody);
client = WithGetProperty(client, getPropertyBody, false);
server = WithGetProperty(server, serverGetPropertyBody, true);
server = WithGetPropertyForAnimation(server, serverGetPropertyBody);
server = WithGetFieldOffset(server, serverGetFieldOffsetBody);
if(cl.Implements.Count > 0)
foreach (var impl in cl.Implements)
{
@ -441,57 +362,121 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
}
SaveTo(unit.AddMembers(GenerateChangedFieldsEnum(cl)), "Transport",
ChangedFieldsTypeName(cl) + ".generated.cs");
SaveTo(unit.AddMembers(clientNs.AddMembers(client)),
cl.Name + ".generated.cs");
SaveTo(unit.AddMembers(serverNs.AddMembers(server)),
"Server", "Server" + cl.Name + ".generated.cs");
SaveTo(unit.AddMembers(transportNs.AddMembers(transport)),
"Transport", cl.Name + "Changes.generated.cs");
}
private ClassDeclarationSyntax GenerateClientProperty(ClassDeclarationSyntax client, GClass cl, GProperty prop,
TypeSyntax propType, bool isObject, bool isNullable)
{
var fieldName = PropertyBackingFieldName(prop);
return client
.AddMembers(DeclareField(prop.Type, fieldName))
.AddMembers(PropertyDeclaration(propType, prop.Name)
.AddModifiers(SyntaxKind.PublicKeyword)
.AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration,
Block(ReturnStatement(IdentifierName(fieldName)))),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration,
Block(
ParseStatement("var changed = false;"),
IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression,
IdentifierName(fieldName),
IdentifierName("value")),
Block(
ParseStatement("On" + prop.Name + "Changing();"),
ParseStatement("changed = true;"),
GeneratePropertySetterAssignment(cl, prop, isObject, isNullable))
),
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(fieldName), IdentifierName("value"))),
ParseStatement($"if(changed) On" + prop.Name + "Changed();")
)).WithModifiers(TokenList(prop.InternalSet ? new[]{Token(SyntaxKind.InternalKeyword)} : Array.Empty<SyntaxToken>()))
))
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changed")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()))
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changing")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
}
StatementSyntax GeneratePropertySetterAssignment(GProperty prop, string fieldName, bool isObject, bool isNullable)
EnumDeclarationSyntax GenerateChangedFieldsEnum(GClass cl)
{
var normalChangesAssignment = (StatementSyntax)ExpressionStatement(AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
MemberAccess((ExpressionSyntax) IdentifierName("Changes"), prop.Name,
"Value"),
isObject
?
ConditionalMemberAccess(IdentifierName("value"), "Server", isNullable)
: IdentifierName("value")));
if (!prop.Animated)
return normalChangesAssignment;
var changedFieldsEnum = EnumDeclaration(Identifier(ChangedFieldsTypeName(cl)));
int count = 0;
var code = $@"
{{
if(animation is CompositionAnimation a)
Changes.{prop.Name}.Animation = a.CreateInstance(this.Server, value);
else
void AddValue(string name)
{
var value = 1ul << count;
changedFieldsEnum = changedFieldsEnum.AddMembers(
EnumMemberDeclaration(name)
.WithEqualsValue(EqualsValueClause(ParseExpression(value.ToString()))));
count++;
}
foreach (var prop in cl.Properties)
{
AddValue(prop.Name);
if (prop.Animated)
AddValue(prop.Name + "Animated");
}
var baseType = count <= 8 ? "byte" : count <= 16 ? "ushort" : count <= 32 ? "uint" : "ulong";
return changedFieldsEnum.AddBaseListTypes(SimpleBaseType(ParseTypeName(baseType)))
.AddAttributeLists(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("System.Flags")))));
}
StatementSyntax GeneratePropertySetterAssignment(GClass cl, GProperty prop, bool isObject, bool isNullable)
{
var pendingAnimationField = PropertyPendingAnimationFieldName(prop);
var code = @$"
// Update the backing value
{PropertyBackingFieldName(prop)} = value;
// Register object for serialization in the next batch
{ChangedFieldsFieldName(cl)} |= {ChangedFieldsTypeName(cl)}.{prop.Name};
RegisterForSerialization();
";
if (prop.Animated)
{
code += @$"
// Reset previous animation if any
{pendingAnimationField} = null;
{ChangedFieldsFieldName(cl)} &= ~{ChangedFieldsTypeName(cl)}.{prop.Name}Animated;
// Check for implicit animations
if(ImplicitAnimations != null && ImplicitAnimations.TryGetValue(""{prop.Name}"", out var animation) == true)
{{
var saved = Changes.{prop.Name};
if(!StartAnimationGroup(animation, ""{prop.Name}"", value))
Changes.{prop.Name}.Value = value;
// Animation affects only current property
if(animation is CompositionAnimation a)
{{
{ChangedFieldsFieldName(cl)} |= {ChangedFieldsTypeName(cl)}.{prop.Name}Animated;
{pendingAnimationField} = a.CreateInstance(this.Server, value);
}}
// Animation is triggered by the current field, but does not necessary affects it
StartAnimationGroup(animation, ""{prop.Name}"", value);
}}
}}
";
return IfStatement(
ParseExpression(
$"ImplicitAnimations != null && ImplicitAnimations.TryGetValue(\"{prop.Name}\", out var animation) == true"),
ParseStatement(code),
ElseClause(normalChangesAssignment)
);
}
return ParseStatement("{\n" + code + "\n}");
}
BlockSyntax ApplyStartAnimation(BlockSyntax body, GProperty prop, string fieldName)
BlockSyntax ApplyStartAnimation(BlockSyntax body, GClass cl, GProperty prop)
{
var code = $@"
if (propertyName == ""{prop.Name}"")
{{
var current = {fieldName};
var current = {PropertyBackingFieldName(prop)};
var server = animation.CreateInstance(this.Server, finalValue);
Changes.{prop.Name}.Animation = server;
{PropertyPendingAnimationFieldName(prop)} = server;
{ChangedFieldsFieldName(cl)} |= {ChangedFieldsTypeName(cl)}.{prop.Name}Animated;
RegisterForSerialization();
return;
}}
";
@ -522,15 +507,75 @@ return;
return body;
}
private BlockSyntax SerializeChangesPrologue(GClass cl)
{
return Block(
ParseStatement("base.SerializeChangesCore(writer);"),
ParseStatement($"writer.Write({ChangedFieldsFieldName(cl)});")
);
}
BlockSyntax ApplySerializeField(BlockSyntax body, GClass cl, GProperty prop, bool isObject, bool isPassthrough)
{
var changedFields = ChangedFieldsFieldName(cl);
var changedFieldsType = ChangedFieldsTypeName(cl);
var code = "";
if (prop.Animated)
{
code = $@"
if(({changedFields} & {changedFieldsType}.{prop.Name}Animated) == {changedFieldsType}.{prop.Name}Animated)
writer.WriteObject({PropertyPendingAnimationFieldName(prop)});
else ";
}
code += $@"
if(({changedFields} & {changedFieldsType}.{prop.Name}) == {changedFieldsType}.{prop.Name})
writer.Write{(isObject ? "Object" : "")}({PropertyBackingFieldName(prop)}{(isObject && !isPassthrough ? "?.Server!":"")});
";
return body.AddStatements(ParseStatement(code));
}
private BlockSyntax DeserializeChangesPrologue(GClass cl)
{
return Block(ParseStatement($@"
base.DeserializeChangesCore(reader, commitedAt);
DeserializeChangesExtra(reader);
var changed = reader.Read<{ChangedFieldsTypeName(cl)}>();
"));
}
BlockSyntax ApplyDeserializeField(BlockSyntax body, GClass cl, GProperty prop, string serverType, bool isObject)
{
var changedFieldsType = ChangedFieldsTypeName(cl);
var code = "";
if (prop.Animated)
{
code = $@"
if((changed & {changedFieldsType}.{prop.Name}Animated) == {changedFieldsType}.{prop.Name}Animated)
{PropertyBackingFieldName(prop)}.SetAnimation(this, commitedAt, reader.ReadObject<IAnimationInstance>(), {ServerPropertyOffsetFieldName(prop)});
else ";
}
var readValueCode = $"reader.Read{(isObject ? "Object" : "")}<{serverType}>()";
code += $@"
if((changed & {changedFieldsType}.{prop.Name}) == {changedFieldsType}.{prop.Name})
";
if (prop.Animated)
code += $"{PropertyBackingFieldName(prop)}.SetValue(this, {readValueCode});";
else code += $"{prop.Name} = {readValueCode};";
return body.AddStatements(ParseStatement(code));
}
ClassDeclarationSyntax WithGetProperty(ClassDeclarationSyntax cl, BlockSyntax body, bool server)
ClassDeclarationSyntax WithGetPropertyForAnimation(ClassDeclarationSyntax cl, BlockSyntax body)
{
if (body.Statements.Count == 0)
return cl;
body = body.AddStatements(
ParseStatement("return base.GetPropertyForAnimation(name);"));
var method = ((MethodDeclarationSyntax) ParseMemberDeclaration(
$"{(server ? "public" : "internal")} override Avalonia.Rendering.Composition.Expressions.ExpressionVariant GetPropertyForAnimation(string name){{}}"))
$"public override Avalonia.Rendering.Composition.Expressions.ExpressionVariant GetPropertyForAnimation(string name){{}}"))
.WithBody(body);
return cl.AddMembers(method);

2
src/Avalonia.X11/X11Platform.cs

@ -104,7 +104,7 @@ namespace Avalonia.X11
}
if (options.UseCompositor)
Compositor = Compositor.Create(AvaloniaLocator.Current.GetService<IRenderLoop>()!);
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IRenderLoop>()!);
}

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -188,7 +188,7 @@ namespace Avalonia.Win32
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
if (Options.UseCompositor)
Compositor = Compositor.Create(AvaloniaLocator.Current.GetRequiredService<IRenderLoop>());
Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService<IRenderLoop>());
}
public bool HasMessages()

45
tests/Avalonia.Base.UnitTests/Composition/BatchStreamTests.cs

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks.Dataflow;
using Avalonia.Rendering.Composition.Transport;
using Xunit;
namespace Avalonia.Base.UnitTests.Composition;
public class BatchStreamTests
{
[Fact]
public void BatchStreamCorrectlyWritesAndReadsData()
{
var data = new BatchStreamData();
var memPool = new BatchStreamMemoryPool(100, _ => { });
var objPool = new BatchStreamObjectPool<object>(10, _ => { });
var guids = new List<Guid>();
var objects = new List<object>();
for (var c = 0; c < 453; c++)
{
guids.Add(Guid.NewGuid());
objects.Add(new object());
}
using (var writer = new BatchStreamWriter(data, memPool, objPool))
{
foreach(var guid in guids)
writer.Write(guid);
foreach (var obj in objects)
writer.WriteObject(obj);
}
using (var reader = new BatchStreamReader(data, memPool, objPool))
{
foreach (var guid in guids)
Assert.Equal(guid, reader.Read<Guid>());
foreach (var obj in objects)
Assert.Equal(obj, reader.ReadObject());
}
}
}
Loading…
Cancel
Save