using System; using System.Collections; using System.Collections.Generic; using Avalonia.Logging; namespace Avalonia.Layout { internal class LayoutQueue : IReadOnlyCollection, IDisposable where T : notnull { private struct Info { public bool Active; public int Count; } public LayoutQueue(Func shouldEnqueue) { _shouldEnqueue = shouldEnqueue; } private readonly Func _shouldEnqueue; private readonly Queue _inner = new Queue(); private readonly Dictionary _loopQueueInfo = new Dictionary(); private readonly List> _notFinalizedBuffer = new List>(); private int _maxEnqueueCountPerLoop = 1; public int Count => _inner.Count; public IEnumerator GetEnumerator() => (_inner as IEnumerable).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); public T Dequeue() { var result = _inner.Dequeue(); if (_loopQueueInfo.TryGetValue(result, out var info)) { info.Active = false; _loopQueueInfo[result] = info; } return result; } public void Enqueue(T item) { _loopQueueInfo.TryGetValue(item, out var info); if (!info.Active) { if (info.Count < _maxEnqueueCountPerLoop) { _inner.Enqueue(item); _loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 }; } else { Logger.TryGet(LogEventLevel.Warning, LogArea.Layout)?.Log( this, "Layout cycle detected. Item {Item} was enqueued {Count} times.", item, info.Count); } } } public void BeginLoop(int maxEnqueueCountPerLoop) { _maxEnqueueCountPerLoop = maxEnqueueCountPerLoop; } public void EndLoop() { foreach (KeyValuePair info in _loopQueueInfo) { if (info.Value.Count >= _maxEnqueueCountPerLoop) { _notFinalizedBuffer.Add(info); } } _loopQueueInfo.Clear(); // Prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle // one more time as a final attempt. foreach (var item in _notFinalizedBuffer) { if (_shouldEnqueue(item.Key)) { _loopQueueInfo[item.Key] = new Info() { Active = true, Count = 0 }; _inner.Enqueue(item.Key); } } _notFinalizedBuffer.Clear(); } public void Dispose() { _inner.Clear(); _loopQueueInfo.Clear(); _notFinalizedBuffer.Clear(); } } }