perf: significant performance optimizations on ordered behaviour collectors by using a sorted dictionary

This commit is contained in:
2025-10-13 09:58:58 +03:00
parent 9ccf7b754d
commit c7d170fad9
9 changed files with 152 additions and 141 deletions

View File

@@ -3,77 +3,61 @@ using System.Collections.Generic;
namespace Engine.Core; namespace Engine.Core;
public class ActiveBehaviourCollectorOrdered<T> : ActiveBehaviourCollector<T> where T : class, IBehaviour public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCollector<TItem> where TItem : class, IBehaviour where TIndex : IComparable
{ {
private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!; private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!;
private readonly LinkedList<T> behaviours = new(); private readonly SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
private readonly List<T> sortList = [];
private IComparer<T>? _sortBy = null; private readonly Func<TItem, TIndex> getIndexFunc = null!;
public IComparer<T>? SortBy private readonly IComparer<TIndex> sortBy = null!;
{
get => _sortBy;
set
{
_sortBy = value;
if (value is not null) private int count = 0;
Sort(value); public override int Count => count;
}
}
public override int Count => behaviours.Count; public override TItem this[Index index]
public override T this[Index index]
{ {
get get
{ {
int actualIndex = index.IsFromEnd int actualIndex = index.IsFromEnd
? behaviours.Count - index.Value ? count - index.Value
: index.Value; : index.Value;
if (actualIndex < 0 || actualIndex >= behaviours.Count) if (actualIndex < 0 || actualIndex >= count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
int currentIndex = 0; int leftIndex = actualIndex;
foreach (T item in behaviours) foreach ((TIndex i, FastList<TItem> list) in behaviours)
if (currentIndex++ == actualIndex) {
return item; if (leftIndex < list.Count)
return list[leftIndex];
leftIndex -= list.Count;
}
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
} }
} }
protected override bool RemoveBehaviour(T tBehaviour) => behaviours.Remove(tBehaviour); protected override bool RemoveBehaviour(TItem tBehaviour)
protected override void AddBehaviour(T behaviour)
{ {
if (SortBy is null) TIndex index = getIndexFunc(tBehaviour);
{ if (!behaviours.TryGetValue(index, out FastList<TItem>? list))
behaviours.AddLast(behaviour); throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
return;
}
LinkedListNode<T>? insertionNode = FindInsertionNodeFor(behaviour, SortBy); if (!list.Remove(tBehaviour))
return false;
if (insertionNode is not null) count--;
behaviours.AddAfter(insertionNode, behaviour); return true;
else
behaviours.AddLast(behaviour);
} }
private LinkedListNode<T>? FindInsertionNodeFor(T behaviour, IComparer<T> sortBy) protected override void AddBehaviour(TItem behaviour)
{ {
LinkedListNode<T>? node = behaviours.First; TIndex key = getIndexFunc(behaviour);
if (!behaviours.TryGetValue(key, out FastList<TItem>? list))
behaviours[key] = list = [];
while (node is not null) count++;
{ list.Add(behaviour);
int compareValue = sortBy.Compare(node.Value, behaviour);
if (compareValue < 0)
return node.Previous;
node = node.Next;
}
return null;
} }
protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged); protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged);
@@ -81,23 +65,40 @@ public class ActiveBehaviourCollectorOrdered<T> : ActiveBehaviourCollector<T> wh
private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args) private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{ {
T behaviour = (T)sender; TItem behaviour = (TItem)sender;
behaviours.Remove(behaviour); RemoveBehaviour(behaviour);
AddBehaviour(behaviour); AddBehaviour(behaviour);
} }
private void Sort(IComparer<T> comparer) public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
{
sortList.Sort(comparer);
behaviours.Clear();
foreach (T item in sortList)
behaviours.AddLast(item);
}
public ActiveBehaviourCollectorOrdered() => delegateOnPriorityChanged = OnPriorityChanged;
public ActiveBehaviourCollectorOrdered(IUniverse universe, Comparison<T> sortBy) : base(universe)
{ {
delegateOnPriorityChanged = OnPriorityChanged; delegateOnPriorityChanged = OnPriorityChanged;
SortBy = Comparer<T>.Create(sortBy); this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
}
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
}
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
{
this.getIndexFunc = getIndexFunc;
delegateOnPriorityChanged = OnPriorityChanged;
this.sortBy = sortBy;
behaviours = new(sortBy);
}
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = sortBy;
behaviours = new(sortBy);
} }
} }

View File

@@ -3,76 +3,61 @@ using System.Collections.Generic;
namespace Engine.Core; namespace Engine.Core;
public class BehaviourCollectorOrdered<T> : BehaviourCollectorBase<T> where T : class public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<TItem> where TItem : class where TIndex : IComparable
{ {
private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!; private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!;
private readonly LinkedList<T> behaviours = new(); private readonly SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
private readonly List<T> sortList = [];
private IComparer<T>? _sortBy = null; private readonly Func<TItem, TIndex> getIndexFunc = null!;
public IComparer<T>? SortBy private readonly IComparer<TIndex> sortBy = null!;
{
get => _sortBy;
set
{
_sortBy = value;
if (value is not null) private int count = 0;
Sort(value); public override int Count => count;
}
}
public override int Count => behaviours.Count; public override TItem this[Index index]
public override T this[Index index]
{ {
get get
{ {
int actualIndex = index.IsFromEnd int actualIndex = index.IsFromEnd
? behaviours.Count - index.Value ? count - index.Value
: index.Value; : index.Value;
if (actualIndex < 0 || actualIndex >= behaviours.Count) if (actualIndex < 0 || actualIndex >= count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
int currentIndex = 0; int leftIndex = actualIndex;
foreach (T item in behaviours) foreach ((TIndex i, FastList<TItem> list) in behaviours)
if (currentIndex++ == actualIndex) {
return item; if (leftIndex < list.Count)
return list[leftIndex];
leftIndex -= list.Count;
}
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
} }
} }
protected override bool RemoveBehaviour(T tBehaviour) => behaviours.Remove(tBehaviour); protected override bool RemoveBehaviour(TItem tBehaviour)
protected override void AddBehaviour(T behaviour)
{ {
if (SortBy is null) TIndex index = getIndexFunc(tBehaviour);
{ if (!behaviours.TryGetValue(index, out FastList<TItem>? list))
behaviours.AddLast(behaviour); throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
return;
}
LinkedListNode<T>? insertionNode = FindInsertionNodeFor(behaviour, SortBy); if (!list.Remove(tBehaviour))
return false;
if (insertionNode is not null) count--;
behaviours.AddAfter(insertionNode, behaviour); return true;
else
behaviours.AddLast(behaviour);
} }
private LinkedListNode<T>? FindInsertionNodeFor(T behaviour, IComparer<T> sortBy) protected override void AddBehaviour(TItem behaviour)
{ {
LinkedListNode<T>? node = behaviours.First; TIndex key = getIndexFunc(behaviour);
if (!behaviours.TryGetValue(key, out FastList<TItem>? list))
behaviours[key] = list = [];
while (node is not null) count++;
{ list.Add(behaviour);
int compareValue = sortBy.Compare(node.Value, behaviour);
if (compareValue < 0)
return node.Previous;
node = node.Next;
}
return null;
} }
protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged); protected override void OnBehaviourAdd(IBehaviour behaviour) => behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged);
@@ -80,23 +65,40 @@ public class BehaviourCollectorOrdered<T> : BehaviourCollectorBase<T> where T :
private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args) private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{ {
T behaviour = (T)sender; TItem behaviour = (TItem)sender;
behaviours.Remove(behaviour); RemoveBehaviour(behaviour);
AddBehaviour(behaviour); AddBehaviour(behaviour);
} }
private void Sort(IComparer<T> comparer) public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
{
sortList.Sort(comparer);
behaviours.Clear();
foreach (T item in sortList)
behaviours.AddLast(item);
}
public BehaviourCollectorOrdered() => delegateOnPriorityChanged = OnPriorityChanged;
public BehaviourCollectorOrdered(IUniverse universe, Comparison<T> sortBy) : base(universe)
{ {
delegateOnPriorityChanged = OnPriorityChanged; delegateOnPriorityChanged = OnPriorityChanged;
SortBy = Comparer<T>.Create(sortBy); this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
}
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = Comparer<TIndex>.Create(sortBy);
behaviours = new(this.sortBy);
}
public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
{
this.getIndexFunc = getIndexFunc;
delegateOnPriorityChanged = OnPriorityChanged;
this.sortBy = sortBy;
behaviours = new(sortBy);
}
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
this.getIndexFunc = getIndexFunc;
this.sortBy = sortBy;
behaviours = new(sortBy);
} }
} }

View File

@@ -5,11 +5,12 @@ namespace Engine.Core;
public class DrawManager : Behaviour public class DrawManager : Behaviour
{ {
// We use Descending order because draw calls are running from last to first // We use Descending order because draw calls are running from last to first
private static Comparer<IBehaviour> SortByDescendingPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority)); private static Comparer<int> SortByDescendingPriority() => Comparer<int>.Create((x, y) => y.CompareTo(x));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorOrdered<IPreDraw> preDrawEntities = new() { SortBy = SortByDescendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IPreDraw> preDrawEntities = new(GetPriority(), SortByDescendingPriority());
private readonly ActiveBehaviourCollectorOrdered<IDraw> drawEntities = new() { SortBy = SortByDescendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IDraw> drawEntities = new(GetPriority(), SortByDescendingPriority());
private readonly ActiveBehaviourCollectorOrdered<IPostDraw> postDrawEntities = new() { SortBy = SortByDescendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IPostDraw> postDrawEntities = new(GetPriority(), SortByDescendingPriority());
private void OnPreDraw(IUniverse sender) private void OnPreDraw(IUniverse sender)
{ {

View File

@@ -5,10 +5,11 @@ namespace Engine.Core;
public class UniverseEntranceManager : Behaviour public class UniverseEntranceManager : Behaviour
{ {
// We use Ascending order because we are using reverse for loop to call them // We use Ascending order because we are using reverse for loop to call them
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorOrdered<IEnterUniverse> enterUniverses = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IEnterUniverse> enterUniverses = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollectorOrdered<IExitUniverse> exitUniverses = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IExitUniverse> exitUniverses = new(GetPriority(), SortByAscendingPriority());
private readonly List<IEnterUniverse> toCallEnterUniverses = new(32); private readonly List<IEnterUniverse> toCallEnterUniverses = new(32);
private readonly List<IExitUniverse> toCallExitUniverses = new(32); private readonly List<IExitUniverse> toCallExitUniverses = new(32);

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Engine.Core; namespace Engine.Core;
@@ -6,13 +5,14 @@ namespace Engine.Core;
public class UpdateManager : Behaviour public class UpdateManager : Behaviour
{ {
// We use Ascending order because we are using reverse for loop to call them // We use Ascending order because we are using reverse for loop to call them
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorOrdered<IFirstFrameUpdate> firstFrameUpdates = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IFirstFrameUpdate> firstFrameUpdates = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollector<ILastFrameUpdate> lastFrameUpdates = new(); private readonly ActiveBehaviourCollector<ILastFrameUpdate> lastFrameUpdates = new();
private readonly ActiveBehaviourCollectorOrdered<IPreUpdate> preUpdateEntities = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IPreUpdate> preUpdateEntities = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollectorOrdered<IUpdate> updateEntities = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IUpdate> updateEntities = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollectorOrdered<IPostUpdate> postUpdateEntities = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IPostUpdate> postUpdateEntities = new(GetPriority(), SortByAscendingPriority());
private readonly List<IFirstFrameUpdate> toCallFirstFrameUpdates = new(32); private readonly List<IFirstFrameUpdate> toCallFirstFrameUpdates = new(32);

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Engine.Core; using Engine.Core;
namespace Engine.Integration.MonoGame; namespace Engine.Integration.MonoGame;
@@ -6,9 +7,10 @@ namespace Engine.Integration.MonoGame;
public class LoadContentManager : Behaviour, IFirstFrameUpdate public class LoadContentManager : Behaviour, IFirstFrameUpdate
{ {
// We use Ascending order because we are using reverse for loop to call them // We use Ascending order because we are using reverse for loop to call them
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorOrdered<ILoadContent> loadContents = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, ILoadContent> loadContents = new(GetPriority(), SortByAscendingPriority());
private readonly List<ILoadContent> toCallLoadContents = new(32); private readonly List<ILoadContent> toCallLoadContents = new(32);
private MonoGameWindowContainer monoGameWindowContainer = null!; private MonoGameWindowContainer monoGameWindowContainer = null!;

View File

@@ -6,12 +6,13 @@ namespace Engine.Integration.MonoGame;
public class SpriteBatcher : BehaviourBase, IFirstFrameUpdate, IDraw public class SpriteBatcher : BehaviourBase, IFirstFrameUpdate, IDraw
{ {
private static Comparer<IBehaviour> SortByPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority)); private static Comparer<int> SortByPriority() => Comparer<int>.Create((x, y) => y.CompareTo(x));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private ISpriteBatch spriteBatch = null!; private ISpriteBatch spriteBatch = null!;
private MonoGameCamera2D camera2D = null!; private MonoGameCamera2D camera2D = null!;
private readonly ActiveBehaviourCollectorOrdered<IDrawableSprite> drawableSprites = new() { SortBy = SortByPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IDrawableSprite> drawableSprites = new(GetPriority(), SortByPriority());
public void FirstActiveFrame() public void FirstActiveFrame()
{ {

View File

@@ -8,11 +8,13 @@ namespace Engine.Integration.MonoGame;
public class TriangleBatcher : BehaviourBase, ITriangleBatch, IFirstFrameUpdate, IDraw public class TriangleBatcher : BehaviourBase, ITriangleBatch, IFirstFrameUpdate, IDraw
{ {
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private TriangleBatch triangleBatch = null!; private TriangleBatch triangleBatch = null!;
private MonoGameCamera2D camera2D = null!; private MonoGameCamera2D camera2D = null!;
private readonly ActiveBehaviourCollectorOrdered<IDrawableTriangle> drawableShapes = new() { SortBy = SortByAscendingPriority() };
private readonly ActiveBehaviourCollectorOrdered<int, IDrawableTriangle> drawableShapes = new(GetPriority(), SortByAscendingPriority());
public void FirstActiveFrame() public void FirstActiveFrame()
{ {

View File

@@ -17,11 +17,12 @@ public class PhysicsEngine2D : Behaviour, IPreUpdate, IPhysicsEngine2D
protected readonly ICollisionResolver2D collisionResolver = null!; protected readonly ICollisionResolver2D collisionResolver = null!;
protected readonly IRaycastResolver2D raycastResolver = null!; protected readonly IRaycastResolver2D raycastResolver = null!;
private static Comparer<IBehaviour> SortByPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority)); private static Comparer<int> SortByPriority() => Comparer<int>.Create((x, y) => y.CompareTo(x));
protected ActiveBehaviourCollectorOrdered<IPrePhysicsUpdate> physicsPreUpdateCollector = new() { SortBy = SortByPriority() }; private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
protected ActiveBehaviourCollectorOrdered<IPhysicsUpdate> physicsUpdateCollector = new() { SortBy = SortByPriority() }; protected ActiveBehaviourCollectorOrdered<int, IPrePhysicsUpdate> physicsPreUpdateCollector = new(GetPriority(), SortByPriority());
protected ActiveBehaviourCollectorOrdered<IPhysicsIteration> physicsIterationCollector = new() { SortBy = SortByPriority() }; protected ActiveBehaviourCollectorOrdered<int, IPhysicsUpdate> physicsUpdateCollector = new(GetPriority(), SortByPriority());
protected ActiveBehaviourCollectorOrdered<IPostPhysicsUpdate> physicsPostUpdateCollector = new() { SortBy = SortByPriority() }; protected ActiveBehaviourCollectorOrdered<int, IPhysicsIteration> physicsIterationCollector = new(GetPriority(), SortByPriority());
protected ActiveBehaviourCollectorOrdered<int, IPostPhysicsUpdate> physicsPostUpdateCollector = new(GetPriority(), SortByPriority());
protected BehaviourCollector<IRigidBody2D> rigidBodyCollector = new(); protected BehaviourCollector<IRigidBody2D> rigidBodyCollector = new();
protected BehaviourCollector<ICollider2D> colliderCollector = new(); protected BehaviourCollector<ICollider2D> colliderCollector = new();