diff --git a/src/OECS/CommandQueue.cs b/src/OECS/CommandQueue.cs
index 8dedcff..1f52e2a 100644
--- a/src/OECS/CommandQueue.cs
+++ b/src/OECS/CommandQueue.cs
@@ -28,9 +28,13 @@ public class CommandQueue
///
/// Adds a command to the end of the queue.
+ /// Uses a constrained generic to avoid boxing struct commands.
///
- public void Enqueue(ICommand command)
+ public void Enqueue(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);
}
diff --git a/src/OECS/EntityIterator.cs b/src/OECS/EntityIterator.cs
index 1f57166..95352f3 100644
--- a/src/OECS/EntityIterator.cs
+++ b/src/OECS/EntityIterator.cs
@@ -105,6 +105,7 @@ public ref struct Select1 where T1 : struct
///
/// Ref struct iterator for queries with two component types.
+/// Drives iteration from the smaller sparse set to minimize probes.
///
public ref struct Select2
where T1 : struct where T2 : struct
@@ -116,6 +117,7 @@ public ref struct Select2
private readonly IReadOnlySet _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
_without = query.Without;
_set1 = _store.GetSet(typeof(T1)) as SparseSet;
_set2 = _store.GetSet(typeof(T2)) as SparseSet;
- _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
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
///
/// Ref struct iterator for queries with three component types.
+/// Drives iteration from the smallest sparse set to minimize probes.
///
public ref struct Select3
where T1 : struct where T2 : struct where T3 : struct
@@ -182,6 +198,7 @@ public ref struct Select3
private readonly IReadOnlySet _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
_set1 = _store.GetSet(typeof(T1)) as SparseSet;
_set2 = _store.GetSet(typeof(T2)) as SparseSet;
_set3 = _store.GetSet(typeof(T3)) as SparseSet;
- _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
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;
}
diff --git a/src/OECS/World.cs b/src/OECS/World.cs
index 2435b0d..fe5e31b 100644
--- a/src/OECS/World.cs
+++ b/src/OECS/World.cs
@@ -251,7 +251,6 @@ public class World : IDisposable
_changes.Pending.MarkComponentRemoved(entity, typeof(T));
}
- ///
///
/// Returns a reference to the component of type
/// for the given entity. Throws if the entity does not have the component.
@@ -398,7 +397,7 @@ public class World : IDisposable
RemoveComponent(SingletonEntity);
}
- private void EnsureSingleton()
+ internal void EnsureSingleton()
{
if (_singletonCreated)
return;
diff --git a/src/OECS/WorldSerializer.cs b/src/OECS/WorldSerializer.cs
index 0a8ecec..e2d3476 100644
--- a/src/OECS/WorldSerializer.cs
+++ b/src/OECS/WorldSerializer.cs
@@ -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)
diff --git a/tests/OECS.Tests/CommandTests.cs b/tests/OECS.Tests/CommandTests.cs
index a3626a2..2f55293 100644
--- a/tests/OECS.Tests/CommandTests.cs
+++ b/tests/OECS.Tests/CommandTests.cs
@@ -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;