Skip to content

Commit

Permalink
experimental parse archetype optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
andreakarasho committed Feb 26, 2024
1 parent f04cada commit 381deca
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 31 deletions.
54 changes: 40 additions & 14 deletions samples/MyBattleground/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Diagnostics;
using System;
using TinyEcs;
using System.Runtime.CompilerServices;

const int ENTITIES_COUNT = (524_288 * 2 * 1);

Expand All @@ -18,6 +19,7 @@
});



var e2 = ecs.Entity("Main");
ref var pp = ref e2.Get<Position>();

Expand Down Expand Up @@ -73,19 +75,15 @@

e.Delete();

ecs.Entity()
.Set<Position>(new Position())
.Set<Velocity>(new Velocity());

ecs.Entity()
.Set<Position>(new Position())
.Set<Velocity>(new Velocity());

for (int i = 0; i < ENTITIES_COUNT; i++)
ecs.Entity()
.Set<Position>(new Position())
.Set<Velocity>(new Velocity())
.Set<PlayerTag>();
.Set<PlayerTag>()
.Set<Dogs>()
.Set<Likes>()
;

var sw = Stopwatch.StartNew();
var start = 0f;
Expand All @@ -96,12 +94,40 @@
//var cur = (start - last) / 1000f;
for (int i = 0; i < 3600; ++i)
{
ecs.Filter<With<PlayerTag>>()
.Query((ref Position pos, ref Velocity vel) =>
{
pos.X *= vel.X;
pos.Y *= vel.Y;
});
// ecs.Filter<With<PlayerTag>>()
// .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<Position>();
// var column1 = archetype.GetComponentIndex<Velocity>();

// foreach (ref readonly var chunk in archetype)
// {
// ref var pos = ref chunk.GetReference<Position>(column0);
// ref var vel = ref chunk.GetReference<Velocity>(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)>())
// {
Expand Down
219 changes: 202 additions & 17 deletions src/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public sealed partial class World : IDisposable
private readonly DictionarySlim<ulong, Archetype> _typeIndex = new();
private readonly Dictionary<string, EcsID> _entityNames = new();
private Archetype[] _archetypes = new Archetype[16];
private Dictionary<ulong, ArchetypeList> _cachedArchetypesMatch = new();
private int _archetypeCount;
private readonly ComponentComparer _comparer;
private readonly Commands _commands;
Expand Down Expand Up @@ -55,6 +56,7 @@ public void Dispose()
_typeIndex.Clear();
_commands.Clear();
_entityNames.Clear();
_cachedArchetypesMatch.Clear();

Array.Clear(_archetypes, 0, _archetypeCount);
_archetypeCount = 0;
Expand Down Expand Up @@ -346,32 +348,113 @@ public FilterQuery Filter(ReadOnlySpan<Term> terms)
}

public IQueryConstruct QueryBuilder() => new QueryBuilder(this);
}

public readonly ref struct QueryInternal
{
private readonly ReadOnlySpan<Archetype> _archetypes;
private readonly ReadOnlySpan<Term> _terms;
public QueryIterator2 Query2<TFilter>() where TFilter : struct
{
var hash = Lookup.Query<TFilter>.Hash;

internal QueryInternal(ReadOnlySpan<Archetype> archetypes, ReadOnlySpan<Term> terms)
if (!_cachedArchetypesMatch.TryGetValue(hash, out var list))
{
list = new ArchetypeList(_archRoot);
list.Renew(Lookup.Query<TFilter>.Terms.AsSpan());

_cachedArchetypesMatch.Add(hash, list);
}

return new QueryIterator2(list);
}

public void System<T0, T1>(QueryFilterDelegate<T0, T1> 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<T0, T1> fn, ReadOnlySpan<Term> terms)
{
if (root.Count > 0)
{
var column0 = root.GetComponentIndex<T0>();
var column1 = root.GetComponentIndex<T1>();

foreach (ref readonly var chunk in root)
{
ref var c0 = ref chunk.GetReference<T0>(column0);
ref var c1 = ref chunk.GetReference<T1>(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<Term> 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<Term> _terms;
private readonly ReadOnlySpan<Archetype> _archetypes;
private readonly ReadOnlySpan<Term> _terms;
private int _index;

internal QueryIterator(ReadOnlySpan<Archetype> archetypes, ReadOnlySpan<Term> terms)
internal QueryInternal(ReadOnlySpan<Archetype> archetypes, ReadOnlySpan<Term> terms)
{
_archetypes = archetypes;
_terms = terms;
Expand All @@ -394,6 +477,93 @@ public bool MoveNext()
}

public void Reset() => _index = -1;

public readonly QueryInternal GetEnumerator() => this;
}

public ref struct QueryIterator2
{
private LinkedListNode<Archetype> _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<Archetype>
{
private readonly Archetype _root;
internal ArchetypeList(Archetype root) => _root = root;


public void Renew<TFilter>() where TFilter : struct
{
Renew(Lookup.Query<TFilter>.Terms.AsSpan());
}

public void Renew<TQuery, TFilter>()
where TQuery : struct where TFilter : struct
{
Renew(Lookup.Query<TQuery, TFilter>.Terms.AsSpan());
}

public void Renew(ReadOnlySpan<Term> terms)
{
Clear();
Find(_root, terms, this);
}

static void Find(Archetype root, ReadOnlySpan<Term> 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);
Expand Down Expand Up @@ -426,9 +596,9 @@ public void Query(QueryFilterDelegateWithEntity fn)
}
}

public QueryIterator GetEnumerator()
public QueryInternal GetEnumerator()
{
return new QueryIterator(_archetypes, Lookup.Query<TFilter>.Terms.AsSpan());
return new QueryInternal(_archetypes, Lookup.Query<TFilter>.Terms.AsSpan());
}
}

Expand All @@ -443,9 +613,9 @@ internal FilterQuery(ReadOnlySpan<Archetype> archetypes, ReadOnlySpan<Term> term
_terms = terms;
}

public QueryIterator GetEnumerator()
public QueryInternal GetEnumerator()
{
return new QueryIterator(_archetypes, _terms);
return new QueryInternal(_archetypes, _terms);
}
}

Expand Down Expand Up @@ -596,10 +766,20 @@ static void ParseType<T>(SortedSet<Term> terms) where T : struct
EcsAssert.Assert(false, $"type not found {type}");
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static ulong GetHash(ReadOnlySpan<Term> 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<TQuery, TFilter> where TQuery : struct where TFilter : struct
{
public static readonly ImmutableArray<Term> Terms;
public static readonly ImmutableArray<Term> Columns;
public static readonly ulong Hash;

static Query()
{
Expand All @@ -609,18 +789,23 @@ static Query()

ParseType<TFilter>(list);
Terms = list.ToImmutableArray();

Hash = GetHash(Terms.AsSpan());
}
}

internal static class Query<T> where T : struct
{
public static readonly ImmutableArray<Term> Terms;
public static readonly ulong Hash;

static Query()
{
var list = new SortedSet<Term>();
ParseType<T>(list);
Terms = list.ToImmutableArray();

Hash = GetHash(Terms.AsSpan());
}
}
}
Expand Down

0 comments on commit 381deca

Please sign in to comment.