Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,33 @@ END TEMPLATE-->

### New features

*None yet*
- `EntityQuery<TComp1>` now implements `IEnumerable<Entity<TComp1>>` and can be used with `foreach` performantly.
- `EntityQuery<TComp1, TComp2>`, `EntityQuery<TComp1, TComp2, TComp3>`, and `EntityQuery<TComp1, TComp2, TComp3, TComp4>`
have been added and implement `IEnumerable`.
<br/><br/>
They're direct counterparts to `EntityQuery<TComp1>` with similar methods and constructors.
Additionally, like `EntityQuery<TComp1>`, all of these types can be resolved with a `[Dependency]` in systems.
- `DynamicEntityQuery` has been added. It implements component queries for an unbounded set of components, with the
ability to mark the presence of specific components as optional (may be null) or excluded (query doesn't match
entities with that component).
<br/><br/>
It is currently primarily an implementation detail of `EntityQuery<>` but can be used directly and will lead to
expanded functionality in the future.

### Bugfixes

*None yet*

### Other

*None yet*
- `EntityQueryEnumerator<TComp1>`, `EntityQueryEnumerator<TComp1, TComp2>`, `EntityQueryEnumerator<TComp1, TComp2, TComp3>`,
`EntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>`, `AllEntityQueryEnumerator<TComp1>`, `AllEntityQueryEnumerator<TComp1, TComp2>`,
`AllEntityQueryEnumerator<TComp1, TComp2, TComp3>`, `AllEntityQueryEnumerator<TComp1, TComp2, TComp3, TComp4>`,
and `ComponentQueryEnumerator` are now obsolete.
- The EntityManager methods `EntityQuery<TComp1>`, `EntityQuery<TComp1, TComp2>`, `EntityQuery<TComp1, TComp2, TComp3>`,
and `EntityQuery<TComp1, TComp2, TComp3, TComp4>`, `EntityQueryEnumerator`, `AllEntityQueryEnumerator`,
`ComponentQueryEnumerator`, `AllComponentsList`, `AllEntityUids`, `AllEntityUids`, `AllEntities`, and `AllComponents`
are now obsolete.

### Internal

Expand Down
138 changes: 136 additions & 2 deletions Robust.Shared.IntegrationTests/GameObjects/IEntityManagerTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using System.Numerics;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Map.Components;
using Robust.UnitTesting.Server;

namespace Robust.UnitTesting.Shared.GameObjects
Expand Down Expand Up @@ -33,6 +32,141 @@ public void SpawnEntity_PrototypeTransform_Works()
Assert.That(newEnt, Is.Not.EqualTo(EntityUid.Invalid));
}

[Test]
[Description("""
Tests that a three-component EntityQuery behaves as expected.
This covers the backend for EntityQuery`2 and EntityQuery`4 as well due to shared code.
""")]
public void EntityQuery3_Works()
{
var sim = SimulationFactory();
var map = sim.CreateMap();
var map2 = sim.CreateMap();

var entMan = sim.Resolve<IEntityManager>();
// Query all maps.
var query = entMan.GetEntityQuery<TransformComponent, MetaDataComponent, MapComponent>();

Assert.That(query.Count(), NUnit.Framework.Is.EqualTo(2));

Assert.That(query.Matches(map.Uid));

foreach (var entity in query)
{
Assert.That(entity.Owner, NUnit.Framework.Is.EqualTo(map.Uid).Or.EqualTo(map2.Uid));
Assert.That(entity.Comp1, NUnit.Framework.Is.TypeOf<TransformComponent>());
Assert.That(entity.Comp2, NUnit.Framework.Is.TypeOf<MetaDataComponent>());
Assert.That(entity.Comp3, NUnit.Framework.Is.TypeOf<MapComponent>());
#pragma warning disable CS0618 // Type or member is obsolete
Assert.That(entity.Comp1.Owner, NUnit.Framework.Is.EqualTo(entity.Owner));
Assert.That(entity.Comp2.Owner, NUnit.Framework.Is.EqualTo(entity.Owner));
Assert.That(entity.Comp3.Owner, NUnit.Framework.Is.EqualTo(entity.Owner));
#pragma warning restore CS0618 // Type or member is obsolete
}
}

[Test]
[Description("""
Tests DynamicEntityQuery behavior with Without and Optional, ensuring they behave as expected.
Uses a few maps and blank entities as test subjects.
""")]
public void DynamicQueryTest_OptionalWithout()
{
var sim = SimulationFactory();
_ = sim.CreateMap();
_ = sim.CreateMap();
_ = sim.SpawnEntity(null, MapCoordinates.Nullspace);
_ = sim.SpawnEntity(null, MapCoordinates.Nullspace);

var entMan = sim.Resolve<IEntityManager>();

// Should contain all spawned entities.
var queryAll = entMan.GetDynamicQuery(
(typeof(TransformComponent), DynamicEntityQuery.QueryFlags.None),
(typeof(MetaDataComponent), DynamicEntityQuery.QueryFlags.None)
);

// Should contain all spawned entities.
var queryAllAndMaps = entMan.GetDynamicQuery(
(typeof(TransformComponent), DynamicEntityQuery.QueryFlags.None),
(typeof(MetaDataComponent), DynamicEntityQuery.QueryFlags.None),
(typeof(MapComponent), DynamicEntityQuery.QueryFlags.Optional)
);

// Should only contain the non-map entities.
var queryNotMaps = entMan.GetDynamicQuery(
(typeof(TransformComponent), DynamicEntityQuery.QueryFlags.None),
(typeof(MetaDataComponent), DynamicEntityQuery.QueryFlags.None),
(typeof(MapComponent), DynamicEntityQuery.QueryFlags.Without)
);

var buffer = new IComponent?[4].AsSpan();

var queryAllEnum = queryAll.GetEnumerator(false);
var queryAllCount = 0;

while (queryAllEnum.MoveNext(out _, buffer[0..2]))
{
queryAllCount += 1;
// Ensure components get filled out as we expect, only meta and transform.
using (Assert.EnterMultipleScope())
{
Assert.That(buffer[0], NUnit.Framework.Is.TypeOf<TransformComponent>());
Assert.That(buffer[1], NUnit.Framework.Is.TypeOf<MetaDataComponent>());
}
}

Assert.That(queryAllCount, NUnit.Framework.Is.EqualTo(4), "Expected to iterate all entities");

var queryAllAndMapsEnum = queryAllAndMaps.GetEnumerator(false);
var queryAllAndMapsCount = 0;
var mapCount = 0;

while (queryAllAndMapsEnum.MoveNext(out _, buffer[0..3]))
{
queryAllAndMapsCount += 1;
using (Assert.EnterMultipleScope())
{
using (Assert.EnterMultipleScope())
{
Assert.That(buffer[0], NUnit.Framework.Is.TypeOf<TransformComponent>());
Assert.That(buffer[1], NUnit.Framework.Is.TypeOf<MetaDataComponent>());
Assert.That(buffer[2], NUnit.Framework.Is.TypeOf<MapComponent>().Or.Null);
}

if (buffer[2] is not null)
mapCount += 1;
}
}

using (Assert.EnterMultipleScope())
{
Assert.That(queryAllAndMapsCount, NUnit.Framework.Is.EqualTo(4), "Expected to iterate all entities.");
Assert.That(mapCount, NUnit.Framework.Is.EqualTo(2), "Expected to pick up both maps in the query.");
}

var queryNotMapsEnum = queryNotMaps.GetEnumerator(false);
var queryNotMapsCount = 0;

while (queryNotMapsEnum.MoveNext(out var ent, buffer[0..3]))
{
queryNotMapsCount += 1;
using (Assert.EnterMultipleScope())
{
using (Assert.EnterMultipleScope())
{
Assert.That(buffer[0], NUnit.Framework.Is.TypeOf<TransformComponent>());
Assert.That(buffer[1], NUnit.Framework.Is.TypeOf<MetaDataComponent>());
Assert.That(buffer[2], NUnit.Framework.Is.Null, "Without constraints should never fill their slot.");
Assert.That(entMan.HasComponent<MapComponent>(ent), NUnit.Framework.Is.False, "Without constraints shouldn't return entities that match it.");
}
}
}


Assert.That(queryNotMapsCount, NUnit.Framework.Is.EqualTo(2), "Expected to only iterate non-maps.");
}

[Test]
public void ComponentCount_Works()
{
Expand Down
37 changes: 37 additions & 0 deletions Robust.Shared/GameObjects/Docs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,41 @@
This is also preferable if you may have already looked up the component, saving on lookup time.
</remarks>
</entry>
<entry name="EntityQueryT">
<summary>
An index of all entities with a given component, avoiding looking up the component's storage every time.
Using these saves on dictionary lookups, making your code slightly more efficient, and ties in nicely with
<see cref="Entity{T}"/>.
</summary>
<example>
<code>
public sealed class MySystem : EntitySystem
{
[Dependency] private EntityQuery&lt;TransformComponent&gt; _transforms = default!;
<br/>
public void Update(float ft)
{
foreach (var ent in _transforms)
{
// iterate matching entities, excluding paused ones.
}
}
<br/>
public void DoThings(EntityUid myEnt)
{
var ent = _transforms.Get(myEnt);
// ...
}
}
</code>
</example>
<remarks>
Queries hold references to <see cref="IEntityManager"/> internals, and are always up to date with the world.
They can not however perform mutation, if you need to add or remove components you must use
<see cref="EntitySystem"/> or <see cref="IEntityManager"/> methods.
</remarks>
<seealso cref="M:Robust.Shared.GameObjects.EntitySystem.GetEntityQuery``1">EntitySystem.GetEntityQuery()</seealso>
<seealso cref="M:Robust.Shared.GameObjects.EntityManager.GetEntityQuery``1">EntityManager.GetEntityQuery()</seealso>

</entry>
</entries>
Loading
Loading