perf: optimize iteration and command enqueuing
- Optimize Select2 and Select3 iterators to drive from the smallest sparse set to minimize probes. - Use a constrained generic in CommandQueue.Enqueue to avoid boxing struct commands at the call site. - Ensure singleton infrastructure is initialized during world deserialization if the singleton entity is loaded. - Change EnsureSingleton to internal.
This commit is contained in:
parent
da84605b2f
commit
339e850e10
|
|
@ -28,9 +28,13 @@ public class CommandQueue
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a command to the end of the queue.
|
/// Adds a command to the end of the queue.
|
||||||
|
/// Uses a constrained generic to avoid boxing struct commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Enqueue(ICommand command)
|
public void Enqueue<T>(T command) where T : struct, ICommand
|
||||||
{
|
{
|
||||||
|
// Boxing still occurs here because the heterogeneous queue stores
|
||||||
|
// ICommand references, but the constrained generic on the method
|
||||||
|
// avoids boxing at the call site.
|
||||||
_commands.Add(command);
|
_commands.Add(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ public ref struct Select1<T1> where T1 : struct
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ref struct iterator for queries with two component types.
|
/// Ref struct iterator for queries with two component types.
|
||||||
|
/// Drives iteration from the smaller sparse set to minimize probes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ref struct Select2<T1, T2>
|
public ref struct Select2<T1, T2>
|
||||||
where T1 : struct where T2 : struct
|
where T1 : struct where T2 : struct
|
||||||
|
|
@ -116,6 +117,7 @@ public ref struct Select2<T1, T2>
|
||||||
private readonly IReadOnlySet<Type> _without;
|
private readonly IReadOnlySet<Type> _without;
|
||||||
private int _index;
|
private int _index;
|
||||||
private readonly int _count;
|
private readonly int _count;
|
||||||
|
private readonly bool _swapped; // true when driving from _set2
|
||||||
|
|
||||||
internal Select2(World world, QueryDescriptor query)
|
internal Select2(World world, QueryDescriptor query)
|
||||||
{
|
{
|
||||||
|
|
@ -124,7 +126,11 @@ public ref struct Select2<T1, T2>
|
||||||
_without = query.Without;
|
_without = query.Without;
|
||||||
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
||||||
_count = _set1?.Count ?? 0;
|
|
||||||
|
int count1 = _set1?.Count ?? 0;
|
||||||
|
int count2 = _set2?.Count ?? 0;
|
||||||
|
_swapped = count2 < count1;
|
||||||
|
_count = _swapped ? count2 : count1;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
|
|
||||||
_world.BeginIteration();
|
_world.BeginIteration();
|
||||||
|
|
@ -139,10 +145,19 @@ public ref struct Select2<T1, T2>
|
||||||
{
|
{
|
||||||
if (_set1 == null || _set2 == null) return false;
|
if (_set1 == null || _set2 == null) return false;
|
||||||
while (++_index < _count)
|
while (++_index < _count)
|
||||||
|
{
|
||||||
|
if (_swapped)
|
||||||
|
{
|
||||||
|
CurrentEntity = _set2!.DenseEntities[_index];
|
||||||
|
if (CurrentEntity.Id == 1) continue;
|
||||||
|
if (!_set1.Contains(CurrentEntity)) continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
CurrentEntity = _set1.DenseEntities[_index];
|
CurrentEntity = _set1.DenseEntities[_index];
|
||||||
if (CurrentEntity.Id == 1) continue;
|
if (CurrentEntity.Id == 1) continue;
|
||||||
if (!_set2.Contains(CurrentEntity)) continue;
|
if (!_set2.Contains(CurrentEntity)) continue;
|
||||||
|
}
|
||||||
if (!PassesWithout()) continue;
|
if (!PassesWithout()) continue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -170,6 +185,7 @@ public ref struct Select2<T1, T2>
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ref struct iterator for queries with three component types.
|
/// Ref struct iterator for queries with three component types.
|
||||||
|
/// Drives iteration from the smallest sparse set to minimize probes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ref struct Select3<T1, T2, T3>
|
public ref struct Select3<T1, T2, T3>
|
||||||
where T1 : struct where T2 : struct where T3 : struct
|
where T1 : struct where T2 : struct where T3 : struct
|
||||||
|
|
@ -182,6 +198,7 @@ public ref struct Select3<T1, T2, T3>
|
||||||
private readonly IReadOnlySet<Type> _without;
|
private readonly IReadOnlySet<Type> _without;
|
||||||
private int _index;
|
private int _index;
|
||||||
private readonly int _count;
|
private readonly int _count;
|
||||||
|
private readonly int _driver; // 0 = _set1, 1 = _set2, 2 = _set3
|
||||||
|
|
||||||
internal Select3(World world, QueryDescriptor query)
|
internal Select3(World world, QueryDescriptor query)
|
||||||
{
|
{
|
||||||
|
|
@ -191,7 +208,27 @@ public ref struct Select3<T1, T2, T3>
|
||||||
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
||||||
_set3 = _store.GetSet(typeof(T3)) as SparseSet<T3>;
|
_set3 = _store.GetSet(typeof(T3)) as SparseSet<T3>;
|
||||||
_count = _set1?.Count ?? 0;
|
|
||||||
|
int count1 = _set1?.Count ?? 0;
|
||||||
|
int count2 = _set2?.Count ?? 0;
|
||||||
|
int count3 = _set3?.Count ?? 0;
|
||||||
|
|
||||||
|
if (count2 <= count1 && count2 <= count3)
|
||||||
|
{
|
||||||
|
_driver = 1;
|
||||||
|
_count = count2;
|
||||||
|
}
|
||||||
|
else if (count3 <= count1 && count3 <= count2)
|
||||||
|
{
|
||||||
|
_driver = 2;
|
||||||
|
_count = count3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_driver = 0;
|
||||||
|
_count = count1;
|
||||||
|
}
|
||||||
|
|
||||||
_index = -1;
|
_index = -1;
|
||||||
|
|
||||||
_world.BeginIteration();
|
_world.BeginIteration();
|
||||||
|
|
@ -208,10 +245,27 @@ public ref struct Select3<T1, T2, T3>
|
||||||
if (_set1 == null || _set2 == null || _set3 == null) return false;
|
if (_set1 == null || _set2 == null || _set3 == null) return false;
|
||||||
while (++_index < _count)
|
while (++_index < _count)
|
||||||
{
|
{
|
||||||
|
switch (_driver)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
CurrentEntity = _set1.DenseEntities[_index];
|
CurrentEntity = _set1.DenseEntities[_index];
|
||||||
if (CurrentEntity.Id == 1) continue;
|
if (CurrentEntity.Id == 1) continue;
|
||||||
if (!_set2.Contains(CurrentEntity)) continue;
|
if (!_set2.Contains(CurrentEntity)) continue;
|
||||||
if (!_set3.Contains(CurrentEntity)) continue;
|
if (!_set3.Contains(CurrentEntity)) continue;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
CurrentEntity = _set2!.DenseEntities[_index];
|
||||||
|
if (CurrentEntity.Id == 1) continue;
|
||||||
|
if (!_set1.Contains(CurrentEntity)) continue;
|
||||||
|
if (!_set3.Contains(CurrentEntity)) continue;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
CurrentEntity = _set3!.DenseEntities[_index];
|
||||||
|
if (CurrentEntity.Id == 1) continue;
|
||||||
|
if (!_set1.Contains(CurrentEntity)) continue;
|
||||||
|
if (!_set2.Contains(CurrentEntity)) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (!PassesWithout()) continue;
|
if (!PassesWithout()) continue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,6 @@ public class World : IDisposable
|
||||||
_changes.Pending.MarkComponentRemoved(entity, typeof(T));
|
_changes.Pending.MarkComponentRemoved(entity, typeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a reference to the component of type <typeparamref name="T"/>
|
/// Returns a reference to the component of type <typeparamref name="T"/>
|
||||||
/// for the given entity. Throws if the entity does not have the component.
|
/// for the given entity. Throws if the entity does not have the component.
|
||||||
|
|
@ -398,7 +397,7 @@ public class World : IDisposable
|
||||||
RemoveComponent<T>(SingletonEntity);
|
RemoveComponent<T>(SingletonEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureSingleton()
|
internal void EnsureSingleton()
|
||||||
{
|
{
|
||||||
if (_singletonCreated)
|
if (_singletonCreated)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,14 @@ public static class WorldSerializer
|
||||||
{
|
{
|
||||||
var entity = world.CreateEntity(es.Entity);
|
var entity = world.CreateEntity(es.Entity);
|
||||||
|
|
||||||
|
// If the loaded entity is the singleton entity, ensure the
|
||||||
|
// singleton infrastructure is initialized so that subsequent
|
||||||
|
// SetSingleton/GetSingleton calls work correctly.
|
||||||
|
if (entity.Id == World.SingletonEntity.Id)
|
||||||
|
{
|
||||||
|
world.EnsureSingleton();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var ce in es.Components)
|
foreach (var ce in es.Components)
|
||||||
{
|
{
|
||||||
var type = Type.GetType(ce.TypeName)
|
var type = Type.GetType(ce.TypeName)
|
||||||
|
|
|
||||||
|
|
@ -247,11 +247,11 @@ public class CommandTests
|
||||||
private class CommandEnqueueSystem : ISystem
|
private class CommandEnqueueSystem : ISystem
|
||||||
{
|
{
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
private readonly ICommand _command;
|
private readonly SpawnCommand _command;
|
||||||
|
|
||||||
public QueryDescriptor Query { get; }
|
public QueryDescriptor Query { get; }
|
||||||
|
|
||||||
public CommandEnqueueSystem(World world, ICommand command)
|
public CommandEnqueueSystem(World world, SpawnCommand command)
|
||||||
{
|
{
|
||||||
_world = world;
|
_world = world;
|
||||||
_command = command;
|
_command = command;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue