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;