From 381deca468ad330dd2ce68492b5d186de3162268 Mon Sep 17 00:00:00 2001 From: andreakarasho Date: Mon, 26 Feb 2024 01:06:35 +0100 Subject: [PATCH] experimental parse archetype optimization --- samples/MyBattleground/Program.cs | 54 ++++++-- src/World.cs | 219 +++++++++++++++++++++++++++--- 2 files changed, 242 insertions(+), 31 deletions(-) diff --git a/samples/MyBattleground/Program.cs b/samples/MyBattleground/Program.cs index c6f18b3..6a6cbab 100644 --- a/samples/MyBattleground/Program.cs +++ b/samples/MyBattleground/Program.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System; using TinyEcs; +using System.Runtime.CompilerServices; const int ENTITIES_COUNT = (524_288 * 2 * 1); @@ -18,6 +19,7 @@ }); + var e2 = ecs.Entity("Main"); ref var pp = ref e2.Get(); @@ -73,19 +75,15 @@ e.Delete(); -ecs.Entity() - .Set(new Position()) - .Set(new Velocity()); - -ecs.Entity() - .Set(new Position()) - .Set(new Velocity()); for (int i = 0; i < ENTITIES_COUNT; i++) ecs.Entity() .Set(new Position()) .Set(new Velocity()) - .Set(); + .Set() + .Set() + .Set() + ; var sw = Stopwatch.StartNew(); var start = 0f; @@ -96,12 +94,40 @@ //var cur = (start - last) / 1000f; for (int i = 0; i < 3600; ++i) { - ecs.Filter>() - .Query((ref Position pos, ref Velocity vel) => - { - pos.X *= vel.X; - pos.Y *= vel.Y; - }); + // ecs.Filter>() + // .Query((ref Position pos, ref Velocity vel) => + // { + // pos.X *= vel.X; + // pos.Y *= vel.Y; + // }); + + ecs.System((ref Position pos , ref Velocity vel) => { + pos.X *= vel.X; + pos.Y *= vel.Y; + }); + + // foreach (var archetype in ecs.Query2<(Position, Velocity)>()) + // { + // var column0 = archetype.GetComponentIndex(); + // var column1 = archetype.GetComponentIndex(); + + // foreach (ref readonly var chunk in archetype) + // { + // ref var pos = ref chunk.GetReference(column0); + // ref var vel = ref chunk.GetReference(column1); + + // ref var last2 = ref Unsafe.Add(ref pos, chunk.Count); + + // while (Unsafe.IsAddressLessThan(ref pos, ref last2)) + // { + // pos.X *= vel.X; + // pos.Y *= vel.Y; + + // pos = ref Unsafe.Add(ref pos, 1); + // vel = ref Unsafe.Add(ref vel, 1); + // } + // } + // } // foreach (var archetype in ecs.Filter<(Position, Velocity)>()) // { diff --git a/src/World.cs b/src/World.cs index db1400c..bdf106a 100644 --- a/src/World.cs +++ b/src/World.cs @@ -13,6 +13,7 @@ public sealed partial class World : IDisposable private readonly DictionarySlim _typeIndex = new(); private readonly Dictionary _entityNames = new(); private Archetype[] _archetypes = new Archetype[16]; + private Dictionary _cachedArchetypesMatch = new(); private int _archetypeCount; private readonly ComponentComparer _comparer; private readonly Commands _commands; @@ -55,6 +56,7 @@ public void Dispose() _typeIndex.Clear(); _commands.Clear(); _entityNames.Clear(); + _cachedArchetypesMatch.Clear(); Array.Clear(_archetypes, 0, _archetypeCount); _archetypeCount = 0; @@ -346,32 +348,113 @@ public FilterQuery Filter(ReadOnlySpan terms) } public IQueryConstruct QueryBuilder() => new QueryBuilder(this); -} -public readonly ref struct QueryInternal -{ - private readonly ReadOnlySpan _archetypes; - private readonly ReadOnlySpan _terms; + public QueryIterator2 Query2() where TFilter : struct + { + var hash = Lookup.Query.Hash; - internal QueryInternal(ReadOnlySpan archetypes, ReadOnlySpan terms) + if (!_cachedArchetypesMatch.TryGetValue(hash, out var list)) + { + list = new ArchetypeList(_archRoot); + list.Renew(Lookup.Query.Terms.AsSpan()); + + _cachedArchetypesMatch.Add(hash, list); + } + + return new QueryIterator2(list); + } + + public void System(QueryFilterDelegate fn) where T0 : struct where T1 : struct { - _archetypes = archetypes; - _terms = terms; + var hash = Lookup.Query<(T0, T1)>.Hash; + var terms = Lookup.Query<(T0, T1)>.Terms.AsSpan(); + + if (!_typeIndex.TryGetValue(hash, out var arch)) + { + arch = FindFirst(_archRoot, terms); + } + + if (arch == null) + return; + + InternalQuery(arch, fn, terms); + + static void InternalQuery(Archetype root, QueryFilterDelegate fn, ReadOnlySpan terms) + { + if (root.Count > 0) + { + var column0 = root.GetComponentIndex(); + var column1 = root.GetComponentIndex(); + + foreach (ref readonly var chunk in root) + { + ref var c0 = ref chunk.GetReference(column0); + ref var c1 = ref chunk.GetReference(column1); + ref var last = ref Unsafe.Add(ref c0, chunk.Count); + + while (Unsafe.IsAddressLessThan(ref c0, ref last)) + { + fn(ref c0, ref c1); + c0 = ref Unsafe.Add(ref c0, 1); + c1 = ref Unsafe.Add(ref c1, 1); + } + } + } + + var span = CollectionsMarshal.AsSpan(root._edgesRight); + + ref var start = ref MemoryMarshal.GetReference(span); + ref var end = ref Unsafe.Add(ref start, span.Length); + + while (Unsafe.IsAddressLessThan(ref start, ref end)) + { + InternalQuery(start.Archetype, fn, terms); + + start = ref Unsafe.Add(ref start, 1); + } + } } - public QueryIterator GetEnumerator() + static Archetype? FindFirst(Archetype root, ReadOnlySpan terms) { - return new QueryIterator(_archetypes, _terms); + var result = root.FindMatch(terms); + if (result < 0) + { + return null; + } + + if (result == 0) + { + return root; + } + + var span = CollectionsMarshal.AsSpan(root._edgesRight); + if (span.IsEmpty) + return null; + + ref var start = ref MemoryMarshal.GetReference(span); + ref var end = ref Unsafe.Add(ref start, span.Length); + + while (Unsafe.IsAddressLessThan(ref start, ref end)) + { + var res = FindFirst(start.Archetype, terms); + if (res != null) + return res; + + start = ref Unsafe.Add(ref start, 1); + } + + return null; } } -public ref struct QueryIterator +public ref struct QueryInternal { - private readonly ReadOnlySpan _terms; private readonly ReadOnlySpan _archetypes; + private readonly ReadOnlySpan _terms; private int _index; - internal QueryIterator(ReadOnlySpan archetypes, ReadOnlySpan terms) + internal QueryInternal(ReadOnlySpan archetypes, ReadOnlySpan terms) { _archetypes = archetypes; _terms = terms; @@ -394,6 +477,93 @@ public bool MoveNext() } public void Reset() => _index = -1; + + public readonly QueryInternal GetEnumerator() => this; +} + +public ref struct QueryIterator2 +{ + private LinkedListNode _current, _next; + + internal QueryIterator2(ArchetypeList list) + { + _next = list.First!; + _current = list.First!; + } + + public readonly Archetype Current => _current.Value; + + public bool MoveNext() + { + _current = _next; + + while (_next != null) + { + _next = _next.Next!; + + if (_next != null && _next.Value.Count == 0) + continue; + + break; + } + + return _current != null; + } + + public readonly QueryIterator2 GetEnumerator() => this; +} + + +internal sealed class ArchetypeList : LinkedList +{ + private readonly Archetype _root; + internal ArchetypeList(Archetype root) => _root = root; + + + public void Renew() where TFilter : struct + { + Renew(Lookup.Query.Terms.AsSpan()); + } + + public void Renew() + where TQuery : struct where TFilter : struct + { + Renew(Lookup.Query.Terms.AsSpan()); + } + + public void Renew(ReadOnlySpan terms) + { + Clear(); + Find(_root, terms, this); + } + + static void Find(Archetype root, ReadOnlySpan terms, ArchetypeList list) + { + var result = root.FindMatch(terms); + if (result < 0) + { + return; + } + + if (result == 0 && root.Count > 0) + { + list.AddLast(root); + } + + var span = CollectionsMarshal.AsSpan(root._edgesRight); + if (span.IsEmpty) + return; + + ref var start = ref MemoryMarshal.GetReference(span); + ref var end = ref Unsafe.Add(ref start, span.Length); + + while (Unsafe.IsAddressLessThan(ref start, ref end)) + { + Find(start.Archetype, terms, list); + + start = ref Unsafe.Add(ref start, 1); + } + } } public delegate void QueryFilterDelegateWithEntity(EntityView entity); @@ -426,9 +596,9 @@ public void Query(QueryFilterDelegateWithEntity fn) } } - public QueryIterator GetEnumerator() + public QueryInternal GetEnumerator() { - return new QueryIterator(_archetypes, Lookup.Query.Terms.AsSpan()); + return new QueryInternal(_archetypes, Lookup.Query.Terms.AsSpan()); } } @@ -443,9 +613,9 @@ internal FilterQuery(ReadOnlySpan archetypes, ReadOnlySpan term _terms = terms; } - public QueryIterator GetEnumerator() + public QueryInternal GetEnumerator() { - return new QueryIterator(_archetypes, _terms); + return new QueryInternal(_archetypes, _terms); } } @@ -596,10 +766,20 @@ static void ParseType(SortedSet terms) where T : struct EcsAssert.Assert(false, $"type not found {type}"); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static ulong GetHash(ReadOnlySpan terms) + { + var hc = (ulong)terms.Length; + foreach (ref readonly var val in terms) + hc = unchecked(hc * 314159 + val.ID); + return hc; + } + internal static class Query where TQuery : struct where TFilter : struct { public static readonly ImmutableArray Terms; public static readonly ImmutableArray Columns; + public static readonly ulong Hash; static Query() { @@ -609,18 +789,23 @@ static Query() ParseType(list); Terms = list.ToImmutableArray(); + + Hash = GetHash(Terms.AsSpan()); } } internal static class Query where T : struct { public static readonly ImmutableArray Terms; + public static readonly ulong Hash; static Query() { var list = new SortedSet(); ParseType(list); Terms = list.ToImmutableArray(); + + Hash = GetHash(Terms.AsSpan()); } } }