refactor: replace ForEach with Select iterators
Replace the delegate-based ForEach pattern with zero-allocation ref struct Select iterators. This change moves iteration logic from QueryExecutor to WorldQueryExtensions and introduces a new fluent Query<T> API. Other changes: - Update singleton handling to use a dictionary instead of a reserved ID. - Add IsSingleton flag to ComponentDescriptor. - Replace QueryDescriptor with type-safe Query<T> structs. - Update tests to use the new Select API.
This commit is contained in:
parent
91c6f4aa2c
commit
737136e2ef
|
|
@ -114,13 +114,11 @@ public class CommandTests
|
||||||
|
|
||||||
world.ExecuteCommands();
|
world.ExecuteCommands();
|
||||||
|
|
||||||
var query = world.Query().With<TestPosition>().With<TestHealth>().Build();
|
|
||||||
var positions = new List<(float X, float Y, int Health)>();
|
var positions = new List<(float X, float Y, int Health)>();
|
||||||
|
foreach (var it in world.Select<TestPosition, TestHealth>())
|
||||||
world.ForEach(query, (Entity e, ref TestPosition pos, ref TestHealth hp) =>
|
|
||||||
{
|
{
|
||||||
positions.Add((pos.X, pos.Y, hp.Value));
|
positions.Add((it.Item1.X, it.Item1.Y, it.Item2.Value));
|
||||||
});
|
}
|
||||||
|
|
||||||
positions.Should().BeEquivalentTo([
|
positions.Should().BeEquivalentTo([
|
||||||
(1, 2, 100),
|
(1, 2, 100),
|
||||||
|
|
@ -151,13 +149,11 @@ public class CommandTests
|
||||||
world.Commands.Enqueue(new ChainedCommand { X = 0, Y = 0 });
|
world.Commands.Enqueue(new ChainedCommand { X = 0, Y = 0 });
|
||||||
world.ExecuteCommands();
|
world.ExecuteCommands();
|
||||||
|
|
||||||
var query = world.Query().With<TestPosition>().Build();
|
|
||||||
var positions = new List<(float X, float Y)>();
|
var positions = new List<(float X, float Y)>();
|
||||||
|
foreach (var it in world.Select<TestPosition>())
|
||||||
world.ForEach(query, (Entity e, ref TestPosition pos) =>
|
|
||||||
{
|
{
|
||||||
positions.Add((pos.X, pos.Y));
|
positions.Add((it.Item1.X, it.Item1.Y));
|
||||||
});
|
}
|
||||||
|
|
||||||
positions.Should().BeEquivalentTo([(10, 20)]);
|
positions.Should().BeEquivalentTo([(10, 20)]);
|
||||||
}
|
}
|
||||||
|
|
@ -173,9 +169,9 @@ public class CommandTests
|
||||||
|
|
||||||
world.ExecuteCommands();
|
world.ExecuteCommands();
|
||||||
|
|
||||||
var query = world.Query().With<TestPosition>().Build();
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
world.ForEach(query, (Entity e, ref TestPosition pos) => count++);
|
foreach (var it in world.Select<TestPosition>())
|
||||||
|
count++;
|
||||||
count.Should().Be(2);
|
count.Should().Be(2);
|
||||||
|
|
||||||
world.Commands.Errors.Should().HaveCount(1);
|
world.Commands.Errors.Should().HaveCount(1);
|
||||||
|
|
@ -222,9 +218,9 @@ public class CommandTests
|
||||||
|
|
||||||
world.ExecuteCommands();
|
world.ExecuteCommands();
|
||||||
|
|
||||||
var query = world.Query().With<TestPosition>().Build();
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
world.ForEach(query, (Entity e, ref TestPosition pos) => count++);
|
foreach (var it in world.Select<TestPosition>())
|
||||||
|
count++;
|
||||||
count.Should().Be(0);
|
count.Should().Be(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,7 +232,7 @@ public class CommandTests
|
||||||
|
|
||||||
group.Add(new CommandEnqueueSystem(world, new SpawnCommand { X = 1, Y = 2, Health = 100 }));
|
group.Add(new CommandEnqueueSystem(world, new SpawnCommand { X = 1, Y = 2, Health = 100 }));
|
||||||
|
|
||||||
var observer = new EntityCountObserver(world);
|
var observer = new EntityCountObserver();
|
||||||
group.Add(observer);
|
group.Add(observer);
|
||||||
|
|
||||||
group.RunLogical();
|
group.RunLogical();
|
||||||
|
|
@ -263,20 +259,12 @@ public class CommandTests
|
||||||
|
|
||||||
private class EntityCountObserver : ISystem
|
private class EntityCountObserver : ISystem
|
||||||
{
|
{
|
||||||
private readonly QueryDescriptor _query;
|
|
||||||
public int SeenCount { get; private set; }
|
public int SeenCount { get; private set; }
|
||||||
|
|
||||||
public EntityCountObserver(World world)
|
|
||||||
{
|
|
||||||
_query = world.Query().With<TestPosition>().Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run(World world)
|
public void Run(World world)
|
||||||
{
|
{
|
||||||
world.ForEach(_query, (Entity e, ref TestPosition pos) =>
|
foreach (var it in world.Select<TestPosition>())
|
||||||
{
|
|
||||||
SeenCount++;
|
SeenCount++;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -153,45 +153,6 @@ public class EdgeCaseTests
|
||||||
c.Kind == ChangeKind.EntityRemoved && c.Entity == entity);
|
c.Kind == ChangeKind.EntityRemoved && c.Entity == entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── QueryDescriptor equality ─────────────────────────────────────
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void QueryDescriptor_EqualQueries_AreEqual()
|
|
||||||
{
|
|
||||||
var q1 = new QueryBuilder().With<Position>().With<Velocity>().Without<Frozen>().Build();
|
|
||||||
var q2 = new QueryBuilder().With<Position>().With<Velocity>().Without<Frozen>().Build();
|
|
||||||
|
|
||||||
q1.Should().Be(q2);
|
|
||||||
q1.GetHashCode().Should().Be(q2.GetHashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void QueryDescriptor_DifferentWith_AreNotEqual()
|
|
||||||
{
|
|
||||||
var q1 = new QueryBuilder().With<Position>().Build();
|
|
||||||
var q2 = new QueryBuilder().With<Velocity>().Build();
|
|
||||||
|
|
||||||
q1.Should().NotBe(q2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void QueryDescriptor_DifferentWithout_AreNotEqual()
|
|
||||||
{
|
|
||||||
var q1 = new QueryBuilder().With<Position>().Without<Frozen>().Build();
|
|
||||||
var q2 = new QueryBuilder().With<Position>().Without<Burning>().Build();
|
|
||||||
|
|
||||||
q1.Should().NotBe(q2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void QueryDescriptor_DifferentCount_AreNotEqual()
|
|
||||||
{
|
|
||||||
var q1 = new QueryBuilder().With<Position>().Build();
|
|
||||||
var q2 = new QueryBuilder().With<Position>().With<Velocity>().Build();
|
|
||||||
|
|
||||||
q1.Should().NotBe(q2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Query with 4, 5, 6 components ────────────────────────────────
|
// ── Query with 4, 5, 6 components ────────────────────────────────
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -204,18 +165,14 @@ public class EdgeCaseTests
|
||||||
world.AddComponent(entity, new Health { Value = 100 });
|
world.AddComponent(entity, new Health { Value = 100 });
|
||||||
world.AddComponent(entity, new Frozen());
|
world.AddComponent(entity, new Frozen());
|
||||||
|
|
||||||
var query = world.Query()
|
|
||||||
.With<Position>().With<Velocity>().With<Health>().With<Frozen>()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
world.ForEach(query, (Entity e, ref Position pos, ref Velocity vel, ref Health hp, ref Frozen f) =>
|
foreach (var it in world.Select<Position, Velocity, Health, Frozen>())
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
pos.X.Should().Be(1);
|
it.Item1.X.Should().Be(1);
|
||||||
vel.Y.Should().Be(4);
|
it.Item2.Y.Should().Be(4);
|
||||||
hp.Value.Should().Be(100);
|
it.Item3.Value.Should().Be(100);
|
||||||
});
|
}
|
||||||
count.Should().Be(1);
|
count.Should().Be(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,15 +187,11 @@ public class EdgeCaseTests
|
||||||
world.AddComponent(entity, new Frozen());
|
world.AddComponent(entity, new Frozen());
|
||||||
world.AddComponent(entity, new Burning());
|
world.AddComponent(entity, new Burning());
|
||||||
|
|
||||||
var query = world.Query()
|
|
||||||
.With<Position>().With<Velocity>().With<Health>().With<Frozen>().With<Burning>()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
world.ForEach(query, (Entity e, ref Position pos, ref Velocity vel, ref Health hp, ref Frozen f, ref Burning b) =>
|
foreach (var it in world.Select<Position, Velocity, Health, Frozen, Burning>())
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
});
|
}
|
||||||
count.Should().Be(1);
|
count.Should().Be(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,17 +207,11 @@ public class EdgeCaseTests
|
||||||
world.AddComponent(entity, new Burning());
|
world.AddComponent(entity, new Burning());
|
||||||
world.AddComponent(entity, new Poisoned());
|
world.AddComponent(entity, new Poisoned());
|
||||||
|
|
||||||
var query = world.Query()
|
|
||||||
.With<Position>().With<Velocity>().With<Health>()
|
|
||||||
.With<Frozen>().With<Burning>().With<Poisoned>()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
world.ForEach(query, (Entity e, ref Position pos, ref Velocity vel, ref Health hp,
|
foreach (var it in world.Select<Position, Velocity, Health, Frozen, Burning, Poisoned>())
|
||||||
ref Frozen f, ref Burning b, ref Poisoned p) =>
|
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
});
|
}
|
||||||
count.Should().Be(1);
|
count.Should().Be(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,13 +229,13 @@ public class EdgeCaseTests
|
||||||
world.AddComponent(b, new Frozen());
|
world.AddComponent(b, new Frozen());
|
||||||
|
|
||||||
// Without<Frozen> filters out entity b which has Frozen.
|
// Without<Frozen> filters out entity b which has Frozen.
|
||||||
var query = world.Query().Without<Frozen>().Build();
|
var query = new Query<Position>().Without<Frozen>();
|
||||||
var results = new List<Entity>();
|
var results = new List<Entity>();
|
||||||
|
|
||||||
world.ForEach(query, (Entity e, ref Position pos) =>
|
foreach (var it in world.Select(query))
|
||||||
{
|
{
|
||||||
results.Add(e);
|
results.Add(it.Entity);
|
||||||
});
|
}
|
||||||
|
|
||||||
results.Should().BeEquivalentTo([a]);
|
results.Should().BeEquivalentTo([a]);
|
||||||
}
|
}
|
||||||
|
|
@ -416,16 +363,15 @@ public class EdgeCaseTests
|
||||||
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
||||||
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
||||||
|
|
||||||
var query = world.Query().With<Position>().Build();
|
|
||||||
var seen = new List<Entity>();
|
var seen = new List<Entity>();
|
||||||
|
|
||||||
world.ForEach(query, (Entity e, ref Position pos) =>
|
foreach (var it in world.Select<Position>())
|
||||||
{
|
{
|
||||||
seen.Add(e);
|
seen.Add(it.Entity);
|
||||||
// Add a component to the other entity during iteration.
|
// Add a component to the other entity during iteration.
|
||||||
// This should not affect the current iteration.
|
// This should not affect the current iteration.
|
||||||
world.AddComponent(e, new Velocity { X = 1, Y = 1 });
|
world.AddComponent(it.Entity, new Velocity { X = 1, Y = 1 });
|
||||||
});
|
}
|
||||||
|
|
||||||
seen.Should().BeEquivalentTo([a, b]);
|
seen.Should().BeEquivalentTo([a, b]);
|
||||||
}
|
}
|
||||||
|
|
@ -439,94 +385,16 @@ public class EdgeCaseTests
|
||||||
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
||||||
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
||||||
|
|
||||||
var query = world.Query().With<Position>().Build();
|
|
||||||
|
|
||||||
// Destroying an entity during iteration: the sparse set uses
|
// Destroying an entity during iteration: the sparse set uses
|
||||||
// swap-remove, so the destroyed entity's slot is replaced by
|
// swap-remove, so the destroyed entity's slot is replaced by
|
||||||
// the last element. This is safe as long as we don't re-iterate
|
// the last element. This is safe as long as we don't re-iterate
|
||||||
// the destroyed entity (which we won't since it's swap-removed).
|
// the destroyed entity (which we won't since it's swap-removed).
|
||||||
var act = () =>
|
var act = () =>
|
||||||
{
|
{
|
||||||
world.ForEach(query, (Entity e, ref Position pos) =>
|
foreach (var it in world.Select<Position>())
|
||||||
{
|
{
|
||||||
world.DestroyEntity(e);
|
world.DestroyEntity(it.Entity);
|
||||||
});
|
|
||||||
};
|
|
||||||
act.Should().NotThrow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── QueryDescriptor.With enforced during ForEach ─────────────────
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ForEach_Throws_WhenTypeParamsDivergeFromQueryWith()
|
|
||||||
{
|
|
||||||
var world = new World();
|
|
||||||
var entity = world.CreateEntity();
|
|
||||||
world.AddComponent(entity, new Position { X = 1, Y = 2 });
|
|
||||||
world.AddComponent(entity, new Velocity { X = 3, Y = 4 });
|
|
||||||
|
|
||||||
// Build a query requiring Position + Velocity, but iterate only Position.
|
|
||||||
var query = world.Query().With<Position>().With<Velocity>().Build();
|
|
||||||
|
|
||||||
var act = () =>
|
|
||||||
{
|
|
||||||
world.ForEach(query, (Entity e, ref Position pos) => { });
|
|
||||||
};
|
|
||||||
act.Should().Throw<InvalidOperationException>()
|
|
||||||
.WithMessage("*ForEach*type*Position*Velocity*");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ForEach_Throws_WhenTypeParamsAreSubsetOfQueryWith()
|
|
||||||
{
|
|
||||||
var world = new World();
|
|
||||||
var entity = world.CreateEntity();
|
|
||||||
world.AddComponent(entity, new Position { X = 1, Y = 2 });
|
|
||||||
world.AddComponent(entity, new Velocity { X = 3, Y = 4 });
|
|
||||||
world.AddComponent(entity, new Health { Value = 100 });
|
|
||||||
|
|
||||||
// Build a query requiring 3 components, but iterate only 2.
|
|
||||||
var query = world.Query().With<Position>().With<Velocity>().With<Health>().Build();
|
|
||||||
|
|
||||||
var act = () =>
|
|
||||||
{
|
|
||||||
world.ForEach(query, (Entity e, ref Position pos, ref Velocity vel) => { });
|
|
||||||
};
|
|
||||||
act.Should().Throw<InvalidOperationException>()
|
|
||||||
.WithMessage("*ForEach*type*Health*");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ForEach_Throws_WhenTypeParamsAreSupersetOfQueryWith()
|
|
||||||
{
|
|
||||||
var world = new World();
|
|
||||||
var entity = world.CreateEntity();
|
|
||||||
world.AddComponent(entity, new Position { X = 1, Y = 2 });
|
|
||||||
|
|
||||||
// Build a query requiring only Position, but iterate Position + Velocity.
|
|
||||||
var query = world.Query().With<Position>().Build();
|
|
||||||
|
|
||||||
var act = () =>
|
|
||||||
{
|
|
||||||
world.ForEach(query, (Entity e, ref Position pos, ref Velocity vel) => { });
|
|
||||||
};
|
|
||||||
act.Should().Throw<InvalidOperationException>()
|
|
||||||
.WithMessage("*ForEach*type*Velocity*");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ForEach_DoesNotThrow_WhenTypeParamsMatchQueryWith()
|
|
||||||
{
|
|
||||||
var world = new World();
|
|
||||||
var entity = world.CreateEntity();
|
|
||||||
world.AddComponent(entity, new Position { X = 1, Y = 2 });
|
|
||||||
world.AddComponent(entity, new Velocity { X = 3, Y = 4 });
|
|
||||||
|
|
||||||
var query = world.Query().With<Position>().With<Velocity>().Build();
|
|
||||||
|
|
||||||
var act = () =>
|
|
||||||
{
|
|
||||||
world.ForEach(query, (Entity e, ref Position pos, ref Velocity vel) => { });
|
|
||||||
};
|
};
|
||||||
act.Should().NotThrow();
|
act.Should().NotThrow();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,9 @@ public class QueryTests
|
||||||
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
||||||
// c has no Position
|
// c has no Position
|
||||||
|
|
||||||
var query = world.Query().With<Position>().Build();
|
|
||||||
var results = new List<Entity>();
|
var results = new List<Entity>();
|
||||||
|
foreach (var it in world.Select<Position>())
|
||||||
world.ForEach(query, (Entity entity, ref Position pos) =>
|
results.Add(it.Entity);
|
||||||
{
|
|
||||||
results.Add(entity);
|
|
||||||
});
|
|
||||||
|
|
||||||
results.Should().BeEquivalentTo([a, b]);
|
results.Should().BeEquivalentTo([a, b]);
|
||||||
}
|
}
|
||||||
|
|
@ -50,13 +46,9 @@ public class QueryTests
|
||||||
world.AddComponent(c, new Velocity { X = 0, Y = 1 });
|
world.AddComponent(c, new Velocity { X = 0, Y = 1 });
|
||||||
// c has no Position
|
// c has no Position
|
||||||
|
|
||||||
var query = world.Query().With<Position>().With<Velocity>().Build();
|
|
||||||
var results = new List<Entity>();
|
var results = new List<Entity>();
|
||||||
|
foreach (var it in world.Select<Position, Velocity>())
|
||||||
world.ForEach(query, (Entity entity, ref Position pos, ref Velocity vel) =>
|
results.Add(it.Entity);
|
||||||
{
|
|
||||||
results.Add(entity);
|
|
||||||
});
|
|
||||||
|
|
||||||
results.Should().BeEquivalentTo([a]);
|
results.Should().BeEquivalentTo([a]);
|
||||||
}
|
}
|
||||||
|
|
@ -72,13 +64,10 @@ public class QueryTests
|
||||||
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
||||||
world.AddComponent(b, new Frozen());
|
world.AddComponent(b, new Frozen());
|
||||||
|
|
||||||
var query = world.Query().With<Position>().Without<Frozen>().Build();
|
var query = new Query<Position>().Without<Frozen>();
|
||||||
var results = new List<Entity>();
|
var results = new List<Entity>();
|
||||||
|
foreach (var it in world.Select(query))
|
||||||
world.ForEach(query, (Entity entity, ref Position pos) =>
|
results.Add(it.Entity);
|
||||||
{
|
|
||||||
results.Add(entity);
|
|
||||||
});
|
|
||||||
|
|
||||||
results.Should().BeEquivalentTo([a]);
|
results.Should().BeEquivalentTo([a]);
|
||||||
}
|
}
|
||||||
|
|
@ -87,14 +76,9 @@ public class QueryTests
|
||||||
public void Query_EmptyResult_WhenNoEntitiesMatch()
|
public void Query_EmptyResult_WhenNoEntitiesMatch()
|
||||||
{
|
{
|
||||||
var world = new World();
|
var world = new World();
|
||||||
var query = world.Query().With<Position>().Build();
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
foreach (var it in world.Select<Position>())
|
||||||
world.ForEach(query, (Entity entity, ref Position pos) =>
|
|
||||||
{
|
|
||||||
count++;
|
count++;
|
||||||
});
|
|
||||||
|
|
||||||
count.Should().Be(0);
|
count.Should().Be(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,12 +89,8 @@ public class QueryTests
|
||||||
var entity = world.CreateEntity();
|
var entity = world.CreateEntity();
|
||||||
world.AddComponent(entity, new Position { X = 0, Y = 0 });
|
world.AddComponent(entity, new Position { X = 0, Y = 0 });
|
||||||
|
|
||||||
var query = world.Query().With<Position>().Build();
|
foreach (var it in world.Select<Position>())
|
||||||
|
it.Item1.X = 42;
|
||||||
world.ForEach(query, (Entity e, ref Position pos) =>
|
|
||||||
{
|
|
||||||
pos.X = 42;
|
|
||||||
});
|
|
||||||
|
|
||||||
ref var pos = ref world.GetComponent<Position>(entity);
|
ref var pos = ref world.GetComponent<Position>(entity);
|
||||||
pos.X.Should().Be(42);
|
pos.X.Should().Be(42);
|
||||||
|
|
@ -128,13 +108,9 @@ public class QueryTests
|
||||||
|
|
||||||
world.DestroyEntity(a);
|
world.DestroyEntity(a);
|
||||||
|
|
||||||
var query = world.Query().With<Position>().Build();
|
|
||||||
var results = new List<Entity>();
|
var results = new List<Entity>();
|
||||||
|
foreach (var it in world.Select<Position>())
|
||||||
world.ForEach(query, (Entity entity, ref Position pos) =>
|
results.Add(it.Entity);
|
||||||
{
|
|
||||||
results.Add(entity);
|
|
||||||
});
|
|
||||||
|
|
||||||
results.Should().BeEquivalentTo([b]);
|
results.Should().BeEquivalentTo([b]);
|
||||||
}
|
}
|
||||||
|
|
@ -149,23 +125,31 @@ public class QueryTests
|
||||||
world.AddComponent(entity, new Velocity { X = 3, Y = 4 });
|
world.AddComponent(entity, new Velocity { X = 3, Y = 4 });
|
||||||
world.AddComponent(entity, new Health { Value = 100 });
|
world.AddComponent(entity, new Health { Value = 100 });
|
||||||
|
|
||||||
var query = world.Query()
|
|
||||||
.With<Position>()
|
|
||||||
.With<Velocity>()
|
|
||||||
.With<Health>()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
world.ForEach(query, (Entity e, ref Position pos, ref Velocity vel, ref Health hp) =>
|
foreach (var it in world.Select<Position, Velocity, Health>())
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
pos.X.Should().Be(1);
|
it.Item1.X.Should().Be(1);
|
||||||
vel.Y.Should().Be(4);
|
it.Item2.Y.Should().Be(4);
|
||||||
hp.Value.Should().Be(100);
|
it.Item3.Value.Should().Be(100);
|
||||||
});
|
}
|
||||||
|
|
||||||
count.Should().Be(1);
|
count.Should().Be(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FindEntity_ReturnsFirstMatch()
|
||||||
|
{
|
||||||
|
var world = new World();
|
||||||
|
var a = world.CreateEntity();
|
||||||
|
var b = world.CreateEntity();
|
||||||
|
|
||||||
|
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
||||||
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
||||||
|
|
||||||
|
var found = world.FindEntity<Position>();
|
||||||
|
found.Should().NotBe(Entity.Null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SystemTests
|
public class SystemTests
|
||||||
|
|
@ -175,23 +159,16 @@ public class SystemTests
|
||||||
|
|
||||||
private class MovementSystem : ITickedSystem
|
private class MovementSystem : ITickedSystem
|
||||||
{
|
{
|
||||||
private readonly QueryDescriptor _query;
|
|
||||||
|
|
||||||
public MovementSystem(World world)
|
|
||||||
{
|
|
||||||
_query = world.Query().With<Position>().With<Velocity>().Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run(World world) => Run(world, Tick.Logical());
|
public void Run(World world) => Run(world, Tick.Logical());
|
||||||
|
|
||||||
public void Run(World world, Tick tick)
|
public void Run(World world, Tick tick)
|
||||||
{
|
{
|
||||||
float dt = tick.DeltaTime;
|
float dt = tick.DeltaTime;
|
||||||
world.ForEach(_query, (Entity entity, ref Position pos, ref Velocity vel) =>
|
foreach (var it in world.Select<Position, Velocity>())
|
||||||
{
|
{
|
||||||
pos.X += vel.X * dt;
|
it.Item1.X += it.Item2.X * dt;
|
||||||
pos.Y += vel.Y * dt;
|
it.Item1.Y += it.Item2.Y * dt;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,8 +177,7 @@ public class SystemTests
|
||||||
{
|
{
|
||||||
var world = new World();
|
var world = new World();
|
||||||
var group = new SystemGroup(world);
|
var group = new SystemGroup(world);
|
||||||
var system = new MovementSystem(world);
|
group.Add(new MovementSystem());
|
||||||
group.Add(system);
|
|
||||||
|
|
||||||
var entity = world.CreateEntity();
|
var entity = world.CreateEntity();
|
||||||
world.AddComponent(entity, new Position { X = 0, Y = 0 });
|
world.AddComponent(entity, new Position { X = 0, Y = 0 });
|
||||||
|
|
@ -220,7 +196,7 @@ public class SystemTests
|
||||||
var world = new World();
|
var world = new World();
|
||||||
var group = new SystemGroup(world);
|
var group = new SystemGroup(world);
|
||||||
|
|
||||||
var system = new TestLogicalSystem(world);
|
var system = new TestLogicalSystem();
|
||||||
group.Add(system);
|
group.Add(system);
|
||||||
|
|
||||||
group.RunLogical();
|
group.RunLogical();
|
||||||
|
|
@ -235,11 +211,7 @@ public class SystemTests
|
||||||
public bool WasCalled { get; private set; }
|
public bool WasCalled { get; private set; }
|
||||||
public Tick ReceivedTick { get; private set; }
|
public Tick ReceivedTick { get; private set; }
|
||||||
|
|
||||||
public TestLogicalSystem(World world)
|
public void Run(World world) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run(World world) => Run(world, Tick.Logical());
|
|
||||||
|
|
||||||
public void Run(World world, Tick tick)
|
public void Run(World world, Tick tick)
|
||||||
{
|
{
|
||||||
|
|
@ -247,55 +219,4 @@ public class SystemTests
|
||||||
ReceivedTick = tick;
|
ReceivedTick = tick;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Systems_RunInRegistrationOrder()
|
|
||||||
{
|
|
||||||
var world = new World();
|
|
||||||
var group = new SystemGroup(world);
|
|
||||||
var order = new List<int>();
|
|
||||||
|
|
||||||
group.Add(new OrderTrackingSystem(world, 1, order));
|
|
||||||
group.Add(new OrderTrackingSystem(world, 2, order));
|
|
||||||
group.Add(new OrderTrackingSystem(world, 3, order));
|
|
||||||
|
|
||||||
group.RunLogical();
|
|
||||||
|
|
||||||
order.Should().BeInAscendingOrder();
|
|
||||||
order.Should().BeEquivalentTo([1, 2, 3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OrderTrackingSystem : ISystem
|
|
||||||
{
|
|
||||||
private readonly int _id;
|
|
||||||
private readonly List<int> _order;
|
|
||||||
|
|
||||||
public OrderTrackingSystem(World world, int id, List<int> order)
|
|
||||||
{
|
|
||||||
_id = id;
|
|
||||||
_order = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run(World world)
|
|
||||||
{
|
|
||||||
_order.Add(_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void System_CanBeRemoved()
|
|
||||||
{
|
|
||||||
var world = new World();
|
|
||||||
var group = new SystemGroup(world);
|
|
||||||
var system = new TestLogicalSystem(world);
|
|
||||||
|
|
||||||
group.Add(system);
|
|
||||||
group.Count.Should().Be(1);
|
|
||||||
|
|
||||||
group.Remove(system);
|
|
||||||
group.Count.Should().Be(0);
|
|
||||||
|
|
||||||
group.RunLogical();
|
|
||||||
system.WasCalled.Should().BeFalse();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +208,7 @@ public class ReactivityTests
|
||||||
public void ObserveQuery_OnlyReceivesMatchingChanges()
|
public void ObserveQuery_OnlyReceivesMatchingChanges()
|
||||||
{
|
{
|
||||||
var world = new World();
|
var world = new World();
|
||||||
var query = world.Query().With<Position>().Without<Frozen>().Build();
|
var query = new Query<Position>().Without<Frozen>();
|
||||||
var collector = new ChangeCollector();
|
var collector = new ChangeCollector();
|
||||||
|
|
||||||
using var sub = world.ObserveQuery(query).Subscribe(collector.ToObserver());
|
using var sub = world.ObserveQuery(query).Subscribe(collector.ToObserver());
|
||||||
|
|
|
||||||
|
|
@ -186,14 +186,13 @@ public class RelationshipTests
|
||||||
Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
|
|
||||||
var query = world.Query().With<Relationship<ChildOf, ParentOf>>().Build();
|
|
||||||
var results = new List<Entity>();
|
var results = new List<Entity>();
|
||||||
|
|
||||||
world.ForEach(query, (Entity e, ref Relationship<ChildOf, ParentOf> rel) =>
|
foreach (var it in world.Select<Relationship<ChildOf, ParentOf>>())
|
||||||
{
|
{
|
||||||
results.Add(e);
|
results.Add(it.Entity);
|
||||||
rel.Target.Should().Be(parent);
|
it.Item1.Target.Should().Be(parent);
|
||||||
});
|
}
|
||||||
|
|
||||||
results.Should().BeEquivalentTo([child]);
|
results.Should().BeEquivalentTo([child]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,13 +75,12 @@ public class SingletonTests
|
||||||
var regular = world.CreateEntity();
|
var regular = world.CreateEntity();
|
||||||
world.AddComponent(regular, new GameConfig { Gravity = 1.6f, MaxPlayers = 2 });
|
world.AddComponent(regular, new GameConfig { Gravity = 1.6f, MaxPlayers = 2 });
|
||||||
|
|
||||||
var query = world.Query().With<GameConfig>().Build();
|
|
||||||
var results = new List<Entity>();
|
var results = new List<Entity>();
|
||||||
|
|
||||||
world.ForEach(query, (Entity e, ref GameConfig cfg) =>
|
foreach (var it in world.Select<GameConfig>())
|
||||||
{
|
{
|
||||||
results.Add(e);
|
results.Add(it.Entity);
|
||||||
});
|
}
|
||||||
|
|
||||||
// Only the regular entity should appear, not the singleton.
|
// Only the regular entity should appear, not the singleton.
|
||||||
results.Should().BeEquivalentTo([regular]);
|
results.Should().BeEquivalentTo([regular]);
|
||||||
|
|
@ -94,7 +93,9 @@ public class SingletonTests
|
||||||
|
|
||||||
world.SetSingleton(new GameConfig());
|
world.SetSingleton(new GameConfig());
|
||||||
|
|
||||||
world.IsAlive(World.SingletonEntity).Should().BeTrue();
|
// Singletons are now on their own dedicated entities.
|
||||||
|
// Verify the singleton exists (its entity is alive).
|
||||||
|
world.HasSingleton<GameConfig>().Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ internal class ChangeBuffer
|
||||||
|
|
||||||
private readonly Subject<EntityChange> _entitySubject = new();
|
private readonly Subject<EntityChange> _entitySubject = new();
|
||||||
private readonly Dictionary<Type, TrackedSubject> _componentSubjects = new();
|
private readonly Dictionary<Type, TrackedSubject> _componentSubjects = new();
|
||||||
private readonly Dictionary<QueryDescriptor, TrackedSubject> _querySubjects = new();
|
private readonly Dictionary<QueryKey, TrackedSubject> _querySubjects = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wraps a <see cref="Subject{T}"/> with a subscriber count so that
|
/// Wraps a <see cref="Subject{T}"/> with a subscriber count so that
|
||||||
|
|
@ -27,6 +27,42 @@ internal class ChangeBuffer
|
||||||
public int SubscriberCount;
|
public int SubscriberCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies a query subscription for cleanup tracking.
|
||||||
|
/// </summary>
|
||||||
|
private readonly struct QueryKey : IEquatable<QueryKey>
|
||||||
|
{
|
||||||
|
public readonly Type[] WithTypes;
|
||||||
|
public readonly Type[] WithoutTypes;
|
||||||
|
|
||||||
|
public QueryKey(Type[] withTypes, Type[] withoutTypes)
|
||||||
|
{
|
||||||
|
WithTypes = withTypes;
|
||||||
|
WithoutTypes = withoutTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(QueryKey other)
|
||||||
|
{
|
||||||
|
if (WithTypes.Length != other.WithTypes.Length) return false;
|
||||||
|
if (WithoutTypes.Length != other.WithoutTypes.Length) return false;
|
||||||
|
for (int i = 0; i < WithTypes.Length; i++)
|
||||||
|
if (WithTypes[i] != other.WithTypes[i]) return false;
|
||||||
|
for (int i = 0; i < WithoutTypes.Length; i++)
|
||||||
|
if (WithoutTypes[i] != other.WithoutTypes[i]) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) => obj is QueryKey other && Equals(other);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var hash = new HashCode();
|
||||||
|
foreach (var t in WithTypes) hash.Add(t);
|
||||||
|
foreach (var t in WithoutTypes) hash.Add(t);
|
||||||
|
return hash.ToHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The change set currently accumulating. Cleared after each <see cref="Post"/>.
|
/// The change set currently accumulating. Cleared after each <see cref="Post"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -101,14 +137,33 @@ internal class ChangeBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns an observable that emits changes matching the given query.
|
/// Returns an observable that emits component-level changes matching
|
||||||
|
/// the given query's With types and excluding its Without types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Observable<EntityChange> ObserveQuery(QueryDescriptor query)
|
public Observable<EntityChange> ObserveQuery<T1>(Query<T1> query)
|
||||||
|
where T1 : struct
|
||||||
{
|
{
|
||||||
if (!_querySubjects.TryGetValue(query, out var tracked))
|
var key = MakeKey(query);
|
||||||
|
return SubscribeQuery(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an observable that emits component-level changes matching
|
||||||
|
/// the given query.
|
||||||
|
/// </summary>
|
||||||
|
public Observable<EntityChange> ObserveQuery<T1, T2>(Query<T1, T2> query)
|
||||||
|
where T1 : struct where T2 : struct
|
||||||
|
{
|
||||||
|
var key = MakeKey(query);
|
||||||
|
return SubscribeQuery(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Observable<EntityChange> SubscribeQuery(QueryKey key)
|
||||||
|
{
|
||||||
|
if (!_querySubjects.TryGetValue(key, out var tracked))
|
||||||
{
|
{
|
||||||
tracked = new TrackedSubject();
|
tracked = new TrackedSubject();
|
||||||
_querySubjects[query] = tracked;
|
_querySubjects[key] = tracked;
|
||||||
}
|
}
|
||||||
|
|
||||||
tracked.SubscriberCount++;
|
tracked.SubscriberCount++;
|
||||||
|
|
@ -118,11 +173,28 @@ internal class ChangeBuffer
|
||||||
if (tracked.SubscriberCount <= 0)
|
if (tracked.SubscriberCount <= 0)
|
||||||
{
|
{
|
||||||
tracked.Subject.Dispose();
|
tracked.Subject.Dispose();
|
||||||
_querySubjects.Remove(query);
|
_querySubjects.Remove(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static QueryKey MakeKey<T1>(Query<T1> query) where T1 : struct
|
||||||
|
{
|
||||||
|
var without = query.WithoutTypes;
|
||||||
|
return new QueryKey(
|
||||||
|
[typeof(T1)],
|
||||||
|
without != null ? [.. without] : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static QueryKey MakeKey<T1, T2>(Query<T1, T2> query)
|
||||||
|
where T1 : struct where T2 : struct
|
||||||
|
{
|
||||||
|
var without = query.WithoutTypes;
|
||||||
|
return new QueryKey(
|
||||||
|
[typeof(T1), typeof(T2)],
|
||||||
|
without != null ? [.. without] : []);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wraps an observable so that <paramref name="onLastDispose"/> is called
|
/// Wraps an observable so that <paramref name="onLastDispose"/> is called
|
||||||
/// when the last subscriber disposes.
|
/// when the last subscriber disposes.
|
||||||
|
|
@ -159,21 +231,26 @@ internal class ChangeBuffer
|
||||||
_querySubjects.Clear();
|
_querySubjects.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ChangeMatchesQuery(EntityChange change, QueryDescriptor query)
|
private static bool ChangeMatchesQuery(EntityChange change, QueryKey query)
|
||||||
{
|
{
|
||||||
// Entity-level changes: match if the entity was added/removed and
|
// Entity-level changes don't have a component type.
|
||||||
// the query has any "with" types (we can't know component state for
|
|
||||||
// removed entities, so we only match added entities that would satisfy
|
|
||||||
// the query — but we don't have component data here).
|
|
||||||
// For simplicity, we only match component-level changes against queries.
|
|
||||||
if (change.ComponentType == null)
|
if (change.ComponentType == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// A component change matches a query if the component type is in the
|
// Must be in With types.
|
||||||
// query's With set and not in the Without set.
|
bool inWith = false;
|
||||||
if (query.Without.Contains(change.ComponentType))
|
foreach (var t in query.WithTypes)
|
||||||
return false;
|
{
|
||||||
|
if (t == change.ComponentType) { inWith = true; break; }
|
||||||
|
}
|
||||||
|
if (!inWith) return false;
|
||||||
|
|
||||||
return query.With.Contains(change.ComponentType);
|
// Must not be in Without types.
|
||||||
|
foreach (var t in query.WithoutTypes)
|
||||||
|
{
|
||||||
|
if (t == change.ComponentType) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,17 +34,25 @@ public sealed class ComponentDescriptor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<byte[], object> Deserialize { get; }
|
public Func<byte[], object> Deserialize { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if this component type is used as a singleton (via <c>SetSingleton</c>).
|
||||||
|
/// Set by the source generator at compile time.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSingleton { get; }
|
||||||
|
|
||||||
public ComponentDescriptor(
|
public ComponentDescriptor(
|
||||||
string typeName,
|
string typeName,
|
||||||
Type type,
|
Type type,
|
||||||
Func<object, byte[]> serialize,
|
Func<object, byte[]> serialize,
|
||||||
Action<World, Entity, byte[]> deserializeAndAdd,
|
Action<World, Entity, byte[]> deserializeAndAdd,
|
||||||
Func<byte[], object> deserialize)
|
Func<byte[], object> deserialize,
|
||||||
|
bool isSingleton = false)
|
||||||
{
|
{
|
||||||
TypeName = typeName;
|
TypeName = typeName;
|
||||||
Type = type;
|
Type = type;
|
||||||
Serialize = serialize;
|
Serialize = serialize;
|
||||||
DeserializeAndAdd = deserializeAndAdd;
|
DeserializeAndAdd = deserializeAndAdd;
|
||||||
Deserialize = deserialize;
|
Deserialize = deserialize;
|
||||||
|
IsSingleton = isSingleton;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ namespace OECS;
|
||||||
internal class EntityAllocator
|
internal class EntityAllocator
|
||||||
{
|
{
|
||||||
private const int DefaultCapacity = 1024;
|
private const int DefaultCapacity = 1024;
|
||||||
private const uint FirstValidId = 2; // 0 = Null, 1 = Singleton
|
private const uint FirstValidId = 1; // 0 = Null
|
||||||
|
|
||||||
private byte[] _versions;
|
private byte[] _versions;
|
||||||
private readonly Stack<uint> _freeIds;
|
private readonly Stack<uint> _freeIds;
|
||||||
|
|
@ -63,16 +63,6 @@ internal class EntityAllocator
|
||||||
_freeIds.Push(id);
|
_freeIds.Push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers the singleton entity so that <see cref="IsAlive"/> returns true for it.
|
|
||||||
/// Called once by <see cref="World"/> when the first singleton accessor is used.
|
|
||||||
/// </summary>
|
|
||||||
public void RegisterSingleton(Entity singleton)
|
|
||||||
{
|
|
||||||
EnsureCapacity(singleton.Id);
|
|
||||||
_versions[singleton.Id] = (byte)singleton.Version;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserves a specific entity ID and version for deserialization.
|
/// Reserves a specific entity ID and version for deserialization.
|
||||||
/// Advances <see cref="_nextId"/> past the reserved ID so future
|
/// Advances <see cref="_nextId"/> past the reserved ID so future
|
||||||
|
|
@ -94,7 +84,6 @@ internal class EntityAllocator
|
||||||
{
|
{
|
||||||
uint id = entity.Id;
|
uint id = entity.Id;
|
||||||
if (entity.IsNull) return false;
|
if (entity.IsNull) return false;
|
||||||
if (id == 1) return true; // Singleton is always alive.
|
|
||||||
if (id >= _versions.Length) return false;
|
if (id >= _versions.Length) return false;
|
||||||
return _versions[id] == entity.Version && _versions[id] != 0;
|
return _versions[id] == entity.Version && _versions[id] != 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,327 +0,0 @@
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace OECS;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides zero-allocation ref struct iterators for querying entities.
|
|
||||||
/// Use with <c>foreach</c> or manual <c>while (iter.MoveNext())</c>.
|
|
||||||
/// Supports <c>break</c>, <c>continue</c>, and early returns.
|
|
||||||
/// </summary>
|
|
||||||
public static class EntityIterator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an iterator over entities matching the query, with ref access
|
|
||||||
/// to one component type.
|
|
||||||
/// </summary>
|
|
||||||
public static Select1<T1> Select<T1>(
|
|
||||||
this World world, QueryDescriptor query)
|
|
||||||
where T1 : struct
|
|
||||||
{
|
|
||||||
return new Select1<T1>(world, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an iterator over entities that have a component of type
|
|
||||||
/// <typeparamref name="T1"/>. No <c>Without</c> filter is applied.
|
|
||||||
/// </summary>
|
|
||||||
public static Select1<T1> Select<T1>(
|
|
||||||
this World world)
|
|
||||||
where T1 : struct
|
|
||||||
{
|
|
||||||
return new Select1<T1>(world, new QueryDescriptor(new HashSet<Type>(), new HashSet<Type>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an iterator over entities matching the query, with ref access
|
|
||||||
/// to two component types.
|
|
||||||
/// </summary>
|
|
||||||
public static Select2<T1, T2> Select<T1, T2>(
|
|
||||||
this World world, QueryDescriptor query)
|
|
||||||
where T1 : struct where T2 : struct
|
|
||||||
{
|
|
||||||
return new Select2<T1, T2>(world, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an iterator over entities that have both components of type
|
|
||||||
/// <typeparamref name="T1"/> and <typeparamref name="T2"/>.
|
|
||||||
/// No <c>Without</c> filter is applied.
|
|
||||||
/// </summary>
|
|
||||||
public static Select2<T1, T2> Select<T1, T2>(
|
|
||||||
this World world)
|
|
||||||
where T1 : struct where T2 : struct
|
|
||||||
{
|
|
||||||
return new Select2<T1, T2>(world, new QueryDescriptor(new HashSet<Type>(), new HashSet<Type>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an iterator over entities matching the query, with ref access
|
|
||||||
/// to three component types.
|
|
||||||
/// </summary>
|
|
||||||
public static Select3<T1, T2, T3> Select<T1, T2, T3>(
|
|
||||||
this World world, QueryDescriptor query)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct
|
|
||||||
{
|
|
||||||
return new Select3<T1, T2, T3>(world, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an iterator over entities that have all three components of type
|
|
||||||
/// <typeparamref name="T1"/>, <typeparamref name="T2"/>, and
|
|
||||||
/// <typeparamref name="T3"/>. No <c>Without</c> filter is applied.
|
|
||||||
/// </summary>
|
|
||||||
public static Select3<T1, T2, T3> Select<T1, T2, T3>(
|
|
||||||
this World world)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct
|
|
||||||
{
|
|
||||||
return new Select3<T1, T2, T3>(world, new QueryDescriptor(new HashSet<Type>(), new HashSet<Type>()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ref struct iterator for queries with one component type.
|
|
||||||
/// </summary>
|
|
||||||
public ref struct Select1<T1> where T1 : struct
|
|
||||||
{
|
|
||||||
private readonly World _world;
|
|
||||||
private readonly SparseSet<T1>? _set;
|
|
||||||
private readonly ComponentStore _store;
|
|
||||||
private readonly IReadOnlySet<Type> _without;
|
|
||||||
private int _index;
|
|
||||||
private readonly int _count;
|
|
||||||
|
|
||||||
internal Select1(World world, QueryDescriptor query)
|
|
||||||
{
|
|
||||||
_world = world;
|
|
||||||
_store = world.Components;
|
|
||||||
_without = query.Without;
|
|
||||||
_set = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
_count = _set?.Count ?? 0;
|
|
||||||
_index = -1;
|
|
||||||
|
|
||||||
_world.BeginIteration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entity CurrentEntity { get; private set; }
|
|
||||||
public ref T1 Current1 => ref _set!.Get(CurrentEntity);
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool MoveNext()
|
|
||||||
{
|
|
||||||
if (_set == null) return false;
|
|
||||||
while (++_index < _count)
|
|
||||||
{
|
|
||||||
CurrentEntity = _set.DenseEntities[_index];
|
|
||||||
if (CurrentEntity.Id == 1) continue; // Skip singleton.
|
|
||||||
if (!PassesWithout()) continue;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Select1<T1> GetEnumerator() => this;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_world.EndIteration();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool PassesWithout()
|
|
||||||
{
|
|
||||||
foreach (var type in _without)
|
|
||||||
{
|
|
||||||
var set = _store.GetSet(type);
|
|
||||||
if (set != null && set.Contains(CurrentEntity))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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
|
|
||||||
{
|
|
||||||
private readonly World _world;
|
|
||||||
private readonly SparseSet<T1>? _set1;
|
|
||||||
private readonly SparseSet<T2>? _set2;
|
|
||||||
private readonly ComponentStore _store;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_world = world;
|
|
||||||
_store = world.Components;
|
|
||||||
_without = query.Without;
|
|
||||||
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
|
||||||
|
|
||||||
int count1 = _set1?.Count ?? 0;
|
|
||||||
int count2 = _set2?.Count ?? 0;
|
|
||||||
_swapped = count2 < count1;
|
|
||||||
_count = _swapped ? count2 : count1;
|
|
||||||
_index = -1;
|
|
||||||
|
|
||||||
_world.BeginIteration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entity CurrentEntity { get; private set; }
|
|
||||||
public ref T1 Current1 => ref _set1!.Get(CurrentEntity);
|
|
||||||
public ref T2 Current2 => ref _set2!.Get(CurrentEntity);
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool MoveNext()
|
|
||||||
{
|
|
||||||
if (_set1 == null || _set2 == null) return false;
|
|
||||||
while (++_index < _count)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Select2<T1, T2> GetEnumerator() => this;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_world.EndIteration();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool PassesWithout()
|
|
||||||
{
|
|
||||||
foreach (var type in _without)
|
|
||||||
{
|
|
||||||
var set = _store.GetSet(type);
|
|
||||||
if (set != null && set.Contains(CurrentEntity))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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
|
|
||||||
{
|
|
||||||
private readonly World _world;
|
|
||||||
private readonly SparseSet<T1>? _set1;
|
|
||||||
private readonly SparseSet<T2>? _set2;
|
|
||||||
private readonly SparseSet<T3>? _set3;
|
|
||||||
private readonly ComponentStore _store;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_world = world;
|
|
||||||
_store = world.Components;
|
|
||||||
_without = query.Without;
|
|
||||||
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
|
||||||
_set3 = _store.GetSet(typeof(T3)) as SparseSet<T3>;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entity CurrentEntity { get; private set; }
|
|
||||||
public ref T1 Current1 => ref _set1!.Get(CurrentEntity);
|
|
||||||
public ref T2 Current2 => ref _set2!.Get(CurrentEntity);
|
|
||||||
public ref T3 Current3 => ref _set3!.Get(CurrentEntity);
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool MoveNext()
|
|
||||||
{
|
|
||||||
if (_set1 == null || _set2 == null || _set3 == null) return false;
|
|
||||||
while (++_index < _count)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Select3<T1, T2, T3> GetEnumerator() => this;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_world.EndIteration();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool PassesWithout()
|
|
||||||
{
|
|
||||||
foreach (var type in _without)
|
|
||||||
{
|
|
||||||
var set = _store.GetSet(type);
|
|
||||||
if (set != null && set.Contains(CurrentEntity))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
namespace OECS;
|
|
||||||
|
|
||||||
// Custom delegate types for query iteration with ref parameters.
|
|
||||||
// The built-in Action<...> delegates do not support ref parameters.
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback for iterating entities with one component type.
|
|
||||||
/// </summary>
|
|
||||||
public delegate void ForEachAction<T1>(Entity entity, ref T1 c1) where T1 : struct;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback for iterating entities with two component types.
|
|
||||||
/// </summary>
|
|
||||||
public delegate void ForEachAction<T1, T2>(Entity entity, ref T1 c1, ref T2 c2)
|
|
||||||
where T1 : struct where T2 : struct;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback for iterating entities with three component types.
|
|
||||||
/// </summary>
|
|
||||||
public delegate void ForEachAction<T1, T2, T3>(Entity entity, ref T1 c1, ref T2 c2, ref T3 c3)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback for iterating entities with four component types.
|
|
||||||
/// </summary>
|
|
||||||
public delegate void ForEachAction<T1, T2, T3, T4>(Entity entity, ref T1 c1, ref T2 c2, ref T3 c3, ref T4 c4)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback for iterating entities with five component types.
|
|
||||||
/// </summary>
|
|
||||||
public delegate void ForEachAction<T1, T2, T3, T4, T5>(Entity entity, ref T1 c1, ref T2 c2, ref T3 c3, ref T4 c4, ref T5 c5)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback for iterating entities with six component types.
|
|
||||||
/// </summary>
|
|
||||||
public delegate void ForEachAction<T1, T2, T3, T4, T5, T6>(Entity entity, ref T1 c1, ref T2 c2, ref T3 c3, ref T4 c4, ref T5 c5, ref T6 c6)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct;
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
namespace OECS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a query over the ECS world with one required component type.
|
||||||
|
/// Add optional <c>Without</c> filters via the fluent API.
|
||||||
|
/// </summary>
|
||||||
|
public struct Query<T1> where T1 : struct
|
||||||
|
{
|
||||||
|
internal HashSet<Type>? WithoutTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Excludes entities that have a component of type <typeparamref name="W"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Query<T1> Without<W>() where W : struct
|
||||||
|
{
|
||||||
|
WithoutTypes ??= new HashSet<Type>();
|
||||||
|
WithoutTypes.Add(typeof(W));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a query over the ECS world with two required component types.
|
||||||
|
/// </summary>
|
||||||
|
public struct Query<T1, T2> where T1 : struct where T2 : struct
|
||||||
|
{
|
||||||
|
internal HashSet<Type>? WithoutTypes;
|
||||||
|
|
||||||
|
public Query<T1, T2> Without<W>() where W : struct
|
||||||
|
{
|
||||||
|
WithoutTypes ??= new HashSet<Type>();
|
||||||
|
WithoutTypes.Add(typeof(W));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a query over the ECS world with three required component types.
|
||||||
|
/// </summary>
|
||||||
|
public struct Query<T1, T2, T3>
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct
|
||||||
|
{
|
||||||
|
internal HashSet<Type>? WithoutTypes;
|
||||||
|
|
||||||
|
public Query<T1, T2, T3> Without<W>() where W : struct
|
||||||
|
{
|
||||||
|
WithoutTypes ??= new HashSet<Type>();
|
||||||
|
WithoutTypes.Add(typeof(W));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a query over the ECS world with four required component types.
|
||||||
|
/// </summary>
|
||||||
|
public struct Query<T1, T2, T3, T4>
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct
|
||||||
|
{
|
||||||
|
internal HashSet<Type>? WithoutTypes;
|
||||||
|
|
||||||
|
public Query<T1, T2, T3, T4> Without<W>() where W : struct
|
||||||
|
{
|
||||||
|
WithoutTypes ??= new HashSet<Type>();
|
||||||
|
WithoutTypes.Add(typeof(W));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a query over the ECS world with five required component types.
|
||||||
|
/// </summary>
|
||||||
|
public struct Query<T1, T2, T3, T4, T5>
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct
|
||||||
|
{
|
||||||
|
internal HashSet<Type>? WithoutTypes;
|
||||||
|
|
||||||
|
public Query<T1, T2, T3, T4, T5> Without<W>() where W : struct
|
||||||
|
{
|
||||||
|
WithoutTypes ??= new HashSet<Type>();
|
||||||
|
WithoutTypes.Add(typeof(W));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a query over the ECS world with six required component types.
|
||||||
|
/// </summary>
|
||||||
|
public struct Query<T1, T2, T3, T4, T5, T6>
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct
|
||||||
|
where T4 : struct where T5 : struct where T6 : struct
|
||||||
|
{
|
||||||
|
internal HashSet<Type>? WithoutTypes;
|
||||||
|
|
||||||
|
public Query<T1, T2, T3, T4, T5, T6> Without<W>() where W : struct
|
||||||
|
{
|
||||||
|
WithoutTypes ??= new HashSet<Type>();
|
||||||
|
WithoutTypes.Add(typeof(W));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
namespace OECS;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fluent builder for constructing <see cref="QueryDescriptor"/> instances.
|
|
||||||
/// Returned by <see cref="World.Query"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class QueryBuilder
|
|
||||||
{
|
|
||||||
private readonly HashSet<Type> _with = new();
|
|
||||||
private readonly HashSet<Type> _without = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Requires entities to have a component of type <typeparamref name="T"/>.
|
|
||||||
/// </summary>
|
|
||||||
public QueryBuilder With<T>() where T : struct
|
|
||||||
{
|
|
||||||
_with.Add(typeof(T));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Excludes entities that have a component of type <typeparamref name="T"/>.
|
|
||||||
/// </summary>
|
|
||||||
public QueryBuilder Without<T>() where T : struct
|
|
||||||
{
|
|
||||||
_without.Add(typeof(T));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Builds the query descriptor.
|
|
||||||
/// </summary>
|
|
||||||
public QueryDescriptor Build()
|
|
||||||
{
|
|
||||||
return new QueryDescriptor(
|
|
||||||
new HashSet<Type>(_with),
|
|
||||||
new HashSet<Type>(_without));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
namespace OECS;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Describes a query over the ECS world.
|
|
||||||
/// Composed of a set of required component types ("with") and
|
|
||||||
/// a set of excluded component types ("without").
|
|
||||||
/// </summary>
|
|
||||||
public class QueryDescriptor
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Component types that must be present on matching entities.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlySet<Type> With { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Component types that must NOT be present on matching entities.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlySet<Type> Without { get; }
|
|
||||||
|
|
||||||
private readonly int _hashCode;
|
|
||||||
|
|
||||||
internal QueryDescriptor(HashSet<Type> with, HashSet<Type> without)
|
|
||||||
{
|
|
||||||
With = with;
|
|
||||||
Without = without;
|
|
||||||
_hashCode = ComputeHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if this query has no "with" components (matches nothing).
|
|
||||||
/// </summary>
|
|
||||||
internal bool IsEmpty => With.Count == 0;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if (obj is not QueryDescriptor other) return false;
|
|
||||||
if (With.Count != other.With.Count || Without.Count != other.Without.Count)
|
|
||||||
return false;
|
|
||||||
return With.SetEquals(other.With) && Without.SetEquals(other.Without);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode() => _hashCode;
|
|
||||||
|
|
||||||
private int ComputeHashCode()
|
|
||||||
{
|
|
||||||
var hash = new HashCode();
|
|
||||||
foreach (var t in With.OrderBy(t => t.GUID.ToString()))
|
|
||||||
hash.Add(t);
|
|
||||||
foreach (var t in Without.OrderBy(t => t.GUID.ToString()))
|
|
||||||
hash.Add(t);
|
|
||||||
return hash.ToHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,507 +0,0 @@
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace OECS;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides query execution logic for <see cref="World"/>.
|
|
||||||
/// Iterates one sparse set as the driver and probes the remaining sets
|
|
||||||
/// for membership. The "without" filter is checked after all "with" probes.
|
|
||||||
///
|
|
||||||
/// The singleton entity (ID 1) is automatically excluded from all queries.
|
|
||||||
/// </summary>
|
|
||||||
internal static class QueryExecutor
|
|
||||||
{
|
|
||||||
private const uint SingletonId = 1;
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static bool PassesWithoutFilter(ComponentStore store, Entity entity, IReadOnlySet<Type> withoutTypes)
|
|
||||||
{
|
|
||||||
foreach (var type in withoutTypes)
|
|
||||||
{
|
|
||||||
var set = store.GetSet(type);
|
|
||||||
if (set != null && set.Contains(entity))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── ForEach overloads ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
public static void ForEach<T1>(
|
|
||||||
ComponentStore store,
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1> action)
|
|
||||||
where T1 : struct
|
|
||||||
{
|
|
||||||
var set = store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
if (set == null) return;
|
|
||||||
|
|
||||||
var dense = set.Dense;
|
|
||||||
var denseEntities = set.DenseEntities;
|
|
||||||
var count = set.Count;
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without))
|
|
||||||
continue;
|
|
||||||
action(entity, ref dense[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ForEach<T1, T2>(
|
|
||||||
ComponentStore store,
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2> action)
|
|
||||||
where T1 : struct where T2 : struct
|
|
||||||
{
|
|
||||||
var set1 = store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
if (set1 == null) return;
|
|
||||||
var set2 = store.GetSet(typeof(T2)) as SparseSet<T2>;
|
|
||||||
if (set2 == null) return;
|
|
||||||
|
|
||||||
if (set1.Count <= set2.Count)
|
|
||||||
{
|
|
||||||
IterateTwo(set1, set2, store, query.Without, action);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IterateTwoSwapped(set2, set1, store, query.Without, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void IterateTwo<TDriver, TOther>(
|
|
||||||
SparseSet<TDriver> driveSet,
|
|
||||||
SparseSet<TOther> otherSet,
|
|
||||||
ComponentStore store,
|
|
||||||
IReadOnlySet<Type> withoutTypes,
|
|
||||||
ForEachAction<TDriver, TOther> action)
|
|
||||||
where TDriver : struct where TOther : struct
|
|
||||||
{
|
|
||||||
var dense = driveSet.Dense;
|
|
||||||
var denseEntities = driveSet.DenseEntities;
|
|
||||||
var count = driveSet.Count;
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!otherSet.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, withoutTypes))
|
|
||||||
continue;
|
|
||||||
action(entity, ref dense[i], ref otherSet.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void IterateTwoSwapped<TOther, TDriver>(
|
|
||||||
SparseSet<TDriver> driveSet,
|
|
||||||
SparseSet<TOther> otherSet,
|
|
||||||
ComponentStore store,
|
|
||||||
IReadOnlySet<Type> withoutTypes,
|
|
||||||
ForEachAction<TOther, TDriver> action)
|
|
||||||
where TDriver : struct where TOther : struct
|
|
||||||
{
|
|
||||||
var dense = driveSet.Dense;
|
|
||||||
var denseEntities = driveSet.DenseEntities;
|
|
||||||
var count = driveSet.Count;
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!otherSet.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, withoutTypes))
|
|
||||||
continue;
|
|
||||||
action(entity, ref otherSet.Get(entity), ref dense[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ForEach<T1, T2, T3>(
|
|
||||||
ComponentStore store,
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2, T3> action)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct
|
|
||||||
{
|
|
||||||
var set1 = store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
if (set1 == null) return;
|
|
||||||
var set2 = store.GetSet(typeof(T2)) as SparseSet<T2>;
|
|
||||||
if (set2 == null) return;
|
|
||||||
var set3 = store.GetSet(typeof(T3)) as SparseSet<T3>;
|
|
||||||
if (set3 == null) return;
|
|
||||||
|
|
||||||
// Pick the smallest set as the driver.
|
|
||||||
int c1 = set1.Count, c2 = set2.Count, c3 = set3.Count;
|
|
||||||
|
|
||||||
if (c2 <= c1 && c2 <= c3)
|
|
||||||
{
|
|
||||||
var dense = set2.Dense;
|
|
||||||
var denseEntities = set2.DenseEntities;
|
|
||||||
var count = set2.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref dense[i], ref set3.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c3 <= c1 && c3 <= c2)
|
|
||||||
{
|
|
||||||
var dense = set3.Dense;
|
|
||||||
var denseEntities = set3.DenseEntities;
|
|
||||||
var count = set3.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref dense[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var dense = set1.Dense;
|
|
||||||
var denseEntities = set1.DenseEntities;
|
|
||||||
var count = set1.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref dense[i], ref set2.Get(entity), ref set3.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ForEach<T1, T2, T3, T4>(
|
|
||||||
ComponentStore store,
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2, T3, T4> action)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct
|
|
||||||
{
|
|
||||||
var set1 = store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
if (set1 == null) return;
|
|
||||||
var set2 = store.GetSet(typeof(T2)) as SparseSet<T2>;
|
|
||||||
if (set2 == null) return;
|
|
||||||
var set3 = store.GetSet(typeof(T3)) as SparseSet<T3>;
|
|
||||||
if (set3 == null) return;
|
|
||||||
var set4 = store.GetSet(typeof(T4)) as SparseSet<T4>;
|
|
||||||
if (set4 == null) return;
|
|
||||||
|
|
||||||
// Pick the smallest set as the driver.
|
|
||||||
int c1 = set1.Count, c2 = set2.Count, c3 = set3.Count, c4 = set4.Count;
|
|
||||||
int min = Math.Min(Math.Min(c1, c2), Math.Min(c3, c4));
|
|
||||||
|
|
||||||
if (min == c2)
|
|
||||||
{
|
|
||||||
var dense = set2.Dense;
|
|
||||||
var denseEntities = set2.DenseEntities;
|
|
||||||
var count = set2.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref dense[i], ref set3.Get(entity), ref set4.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c3)
|
|
||||||
{
|
|
||||||
var dense = set3.Dense;
|
|
||||||
var denseEntities = set3.DenseEntities;
|
|
||||||
var count = set3.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref dense[i], ref set4.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c4)
|
|
||||||
{
|
|
||||||
var dense = set4.Dense;
|
|
||||||
var denseEntities = set4.DenseEntities;
|
|
||||||
var count = set4.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref set3.Get(entity), ref dense[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var dense = set1.Dense;
|
|
||||||
var denseEntities = set1.DenseEntities;
|
|
||||||
var count = set1.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref dense[i], ref set2.Get(entity), ref set3.Get(entity), ref set4.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ForEach<T1, T2, T3, T4, T5>(
|
|
||||||
ComponentStore store,
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2, T3, T4, T5> action)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct
|
|
||||||
{
|
|
||||||
var set1 = store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
if (set1 == null) return;
|
|
||||||
var set2 = store.GetSet(typeof(T2)) as SparseSet<T2>;
|
|
||||||
if (set2 == null) return;
|
|
||||||
var set3 = store.GetSet(typeof(T3)) as SparseSet<T3>;
|
|
||||||
if (set3 == null) return;
|
|
||||||
var set4 = store.GetSet(typeof(T4)) as SparseSet<T4>;
|
|
||||||
if (set4 == null) return;
|
|
||||||
var set5 = store.GetSet(typeof(T5)) as SparseSet<T5>;
|
|
||||||
if (set5 == null) return;
|
|
||||||
|
|
||||||
// Pick the smallest set as the driver.
|
|
||||||
int c1 = set1.Count, c2 = set2.Count, c3 = set3.Count, c4 = set4.Count, c5 = set5.Count;
|
|
||||||
int min = Math.Min(Math.Min(Math.Min(c1, c2), Math.Min(c3, c4)), c5);
|
|
||||||
|
|
||||||
if (min == c2)
|
|
||||||
{
|
|
||||||
var dense = set2.Dense;
|
|
||||||
var denseEntities = set2.DenseEntities;
|
|
||||||
var count = set2.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref dense[i], ref set3.Get(entity), ref set4.Get(entity), ref set5.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c3)
|
|
||||||
{
|
|
||||||
var dense = set3.Dense;
|
|
||||||
var denseEntities = set3.DenseEntities;
|
|
||||||
var count = set3.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref dense[i], ref set4.Get(entity), ref set5.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c4)
|
|
||||||
{
|
|
||||||
var dense = set4.Dense;
|
|
||||||
var denseEntities = set4.DenseEntities;
|
|
||||||
var count = set4.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref set3.Get(entity), ref dense[i], ref set5.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c5)
|
|
||||||
{
|
|
||||||
var dense = set5.Dense;
|
|
||||||
var denseEntities = set5.DenseEntities;
|
|
||||||
var count = set5.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref set3.Get(entity), ref set4.Get(entity), ref dense[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var dense = set1.Dense;
|
|
||||||
var denseEntities = set1.DenseEntities;
|
|
||||||
var count = set1.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref dense[i], ref set2.Get(entity), ref set3.Get(entity), ref set4.Get(entity), ref set5.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ForEach<T1, T2, T3, T4, T5, T6>(
|
|
||||||
ComponentStore store,
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2, T3, T4, T5, T6> action)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct
|
|
||||||
{
|
|
||||||
var set1 = store.GetSet(typeof(T1)) as SparseSet<T1>;
|
|
||||||
if (set1 == null) return;
|
|
||||||
var set2 = store.GetSet(typeof(T2)) as SparseSet<T2>;
|
|
||||||
if (set2 == null) return;
|
|
||||||
var set3 = store.GetSet(typeof(T3)) as SparseSet<T3>;
|
|
||||||
if (set3 == null) return;
|
|
||||||
var set4 = store.GetSet(typeof(T4)) as SparseSet<T4>;
|
|
||||||
if (set4 == null) return;
|
|
||||||
var set5 = store.GetSet(typeof(T5)) as SparseSet<T5>;
|
|
||||||
if (set5 == null) return;
|
|
||||||
var set6 = store.GetSet(typeof(T6)) as SparseSet<T6>;
|
|
||||||
if (set6 == null) return;
|
|
||||||
|
|
||||||
// Pick the smallest set as the driver.
|
|
||||||
int c1 = set1.Count, c2 = set2.Count, c3 = set3.Count, c4 = set4.Count, c5 = set5.Count, c6 = set6.Count;
|
|
||||||
int min = Math.Min(Math.Min(Math.Min(c1, c2), Math.Min(c3, c4)), Math.Min(c5, c6));
|
|
||||||
|
|
||||||
if (min == c2)
|
|
||||||
{
|
|
||||||
var dense = set2.Dense;
|
|
||||||
var denseEntities = set2.DenseEntities;
|
|
||||||
var count = set2.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!set6.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref dense[i], ref set3.Get(entity), ref set4.Get(entity), ref set5.Get(entity), ref set6.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c3)
|
|
||||||
{
|
|
||||||
var dense = set3.Dense;
|
|
||||||
var denseEntities = set3.DenseEntities;
|
|
||||||
var count = set3.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!set6.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref dense[i], ref set4.Get(entity), ref set5.Get(entity), ref set6.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c4)
|
|
||||||
{
|
|
||||||
var dense = set4.Dense;
|
|
||||||
var denseEntities = set4.DenseEntities;
|
|
||||||
var count = set4.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!set6.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref set3.Get(entity), ref dense[i], ref set5.Get(entity), ref set6.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c5)
|
|
||||||
{
|
|
||||||
var dense = set5.Dense;
|
|
||||||
var denseEntities = set5.DenseEntities;
|
|
||||||
var count = set5.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!set6.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref set3.Get(entity), ref set4.Get(entity), ref dense[i], ref set6.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (min == c6)
|
|
||||||
{
|
|
||||||
var dense = set6.Dense;
|
|
||||||
var denseEntities = set6.DenseEntities;
|
|
||||||
var count = set6.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set1.Contains(entity)) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref set1.Get(entity), ref set2.Get(entity), ref set3.Get(entity), ref set4.Get(entity), ref set5.Get(entity), ref dense[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var dense = set1.Dense;
|
|
||||||
var denseEntities = set1.DenseEntities;
|
|
||||||
var count = set1.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var entity = denseEntities[i];
|
|
||||||
if (entity.Id == SingletonId) continue;
|
|
||||||
if (!set2.Contains(entity)) continue;
|
|
||||||
if (!set3.Contains(entity)) continue;
|
|
||||||
if (!set4.Contains(entity)) continue;
|
|
||||||
if (!set5.Contains(entity)) continue;
|
|
||||||
if (!set6.Contains(entity)) continue;
|
|
||||||
if (!PassesWithoutFilter(store, entity, query.Without)) continue;
|
|
||||||
action(entity, ref dense[i], ref set2.Get(entity), ref set3.Get(entity), ref set4.Get(entity), ref set5.Get(entity), ref set6.Get(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
274
OECS/World.cs
274
OECS/World.cs
|
|
@ -11,17 +11,12 @@ namespace OECS;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class World : IDisposable
|
public class World : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The reserved entity ID for the singleton entity.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Entity SingletonEntity = new(1, 1);
|
|
||||||
|
|
||||||
private readonly EntityAllocator _allocator;
|
private readonly EntityAllocator _allocator;
|
||||||
private readonly ComponentStore _components;
|
private readonly ComponentStore _components;
|
||||||
private readonly CommandQueue _commands;
|
private readonly CommandQueue _commands;
|
||||||
private readonly RelationshipIndex _relationships;
|
private readonly RelationshipIndex _relationships;
|
||||||
private readonly ChangeBuffer _changes;
|
private readonly ChangeBuffer _changes;
|
||||||
private bool _singletonCreated;
|
private readonly Dictionary<Type, Entity> _singletonEntities = new();
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
// Deferred structural mutation support: when iterating entities,
|
// Deferred structural mutation support: when iterating entities,
|
||||||
|
|
@ -109,10 +104,6 @@ public class World : IDisposable
|
||||||
if (!_allocator.IsAlive(entity))
|
if (!_allocator.IsAlive(entity))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Singleton entity is never destroyed.
|
|
||||||
if (entity.Id == 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Remove all relationships where this entity is the target.
|
// Remove all relationships where this entity is the target.
|
||||||
// Materialize to avoid collection-modified-during-enumeration when
|
// Materialize to avoid collection-modified-during-enumeration when
|
||||||
// OnRemoved mutates the same HashSet we're iterating.
|
// OnRemoved mutates the same HashSet we're iterating.
|
||||||
|
|
@ -316,9 +307,9 @@ public class World : IDisposable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks a component of type <typeparamref name="T"/> on the given entity
|
/// Marks a component of type <typeparamref name="T"/> on the given entity
|
||||||
/// as modified. Only needed outside of iteration scopes — during
|
/// as modified. Only needed outside of iteration scopes — during a
|
||||||
/// <see cref="ForEach"/> or a <c>Select</c> loop, components accessed
|
/// <c>Select</c> loop, components accessed via <see cref="GetComponent{T}"/>
|
||||||
/// via <see cref="GetComponent{T}"/> are auto-marked.
|
/// are auto-marked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void MarkModified<T>(Entity entity) where T : struct
|
public void MarkModified<T>(Entity entity) where T : struct
|
||||||
{
|
{
|
||||||
|
|
@ -355,11 +346,21 @@ public class World : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns an observable that emits changes matching the given query.
|
/// Returns an observable that emits component-level changes matching
|
||||||
/// Only component-level changes whose component type is in the query's
|
/// the given query's With types and excluding its Without types.
|
||||||
/// "with" set and not in the "without" set are emitted.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Observable<EntityChange> ObserveQuery(QueryDescriptor query)
|
public Observable<EntityChange> ObserveQuery<T1>(Query<T1> query = default)
|
||||||
|
where T1 : struct
|
||||||
|
{
|
||||||
|
return _changes.ObserveQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an observable that emits component-level changes matching
|
||||||
|
/// the given query.
|
||||||
|
/// </summary>
|
||||||
|
public Observable<EntityChange> ObserveQuery<T1, T2>(Query<T1, T2> query = default)
|
||||||
|
where T1 : struct where T2 : struct
|
||||||
{
|
{
|
||||||
return _changes.ObserveQuery(query);
|
return _changes.ObserveQuery(query);
|
||||||
}
|
}
|
||||||
|
|
@ -368,13 +369,16 @@ public class World : IDisposable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets (adds or replaces) a singleton component of type <typeparamref name="T"/>.
|
/// Sets (adds or replaces) a singleton component of type <typeparamref name="T"/>.
|
||||||
/// Singletons are stored on a reserved entity that is never destroyed
|
/// Each singleton component type gets its own dedicated entity.
|
||||||
/// and excluded from normal queries.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetSingleton<T>(T component) where T : struct
|
public void SetSingleton<T>(T component) where T : struct
|
||||||
{
|
{
|
||||||
EnsureSingleton();
|
if (!_singletonEntities.TryGetValue(typeof(T), out var entity))
|
||||||
AddComponent(SingletonEntity, component);
|
{
|
||||||
|
entity = _allocator.Allocate();
|
||||||
|
_singletonEntities[typeof(T)] = entity;
|
||||||
|
}
|
||||||
|
AddComponent(entity, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -383,8 +387,7 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ref T GetSingleton<T>() where T : struct
|
public ref T GetSingleton<T>() where T : struct
|
||||||
{
|
{
|
||||||
EnsureSingleton();
|
return ref GetComponent<T>(GetSingletonEntity<T>());
|
||||||
return ref GetComponent<T>(SingletonEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -393,8 +396,7 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T ReadSingleton<T>() where T : struct
|
public T ReadSingleton<T>() where T : struct
|
||||||
{
|
{
|
||||||
EnsureSingleton();
|
return ReadComponent<T>(GetSingletonEntity<T>());
|
||||||
return ReadComponent<T>(SingletonEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -402,7 +404,8 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasSingleton<T>() where T : struct
|
public bool HasSingleton<T>() where T : struct
|
||||||
{
|
{
|
||||||
return HasComponent<T>(SingletonEntity);
|
return _singletonEntities.TryGetValue(typeof(T), out var entity)
|
||||||
|
&& HasComponent<T>(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -411,20 +414,22 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RemoveSingleton<T>() where T : struct
|
public void RemoveSingleton<T>() where T : struct
|
||||||
{
|
{
|
||||||
RemoveComponent<T>(SingletonEntity);
|
if (_singletonEntities.TryGetValue(typeof(T), out var entity))
|
||||||
|
RemoveComponent<T>(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EnsureSingleton()
|
/// <summary>
|
||||||
|
/// Gets the entity backing the singleton of type <typeparamref name="T"/>,
|
||||||
|
/// creating it if it doesn't exist yet.
|
||||||
|
/// </summary>
|
||||||
|
private Entity GetSingletonEntity<T>() where T : struct
|
||||||
{
|
{
|
||||||
if (_singletonCreated)
|
if (!_singletonEntities.TryGetValue(typeof(T), out var entity))
|
||||||
return;
|
{
|
||||||
|
entity = _allocator.Allocate();
|
||||||
_singletonCreated = true;
|
_singletonEntities[typeof(T)] = entity;
|
||||||
|
}
|
||||||
// Manually register the singleton entity in the allocator so
|
return entity;
|
||||||
// IsAlive returns true for it. We bypass Allocate() because
|
|
||||||
// the singleton has a fixed ID and version.
|
|
||||||
_allocator.RegisterSingleton(SingletonEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Relationships ────────────────────────────────────────────────
|
// ── Relationships ────────────────────────────────────────────────
|
||||||
|
|
@ -457,140 +462,7 @@ public class World : IDisposable
|
||||||
_commands.ExecuteAll(this);
|
_commands.ExecuteAll(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Queries ──────────────────────────────────────────────────────
|
// ── Iteration Lifecycle ──────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a new <see cref="QueryBuilder"/> for constructing queries.
|
|
||||||
/// </summary>
|
|
||||||
public QueryBuilder Query() => new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Iterates all entities matching the query, providing ref access to
|
|
||||||
/// one component type. Structural mutations (AddComponent, RemoveComponent,
|
|
||||||
/// DestroyEntity) made during iteration are deferred and applied after
|
|
||||||
/// the iteration completes.
|
|
||||||
/// </summary>
|
|
||||||
public void ForEach<T1>(
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1> action)
|
|
||||||
where T1 : struct
|
|
||||||
{
|
|
||||||
ValidateQueryTypes(query, typeof(T1));
|
|
||||||
BeginIteration();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QueryExecutor.ForEach(_components, query, action);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
EndIteration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Iterates all entities matching the query, providing ref access to
|
|
||||||
/// two component types. Structural mutations are deferred.
|
|
||||||
/// </summary>
|
|
||||||
public void ForEach<T1, T2>(
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2> action)
|
|
||||||
where T1 : struct where T2 : struct
|
|
||||||
{
|
|
||||||
ValidateQueryTypes(query, typeof(T1), typeof(T2));
|
|
||||||
BeginIteration();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QueryExecutor.ForEach(_components, query, action);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
EndIteration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Iterates all entities matching the query, providing ref access to
|
|
||||||
/// three component types. Structural mutations are deferred.
|
|
||||||
/// </summary>
|
|
||||||
public void ForEach<T1, T2, T3>(
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2, T3> action)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct
|
|
||||||
{
|
|
||||||
ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3));
|
|
||||||
BeginIteration();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QueryExecutor.ForEach(_components, query, action);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
EndIteration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Iterates all entities matching the query, providing ref access to
|
|
||||||
/// four component types. Structural mutations are deferred.
|
|
||||||
/// </summary>
|
|
||||||
public void ForEach<T1, T2, T3, T4>(
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2, T3, T4> action)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct
|
|
||||||
{
|
|
||||||
ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4));
|
|
||||||
BeginIteration();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QueryExecutor.ForEach(_components, query, action);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
EndIteration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Iterates all entities matching the query, providing ref access to
|
|
||||||
/// five component types. Structural mutations are deferred.
|
|
||||||
/// </summary>
|
|
||||||
public void ForEach<T1, T2, T3, T4, T5>(
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2, T3, T4, T5> action)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct
|
|
||||||
{
|
|
||||||
ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5));
|
|
||||||
BeginIteration();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QueryExecutor.ForEach(_components, query, action);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
EndIteration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Iterates all entities matching the query, providing ref access to
|
|
||||||
/// six component types. Structural mutations are deferred.
|
|
||||||
/// </summary>
|
|
||||||
public void ForEach<T1, T2, T3, T4, T5, T6>(
|
|
||||||
QueryDescriptor query,
|
|
||||||
ForEachAction<T1, T2, T3, T4, T5, T6> action)
|
|
||||||
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct
|
|
||||||
{
|
|
||||||
ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6));
|
|
||||||
BeginIteration();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QueryExecutor.ForEach(_components, query, action);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
EndIteration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Begins an iteration scope. Structural mutations are buffered
|
/// Begins an iteration scope. Structural mutations are buffered
|
||||||
|
|
@ -670,6 +542,33 @@ public class World : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ComponentStore Components => _components;
|
internal ComponentStore Components => _components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entity that backs the singleton of type <typeparamref name="T"/>,
|
||||||
|
/// or <see cref="Entity.Null"/> if the singleton has not been set.
|
||||||
|
/// </summary>
|
||||||
|
internal Entity GetSingletonEntityOrNull<T>() where T : struct
|
||||||
|
{
|
||||||
|
_singletonEntities.TryGetValue(typeof(T), out var entity);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers an existing entity as the backing entity for a singleton component.
|
||||||
|
/// Used during deserialization to restore singleton state.
|
||||||
|
/// </summary>
|
||||||
|
internal void RegisterSingletonEntity(Type componentType, Entity entity)
|
||||||
|
{
|
||||||
|
_singletonEntities[componentType] = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the given entity is a singleton entity (backs any singleton component).
|
||||||
|
/// </summary>
|
||||||
|
internal bool IsSingletonEntity(Entity entity)
|
||||||
|
{
|
||||||
|
return _singletonEntities.ContainsValue(entity);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void ThrowIfNotAlive(Entity entity)
|
private void ThrowIfNotAlive(Entity entity)
|
||||||
|
|
@ -678,39 +577,6 @@ public class World : IDisposable
|
||||||
throw new InvalidOperationException($"Entity {entity} is not alive.");
|
throw new InvalidOperationException($"Entity {entity} is not alive.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validates that the ForEach type parameters match the query's
|
|
||||||
/// With set. When the With set is empty (only Without filters are
|
|
||||||
/// specified), any ForEach type parameters are accepted.
|
|
||||||
/// </summary>
|
|
||||||
private static void ValidateQueryTypes(QueryDescriptor query, params Type[] forEachTypes)
|
|
||||||
{
|
|
||||||
// When With is empty, the query is using only Without filters.
|
|
||||||
// The ForEach type parameters are the sole source of truth.
|
|
||||||
if (query.With.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (query.With.Count != forEachTypes.Length)
|
|
||||||
ThrowMismatch(query, forEachTypes);
|
|
||||||
|
|
||||||
foreach (var t in forEachTypes)
|
|
||||||
{
|
|
||||||
if (!query.With.Contains(t))
|
|
||||||
ThrowMismatch(query, forEachTypes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ThrowMismatch(QueryDescriptor query, Type[] forEachTypes)
|
|
||||||
{
|
|
||||||
var queryTypes = string.Join(", ", query.With.Select(t => t.Name));
|
|
||||||
var forEachTypeNames = string.Join(", ", forEachTypes.Select(t => t.Name));
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"ForEach type parameters [{forEachTypeNames}] do not match " +
|
|
||||||
$"the query's With types [{queryTypes}]. " +
|
|
||||||
$"Ensure the ForEach type parameters exactly match the types " +
|
|
||||||
$"passed to QueryBuilder.With<T>().");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Cleanup ───────────────────────────────────────────────────────
|
// ── Cleanup ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,682 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace OECS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides zero-allocation ref struct iterators and entity-finding
|
||||||
|
/// extension methods for querying a <see cref="World"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class WorldQueryExtensions
|
||||||
|
{
|
||||||
|
// ── Select (ref struct enumerators) ──────────────────────────────
|
||||||
|
|
||||||
|
public static Select1<T1> Select<T1>(this World world, Query<T1> query = default)
|
||||||
|
where T1 : struct
|
||||||
|
{
|
||||||
|
return new Select1<T1>(world, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Select2<T1, T2> Select<T1, T2>(this World world, Query<T1, T2> query = default)
|
||||||
|
where T1 : struct where T2 : struct
|
||||||
|
{
|
||||||
|
return new Select2<T1, T2>(world, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Select3<T1, T2, T3> Select<T1, T2, T3>(this World world, Query<T1, T2, T3> query = default)
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct
|
||||||
|
{
|
||||||
|
return new Select3<T1, T2, T3>(world, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Select4<T1, T2, T3, T4> Select<T1, T2, T3, T4>(this World world, Query<T1, T2, T3, T4> query = default)
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct
|
||||||
|
{
|
||||||
|
return new Select4<T1, T2, T3, T4>(world, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Select5<T1, T2, T3, T4, T5> Select<T1, T2, T3, T4, T5>(this World world, Query<T1, T2, T3, T4, T5> query = default)
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct
|
||||||
|
{
|
||||||
|
return new Select5<T1, T2, T3, T4, T5>(world, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Select6<T1, T2, T3, T4, T5, T6> Select<T1, T2, T3, T4, T5, T6>(this World world, Query<T1, T2, T3, T4, T5, T6> query = default)
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct
|
||||||
|
{
|
||||||
|
return new Select6<T1, T2, T3, T4, T5, T6>(world, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FindEntity / FindEntities ────────────────────────────────────
|
||||||
|
|
||||||
|
public static Entity FindEntity<T1>(this World world, Query<T1> query = default)
|
||||||
|
where T1 : struct
|
||||||
|
{
|
||||||
|
using var iter = new Select1<T1>(world, query);
|
||||||
|
return iter.MoveNext() ? iter.Entity : Entity.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Entity FindEntity<T1, T2>(this World world, Query<T1, T2> query = default)
|
||||||
|
where T1 : struct where T2 : struct
|
||||||
|
{
|
||||||
|
using var iter = new Select2<T1, T2>(world, query);
|
||||||
|
return iter.MoveNext() ? iter.Entity : Entity.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Entity FindEntity<T1, T2, T3>(this World world, Query<T1, T2, T3> query = default)
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct
|
||||||
|
{
|
||||||
|
using var iter = new Select3<T1, T2, T3>(world, query);
|
||||||
|
return iter.MoveNext() ? iter.Entity : Entity.Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Entity> FindEntities<T1>(this World world, Query<T1> query = default)
|
||||||
|
where T1 : struct
|
||||||
|
{
|
||||||
|
var list = new List<Entity>();
|
||||||
|
using var iter = new Select1<T1>(world, query);
|
||||||
|
while (iter.MoveNext()) list.Add(iter.Entity);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Entity> FindEntities<T1, T2>(this World world, Query<T1, T2> query = default)
|
||||||
|
where T1 : struct where T2 : struct
|
||||||
|
{
|
||||||
|
var list = new List<Entity>();
|
||||||
|
using var iter = new Select2<T1, T2>(world, query);
|
||||||
|
while (iter.MoveNext()) list.Add(iter.Entity);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Entity> FindEntities<T1, T2, T3>(this World world, Query<T1, T2, T3> query = default)
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct
|
||||||
|
{
|
||||||
|
var list = new List<Entity>();
|
||||||
|
using var iter = new Select3<T1, T2, T3>(world, query);
|
||||||
|
while (iter.MoveNext()) list.Add(iter.Entity);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Ref struct enumerators ───────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>Ref struct iterator for queries with one component type.</summary>
|
||||||
|
public ref struct Select1<T1> where T1 : struct
|
||||||
|
{
|
||||||
|
private readonly World _world;
|
||||||
|
private readonly SparseSet<T1>? _set;
|
||||||
|
private readonly ComponentStore _store;
|
||||||
|
private readonly HashSet<Type>? _without;
|
||||||
|
private int _index;
|
||||||
|
private readonly int _count;
|
||||||
|
|
||||||
|
internal Select1(World world, Query<T1> query)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
_store = world.Components;
|
||||||
|
_without = query.WithoutTypes;
|
||||||
|
_set = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
|
_count = _set?.Count ?? 0;
|
||||||
|
_index = -1;
|
||||||
|
_world.BeginIteration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The current entity handle.</summary>
|
||||||
|
public Entity Entity { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>The current enumerator (required for foreach duck-typing).</summary>
|
||||||
|
public Select1<T1> Current => this;
|
||||||
|
|
||||||
|
/// <summary>Read-write reference to the current entity's component of type <typeparamref name="T1"/>.</summary>
|
||||||
|
public ref T1 Item1 => ref _set!.Get(Entity);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_set == null) return false;
|
||||||
|
while (++_index < _count)
|
||||||
|
{
|
||||||
|
Entity = _set.DenseEntities[_index];
|
||||||
|
if (!PassesWithout()) continue;
|
||||||
|
if (_world.IsSingletonEntity(Entity)) continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Select1<T1> GetEnumerator() => this;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_world.EndIteration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PassesWithout()
|
||||||
|
{
|
||||||
|
if (_without == null) return true;
|
||||||
|
foreach (var type in _without)
|
||||||
|
{
|
||||||
|
var set = _store.GetSet(type);
|
||||||
|
if (set != null && set.Contains(Entity))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ref struct iterator for queries with two component types. Drives from the smaller set.</summary>
|
||||||
|
public ref struct Select2<T1, T2> where T1 : struct where T2 : struct
|
||||||
|
{
|
||||||
|
private readonly World _world;
|
||||||
|
private readonly SparseSet<T1>? _set1;
|
||||||
|
private readonly SparseSet<T2>? _set2;
|
||||||
|
private readonly ComponentStore _store;
|
||||||
|
private readonly HashSet<Type>? _without;
|
||||||
|
private int _index;
|
||||||
|
private readonly int _count;
|
||||||
|
private readonly bool _swapped;
|
||||||
|
|
||||||
|
internal Select2(World world, Query<T1, T2> query)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
_store = world.Components;
|
||||||
|
_without = query.WithoutTypes;
|
||||||
|
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
|
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
||||||
|
|
||||||
|
int c1 = _set1?.Count ?? 0;
|
||||||
|
int c2 = _set2?.Count ?? 0;
|
||||||
|
_swapped = c2 < c1;
|
||||||
|
_count = _swapped ? c2 : c1;
|
||||||
|
_index = -1;
|
||||||
|
_world.BeginIteration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity Entity { get; private set; }
|
||||||
|
public Select2<T1, T2> Current => this;
|
||||||
|
public ref T1 Item1 => ref _set1!.Get(Entity);
|
||||||
|
public ref T2 Item2 => ref _set2!.Get(Entity);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_set1 == null || _set2 == null) return false;
|
||||||
|
while (++_index < _count)
|
||||||
|
{
|
||||||
|
if (_swapped)
|
||||||
|
{
|
||||||
|
Entity = _set2!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Entity = _set1.DenseEntities[_index];
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
}
|
||||||
|
if (!PassesWithout()) continue;
|
||||||
|
if (_world.IsSingletonEntity(Entity)) continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Select2<T1, T2> GetEnumerator() => this;
|
||||||
|
|
||||||
|
public void Dispose() => _world.EndIteration();
|
||||||
|
|
||||||
|
private bool PassesWithout()
|
||||||
|
{
|
||||||
|
if (_without == null) return true;
|
||||||
|
foreach (var type in _without)
|
||||||
|
{
|
||||||
|
var set = _store.GetSet(type);
|
||||||
|
if (set != null && set.Contains(Entity))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ref struct iterator for queries with three component types. Drives from the smallest set.</summary>
|
||||||
|
public ref struct Select3<T1, T2, T3>
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct
|
||||||
|
{
|
||||||
|
private readonly World _world;
|
||||||
|
private readonly SparseSet<T1>? _set1;
|
||||||
|
private readonly SparseSet<T2>? _set2;
|
||||||
|
private readonly SparseSet<T3>? _set3;
|
||||||
|
private readonly ComponentStore _store;
|
||||||
|
private readonly HashSet<Type>? _without;
|
||||||
|
private int _index;
|
||||||
|
private readonly int _count;
|
||||||
|
private readonly int _driver; // 0 = set1, 1 = set2, 2 = set3
|
||||||
|
|
||||||
|
internal Select3(World world, Query<T1, T2, T3> query)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
_store = world.Components;
|
||||||
|
_without = query.WithoutTypes;
|
||||||
|
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
|
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
||||||
|
_set3 = _store.GetSet(typeof(T3)) as SparseSet<T3>;
|
||||||
|
|
||||||
|
int c1 = _set1?.Count ?? 0;
|
||||||
|
int c2 = _set2?.Count ?? 0;
|
||||||
|
int c3 = _set3?.Count ?? 0;
|
||||||
|
|
||||||
|
if (c2 <= c1 && c2 <= c3) { _driver = 1; _count = c2; }
|
||||||
|
else if (c3 <= c1 && c3 <= c2) { _driver = 2; _count = c3; }
|
||||||
|
else { _driver = 0; _count = c1; }
|
||||||
|
|
||||||
|
_index = -1;
|
||||||
|
_world.BeginIteration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity Entity { get; private set; }
|
||||||
|
public Select3<T1, T2, T3> Current => this;
|
||||||
|
public ref T1 Item1 => ref _set1!.Get(Entity);
|
||||||
|
public ref T2 Item2 => ref _set2!.Get(Entity);
|
||||||
|
public ref T3 Item3 => ref _set3!.Get(Entity);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_set1 == null || _set2 == null || _set3 == null) return false;
|
||||||
|
while (++_index < _count)
|
||||||
|
{
|
||||||
|
switch (_driver)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
Entity = _set1.DenseEntities[_index];
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
Entity = _set2!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
Entity = _set3!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!PassesWithout()) continue;
|
||||||
|
if (_world.IsSingletonEntity(Entity)) continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Select3<T1, T2, T3> GetEnumerator() => this;
|
||||||
|
|
||||||
|
public void Dispose() => _world.EndIteration();
|
||||||
|
|
||||||
|
private bool PassesWithout()
|
||||||
|
{
|
||||||
|
if (_without == null) return true;
|
||||||
|
foreach (var type in _without)
|
||||||
|
{
|
||||||
|
var set = _store.GetSet(type);
|
||||||
|
if (set != null && set.Contains(Entity))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ref struct iterator for queries with four component types.</summary>
|
||||||
|
public ref struct Select4<T1, T2, T3, T4>
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct
|
||||||
|
{
|
||||||
|
private readonly World _world;
|
||||||
|
private readonly SparseSet<T1>? _set1;
|
||||||
|
private readonly SparseSet<T2>? _set2;
|
||||||
|
private readonly SparseSet<T3>? _set3;
|
||||||
|
private readonly SparseSet<T4>? _set4;
|
||||||
|
private readonly ComponentStore _store;
|
||||||
|
private readonly HashSet<Type>? _without;
|
||||||
|
private int _index;
|
||||||
|
private readonly int _count;
|
||||||
|
private readonly int _driver;
|
||||||
|
|
||||||
|
internal Select4(World world, Query<T1, T2, T3, T4> query)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
_store = world.Components;
|
||||||
|
_without = query.WithoutTypes;
|
||||||
|
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
|
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
||||||
|
_set3 = _store.GetSet(typeof(T3)) as SparseSet<T3>;
|
||||||
|
_set4 = _store.GetSet(typeof(T4)) as SparseSet<T4>;
|
||||||
|
|
||||||
|
int c1 = _set1?.Count ?? 0, c2 = _set2?.Count ?? 0;
|
||||||
|
int c3 = _set3?.Count ?? 0, c4 = _set4?.Count ?? 0;
|
||||||
|
int min = c1;
|
||||||
|
_driver = 0;
|
||||||
|
if (c2 < min) { min = c2; _driver = 1; }
|
||||||
|
if (c3 < min) { min = c3; _driver = 2; }
|
||||||
|
if (c4 < min) { min = c4; _driver = 3; }
|
||||||
|
_count = min;
|
||||||
|
|
||||||
|
_index = -1;
|
||||||
|
_world.BeginIteration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity Entity { get; private set; }
|
||||||
|
public Select4<T1, T2, T3, T4> Current => this;
|
||||||
|
public ref T1 Item1 => ref _set1!.Get(Entity);
|
||||||
|
public ref T2 Item2 => ref _set2!.Get(Entity);
|
||||||
|
public ref T3 Item3 => ref _set3!.Get(Entity);
|
||||||
|
public ref T4 Item4 => ref _set4!.Get(Entity);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_set1 == null || _set2 == null || _set3 == null || _set4 == null) return false;
|
||||||
|
while (++_index < _count)
|
||||||
|
{
|
||||||
|
switch (_driver)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
Entity = _set1.DenseEntities[_index];
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
Entity = _set2!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
Entity = _set3!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
Entity = _set4!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!PassesWithout()) continue;
|
||||||
|
if (_world.IsSingletonEntity(Entity)) continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Select4<T1, T2, T3, T4> GetEnumerator() => this;
|
||||||
|
|
||||||
|
public void Dispose() => _world.EndIteration();
|
||||||
|
|
||||||
|
private bool PassesWithout()
|
||||||
|
{
|
||||||
|
if (_without == null) return true;
|
||||||
|
foreach (var type in _without)
|
||||||
|
{
|
||||||
|
var set = _store.GetSet(type);
|
||||||
|
if (set != null && set.Contains(Entity))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ref struct iterator for queries with five component types.</summary>
|
||||||
|
public ref struct Select5<T1, T2, T3, T4, T5>
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct
|
||||||
|
{
|
||||||
|
private readonly World _world;
|
||||||
|
private readonly SparseSet<T1>? _set1;
|
||||||
|
private readonly SparseSet<T2>? _set2;
|
||||||
|
private readonly SparseSet<T3>? _set3;
|
||||||
|
private readonly SparseSet<T4>? _set4;
|
||||||
|
private readonly SparseSet<T5>? _set5;
|
||||||
|
private readonly ComponentStore _store;
|
||||||
|
private readonly HashSet<Type>? _without;
|
||||||
|
private int _index;
|
||||||
|
private readonly int _count;
|
||||||
|
private readonly int _driver;
|
||||||
|
|
||||||
|
internal Select5(World world, Query<T1, T2, T3, T4, T5> query)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
_store = world.Components;
|
||||||
|
_without = query.WithoutTypes;
|
||||||
|
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
|
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
||||||
|
_set3 = _store.GetSet(typeof(T3)) as SparseSet<T3>;
|
||||||
|
_set4 = _store.GetSet(typeof(T4)) as SparseSet<T4>;
|
||||||
|
_set5 = _store.GetSet(typeof(T5)) as SparseSet<T5>;
|
||||||
|
|
||||||
|
int c1 = _set1?.Count ?? 0, c2 = _set2?.Count ?? 0;
|
||||||
|
int c3 = _set3?.Count ?? 0, c4 = _set4?.Count ?? 0;
|
||||||
|
int c5 = _set5?.Count ?? 0;
|
||||||
|
int min = c1;
|
||||||
|
_driver = 0;
|
||||||
|
if (c2 < min) { min = c2; _driver = 1; }
|
||||||
|
if (c3 < min) { min = c3; _driver = 2; }
|
||||||
|
if (c4 < min) { min = c4; _driver = 3; }
|
||||||
|
if (c5 < min) { min = c5; _driver = 4; }
|
||||||
|
_count = min;
|
||||||
|
|
||||||
|
_index = -1;
|
||||||
|
_world.BeginIteration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity Entity { get; private set; }
|
||||||
|
public Select5<T1, T2, T3, T4, T5> Current => this;
|
||||||
|
public ref T1 Item1 => ref _set1!.Get(Entity);
|
||||||
|
public ref T2 Item2 => ref _set2!.Get(Entity);
|
||||||
|
public ref T3 Item3 => ref _set3!.Get(Entity);
|
||||||
|
public ref T4 Item4 => ref _set4!.Get(Entity);
|
||||||
|
public ref T5 Item5 => ref _set5!.Get(Entity);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_set1 == null || _set2 == null || _set3 == null || _set4 == null || _set5 == null) return false;
|
||||||
|
while (++_index < _count)
|
||||||
|
{
|
||||||
|
switch (_driver)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
Entity = _set1.DenseEntities[_index];
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
Entity = _set2!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
Entity = _set3!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
Entity = _set4!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
Entity = _set5!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!PassesWithout()) continue;
|
||||||
|
if (_world.IsSingletonEntity(Entity)) continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Select5<T1, T2, T3, T4, T5> GetEnumerator() => this;
|
||||||
|
|
||||||
|
public void Dispose() => _world.EndIteration();
|
||||||
|
|
||||||
|
private bool PassesWithout()
|
||||||
|
{
|
||||||
|
if (_without == null) return true;
|
||||||
|
foreach (var type in _without)
|
||||||
|
{
|
||||||
|
var set = _store.GetSet(type);
|
||||||
|
if (set != null && set.Contains(Entity))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ref struct iterator for queries with six component types.</summary>
|
||||||
|
public ref struct Select6<T1, T2, T3, T4, T5, T6>
|
||||||
|
where T1 : struct where T2 : struct where T3 : struct
|
||||||
|
where T4 : struct where T5 : struct where T6 : struct
|
||||||
|
{
|
||||||
|
private readonly World _world;
|
||||||
|
private readonly SparseSet<T1>? _set1;
|
||||||
|
private readonly SparseSet<T2>? _set2;
|
||||||
|
private readonly SparseSet<T3>? _set3;
|
||||||
|
private readonly SparseSet<T4>? _set4;
|
||||||
|
private readonly SparseSet<T5>? _set5;
|
||||||
|
private readonly SparseSet<T6>? _set6;
|
||||||
|
private readonly ComponentStore _store;
|
||||||
|
private readonly HashSet<Type>? _without;
|
||||||
|
private int _index;
|
||||||
|
private readonly int _count;
|
||||||
|
private readonly int _driver;
|
||||||
|
|
||||||
|
internal Select6(World world, Query<T1, T2, T3, T4, T5, T6> query)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
_store = world.Components;
|
||||||
|
_without = query.WithoutTypes;
|
||||||
|
_set1 = _store.GetSet(typeof(T1)) as SparseSet<T1>;
|
||||||
|
_set2 = _store.GetSet(typeof(T2)) as SparseSet<T2>;
|
||||||
|
_set3 = _store.GetSet(typeof(T3)) as SparseSet<T3>;
|
||||||
|
_set4 = _store.GetSet(typeof(T4)) as SparseSet<T4>;
|
||||||
|
_set5 = _store.GetSet(typeof(T5)) as SparseSet<T5>;
|
||||||
|
_set6 = _store.GetSet(typeof(T6)) as SparseSet<T6>;
|
||||||
|
|
||||||
|
int c1 = _set1?.Count ?? 0, c2 = _set2?.Count ?? 0;
|
||||||
|
int c3 = _set3?.Count ?? 0, c4 = _set4?.Count ?? 0;
|
||||||
|
int c5 = _set5?.Count ?? 0, c6 = _set6?.Count ?? 0;
|
||||||
|
int min = c1;
|
||||||
|
_driver = 0;
|
||||||
|
if (c2 < min) { min = c2; _driver = 1; }
|
||||||
|
if (c3 < min) { min = c3; _driver = 2; }
|
||||||
|
if (c4 < min) { min = c4; _driver = 3; }
|
||||||
|
if (c5 < min) { min = c5; _driver = 4; }
|
||||||
|
if (c6 < min) { min = c6; _driver = 5; }
|
||||||
|
_count = min;
|
||||||
|
|
||||||
|
_index = -1;
|
||||||
|
_world.BeginIteration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity Entity { get; private set; }
|
||||||
|
public Select6<T1, T2, T3, T4, T5, T6> Current => this;
|
||||||
|
public ref T1 Item1 => ref _set1!.Get(Entity);
|
||||||
|
public ref T2 Item2 => ref _set2!.Get(Entity);
|
||||||
|
public ref T3 Item3 => ref _set3!.Get(Entity);
|
||||||
|
public ref T4 Item4 => ref _set4!.Get(Entity);
|
||||||
|
public ref T5 Item5 => ref _set5!.Get(Entity);
|
||||||
|
public ref T6 Item6 => ref _set6!.Get(Entity);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_set1 == null || _set2 == null || _set3 == null || _set4 == null || _set5 == null || _set6 == null) return false;
|
||||||
|
while (++_index < _count)
|
||||||
|
{
|
||||||
|
switch (_driver)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
Entity = _set1.DenseEntities[_index];
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
if (!_set6.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
Entity = _set2!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
if (!_set6.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
Entity = _set3!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
if (!_set6.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
Entity = _set4!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
if (!_set6.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
Entity = _set5!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
if (!_set6.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
Entity = _set6!.DenseEntities[_index];
|
||||||
|
if (!_set1.Contains(Entity)) continue;
|
||||||
|
if (!_set2.Contains(Entity)) continue;
|
||||||
|
if (!_set3.Contains(Entity)) continue;
|
||||||
|
if (!_set4.Contains(Entity)) continue;
|
||||||
|
if (!_set5.Contains(Entity)) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!PassesWithout()) continue;
|
||||||
|
if (_world.IsSingletonEntity(Entity)) continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Select6<T1, T2, T3, T4, T5, T6> GetEnumerator() => this;
|
||||||
|
|
||||||
|
public void Dispose() => _world.EndIteration();
|
||||||
|
|
||||||
|
private bool PassesWithout()
|
||||||
|
{
|
||||||
|
if (_without == null) return true;
|
||||||
|
foreach (var type in _without)
|
||||||
|
{
|
||||||
|
var set = _store.GetSet(type);
|
||||||
|
if (set != null && set.Contains(Entity))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -73,17 +73,19 @@ public static class WorldSerializer
|
||||||
foreach (var desc in ComponentRegistry.Descriptors)
|
foreach (var desc in ComponentRegistry.Descriptors)
|
||||||
lookup[desc.TypeName] = desc;
|
lookup[desc.TypeName] = desc;
|
||||||
|
|
||||||
|
// First pass: identify which entities are singleton backings.
|
||||||
|
// Singletons are registered in ComponentRegistry with a known marker.
|
||||||
|
var singletonTypes = new HashSet<Type>();
|
||||||
|
foreach (var desc in ComponentRegistry.Descriptors)
|
||||||
|
{
|
||||||
|
if (desc.IsSingleton)
|
||||||
|
singletonTypes.Add(desc.Type);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var es in snapshot.Entities)
|
foreach (var es in snapshot.Entities)
|
||||||
{
|
{
|
||||||
var entity = world.CreateEntity(es.Entity);
|
var entity = world.CreateEntity(es.Entity);
|
||||||
|
bool isSingleton = false;
|
||||||
// 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)
|
||||||
{
|
{
|
||||||
|
|
@ -93,10 +95,25 @@ public static class WorldSerializer
|
||||||
$"Ensure the component type is used with the World " +
|
$"Ensure the component type is used with the World " +
|
||||||
$"API so the source generator can discover it.");
|
$"API so the source generator can discover it.");
|
||||||
|
|
||||||
|
if (desc.IsSingleton)
|
||||||
|
isSingleton = true;
|
||||||
|
|
||||||
// Deserialize and add via the typed internal method.
|
// Deserialize and add via the typed internal method.
|
||||||
var component = desc.Deserialize(ce.Data);
|
var component = desc.Deserialize(ce.Data);
|
||||||
world.AddComponentBoxed(entity, component, desc.Type);
|
world.AddComponentBoxed(entity, component, desc.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the entity has a singleton component, register it.
|
||||||
|
if (isSingleton)
|
||||||
|
{
|
||||||
|
foreach (var ce in es.Components)
|
||||||
|
{
|
||||||
|
if (lookup.TryGetValue(ce.TypeName, out var desc) && desc.IsSingleton)
|
||||||
|
{
|
||||||
|
world.RegisterSingletonEntity(desc.Type, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue