diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 4c55457d3e2..7b01e58e960 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,18 @@ END TEMPLATE--> ### New features -*None yet* +- `EntityQuery` now implements `IEnumerable>` and can be used with `foreach` performantly. +- `EntityQuery`, `EntityQuery`, and `EntityQuery` + have been added and implement `IEnumerable`. +

+ They're direct counterparts to `EntityQuery` with similar methods and constructors. + Additionally, like `EntityQuery`, 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). +

+ It is currently primarily an implementation detail of `EntityQuery<>` but can be used directly and will lead to + expanded functionality in the future. ### Bugfixes @@ -47,7 +58,14 @@ END TEMPLATE--> ### Other -*None yet* +- `EntityQueryEnumerator`, `EntityQueryEnumerator`, `EntityQueryEnumerator`, + `EntityQueryEnumerator`, `AllEntityQueryEnumerator`, `AllEntityQueryEnumerator`, + `AllEntityQueryEnumerator`, `AllEntityQueryEnumerator`, + and `ComponentQueryEnumerator` are now obsolete. +- The EntityManager methods `EntityQuery`, `EntityQuery`, `EntityQuery`, + and `EntityQuery`, `EntityQueryEnumerator`, `AllEntityQueryEnumerator`, + `ComponentQueryEnumerator`, `AllComponentsList`, `AllEntityUids`, `AllEntityUids`, `AllEntities`, and `AllComponents` + are now obsolete. ### Internal diff --git a/Robust.Shared.IntegrationTests/GameObjects/IEntityManagerTests.cs b/Robust.Shared.IntegrationTests/GameObjects/IEntityManagerTests.cs index d168c7fe1a2..b24f3595cd4 100644 --- a/Robust.Shared.IntegrationTests/GameObjects/IEntityManagerTests.cs +++ b/Robust.Shared.IntegrationTests/GameObjects/IEntityManagerTests.cs @@ -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 @@ -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(); + // Query all maps. + var query = entMan.GetEntityQuery(); + + 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()); + Assert.That(entity.Comp2, NUnit.Framework.Is.TypeOf()); + Assert.That(entity.Comp3, NUnit.Framework.Is.TypeOf()); +#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(); + + // 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()); + Assert.That(buffer[1], NUnit.Framework.Is.TypeOf()); + } + } + + 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()); + Assert.That(buffer[1], NUnit.Framework.Is.TypeOf()); + Assert.That(buffer[2], NUnit.Framework.Is.TypeOf().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()); + Assert.That(buffer[1], NUnit.Framework.Is.TypeOf()); + Assert.That(buffer[2], NUnit.Framework.Is.Null, "Without constraints should never fill their slot."); + Assert.That(entMan.HasComponent(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() { diff --git a/Robust.Shared/GameObjects/Docs.xml b/Robust.Shared/GameObjects/Docs.xml index 6eaf60eb8b2..0bcce0751b3 100644 --- a/Robust.Shared/GameObjects/Docs.xml +++ b/Robust.Shared/GameObjects/Docs.xml @@ -11,4 +11,41 @@ This is also preferable if you may have already looked up the component, saving on lookup time. + + + 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 + . + + + + public sealed class MySystem : EntitySystem + { + [Dependency] private EntityQuery<TransformComponent> _transforms = default!; +
+ public void Update(float ft) + { + foreach (var ent in _transforms) + { + // iterate matching entities, excluding paused ones. + } + } +
+ public void DoThings(EntityUid myEnt) + { + var ent = _transforms.Get(myEnt); + // ... + } + } +
+
+ + Queries hold references to 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 + or methods. + + EntitySystem.GetEntityQuery() + EntityManager.GetEntityQuery() + +
diff --git a/Robust.Shared/GameObjects/DynamicEntityQuery.cs b/Robust.Shared/GameObjects/DynamicEntityQuery.cs new file mode 100644 index 00000000000..d42b8b09281 --- /dev/null +++ b/Robust.Shared/GameObjects/DynamicEntityQuery.cs @@ -0,0 +1,427 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Robust.Shared.GameObjects; + +/// +/// +/// A typeless version of entity queries optimized for more complex query behaviors. +/// This isn't enumerable for allocation reasons, but works for an arbitrary set of components. +/// +/// +/// DynamicEntityQuery supports query constraints, as described by , +/// that control how the query should treat the presence of a given component (is it optional, is it required). +/// +/// +/// +/// The component-returning methods on this type all take in spans to output into, it is recommended to +/// allocate an array once (within a method) and reuse it regularly. +/// +/// +/// +/// var query = GetDynamicQuery( +/// // Query every map, +/// (typeof(MapComponent), DynamicEntityQuery.QueryFlags.None), +/// // that may also be a grid, +/// (typeof(MapGridComponent), DynamicEntityQuery.QueryFlags.Optional), +/// // that is absolutely not funny. +/// (typeof(FunnyComponent), DynamicEntityQuery.QueryFlags.Without) +/// ); +///
+/// var components = new IComponent?[3]; // Must match the number of queried components. +/// var enumerator = query.GetEnumerator(); +///
+/// while (enumerator.MoveNext(out var ent, components)) +/// { +/// // all the components we wanted from this ent are in components. +/// var mapComp = (MapComponent)components[0]!; +/// var mapGridComp = (MapGridComponent?)components[1]; +/// // components[2] is where FunnyComponent would be, but these entities are never funny so it's always null. +/// Obliterate((ent, mapComp, mapGridComp)); +/// } +///
+///
+/// +public readonly struct DynamicEntityQuery +{ + /// + /// Information on a query item, describing how to handle it. + /// + internal readonly struct QueryEntry(Dictionary dict, QueryFlags flags) + { + public readonly Dictionary Dict = dict; + + public readonly QueryFlags Flags = flags; + } + + [Flags] + public enum QueryFlags + { + /// + /// Indicates no special behavior, the component is required. + /// + None = 0, + /// + /// Indicates this entry is optional. + /// + Optional = 1, + /// + /// Indicates this entry is excluded, and the query fails if it's present. + /// + Without = 2, + } + + private readonly QueryEntry[] _entries; + private readonly Dictionary _metaData; + + /// + /// The number of components this query is set up to emit. + /// + /// + /// Components marked Without always get a slot in the list regardless of being used. + /// + public int OutputCount => _entries.Length; + + internal DynamicEntityQuery(QueryEntry[] entries, Dictionary metaData) + { + _entries = entries; + _metaData = metaData; + } + + /// + /// Tries to query an entity for this query's components. + /// + /// The entity to look up components for. + /// The span to output components into. + /// True when all components were found, false otherwise. + public bool TryGet(EntityUid ent, in Span output) + { + // SAFETY: This ensures that the span is exactly as long as we need. + // Any less and we'd write out of bounds, which is Very Bad. + if (output.Length != OutputCount) + ThrowBadLength(OutputCount, output.Length); + + ref var spanEntry = ref MemoryMarshal.GetReference(output); + + var entriesLength = _entries.Length; + + if (entriesLength == 0) + return true; // Okay we got everything. And by everything, I mean nothing whatsoever. + + ref var entryRef = ref MemoryMarshal.GetReference(_entries); + + // REMARK: All this work in here is to avoid a handful of bounds checks. + // Frankly, this isn't that critical given we're also.. indexing a dict. + // and as such bounds checks probably disappear into overhead. + // but I figure every little bit helps in a critical path like this. + for (var i = 0; i < entriesLength; i++) + { + var exists = !entryRef.Dict.TryGetValue(ent, out spanEntry) || spanEntry.Deleted; + // If it exists (or doesn't exist when without is set) and optional is not set, bail. + if ((exists ^ ((entryRef.Flags & QueryFlags.Without) != 0)) + && (entryRef.Flags & QueryFlags.Optional) == 0) + return false; + + if (i >= entriesLength - 1) + break; // Don't create out of bounds refs, I like having a face. + + // Increment our index.. + // ReSharper disable once RedundantTypeArgumentsOfMethod + spanEntry = ref Unsafe.Add(ref spanEntry, 1); + + // and increment our index into the tails array, too. + entryRef = ref Unsafe.Add(ref entryRef, 1); + } + + return true; // We iterated all our tails + } + + /// + /// Tries to query an entity for this query's components, skipping already-filled entries. + /// + /// The entity to look up components for. + /// The span to output components into. + /// True when all components were found, false otherwise. + public bool TryResolve(EntityUid ent, in Span output) + { + // SAFETY: This ensures that the span is exactly as long as we need. + // Any less and we'd write out of bounds, which is Very Bad. + if (output.Length != OutputCount) + ThrowBadLength(OutputCount, output.Length); + + ref var spanEntry = ref MemoryMarshal.GetReference(output); + + var entriesLength = _entries.Length; + + if (entriesLength == 0) + return true; // Okay we got everything. And by everything, I mean nothing whatsoever. + + ref var entryRef = ref MemoryMarshal.GetReference(_entries); + + // REMARK: All this work in here is to avoid a handful of bounds checks. + // Frankly, this isn't that critical given we're also.. indexing a dict. + // and as such bounds checks probably disappear into overhead. + // but I figure every little bit helps in a critical path like this. + for (var i = 0; i < entriesLength; i++) + { + // If the entry is null and we're marked Without, continue. + // and vice versa, if it's not null and we're marked Without, don't continue. + // ..and if it's not null and we're not marked without, continue. + // Resolve behavior here is a little silly, I think. Oh well. + if (spanEntry is not null ^ ((entryRef.Flags & QueryFlags.Without) != 0)) + continue; + + var exists = !entryRef.Dict.TryGetValue(ent, out spanEntry) || spanEntry.Deleted; + // If it exists (or doesn't exist when without is set) and optional is not set, bail. + if ((exists ^ ((entryRef.Flags & QueryFlags.Without) != 0)) + && (entryRef.Flags & QueryFlags.Optional) == 0) + return false; + + if (i >= entriesLength - 1) + break; // Don't create out of bounds refs, I like having a face. + + // Increment our index.. + // ReSharper disable once RedundantTypeArgumentsOfMethod + spanEntry = ref Unsafe.Add(ref spanEntry, 1); + + // and increment our index into the tails array, too. + entryRef = ref Unsafe.Add(ref entryRef, 1); + } + + return true; // We iterated all our tails + } + + /// + /// Tests if a given entity matches this query (ala ) + /// + /// The entity to try matching against. + /// True if the entity matches this query. + public bool Matches(EntityUid ent) + { + var entriesLength = _entries.Length; + + if (entriesLength == 0) + return true; // Okay we got everything, as in literally nothing, but it DOES match. + + ref var entryRef = ref MemoryMarshal.GetReference(_entries); + + // REMARK: All this work in here is to avoid a handful of bounds checks. + // Frankly, this isn't that critical given we're also.. indexing a dict. + // and as such bounds checks probably disappear into overhead. + // but I figure every little bit helps in a critical path like this. + for (var i = 0; i < entriesLength; i++) + { + var exists = !entryRef.Dict.TryGetValue(ent, out var entry) || entry.Deleted; + + // If it exists (or doesn't exist when without is set) and optional is not set, bail. + if ((exists ^ ((entryRef.Flags & QueryFlags.Without) != 0)) + && (entryRef.Flags & QueryFlags.Optional) == 0) + return false; + + if (i >= entriesLength - 1) + break; // Don't create out of bounds refs, I like having a face. + + // and increment our index into the tails array, too. + entryRef = ref Unsafe.Add(ref entryRef, 1); + } + + return true; // We iterated all our component dicts, we win. + } + + /// + /// Tries to query an entity for this query's components. Implementation detail of the enumerator, this skips + /// the first entry. + /// + /// The entity to look up components for. + /// The span to output components into, in ref form. + /// True when all components were found, false otherwise. + private bool TryGetAfterFirst(EntityUid ent, ref IComponent? spanEntry) + { + // SAFETY: We already checked the bounds if this is called. + + var entriesLength = _entries.Length; + + if (entriesLength - 1 == 0) + return true; // Okay we got everything. And by everything, I mean nothing whatsoever. + + ref var entryRef = ref MemoryMarshal.GetReference(_entries); + + // + 1, we already got the first. + entryRef = ref Unsafe.Add(ref entryRef, 1); + + // REMARK: All this work in here is to avoid a handful of bounds checks. + // Frankly, this isn't that critical given we're also.. indexing a dict. + // and as such bounds checks probably disappear into overhead. + // but I figure every little bit helps in a critical path like this. + for (var i = 1; i < entriesLength; i++) + { + var exists = !entryRef.Dict.TryGetValue(ent, out spanEntry) || spanEntry.Deleted; + // If it exists (or doesn't exist when without is set) and optional is not set, bail. + if ((exists ^ ((entryRef.Flags & QueryFlags.Without) != 0)) + && (entryRef.Flags & QueryFlags.Optional) == 0) + return false; + + if (i >= entriesLength - 1) + break; // Don't create out of bounds refs, I like having a face. + + // Increment our index.. + // ReSharper disable once RedundantTypeArgumentsOfMethod + spanEntry = ref Unsafe.Add(ref spanEntry, 1); + + // and increment our index into the tails array, too. + entryRef = ref Unsafe.Add(ref entryRef, 1); + } + + return true; // We iterated all our tails + } + + public Enumerator GetEnumerator(bool checkPaused) + { + return new Enumerator(this, checkPaused); + } + + /// + /// The custom enumerator for dynamic queries. + /// This is intended to be an implementation detail for other queries. + /// + public struct Enumerator + { + private readonly DynamicEntityQuery _owner; + private readonly bool _checkPaused; + private Dictionary.Enumerator _lead; +#if DEBUG + // Anti-misuse assertions, store enumerators for every entry and Reset() them constantly so that + // if you update the ECS while we're enumerating it blows up. + // This does mean DynamicEntityQuery allocates in debug, but it also means I won't have to sort through + // all the ways content code breaks basic assumptions down the line because it'll explode in tests. + private readonly Dictionary.Enumerator[] _mines; +#endif + + internal Enumerator(DynamicEntityQuery owner, bool checkPaused) + { + QueryFlags flags; + _owner = owner; + _checkPaused = checkPaused; + + if (_owner._entries.Length == 0) + { + flags = QueryFlags.None; + } + else + { + flags = _owner._entries[0].Flags; + } + + if (flags != QueryFlags.None) + { + throw new NotSupportedException( + "Query enumerators do not support optional or excluded first components."); + } + +#if DEBUG + if (_owner._entries.Length > 0) + _mines = _owner._entries.Select(x => x.Dict.GetEnumerator()).ToArray(); + else + _mines = [_owner._metaData.GetEnumerator()]; +#endif + + Reset(); + } + +#if DEBUG + private void StepOnMines() + { + try + { + foreach (var mine in _mines) + { + ((IEnumerator)mine).Reset(); + } + } + catch (InvalidOperationException versionError) + { + throw new InvalidOperationException( + "Tried to use an Enumerator that was invalidated by changes to the ECS. You cannot add or remove the components you're querying for, nor add or remove entities with them.", + versionError); + } + } +#endif + + /// + /// Attempts to find the next entity in the query iterator. + /// + /// The discovered entity, if any. + /// The storage for components queried for this entity. + /// True if ent and components are valid, false if there's no items left. + public bool MoveNext(out EntityUid ent, in Span output) + { + if (output.Length != _owner.OutputCount) + ThrowBadLength( _owner.OutputCount, output.Length); + +#if DEBUG + StepOnMines(); +#endif + + // We grab this here to pin it all function instead of constantly pinning in the loop. + ref var span = ref MemoryMarshal.GetReference(output); + var meta = _owner._metaData; + + ent = EntityUid.Invalid; + while (true) + { + if (!_lead.MoveNext()) + return false; + + ref var spanEntry = ref span; + + ent = _lead.Current.Key; + spanEntry = _lead.Current.Value; + + if (_checkPaused && ((MetaDataComponent)meta[ent]).EntityPaused) + continue; // Oops, paused. + + if (spanEntry.Deleted) + continue; // Nevermind, move along. + + if (output.Length == 1) + return true; // Already done. Do NOT create out of bounds refs! + + // Increment our index. + // ReSharper disable once RedundantTypeArgumentsOfMethod + spanEntry = ref Unsafe.Add(ref spanEntry, 1); + + if (_owner.TryGetAfterFirst(ent, ref spanEntry)) + return true; + + // Oops, we failed, try again. + } + } + + public void Reset() + { + if (_owner._entries.Length == 0) + { + _lead = _owner._metaData.GetEnumerator(); + } + else + { + _lead = _owner._entries[0].Dict.GetEnumerator(); + } + +#if DEBUG + StepOnMines(); +#endif + } + } + + [DoesNotReturn] + private static void ThrowBadLength(int expected, int length) + { + throw new IndexOutOfRangeException($"The given span is not large enough to fit all of the query's outputs. Expected {expected}, got {length}"); + } +} diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs index d7308446005..00a43810504 100644 --- a/Robust.Shared/GameObjects/EntityManager.Components.cs +++ b/Robust.Shared/GameObjects/EntityManager.Components.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; @@ -1186,20 +1185,6 @@ private T CopyComponentInternal(EntityUid source, EntityUid target, T sourceC return component; } - public EntityQuery GetEntityQuery() where TComp1 : IComponent - { - var comps = _entTraitArray[CompIdx.ArrayIndex()]; - DebugTools.Assert(comps != null, $"Unknown component: {typeof(TComp1).Name}"); - return new EntityQuery(this, comps); - } - - public EntityQuery GetEntityQuery(Type type) - { - var comps = _entTraitDict[type]; - DebugTools.Assert(comps != null, $"Unknown component: {type.Name}"); - return new EntityQuery(this, comps); - } - /// public IEnumerable GetComponents(EntityUid uid) { @@ -1765,337 +1750,6 @@ public NetComponentEnumerator(Dictionary dictionary) => } } - /// - /// 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 - /// . - /// - /// Any component type. - /// - /// - /// public sealed class MySystem : EntitySystem - /// { - /// private EntityQuery<TransformComponent> _transforms = default!; - ///
- /// public override void Initialize() - /// { - /// _transforms = GetEntityQuery<TransformComponent>(); - /// } - ///
- /// public void DoThings(EntityUid myEnt) - /// { - /// var ent = _transforms.Get(myEnt); - /// // ... - /// } - /// } - ///
- ///
- /// - /// Queries hold references to 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 - /// or methods. - /// - /// EntitySystem.GetEntityQuery() - /// EntityManager.GetEntityQuery() - public readonly struct EntityQuery where TComp1 : IComponent - { - private readonly EntityManager _entMan; - private readonly Dictionary _traitDict; - - internal EntityQuery(EntityManager entMan, Dictionary traitDict) - { - _entMan = entMan; - _traitDict = traitDict; - } - - /// - /// Gets for an entity, throwing if it can't find it. - /// - /// The entity to do a lookup for. - /// The located component. - /// Thrown if the entity does not have a component of type . - /// - /// IEntityManager.GetComponent<T>(EntityUid) - /// - /// - /// EntitySystem.Comp<T>(EntityUid) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public TComp1 GetComponent(EntityUid uid) - { - if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted) - return (TComp1) comp; - - throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}"); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] - public Entity Get(EntityUid uid) - { - if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted) - return new Entity(uid, (TComp1) comp); - - throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}"); - } - - /// - /// Gets for an entity, if it's present. - /// - /// - /// If it is strictly errorenous for a component to not be present, you may want to use - /// instead. - /// - /// The entity to do a lookup for. - /// The located component, if any. - /// Whether the component was found. - /// - /// IEntityManager.TryGetComponent<T>(EntityUid, out T?) - /// - /// - /// EntitySystem.TryComp<T>(EntityUid, out T?) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component) - { - if (uid == null) - { - component = default; - return false; - } - - return TryGetComponent(uid.Value, out component); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool TryGetComponent(EntityUid uid, [NotNullWhen(true)] out TComp1? component) - { - if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted) - { - component = (TComp1) comp; - return true; - } - - component = default; - return false; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool TryComp(EntityUid uid, [NotNullWhen(true)] out TComp1? component) - => TryGetComponent(uid, out component); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component) - => TryGetComponent(uid, out component); - - /// - /// Tests if the given entity has . - /// - /// The entity to do a lookup for. - /// Whether the component exists for that entity. - /// If you immediately need to then look up that component, it's more efficient to use . - /// - /// IEntityManager.HasComponent<T>(EntityUid) - /// - /// - /// EntitySystem.HasComp<T>(EntityUid) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool HasComp(EntityUid uid) => HasComponent(uid); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool HasComp([NotNullWhen(true)] EntityUid? uid) => HasComponent(uid); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool HasComponent(EntityUid uid) - { - return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool HasComponent([NotNullWhen(true)] EntityUid? uid) - { - return uid != null && HasComponent(uid.Value); - } - - /// - /// The entity to do a lookup for. - /// The space to write the component into if found. - /// Whether to log if the component is missing, for diagnostics. - /// Whether the component was found. - /// - /// EntitySystem.Resolve<T>(EntityUid, out T?) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true) - { - if (component != null) - { - DebugTools.AssertOwner(uid, component); - return true; - } - - if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted) - { - component = (TComp1)comp; - return true; - } - - if (logMissing) - _entMan.ResolveSawmill.Error($"Can't resolve \"{typeof(TComp1)}\" on entity {_entMan.ToPrettyString(uid)}!\n{Environment.StackTrace}"); - - return false; - } - - /// - /// The space to write the component into if found. - /// Whether to log if the component is missing, for diagnostics. - /// Whether the component was found. - /// - /// EntitySystem.Resolve<T>(EntityUid, out T?) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Resolve(ref Entity entity, bool logMissing = true) - { - return Resolve(entity.Owner, ref entity.Comp, logMissing); - } - - /// - /// Gets for an entity if it's present, or null if it's not. - /// - /// The entity to do the lookup on. - /// The component, if it exists. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public TComp1? CompOrNull(EntityUid uid) - { - if (TryGetComponent(uid, out var comp)) - return comp; - - return default; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public TComp1 Comp(EntityUid uid) - { - return GetComponent(uid); - } - - #region Internal - - /// - /// Elides the component.Deleted check of - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - internal TComp1 GetComponentInternal(EntityUid uid) - { - if (_traitDict.TryGetValue(uid, out var comp)) - return (TComp1) comp; - - throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}"); - } - - /// - /// Elides the component.Deleted check of - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - internal bool TryGetComponentInternal([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component) - { - if (uid == null) - { - component = default; - return false; - } - - return TryGetComponentInternal(uid.Value, out component); - } - - /// - /// Elides the component.Deleted check of - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - internal bool TryGetComponentInternal(EntityUid uid, [NotNullWhen(true)] out TComp1? component) - { - if (_traitDict.TryGetValue(uid, out var comp)) - { - component = (TComp1) comp; - return true; - } - - component = default; - return false; - } - - /// - /// Elides the component.Deleted check of - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - internal bool HasComponentInternal(EntityUid uid) - { - return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted; - } - - /// - /// Elides the component.Deleted check of - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - internal bool ResolveInternal(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true) - { - if (component != null) - { - DebugTools.AssertOwner(uid, component); - return true; - } - - if (_traitDict.TryGetValue(uid, out var comp)) - { - component = (TComp1)comp; - return true; - } - - if (logMissing) - _entMan.ResolveSawmill.Error($"Can't resolve \"{typeof(TComp1)}\" on entity {_entMan.ToPrettyString(uid)}!\n{new StackTrace(1, true)}"); - - return false; - } - /// - /// Elides the component.Deleted check of - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - internal TComp1? CompOrNullInternal(EntityUid uid) - { - if (TryGetComponent(uid, out var comp)) - return comp; - - return default; - } - - #endregion - } - #region ComponentRegistry Query /// @@ -2170,6 +1824,7 @@ public void Dispose() /// /// Non-generic version of /// + [Obsolete($"Use {nameof(EntityQuery<>)} and non-generic {nameof(IEntityManager.GetEntityQuery)}")] public struct ComponentQueryEnumerator : IDisposable { private Dictionary.Enumerator _traitDict; @@ -2249,6 +1904,7 @@ public void Dispose() /// /// IEntityManager.EntityQueryEnumerator<TComp1, ...>() /// + [Obsolete($"Use {nameof(EntityQuery<>)} and generic {nameof(IEntityManager.GetEntityQuery)}")] public struct EntityQueryEnumerator : IDisposable where TComp1 : IComponent { @@ -2594,6 +2250,7 @@ public void Dispose() /// /// IEntityManager.AllEntityQueryEnumerator<TComp1, ...>() /// + [Obsolete($"Prefer {nameof(IEntityManager.GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property.")] public struct AllEntityQueryEnumerator : IDisposable where TComp1 : IComponent { @@ -2643,6 +2300,7 @@ public void Dispose() } /// + [Obsolete($"Prefer {nameof(IEntityManager.GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property.")] public struct AllEntityQueryEnumerator : IDisposable where TComp1 : IComponent where TComp2 : IComponent @@ -2703,6 +2361,7 @@ public void Dispose() } /// + [Obsolete($"Prefer {nameof(IEntityManager.GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property.")] public struct AllEntityQueryEnumerator : IDisposable where TComp1 : IComponent where TComp2 : IComponent @@ -2777,6 +2436,7 @@ public void Dispose() } /// + [Obsolete($"Prefer {nameof(IEntityManager.GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property.")] public struct AllEntityQueryEnumerator : IDisposable where TComp1 : IComponent where TComp2 : IComponent diff --git a/Robust.Shared/GameObjects/EntityManager.Queries.cs b/Robust.Shared/GameObjects/EntityManager.Queries.cs new file mode 100644 index 00000000000..64ce17f5972 --- /dev/null +++ b/Robust.Shared/GameObjects/EntityManager.Queries.cs @@ -0,0 +1,77 @@ +using System; +using Robust.Shared.Utility; + +namespace Robust.Shared.GameObjects; + +public partial class EntityManager +{ + public EntityQuery GetEntityQuery() + where TComp1 : IComponent + { + DebugTools.Assert(_entTraitArray.Length > CompIdx.ArrayIndex(), + $"Unknown component: {typeof(TComp1).Name}"); + var comps = _entTraitArray[CompIdx.ArrayIndex()]; + var meta = _entTraitArray[CompIdx.ArrayIndex()]; + + return new EntityQuery(this, comps, meta); + } + + public EntityQuery GetEntityQuery(Type type) + { + DebugTools.Assert(_entTraitDict.ContainsKey(type), $"Unknown component: {type.Name}"); + var comps = _entTraitDict[type]; + var meta = _entTraitArray[CompIdx.ArrayIndex()]; + + return new EntityQuery(this, comps, meta); + } + + public DynamicEntityQuery GetDynamicQuery(params (Type, DynamicEntityQuery.QueryFlags)[] userEntries) + { + var entries = new DynamicEntityQuery.QueryEntry[userEntries.Length]; + + for (var i = 0; i < userEntries.Length; i++) + { + var entry = userEntries[i]; + DebugTools.Assert(_entTraitDict.ContainsKey(entry.Item1), $"Unknown component: {entry.Item1.Name}"); + entries[i] = new(_entTraitDict[entry.Item1], entry.Item2); + } + + return new DynamicEntityQuery(entries, _entTraitArray[CompIdx.ArrayIndex()]); + } + + public EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent + { + var dyQuery = GetDynamicQuery((typeof(TComp1), DynamicEntityQuery.QueryFlags.None), + (typeof(TComp2), DynamicEntityQuery.QueryFlags.None)); + + return new(dyQuery, this); + } + + public EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent + where TComp3 : IComponent + { + var dyQuery = GetDynamicQuery((typeof(TComp1), DynamicEntityQuery.QueryFlags.None), + (typeof(TComp2), DynamicEntityQuery.QueryFlags.None), + (typeof(TComp3), DynamicEntityQuery.QueryFlags.None)); + + return new(dyQuery, this); + } + + public EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent + where TComp3 : IComponent + where TComp4 : IComponent + { + var dyQuery = GetDynamicQuery((typeof(TComp1), DynamicEntityQuery.QueryFlags.None), + (typeof(TComp2), DynamicEntityQuery.QueryFlags.None), + (typeof(TComp3), DynamicEntityQuery.QueryFlags.None), + (typeof(TComp4), DynamicEntityQuery.QueryFlags.None)); + + return new(dyQuery, this); + } +} diff --git a/Robust.Shared/GameObjects/EntityQuery.cs b/Robust.Shared/GameObjects/EntityQuery.cs new file mode 100644 index 00000000000..9f56124f1d7 --- /dev/null +++ b/Robust.Shared/GameObjects/EntityQuery.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Robust.Shared.Utility; + +namespace Robust.Shared.GameObjects; + +/// +/// 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 +/// . +/// +/// Any component type. +/// +/// +/// public sealed class MySystem : EntitySystem +/// { +/// [Dependency] private EntityQuery<TransformComponent> _transforms = default!; +///
+/// public void Update(float ft) +/// { +/// foreach (var ent in _transforms) +/// { +/// // iterate matching entities, excluding paused ones. +/// } +/// } +///
+/// public void DoThings(EntityUid myEnt) +/// { +/// var ent = _transforms.Get(myEnt); +/// // ... +/// } +/// } +///
+///
+/// +/// Queries hold references to 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 +/// or methods. +/// +/// EntitySystem.GetEntityQuery() +/// EntityManager.GetEntityQuery() +[PublicAPI] +public readonly struct EntityQuery : IEnumerable> + where TComp1 : IComponent +{ + private readonly EntityManager _entMan; + private readonly Dictionary _traitDict; + private readonly Dictionary _metaData; + + private readonly bool _enumeratePaused; + + /// + /// Returns an entity query that will include paused entities when enumerated. + /// + /// + /// You shouldn't cache this, please, there is no way to turn it back into a normal query and it's a shorthand + /// only meant for foreach. + /// + /// + /// + /// public sealed class MySystem : EntitySystem + /// { + /// [Dependency] private EntityQuery<TransformComponent> _transforms = default!; + ///
+ /// public void Update(float ft) + /// { + /// foreach (var ent in _transforms.All) + /// { + /// // iterate matching entities, including paused ones. + /// } + /// } + /// } + ///
+ ///
+ public EntityQuery All => new(this, true); + + internal EntityQuery(EntityManager entMan, Dictionary traitDict, Dictionary metaData) + { + _entMan = entMan; + _traitDict = traitDict; + _metaData = metaData; + _enumeratePaused = false; + } + + /// + /// Internal constructor used for . + /// + private EntityQuery(EntityQuery derived, bool enumeratePaused) + { + _entMan = derived._entMan; + _traitDict = derived._traitDict; + _metaData = derived._metaData; + _enumeratePaused = enumeratePaused; + } + + /// + /// Gets for an entity, throwing if it can't find it. + /// + /// The entity to do a lookup for. + /// The located component. + /// Thrown if the entity does not have a component of type . + /// + /// IEntityManager.GetComponent<T>(EntityUid) + /// + /// + /// EntitySystem.Comp<T>(EntityUid) + /// + [Pure] + public TComp1 GetComponent(EntityUid uid) + { + if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted) + return (TComp1) comp; + + throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}"); + } + + /// + [Pure] + public Entity Get(EntityUid uid) + { + if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted) + return new Entity(uid, (TComp1) comp); + + throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}"); + } + + /// + /// Gets for an entity, if it's present. + /// + /// + /// If it is strictly errorenous for a component to not be present, you may want to use + /// instead. + /// + /// The entity to do a lookup for. + /// The located component, if any. + /// Whether the component was found. + /// + /// IEntityManager.TryGetComponent<T>(EntityUid, out T?) + /// + /// + /// EntitySystem.TryComp<T>(EntityUid, out T?) + /// + [Pure] + public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component) + { + if (uid == null) + { + component = default; + return false; + } + + return TryGetComponent(uid.Value, out component); + } + + /// + [Pure] + public bool TryGetComponent(EntityUid uid, [NotNullWhen(true)] out TComp1? component) + { + if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted) + { + component = (TComp1) comp; + return true; + } + + component = default; + return false; + } + + /// + [Pure] + public bool TryComp(EntityUid uid, [NotNullWhen(true)] out TComp1? component) + => TryGetComponent(uid, out component); + + /// + [Pure] + public bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component) + => TryGetComponent(uid, out component); + + /// + /// Tests if the given entity has . + /// + /// The entity to do a lookup for. + /// Whether the component exists for that entity. + /// If you immediately need to then look up that component, it's more efficient to use . + /// + /// IEntityManager.HasComponent<T>(EntityUid) + /// + /// + /// EntitySystem.HasComp<T>(EntityUid) + /// + [Pure] + public bool HasComp(EntityUid uid) => HasComponent(uid); + + /// + [Pure] + public bool HasComp([NotNullWhen(true)] EntityUid? uid) => HasComponent(uid); + + /// + [Pure] + public bool HasComponent(EntityUid uid) + { + return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted; + } + + /// + [Pure] + public bool HasComponent([NotNullWhen(true)] EntityUid? uid) + { + return uid != null && HasComponent(uid.Value); + } + + /// + /// The entity to do a lookup for. + /// The space to write the component into if found. + /// Whether to log if the component is missing, for diagnostics. + /// Whether the component was found. + /// + /// EntitySystem.Resolve<T>(EntityUid, out T?) + /// + public bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true) + { + if (component != null) + { + DebugTools.AssertOwner(uid, component); + return true; + } + + if (_traitDict.TryGetValue(uid, out var comp) && !comp.Deleted) + { + component = (TComp1)comp; + return true; + } + + if (logMissing) + _entMan.ResolveSawmill.Error($"Can't resolve \"{typeof(TComp1)}\" on entity {_entMan.ToPrettyString(uid)}!\n{Environment.StackTrace}"); + + return false; + } + + /// + /// The space to write the component into if found. + /// Whether to log if the component is missing, for diagnostics. + /// Whether the component was found. + /// + /// EntitySystem.Resolve<T>(EntityUid, out T?) + /// + public bool Resolve(ref Entity entity, bool logMissing = true) + { + return Resolve(entity.Owner, ref entity.Comp, logMissing); + } + + /// + /// Gets for an entity if it's present, or null if it's not. + /// + /// The entity to do the lookup on. + /// The component, if it exists. + [Pure] + public TComp1? CompOrNull(EntityUid uid) + { + if (TryGetComponent(uid, out var comp)) + return comp; + + return default; + } + + /// + [Pure] + public TComp1 Comp(EntityUid uid) + { + return GetComponent(uid); + } + + #region Internal + + /// + /// Elides the component.Deleted check of + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + internal TComp1 GetComponentInternal(EntityUid uid) + { + if (_traitDict.TryGetValue(uid, out var comp)) + return (TComp1) comp; + + throw new KeyNotFoundException($"Entity {uid} does not have a component of type {typeof(TComp1)}"); + } + + /// + /// Elides the component.Deleted check of + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + internal bool TryGetComponentInternal([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component) + { + if (uid == null) + { + component = default; + return false; + } + + return TryGetComponentInternal(uid.Value, out component); + } + + /// + /// Elides the component.Deleted check of + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + internal bool TryGetComponentInternal(EntityUid uid, [NotNullWhen(true)] out TComp1? component) + { + if (_traitDict.TryGetValue(uid, out var comp)) + { + component = (TComp1) comp; + return true; + } + + component = default; + return false; + } + + /// + /// Elides the component.Deleted check of + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + internal bool HasComponentInternal(EntityUid uid) + { + return _traitDict.TryGetValue(uid, out var comp) && !comp.Deleted; + } + + /// + /// Elides the component.Deleted check of + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + internal bool ResolveInternal(EntityUid uid, [NotNullWhen(true)] ref TComp1? component, bool logMissing = true) + { + if (component != null) + { + DebugTools.AssertOwner(uid, component); + return true; + } + + if (_traitDict.TryGetValue(uid, out var comp)) + { + component = (TComp1)comp; + return true; + } + + if (logMissing) + _entMan.ResolveSawmill.Error($"Can't resolve \"{typeof(TComp1)}\" on entity {_entMan.ToPrettyString(uid)}!\n{new StackTrace(1, true)}"); + + return false; + } + /// + /// Elides the component.Deleted check of + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + internal TComp1? CompOrNullInternal(EntityUid uid) + { + if (TryGetComponent(uid, out var comp)) + return comp; + + return default; + } + + #endregion + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// The concrete enumerator for an EntityQuery, to assist the C# compiler in optimization. + /// + public struct Enumerator : IEnumerator> + { + private readonly EntityQuery _query; + private Dictionary.Enumerator _traitDictEnumerator; + + /// + public Entity Current { get; private set; } + + internal Enumerator(EntityQuery query) + { + _query = query; + Reset(); + } + + public bool MoveNext() + { + // Loop until we find something that matches, or run out of entities. + while (true) + { + if (!_traitDictEnumerator.MoveNext()) + return false; + + var (workingEnt, c) = _traitDictEnumerator.Current; + + if (c.Deleted) + continue; + + // REMARK: You might think this would be better as two separate Enumerator implementations, + // but i'm not actually convinced. The memory overhead of one extra ref is small, + // and the branch is guaranteed to be consistent so the CPU will just skip over + // this check every time in the ignore-paused case. + if (!_query._enumeratePaused && ((MetaDataComponent)_query._metaData[workingEnt]).EntityPaused) + continue; + + Current = new(workingEnt, (TComp1)c); + break; + } + + return true; + } + + public void Reset() + { + _traitDictEnumerator = _query._traitDict.GetEnumerator(); + } + + Entity IEnumerator>.Current => Current; + + object IEnumerator.Current => Current; + + public void Dispose() + { + _traitDictEnumerator.Dispose(); + } + } + + // I expect this one in particular to get used a bit more than most so.. optimize it :) + /// + public List> ToList() + { + // Estimate the number of entries first. + var list = new List>(_traitDict.Count); + // Then add to it. Saving some allocs. + list.AddRange(this); + return list; + } +} diff --git a/Robust.Shared/GameObjects/EntityQuery2.cs b/Robust.Shared/GameObjects/EntityQuery2.cs new file mode 100644 index 00000000000..6bae70b0dad --- /dev/null +++ b/Robust.Shared/GameObjects/EntityQuery2.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Robust.Shared.Log; +using Robust.Shared.Utility; + +namespace Robust.Shared.GameObjects; + +/// Any component type. +/// Any component type. +/// +[PublicAPI] +public readonly struct EntityQuery : IEnumerable> + where TComp1 : IComponent + where TComp2 : IComponent +{ + /// + /// Our dynamic query. Will always be of form [TComp1, ...] + /// + private readonly DynamicEntityQuery _query; + private readonly EntityManager _entMan; + + private readonly bool _enumeratePaused; + + /// + /// Returns an entity query that will include paused entities when enumerated. + /// + /// + /// You shouldn't cache this, please, there is no way to turn it back into a normal query and it's a shorthand + /// only meant for foreach. + /// + /// + /// + /// public sealed class MySystem : EntitySystem + /// { + /// [Dependency] private EntityQuery<TransformComponent> _transforms = default!; + ///
+ /// public void Update(float ft) + /// { + /// foreach (var ent in _transforms.All) + /// { + /// // iterate matching entities, including paused ones. + /// } + /// } + /// } + ///
+ ///
+ public EntityQuery All => new(this, true); + + internal EntityQuery(DynamicEntityQuery query, EntityManager entMan) + { + DebugTools.AssertEqual(query.OutputCount, 2); + _query = query; + _entMan = entMan; + _enumeratePaused = false; + } + + /// + /// Internal constructor used for . + /// + private EntityQuery(EntityQuery derived, bool enumeratePaused) + { + _query = derived._query; + _entMan = derived._entMan; + _enumeratePaused = enumeratePaused; + } + + /// + /// Tries to query an entity for this query's components. + /// + /// The entity to look up components for. + /// The first component + /// The second component + /// True when all components were found, false otherwise. + public bool TryGet(EntityUid ent, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2) + { + var buffer = new ComponentArray(); + + if (_query.TryGet(ent, buffer)) + { + comp1 = (TComp1)buffer[0]!; + comp2 = (TComp2)buffer[1]!; + return true; + } + + comp1 = default; + comp2 = default; + return false; + } + + /// + /// Tries to query an entity for this query's components. + /// + /// The entity to look up components for. + /// The resolved entity. + /// True when all components were found, false otherwise. + public bool TryGet(EntityUid ent, out Entity resolved) + { + if (TryGet(ent, out var c1, out var c2)) + { + resolved = new(ent, c1, c2); + return true; + } + + resolved = default; + return false; + } + + /// + /// Tries to query an entity for this query's components, skipping already-filled entries. + /// + /// The entity to look up components for. + /// The first component + /// The second component + /// Whether to log if the resolve fails. + /// True when all components were found, false otherwise. + public bool Resolve(EntityUid ent, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2, bool logMissing = true) + { + var buffer = new ComponentArray(); + buffer[0] = comp1; + buffer[1] = comp2; + + if (_query.TryResolve(ent, buffer)) + { + comp1 = (TComp1)buffer[0]!; + comp2 = (TComp2)buffer[1]!; + return true; + } + + if (logMissing) + _entMan.ResolveSawmill.Error($"Can't resolve \"{typeof(TComp1)}\" and \"{typeof(TComp2)}\" on entity {_entMan.ToPrettyString(ent)}!\n{Environment.StackTrace}"); + + return false; + } + + /// + /// Tries to query an entity for this query's components, skipping already-filled entries. + /// + /// The entity to look up components for. + /// Whether to log if the resolve fails. + /// True when all components were found, false otherwise. + public bool Resolve(ref Entity entity, bool logMissing = true) + { + return Resolve(entity.Owner, ref entity.Comp1, ref entity.Comp2, logMissing); + } + + /// + /// Tests if a given entity matches this query (ala ) + /// + /// The entity to try matching against. + /// True if the entity matches this query. + public bool Matches(EntityUid ent) + { + return _query.Matches(ent); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Inline storage for the components we're enumerating. + /// Basically just working around the fact you can't stackalloc the span, error CS0208. + /// + [InlineArray(2)] + private struct ComponentArray + { + public IComponent? Entry; + } + + public struct Enumerator : IEnumerator> + { + private DynamicEntityQuery.Enumerator _enumerator; + public Entity Current { get; private set; } + + internal Enumerator(EntityQuery owner) + { + _enumerator = owner._query.GetEnumerator(!owner._enumeratePaused); + } + + public bool MoveNext() + { + var buffer = new ComponentArray(); + + if (_enumerator.MoveNext(out var ent, buffer!)) + { + Current = new(ent, (TComp1)buffer[0]!, (TComp2)buffer[1]!); + return true; + } + + return false; + } + + public void Reset() + { + _enumerator.Reset(); + } + + Entity IEnumerator>.Current => Current; + + object IEnumerator.Current => Current; + + public void Dispose() + { + // Nothin' + } + } +} diff --git a/Robust.Shared/GameObjects/EntityQuery3.cs b/Robust.Shared/GameObjects/EntityQuery3.cs new file mode 100644 index 00000000000..3e464e375fe --- /dev/null +++ b/Robust.Shared/GameObjects/EntityQuery3.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Robust.Shared.Log; +using Robust.Shared.Utility; + +namespace Robust.Shared.GameObjects; + +/// Any component type. +/// Any component type. +/// Any component type. +/// +[PublicAPI] +public readonly struct EntityQuery : IEnumerable> + where TComp1 : IComponent + where TComp2 : IComponent + where TComp3 : IComponent +{ + /// + /// Our dynamic query. Will always be of form [TComp1, ...] + /// + private readonly DynamicEntityQuery _query; + private readonly EntityManager _entMan; + + private readonly bool _enumeratePaused; + + /// + /// Returns an entity query that will include paused entities when enumerated. + /// + /// + /// You shouldn't cache this, please, there is no way to turn it back into a normal query and it's a shorthand + /// only meant for foreach. + /// + /// + /// + /// public sealed class MySystem : EntitySystem + /// { + /// [Dependency] private EntityQuery<TransformComponent> _transforms = default!; + ///
+ /// public void Update(float ft) + /// { + /// foreach (var ent in _transforms.All) + /// { + /// // iterate matching entities, including paused ones. + /// } + /// } + /// } + ///
+ ///
+ public EntityQuery All => new(this, true); + + internal EntityQuery(DynamicEntityQuery query, EntityManager entMan) + { + DebugTools.AssertEqual(query.OutputCount, 3); + _query = query; + _entMan = entMan; + _enumeratePaused = false; + } + + /// + /// Internal constructor used for . + /// + private EntityQuery(EntityQuery derived, bool enumeratePaused) + { + _query = derived._query; + _entMan = derived._entMan; + _enumeratePaused = enumeratePaused; + } + + /// + /// Tries to query an entity for this query's components. + /// + /// The entity to look up components for. + /// The first component + /// The second component + /// The third component + /// True when all components were found, false otherwise. + public bool TryGet(EntityUid ent, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3) + { + var buffer = new ComponentArray(); + + if (_query.TryGet(ent, buffer)) + { + comp1 = (TComp1)buffer[0]!; + comp2 = (TComp2)buffer[1]!; + comp3 = (TComp3)buffer[2]!; + return true; + } + + comp1 = default; + comp2 = default; + comp3 = default; + return false; + } + + /// + /// Tries to query an entity for this query's components. + /// + /// The entity to look up components for. + /// The resolved entity. + /// True when all components were found, false otherwise. + public bool TryGet(EntityUid ent, out Entity resolved) + { + if (TryGet(ent, out var c1, out var c2, out var c3)) + { + resolved = new(ent, c1, c2, c3); + return true; + } + + resolved = default; + return false; + } + + /// + /// Tries to query an entity for this query's components, skipping already-filled entries. + /// + /// The entity to look up components for. + /// The first component. + /// The second component. + /// The third component. + /// Whether to log if the resolve fails. + /// True when all components were found, false otherwise. + public bool Resolve(EntityUid ent, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2, [NotNullWhen(true)] ref TComp3? comp3, bool logMissing = true) + { + var buffer = new ComponentArray(); + buffer[0] = comp1; + buffer[1] = comp2; + buffer[2] = comp3; + + if (_query.TryResolve(ent, buffer)) + { + comp1 = (TComp1)buffer[0]!; + comp2 = (TComp2)buffer[1]!; + comp3 = (TComp3)buffer[2]!; + return true; + } + + if (logMissing) + _entMan.ResolveSawmill.Error($"Can't resolve \"{typeof(TComp1)}\", \"{typeof(TComp2)}\", and \"{typeof(TComp3)}\" on entity {_entMan.ToPrettyString(ent)}!\n{Environment.StackTrace}"); + + return false; + } + + /// + /// Tries to query an entity for this query's components, skipping already-filled entries. + /// + /// The entity to look up components for. + /// Whether to log if the resolve fails. + /// True when all components were found, false otherwise. + public bool Resolve(ref Entity entity, bool logMissing = true) + { + return Resolve(entity.Owner, ref entity.Comp1, ref entity.Comp2, ref entity.Comp3, logMissing); + } + + /// + /// Tests if a given entity matches this query (ala ) + /// + /// The entity to try matching against. + /// True if the entity matches this query. + public bool Matches(EntityUid ent) + { + return _query.Matches(ent); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Inline storage for the components we're enumerating. + /// Basically just working around the fact you can't stackalloc the span, error CS0208. + /// + [InlineArray(3)] + private struct ComponentArray + { + public IComponent? Entry; + } + + public struct Enumerator : IEnumerator> + { + private DynamicEntityQuery.Enumerator _enumerator; + public Entity Current { get; private set; } + + internal Enumerator(EntityQuery owner) + { + _enumerator = owner._query.GetEnumerator(!owner._enumeratePaused); + } + + public bool MoveNext() + { + var buffer = new ComponentArray(); + + if (_enumerator.MoveNext(out var ent, buffer!)) + { + Current = new(ent, (TComp1)buffer[0]!, (TComp2)buffer[1]!, (TComp3)buffer[2]!); + return true; + } + + return false; + } + + public void Reset() + { + _enumerator.Reset(); + } + + Entity IEnumerator>.Current => Current; + + object IEnumerator.Current => Current; + + public void Dispose() + { + // Nothin' + } + } +} diff --git a/Robust.Shared/GameObjects/EntityQuery4.cs b/Robust.Shared/GameObjects/EntityQuery4.cs new file mode 100644 index 00000000000..1834c276ab7 --- /dev/null +++ b/Robust.Shared/GameObjects/EntityQuery4.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Robust.Shared.Log; +using Robust.Shared.Utility; + +namespace Robust.Shared.GameObjects; + +/// Any component type. +/// Any component type. +/// Any component type. +/// Any component type. +/// +[PublicAPI] +public readonly struct EntityQuery : IEnumerable> + where TComp1 : IComponent + where TComp2 : IComponent + where TComp3 : IComponent + where TComp4 : IComponent +{ + /// + /// Our dynamic query. Will always be of form [TComp1, ...] + /// + private readonly DynamicEntityQuery _query; + private readonly EntityManager _entMan; + + private readonly bool _enumeratePaused; + + /// + /// Returns an entity query that will include paused entities when enumerated. + /// + /// + /// You shouldn't cache this, please, there is no way to turn it back into a normal query and it's a shorthand + /// only meant for foreach. + /// + /// + /// + /// public sealed class MySystem : EntitySystem + /// { + /// [Dependency] private EntityQuery<TransformComponent> _transforms = default!; + ///
+ /// public void Update(float ft) + /// { + /// foreach (var ent in _transforms.All) + /// { + /// // iterate matching entities, including paused ones. + /// } + /// } + /// } + ///
+ ///
+ public EntityQuery All => new(this, true); + + internal EntityQuery(DynamicEntityQuery query, EntityManager entMan) + { + DebugTools.AssertEqual(query.OutputCount, 4); + _query = query; + _entMan = entMan; + _enumeratePaused = false; + } + + /// + /// Internal constructor used for . + /// + private EntityQuery(EntityQuery derived, bool enumeratePaused) + { + _query = derived._query; + _entMan = derived._entMan; + _enumeratePaused = enumeratePaused; + } + + /// + /// Tries to query an entity for this query's components. + /// + /// The entity to look up components for. + /// The first component + /// The second component + /// The third component + /// True when all components were found, false otherwise. + public bool TryGet(EntityUid ent, [NotNullWhen(true)] out TComp1? comp1, [NotNullWhen(true)] out TComp2? comp2, [NotNullWhen(true)] out TComp3? comp3, [NotNullWhen(true)] out TComp4? comp4) + { + var buffer = new ComponentArray(); + + if (_query.TryGet(ent, buffer)) + { + comp1 = (TComp1)buffer[0]!; + comp2 = (TComp2)buffer[1]!; + comp3 = (TComp3)buffer[2]!; + comp4 = (TComp4)buffer[3]!; + return true; + } + + comp1 = default; + comp2 = default; + comp3 = default; + comp4 = default; + return false; + } + + /// + /// Tries to query an entity for this query's components. + /// + /// The entity to look up components for. + /// The resolved entity. + /// True when all components were found, false otherwise. + public bool TryGet(EntityUid ent, out Entity resolved) + { + if (TryGet(ent, out var c1, out var c2, out var c3, out var c4)) + { + resolved = new(ent, c1, c2, c3, c4); + return true; + } + + resolved = default; + return false; + } + + /// + /// Tries to query an entity for this query's components, skipping already-filled entries. + /// + /// The entity to look up components for. + /// The first component. + /// The second component. + /// The third component. + /// Whether to log if the resolve fails. + /// True when all components were found, false otherwise. + public bool Resolve(EntityUid ent, [NotNullWhen(true)] ref TComp1? comp1, [NotNullWhen(true)] ref TComp2? comp2, [NotNullWhen(true)] ref TComp3? comp3, [NotNullWhen(true)] ref TComp4? comp4, bool logMissing = true) + { + var buffer = new ComponentArray(); + buffer[0] = comp1; + buffer[1] = comp2; + buffer[2] = comp3; + buffer[3] = comp4; + + if (_query.TryResolve(ent, buffer)) + { + comp1 = (TComp1)buffer[0]!; + comp2 = (TComp2)buffer[1]!; + comp3 = (TComp3)buffer[2]!; + comp4 = (TComp4)buffer[3]!; + return true; + } + + if (logMissing) + _entMan.ResolveSawmill.Error($"Can't resolve \"{typeof(TComp1)}\", \"{typeof(TComp2)}\", \"{typeof(TComp3)}\" and \"{typeof(TComp4)}\" on entity {_entMan.ToPrettyString(ent)}!\n{Environment.StackTrace}"); + + return false; + } + + /// + /// Tries to query an entity for this query's components, skipping already-filled entries. + /// + /// The entity to look up components for. + /// Whether to log if the resolve fails. + /// True when all components were found, false otherwise. + public bool Resolve(ref Entity entity, bool logMissing = true) + { + return Resolve(entity.Owner, ref entity.Comp1, ref entity.Comp2, ref entity.Comp3, ref entity.Comp4, logMissing); + } + + /// + /// Tests if a given entity matches this query (ala ) + /// + /// The entity to try matching against. + /// True if the entity matches this query. + public bool Matches(EntityUid ent) + { + return _query.Matches(ent); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Inline storage for the components we're enumerating. + /// Basically just working around the fact you can't stackalloc the span, error CS0208. + /// + [InlineArray(4)] + private struct ComponentArray + { + public IComponent? Entry; + } + + public struct Enumerator : IEnumerator> + { + private DynamicEntityQuery.Enumerator _enumerator; + public Entity Current { get; private set; } + + internal Enumerator(EntityQuery owner) + { + _enumerator = owner._query.GetEnumerator(!owner._enumeratePaused); + } + + public bool MoveNext() + { + var buffer = new ComponentArray(); + + if (_enumerator.MoveNext(out var ent, buffer!)) + { + Current = new(ent, (TComp1)buffer[0]!, (TComp2)buffer[1]!, (TComp3)buffer[2]!, (TComp4)buffer[3]!); + return true; + } + + return false; + } + + public void Reset() + { + _enumerator.Reset(); + } + + Entity IEnumerator>.Current => Current; + + object IEnumerator.Current => Current; + + public void Dispose() + { + // Nothin' + } + } +} diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.Queries.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.Queries.cs new file mode 100644 index 00000000000..2886b43226d --- /dev/null +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.Queries.cs @@ -0,0 +1,57 @@ +using System; +using JetBrains.Annotations; + +namespace Robust.Shared.GameObjects; + +public partial class EntitySystem +{ + /// + [ProxyFor(typeof(EntityManager))] + [Pure] + protected DynamicEntityQuery GetDynamicQuery(params (Type, DynamicEntityQuery.QueryFlags)[] userEntries) + { + return EntityManager.GetDynamicQuery(userEntries); + } + + /// + [ProxyFor(typeof(EntityManager))] + [Pure] + protected EntityQuery GetEntityQuery() + where TComp1 : IComponent + { + return EntityManager.GetEntityQuery(); + } + + /// + [ProxyFor(typeof(EntityManager))] + [Pure] + protected EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent + { + return EntityManager.GetEntityQuery(); + } + + /// + [ProxyFor(typeof(EntityManager))] + [Pure] + protected EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent + where TComp3 : IComponent + { + return EntityManager.GetEntityQuery(); + } + + /// + [ProxyFor(typeof(EntityManager))] + [Pure] + protected EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent + where TComp3 : IComponent + where TComp4 : IComponent + { + return EntityManager.GetEntityQuery(); + } +} diff --git a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs index 06b852ceaf5..928ea37d83b 100644 --- a/Robust.Shared/GameObjects/EntitySystem.Proxy.cs +++ b/Robust.Shared/GameObjects/EntitySystem.Proxy.cs @@ -1080,15 +1080,15 @@ private static KeyNotFoundException CompNotFound(EntityUid uid) #region All Entity Query - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager), nameof(EntityManager.AllEntityQueryEnumerator))] + [Obsolete($"Prefer {nameof(IEntityManager.GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property.")] protected AllEntityQueryEnumerator AllEntityQuery() where TComp1 : IComponent { return EntityManager.AllEntityQueryEnumerator(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager), nameof(EntityManager.AllEntityQueryEnumerator))] + [Obsolete($"Prefer {nameof(IEntityManager.GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property.")] protected AllEntityQueryEnumerator AllEntityQuery() where TComp1 : IComponent where TComp2 : IComponent @@ -1096,8 +1096,8 @@ protected AllEntityQueryEnumerator AllEntityQuery(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager), nameof(EntityManager.AllEntityQueryEnumerator))] + [Obsolete($"Prefer {nameof(IEntityManager.GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property.")] protected AllEntityQueryEnumerator AllEntityQuery() where TComp1 : IComponent where TComp2 : IComponent @@ -1106,8 +1106,8 @@ protected AllEntityQueryEnumerator AllEntityQuery(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager), nameof(EntityManager.AllEntityQueryEnumerator))] + [Obsolete($"Prefer {nameof(IEntityManager.GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property.")] protected AllEntityQueryEnumerator AllEntityQuery() where TComp1 : IComponent where TComp2 : IComponent @@ -1121,15 +1121,15 @@ protected AllEntityQueryEnumerator AllEntityQuer #region Get Entity Query - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager))] + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] protected EntityQueryEnumerator EntityQueryEnumerator() where TComp1 : IComponent { return EntityManager.EntityQueryEnumerator(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager))] + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] protected EntityQueryEnumerator EntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent @@ -1137,8 +1137,8 @@ protected EntityQueryEnumerator EntityQueryEnumerator(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager))] + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] protected EntityQueryEnumerator EntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent @@ -1147,8 +1147,8 @@ protected EntityQueryEnumerator EntityQueryEnumerator(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager))] + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] protected EntityQueryEnumerator EntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent @@ -1161,23 +1161,11 @@ protected EntityQueryEnumerator EntityQueryEnume #endregion #region Entity Query - /// /// If you need the EntityUid, use /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ProxyFor(typeof(EntityManager))] - [Pure] - protected EntityQuery GetEntityQuery() where T : IComponent - { - return EntityManager.GetEntityQuery(); - } - - /// - /// If you need the EntityUid, use - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager))] + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] protected IEnumerable EntityQuery(bool includePaused = false) where TComp1 : IComponent { return EntityManager.EntityQuery(includePaused); @@ -1186,8 +1174,8 @@ protected IEnumerable EntityQuery(bool includePaused = false) wh /// /// If you need the EntityUid, use /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager))] + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] protected IEnumerable<(TComp1, TComp2)> EntityQuery(bool includePaused = false) where TComp1 : IComponent where TComp2 : IComponent @@ -1198,8 +1186,8 @@ protected IEnumerable EntityQuery(bool includePaused = false) wh /// /// If you need the EntityUid, use /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager))] + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] protected IEnumerable<(TComp1, TComp2, TComp3)> EntityQuery(bool includePaused = false) where TComp1 : IComponent where TComp2 : IComponent @@ -1211,8 +1199,8 @@ protected IEnumerable EntityQuery(bool includePaused = false) wh /// /// If you need the EntityUid, use /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] [ProxyFor(typeof(EntityManager))] + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] protected IEnumerable<(TComp1, TComp2, TComp3, TComp4)> EntityQuery(bool includePaused = false) where TComp1 : IComponent where TComp2 : IComponent diff --git a/Robust.Shared/GameObjects/EntitySystemManager.cs b/Robust.Shared/GameObjects/EntitySystemManager.cs index 3a4d3eddb0b..ee42375e3c6 100644 --- a/Robust.Shared/GameObjects/EntitySystemManager.cs +++ b/Robust.Shared/GameObjects/EntitySystemManager.cs @@ -201,6 +201,39 @@ public void Initialize(bool discover = true) .Invoke(dep.Resolve(), null)! ); + var queryMethod2 = typeof(EntityManager).GetMethod(nameof(EntityManager.GetEntityQuery), 2, [])!; + SystemDependencyCollection.RegisterBaseGenericLazy( + typeof(EntityQuery<,>), + (queryType, dep) => + { + var args = queryType.GetGenericArguments(); + return queryMethod2 + .MakeGenericMethod(args[0], args[1]) + .Invoke(dep.Resolve(), null)!; + }); + + var queryMethod3 = typeof(EntityManager).GetMethod(nameof(EntityManager.GetEntityQuery), 3, [])!; + SystemDependencyCollection.RegisterBaseGenericLazy( + typeof(EntityQuery<,,>), + (queryType, dep) => + { + var args = queryType.GetGenericArguments(); + return queryMethod3 + .MakeGenericMethod(args[0], args[1], args[2]) + .Invoke(dep.Resolve(), null)!; + }); + + var queryMethod4 = typeof(EntityManager).GetMethod(nameof(EntityManager.GetEntityQuery), 4, [])!; + SystemDependencyCollection.RegisterBaseGenericLazy( + typeof(EntityQuery<,,,>), + (queryType, dep) => + { + var args = queryType.GetGenericArguments(); + return queryMethod4 + .MakeGenericMethod(args[0], args[1], args[2], args[3]) + .Invoke(dep.Resolve(), null)!; + }); + SystemDependencyCollection.BuildGraph(); foreach (var systemType in _systemTypes) diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs index 3b71a269df8..b7bdd4a90b7 100644 --- a/Robust.Shared/GameObjects/IEntityManager.Components.cs +++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs @@ -404,13 +404,6 @@ bool TryCopyComponent( /// Array of component instances to copy. void CopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents); - /// - /// Returns a cached struct enumerator with the specified component. - /// - EntityQuery GetEntityQuery() where TComp1 : IComponent; - - EntityQuery GetEntityQuery(Type type); - /// /// Returns ALL component type instances on an entity. A single component instance /// can have multiple component types. @@ -471,80 +464,93 @@ bool TryCopyComponent( /// Returns all instances of a component in an array. /// Use sparingly. /// + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] (EntityUid Uid, T Component)[] AllComponents() where T : IComponent; /// /// Returns an array of all entities that have the given component. /// Use sparingly. /// + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] Entity[] AllEntities() where T : IComponent; /// /// Returns an array of all entities that have the given component. /// Use sparingly. /// + [Obsolete($"Prefer non-generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] Entity[] AllEntities(Type tComp); /// /// Returns an array uids of all entities that have the given component. /// Use sparingly. /// + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] EntityUid[] AllEntityUids() where T : IComponent; /// /// Returns an array uids of all entities that have the given component. /// Use sparingly. /// + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] EntityUid[] AllEntityUids(Type tComp); /// /// Returns all instances of a component in a List. /// Use sparingly. /// + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property and .ToList()")] List<(EntityUid Uid, T Component)> AllComponentsList() where T : IComponent; - /// - /// - /// + [Obsolete($"Use {nameof(GameObjects.EntityQuery<>)}.All and non-generic {nameof(IEntityManager.GetEntityQuery)}. This API is questionable.")] public ComponentQueryEnumerator ComponentQueryEnumerator(ComponentRegistry registry); /// - /// + /// /// public CompRegistryEntityEnumerator CompRegistryQueryEnumerator(ComponentRegistry registry); + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] AllEntityQueryEnumerator AllEntityQueryEnumerator(Type comp); + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] AllEntityQueryEnumerator AllEntityQueryEnumerator() where TComp1 : IComponent; + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] AllEntityQueryEnumerator AllEntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent; + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] AllEntityQueryEnumerator AllEntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent where TComp3 : IComponent; + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}, using the All property")] AllEntityQueryEnumerator AllEntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent where TComp3 : IComponent where TComp4 : IComponent; + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] EntityQueryEnumerator EntityQueryEnumerator() where TComp1 : IComponent; + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] EntityQueryEnumerator EntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent; + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] EntityQueryEnumerator EntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent where TComp3 : IComponent; + [Obsolete($"Prefer generic {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] EntityQueryEnumerator EntityQueryEnumerator() where TComp1 : IComponent where TComp2 : IComponent @@ -556,6 +562,7 @@ EntityQueryEnumerator EntityQueryEnumerator /// A trait or type of a component to retrieve. /// All components that have the specified type. + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] IEnumerable EntityQuery(bool includePaused = false) where T: IComponent; /// @@ -564,6 +571,7 @@ EntityQueryEnumerator EntityQueryEnumeratorFirst required component. /// Second required component. /// The pairs of components from each entity that has the two required components. + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] IEnumerable<(TComp1, TComp2)> EntityQuery(bool includePaused = false) where TComp1 : IComponent where TComp2 : IComponent; @@ -575,6 +583,7 @@ EntityQueryEnumerator EntityQueryEnumeratorSecond required component. /// Third required component. /// The pairs of components from each entity that has the three required components. + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] IEnumerable<(TComp1, TComp2, TComp3)> EntityQuery(bool includePaused = false) where TComp1 : IComponent where TComp2 : IComponent @@ -588,6 +597,7 @@ EntityQueryEnumerator EntityQueryEnumeratorThird required component. /// Fourth required component. /// The pairs of components from each entity that has the four required components. + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] IEnumerable<(TComp1, TComp2, TComp3, TComp4)> EntityQuery(bool includePaused = false) where TComp1 : IComponent where TComp2 : IComponent @@ -600,6 +610,7 @@ EntityQueryEnumerator EntityQueryEnumeratorA trait or component type to check for. /// /// All components that are the specified type. + [Obsolete($"Prefer {nameof(GetEntityQuery)} and dependencies on {nameof(GameObjects.EntityQuery<>)}")] IEnumerable<(EntityUid Uid, IComponent Component)> GetAllComponents(Type type, bool includePaused = false); /// diff --git a/Robust.Shared/GameObjects/IEntityManager.Queries.cs b/Robust.Shared/GameObjects/IEntityManager.Queries.cs new file mode 100644 index 00000000000..16c51abf61f --- /dev/null +++ b/Robust.Shared/GameObjects/IEntityManager.Queries.cs @@ -0,0 +1,62 @@ +using System; + +namespace Robust.Shared.GameObjects; + +public partial interface IEntityManager +{ + /// + /// Returns a dynamic query over the given component list and properties. + /// + /// The query entries to build into a cached query. + /// A constructed, optimized query for the given entries. + /// + /// + /// Currently, this is the only API that exposes the Optional and Without component query configurations. + /// A typed API to expose them is unfortunately TBD. + /// + /// + /// Having an Optional or Without entry as the first entry prevents you from enumerating the query. + /// + /// + DynamicEntityQuery GetDynamicQuery(params (Type, DynamicEntityQuery.QueryFlags)[] entries); + + /// + /// Returns a query for entities with the given component. + /// + /// + EntityQuery GetEntityQuery() where TComp1 : IComponent; + + /// + /// Returns a generic query for entities with the given component. + /// + /// + /// + EntityQuery GetEntityQuery(Type type); + + /// + /// Returns a query for entities with the given components. + /// + /// + EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent; + + /// + /// Returns a query for entities with the given components. + /// + /// + EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent + where TComp3 : IComponent; + + /// + /// Returns a query for entities with the given components. + /// + /// + EntityQuery GetEntityQuery() + where TComp1 : IComponent + where TComp2 : IComponent + where TComp3 : IComponent + where TComp4 : IComponent; +} diff --git a/Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs b/Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs index 17f84745cfd..e9a93699cc0 100644 --- a/Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs +++ b/Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs @@ -24,7 +24,7 @@ [CommandInverted] bool inverted return input.Where(x => !EntityManager.HasComponent(x, component)); if (input is EntitiesCommand.AllEntityEnumerator) - return EntityManager.AllEntityUids(component); + return EntityManager.GetEntityQuery(component).Select(x => x.Owner); return input.Where(x => EntityManager.HasComponent(x, component)); }