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>
|
||||
/// Adds a command to the end of the queue.
|
||||
/// Uses a constrained generic to avoid boxing struct commands.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ public ref struct Select1<T1> where T1 : struct
|
|||
|
||||
/// <summary>
|
||||
/// Ref struct iterator for queries with two component types.
|
||||
/// Drives iteration from the smaller sparse set to minimize probes.
|
||||
/// </summary>
|
||||
public ref struct Select2<T1, T2>
|
||||
where T1 : struct where T2 : struct
|
||||
|
|
@ -116,6 +117,7 @@ public ref struct Select2<T1, T2>
|
|||
private readonly IReadOnlySet<Type> _without;
|
||||
private int _index;
|
||||
private readonly int _count;
|
||||
private readonly bool _swapped; // true when driving from _set2
|
||||
|
||||
internal Select2(World world, QueryDescriptor query)
|
||||
{
|
||||
|
|
@ -124,7 +126,11 @@ public ref struct Select2<T1, T2>
|
|||
_without = query.Without;
|
||||
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||
_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;
|
||||
|
||||
_world.BeginIteration();
|
||||
|
|
@ -140,9 +146,18 @@ public ref struct Select2<T1, T2>
|
|||
if (_set1 == null || _set2 == null) return false;
|
||||
while (++_index < _count)
|
||||
{
|
||||
CurrentEntity = _set1.DenseEntities[_index];
|
||||
if (CurrentEntity.Id == 1) continue;
|
||||
if (!_set2.Contains(CurrentEntity)) continue;
|
||||
if (_swapped)
|
||||
{
|
||||
CurrentEntity = _set2!.DenseEntities[_index];
|
||||
if (CurrentEntity.Id == 1) continue;
|
||||
if (!_set1.Contains(CurrentEntity)) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentEntity = _set1.DenseEntities[_index];
|
||||
if (CurrentEntity.Id == 1) continue;
|
||||
if (!_set2.Contains(CurrentEntity)) continue;
|
||||
}
|
||||
if (!PassesWithout()) continue;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -170,6 +185,7 @@ public ref struct Select2<T1, T2>
|
|||
|
||||
/// <summary>
|
||||
/// Ref struct iterator for queries with three component types.
|
||||
/// Drives iteration from the smallest sparse set to minimize probes.
|
||||
/// </summary>
|
||||
public ref struct Select3<T1, T2, T3>
|
||||
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 int _index;
|
||||
private readonly int _count;
|
||||
private readonly int _driver; // 0 = _set1, 1 = _set2, 2 = _set3
|
||||
|
||||
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>;
|
||||
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
||||
_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;
|
||||
|
||||
_world.BeginIteration();
|
||||
|
|
@ -208,10 +245,27 @@ public ref struct Select3<T1, T2, T3>
|
|||
if (_set1 == null || _set2 == null || _set3 == null) return false;
|
||||
while (++_index < _count)
|
||||
{
|
||||
CurrentEntity = _set1.DenseEntities[_index];
|
||||
if (CurrentEntity.Id == 1) continue;
|
||||
if (!_set2.Contains(CurrentEntity)) continue;
|
||||
if (!_set3.Contains(CurrentEntity)) continue;
|
||||
switch (_driver)
|
||||
{
|
||||
case 0:
|
||||
CurrentEntity = _set1.DenseEntities[_index];
|
||||
if (CurrentEntity.Id == 1) continue;
|
||||
if (!_set2.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;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,6 @@ public class World : IDisposable
|
|||
_changes.Pending.MarkComponentRemoved(entity, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Returns a reference to the component of type <typeparamref name="T"/>
|
||||
/// for the given entity. Throws if the entity does not have the component.
|
||||
|
|
@ -398,7 +397,7 @@ public class World : IDisposable
|
|||
RemoveComponent<T>(SingletonEntity);
|
||||
}
|
||||
|
||||
private void EnsureSingleton()
|
||||
internal void EnsureSingleton()
|
||||
{
|
||||
if (_singletonCreated)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ public static class WorldSerializer
|
|||
{
|
||||
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)
|
||||
{
|
||||
var type = Type.GetType(ce.TypeName)
|
||||
|
|
|
|||
|
|
@ -247,11 +247,11 @@ public class CommandTests
|
|||
private class CommandEnqueueSystem : ISystem
|
||||
{
|
||||
private readonly World _world;
|
||||
private readonly ICommand _command;
|
||||
private readonly SpawnCommand _command;
|
||||
|
||||
public QueryDescriptor Query { get; }
|
||||
|
||||
public CommandEnqueueSystem(World world, ICommand command)
|
||||
public CommandEnqueueSystem(World world, SpawnCommand command)
|
||||
{
|
||||
_world = world;
|
||||
_command = command;
|
||||
|
|
|
|||
Loading…
Reference in New Issue