diff --git a/src/Arch.Tests/BitSetTest.cs b/src/Arch.Tests/BitSetTest.cs index 548025f8..8af11e14 100644 --- a/src/Arch.Tests/BitSetTest.cs +++ b/src/Arch.Tests/BitSetTest.cs @@ -51,7 +51,7 @@ public void HashSimilarity() [Test] public void BitsetSetAll() { - var bitSet = new BitSet(); + var bitSet = new BitSet(5); bitSet.SetAll(); var count = 0; diff --git a/src/Arch.Tests/ChangedQueryTest.cs b/src/Arch.Tests/ChangedQueryTest.cs new file mode 100644 index 00000000..3af50818 --- /dev/null +++ b/src/Arch.Tests/ChangedQueryTest.cs @@ -0,0 +1,107 @@ +using Arch.Core; + +namespace Arch.Tests; + +[TestFixture] +public class ChangedQueryTest +{ + private struct A; + private struct B; + private struct C; + + private World _world; + private Entity _e0, _e1, _e2, _e3, _e4, _e5; + private Entity[] _nothing = []; + + [OneTimeSetUp] + public void Setup() + { + _world = World.Create(); + + _e0 = _world.Create(); + + _e1 = _world.Create(); + _world.MarkChanged(_e1); + + _e2 = _world.Create(); + _world.MarkChanged(_e2); + + _e3 = _world.Create(); + _world.MarkChanged(_e3); + + _e4 = _world.Create(); + _world.MarkChanged(_e4); + + _e5 = _world.Create(); + } + + [Test] + public void WithChangedOnly() + { + var query = new QueryDescription().WithChanged(); + AssertMatches(query, _e1, _e2, _e3); + } + + [Test] + public void WithAnyAndChanged() + { + var query = new QueryDescription().WithAny().WithChanged(); + AssertMatches(query, _e4); + } + + [Test] + public void WithDisjointAnyAndChanged() + { + var query = new QueryDescription().WithAny().WithChanged(); + AssertMatches(query, _e1, _e2, _e3); + } + + [Test] + public void WithNoneAndChanged() + { + var query = new QueryDescription().WithNone().WithChanged(); + AssertMatches(query, _nothing); + } + + [Test] + public void WithDisjointNoneAndChanged() + { + var query = new QueryDescription().WithNone().WithChanged(); + AssertMatches(query, _e4); + } + + [Test] + public void WithExclusiveAndChanged() + { + var query = new QueryDescription().WithExclusive().WithChanged(); + AssertMatches(query, _e1); + } + + [Test] + public void WithDisjointExclusiveAndChanged() + { + var query = new QueryDescription().WithExclusive().WithChanged(); + AssertMatches(query, _nothing); + } + + [Test] + public void WithDisjointAnyNoneAndChanged() + { + var query = new QueryDescription().WithAny().WithChanged().WithNone(); + AssertMatches(query, _e1, _e3); + } + + private void AssertMatches(QueryDescription query, params Entity[] expected) + { + var entities = new Entity[10]; + var total = _world.GetEntities(query, entities); + entities = entities[..total]; + CollectionAssert.AreEquivalent(expected, entities); + } + + [OneTimeTearDown] + public void TearDown() + { + _world.Dispose(); + } +} diff --git a/src/Arch/Arch.csproj b/src/Arch/Arch.csproj index 35b5ce92..a61a66b4 100644 --- a/src/Arch/Arch.csproj +++ b/src/Arch/Arch.csproj @@ -41,29 +41,29 @@ - TRACE; + TRACE;CHANGED_FLAGS; false AnyCPU - TRACE;PURE_ECS; + TRACE;PURE_ECS;CHANGED_FLAGS; - TRACE;EVENTS; + TRACE;EVENTS;CHANGED_FLAGS; - TRACE + TRACE;CHANGED_FLAGS; - TRACE;EVENTS; + TRACE;EVENTS;CHANGED_FLAGS; - TRACE;PURE_ECS + TRACE;PURE_ECS;CHANGED_FLAGS; @@ -590,6 +590,11 @@ True World.CreateBulk.tt + + True + True + QueryDescription.WithChanged.tt + diff --git a/src/Arch/Core/Archetype.cs b/src/Arch/Core/Archetype.cs index 931df944..e9087ca3 100644 --- a/src/Arch/Core/Archetype.cs +++ b/src/Arch/Core/Archetype.cs @@ -1,14 +1,9 @@ -using System.Buffers; using System.Diagnostics.Contracts; -using Arch.Core.Extensions; using Arch.Core.Extensions.Internal; using Arch.Core.Utils; -using Arch.LowLevel; using Arch.LowLevel.Jagged; using Collections.Pooled; using CommunityToolkit.HighPerformance; -using Array = System.Array; -using System.Runtime.InteropServices; namespace Arch.Core; @@ -339,10 +334,7 @@ internal int[] LookupArray /// The number of 's within the array. /// public int ChunkCount { - get - { - return Chunks.Count; - } + get => Chunks.Count; } /// @@ -350,10 +342,7 @@ public int ChunkCount { /// The total capacity. /// public int ChunkCapacity { - get - { - return Chunks.Capacity; - } + get => Chunks.Capacity; } /// @@ -436,7 +425,7 @@ internal int Add(Entity entity, out Chunk chunk, out Slot slot) // TODO: Store ref var currentChunk = ref GetChunk(count); // Fill chunk - if (currentChunk.IsEmpty) + if (!currentChunk.IsFull) { slot = new Slot(currentChunk.Add(entity), count); chunk = currentChunk; @@ -958,3 +947,67 @@ internal static void CopyComponents(Archetype source, ref Slot fromSlot, Archety Chunk.CopyComponents(ref oldChunk, fromSlot.Index, ref sourceSignature, ref newChunk, toSlot.Index, 1); } } + + +#if CHANGED_FLAGS + +public sealed partial class Archetype +{ + /// + /// Checks whether the component of an at a given has been flagged changed. + /// + /// The at which the component of an is to be checked. + /// The component type. + /// True if the component is changed, false otherwise. + public bool IsChanged(ref Slot slot, ComponentType componentType) + { + ref var chunk = ref GetChunk(slot.ChunkIndex); + return chunk.IsChanged(slot.Index, componentType); + } + + /// + /// Flags the component of an at a given as changed. + /// + /// The at which the component of an is to be marked changed. + /// The component type. + internal void MarkChanged(ref Slot slot, ComponentType componentType) + { + ref var chunk = ref GetChunk(slot.ChunkIndex); + chunk.MarkChanged(slot.Index, componentType); + } + + /// + /// Clears the changed flag of the component of an at a given . + /// + /// The at which the component of an is to be cleared. + /// The component type. + internal void ClearChanged(ref Slot slot, ComponentType componentType) + { + ref var chunk = ref GetChunk(slot.ChunkIndex); + chunk.ClearChanged(slot.Index, componentType); + } + + /// + /// Clears the changed flag for all components of an at a given . + /// + /// The slot. + internal void ClearChanged(ref Slot slot) + { + ref var chunk = ref GetChunk(slot.ChunkIndex); + chunk.ClearChanged(slot.Index); + } + + /// + /// Clears all the changed flags in this Archetype. + /// + internal void ClearAllChanged() + { + for (var i = 0; i < Chunks.Count; i++) + { + ref var chunk = ref Chunks[i]; + chunk.ClearAllChanged(); + } + } +} + +#endif diff --git a/src/Arch/Core/Chunk.cs b/src/Arch/Core/Chunk.cs index 7ce1a838..15b430ed 100644 --- a/src/Arch/Core/Chunk.cs +++ b/src/Arch/Core/Chunk.cs @@ -1,12 +1,9 @@ using System.Buffers; using System.Diagnostics.Contracts; -using System.Drawing; using Arch.Core.Events; -using Arch.Core.Extensions; using Arch.Core.Extensions.Internal; using Arch.Core.Utils; using Arch.LowLevel; -using Collections.Pooled; using CommunityToolkit.HighPerformance; using Array = System.Array; @@ -175,6 +172,15 @@ internal Chunk(int capacity, int[] componentIdToArrayIndex, Span var type = types[index]; Components[index] = ArrayRegistry.GetArray(type, Capacity); } + +#if CHANGED_FLAGS + _changedFlags = new BitSet[Capacity + 1]; // Last index contains the "any" bitset + var typeCapacity = ComponentTypeExtensions.GetMaxValue(types); + for (var i = 0; i <= Capacity; i++) + { + _changedFlags[i] = new BitSet(typeCapacity); + } +#endif } @@ -216,11 +222,6 @@ internal Chunk(int capacity, int[] componentIdToArrayIndex, Span /// public readonly bool IsFull { [Pure] get => Count >= Capacity; } - /// - /// Checks whether this instance is full or not. - /// - public readonly bool IsEmpty { [Pure] get => Count < Capacity; } - /// /// Inserts an entity into the . /// This won't fire an event for . @@ -666,3 +667,102 @@ internal int Transfer(int index, ref Chunk chunk) return lastEntity.Id; } } + +#if CHANGED_FLAGS + +public partial struct Chunk +{ + private readonly BitSet[] _changedFlags; + + /// + /// Checks whether any component of the given type has been flagged changed. + /// + /// The component type. + /// True if the component is changed, false otherwise. + [Pure] + public bool IsAnyChanged(ComponentType type) + { + return _changedFlags.DangerousGetReferenceAt(Capacity).IsSet(type.Id); + } + + /// + /// Checks whether any component of the given types has been flagged changed. + /// + /// A representing the component types. + /// True if any of the components have changed, false otherwise. + [Pure] + public bool IsAnyChanged(BitSet types) + { + var changed = _changedFlags.DangerousGetReferenceAt(Capacity); + return types.Any(changed); + } + + /// + /// Checks whether the component at the given index has been flagged changed. + /// + /// The index. + /// The component type. + /// True if the component is changed, false otherwise. + [Pure] + public bool IsChanged(int index, ComponentType type) + { + return _changedFlags.DangerousGetReferenceAt(index).IsSet(type.Id); + } + + /// + /// Checks whether any of the components at the given index has been flagged changed. + /// + /// The index. + /// A representing the component types. + /// True if the component is changed, false otherwise. + [Pure] + public bool IsChanged(int index, BitSet types) + { + var changed = _changedFlags.DangerousGetReferenceAt(index); + return types.Any(changed); + } + + /// + /// Flags the component at the given index as changed. + /// + /// The index. + /// The component type. + public void MarkChanged(int index, ComponentType type) + { + _changedFlags.DangerousGetReferenceAt(index).SetBit(type.Id); + _changedFlags.DangerousGetReferenceAt(Capacity).SetBit(type.Id); + } + + /// + /// Clears all the changed flags in this Chunk. + /// + public void ClearAllChanged() + { + for (var i = 0; i < _changedFlags.Length; i++) + { + var flags = _changedFlags.DangerousGetReferenceAt(i); + flags.ClearAll(); + } + } + + /// + /// Clears the changed flag for the specified component type at the specified index. + /// + /// The type. + /// The index. + public void ClearChanged(int index, ComponentType type) + { + _changedFlags.DangerousGetReferenceAt(index).ClearBit(type.Id); + } + + /// + /// Clears the changed flag for all components at the specified index. + /// + /// The index. + public void ClearChanged(int index) + { + _changedFlags.DangerousGetReferenceAt(index).ClearAll(); + } +} + +#endif diff --git a/src/Arch/Core/Enumerators.cs b/src/Arch/Core/Enumerators.cs index 6e45292f..f63f87a0 100644 --- a/src/Arch/Core/Enumerators.cs +++ b/src/Arch/Core/Enumerators.cs @@ -171,7 +171,7 @@ public QueryArchetypeEnumerator GetEnumerator() /// represents an enumerator with which one can iterate over all non empty 's that matches the given . /// [SkipLocalsInit] -public ref struct QueryChunkEnumerator +public ref partial struct QueryChunkEnumerator { private QueryArchetypeEnumerator _archetypeEnumerator; private int _index; @@ -538,4 +538,3 @@ public RangeEnumerator GetEnumerator() return new RangeEnumerator(_threads, _size); } } - diff --git a/src/Arch/Core/Extensions/EntityExtensions.cs b/src/Arch/Core/Extensions/EntityExtensions.cs index cc890c4e..e258fe8e 100644 --- a/src/Arch/Core/Extensions/EntityExtensions.cs +++ b/src/Arch/Core/Extensions/EntityExtensions.cs @@ -359,3 +359,60 @@ public static void RemoveRange(this in Entity entity, Span types) #endif } + +public static partial class EntityExtensions +{ + +#if CHANGED_FLAGS && !PURE_ECS + + /// + public static bool IsChanged(this Entity entity) + { + var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId); + return world.IsChanged(entity); + } + + /// + public static bool IsChanged(this Entity entity, ComponentType type) + { + var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId); + return world.IsChanged(entity, type); + } + + /// + public static void Markchanged(this Entity entity) + { + var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId); + world.Markchanged(entity); + } + + /// + public static void Markchanged(this Entity entity, ComponentType componentType) + { + var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId); + world.Markchanged(entity, componentType); + } + + /// + public static void ClearChanged(this Entity entity) + { + var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId); + world.ClearChanged(entity); + } + + /// + public static void ClearChanged(this Entity entity, ComponentType componentType) + { + var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId); + world.ClearChanged(entity, componentType); + } + + /// + public static void ClearChanged(this Entity entity) + { + var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId); + world.ClearChanged(entity); + } + +#endif +} diff --git a/src/Arch/Core/Extensions/Internal/ComponentTypeExtensions.cs b/src/Arch/Core/Extensions/Internal/ComponentTypeExtensions.cs index 6f3c3215..c0a6b1fd 100644 --- a/src/Arch/Core/Extensions/Internal/ComponentTypeExtensions.cs +++ b/src/Arch/Core/Extensions/Internal/ComponentTypeExtensions.cs @@ -38,15 +38,7 @@ internal static int ToByteSize(this Span types) internal static int[] ToLookupArray(this Span types) { // Get maximum component ID. - var max = 0; - foreach (var type in types) - { - var componentId = type.Id; - if (componentId >= max) - { - max = componentId; - } - } + var max = GetMaxValue(types); // Create lookup table where the component ID points to the component index. var array = new int[max + 1]; @@ -61,4 +53,20 @@ internal static int[] ToLookupArray(this Span types) return array; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GetMaxValue(Span types) + { + var max = 0; + foreach (var type in types) + { + var componentId = type.Id; + if (componentId >= max) + { + max = componentId; + } + } + + return max; + } } diff --git a/src/Arch/Core/Query.cs b/src/Arch/Core/Query.cs index feed4a1e..9ce2ef52 100644 --- a/src/Arch/Core/Query.cs +++ b/src/Arch/Core/Query.cs @@ -1,7 +1,5 @@ -using Arch.Core.Extensions; using Arch.Core.Extensions.Internal; using Arch.Core.Utils; -using Collections.Pooled; using CommunityToolkit.HighPerformance; namespace Arch.Core; @@ -356,6 +354,8 @@ public QueryDescription() _hashCode = -1; } +#if !CHANGED_FLAGS + /// /// Initializes a new instance of the struct. /// @@ -374,6 +374,8 @@ public QueryDescription(Signature? all = null, Signature? any = null, Signature? _hashCode = GetHashCode(); } +#endif + /// /// Builds this instance by calculating a new . /// Is actually only needed if the passed arrays are changed afterwards. @@ -487,6 +489,9 @@ public override int GetHashCode() hash = (hash * 23) + Any.GetHashCode(); hash = (hash * 23) + None.GetHashCode(); hash = (hash * 23) + Exclusive.GetHashCode(); +#if CHANGED_FLAGS + hash = (hash * 23) + Changed.GetHashCode(); +#endif _hashCode = hash; return hash; } @@ -535,6 +540,11 @@ public partial class Query : IEquatable private readonly BitSet _none; private readonly BitSet _exclusive; +#if CHANGED_FLAGS + public readonly bool HasChangedFilter; + public readonly BitSet Changed; +#endif + private readonly bool _isExclusive; /// @@ -558,10 +568,26 @@ internal Query(Archetypes allArchetypes, QueryDescription description) // Convert to `BitSet`s. _all = description.All; - _any = description.Any; _none = description.None; _exclusive = description.Exclusive; +#if CHANGED_FLAGS + if (description.Changed.Count > 0) + { + Changed = description.Changed; + _any = Signature.Add(description.Any, description.Changed); + HasChangedFilter = true; + } + else + { + Changed = new BitSet(); + _any = description.Any; + HasChangedFilter = false; + } +#else + _any = description.Any; +#endif + // Handle exclusive. if (description.Exclusive.Count != 0) { @@ -578,7 +604,9 @@ internal Query(Archetypes allArchetypes, QueryDescription description) /// True if it matches, otherwise false. public bool Matches(BitSet bitset) { - return _isExclusive ? _exclusive.Exclusive(bitset) : _all.All(bitset) && _any.Any(bitset) && _none.None(bitset); + return _isExclusive + ? _exclusive.Exclusive(bitset) + : _all.All(bitset) && _any.Any(bitset) && _none.None(bitset); } /// @@ -599,8 +627,7 @@ private void Match() _matchingArchetypes.Clear(); foreach (var archetype in allArchetypes) { - var matches = Matches(archetype.BitSet); - if (matches) + if (Matches(archetype.BitSet)) { _matchingArchetypes.Add(archetype); } @@ -639,6 +666,18 @@ public QueryChunkEnumerator GetEnumerator() return new QueryChunkEnumerator(_matchingArchetypes.AsSpan()); } + public QueryEntityEnumerator GetEntityEnumerator() + { + Match(); + return new QueryEntityEnumerator(this); + } + + public QueryComponentEnumerator GetComponentEnumerator() + { + Match(); + return new QueryComponentEnumerator(this); + } + /// /// Checks this for equality with another. /// @@ -646,7 +685,14 @@ public QueryChunkEnumerator GetEnumerator() /// True if they are equal, false if not. public bool Equals(Query other) { - return Equals(_any, other._any) && Equals(_all, other._all) && Equals(_none, other._none) && Equals(_exclusive, other._exclusive) && _queryDescription.Equals(other._queryDescription); + return Equals(_any, other._any) && + Equals(_all, other._all) && + Equals(_none, other._none) && +#if CHANGED_FLAGS + Equals(Changed, other.Changed) && +#endif + Equals(_exclusive, other._exclusive) && + _queryDescription.Equals(other._queryDescription); } /// @@ -667,10 +713,13 @@ public override int GetHashCode() { unchecked { - var hashCode = _any is not null ? _any.GetHashCode() : 0; - hashCode = (hashCode * 397) ^ (_all is not null ? _all.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (_none is not null ? _none.GetHashCode() : 0); + var hashCode = _any?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (_all?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (_none?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (_exclusive?.GetHashCode() ?? 0); +#if CHANGED_FLAGS + hashCode = (hashCode * 397) ^ (Changed?.GetHashCode() ?? 0); +#endif hashCode = (hashCode * 397) ^ _queryDescription.GetHashCode(); return hashCode; @@ -699,3 +748,57 @@ public override int GetHashCode() return !left.Equals(right); } } + + +// Changed flags support +#if CHANGED_FLAGS + +public partial struct QueryDescription +{ + /// + /// A of all components that an should have and which should be flagged as changed. + /// If the content of the array is subsequently changed, a should be carried out. + /// + public Signature Changed { get; private set; } = Signature.Null; + + /// + /// Initializes a new instance of the struct. + /// + /// An array of all components that an should have mandatory. + /// An array of all components of which an should have at least one. + /// An array of all components of which an should not have any. + /// All components that an should have mandatory. + /// All components that an should have and which should be flagged as changed. + + public QueryDescription(Signature? all = null, Signature? any = null, Signature? none = null, Signature? exclusive = null, Signature? changed = null) + { + All = all ?? All; + Any = any ?? Any; + None = none ?? None; + Exclusive = exclusive ?? Exclusive; + + if (changed != null) + { + Changed = changed.Value; + Any = Signature.Add(Any, Changed); + } + + _hashCode = -1; + _hashCode = GetHashCode(); + } + + /// + /// All components that an should have and which should be flagged as changed. + /// + /// The generic type. + /// The same instance for chained operations. + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } +} + +#endif diff --git a/src/Arch/Core/QueryEnumerators.cs b/src/Arch/Core/QueryEnumerators.cs new file mode 100644 index 00000000..9951ca56 --- /dev/null +++ b/src/Arch/Core/QueryEnumerators.cs @@ -0,0 +1,282 @@ +using Arch.Core.Utils; +using CommunityToolkit.HighPerformance; + +namespace Arch.Core; + +[SkipLocalsInit] +public ref struct QueryEntityEnumerator +{ +#if CHANGED_FLAGS + private readonly bool _checkChanged; + private readonly BitSet _changedMask; +#endif + + private QueryChunkEnumerator _chunkEnumerator; + private Ref _entityPtr; + private Ref _chunk; + private int _entityIndex = -1; + + public QueryEntityEnumerator(Query query) + { +#if CHANGED_FLAGS + _checkChanged = query.HasChangedFilter; + _changedMask = query.Changed; +#endif + _chunkEnumerator = query.GetEnumerator(); + } + + public readonly ref Entity Current + { + get => ref Unsafe.Add(ref _entityPtr.Value, _entityIndex); + } + + public bool MoveNext() + { +#if CHANGED_FLAGS + if (_checkChanged) + { + while (true) + { + // Move to the next entity in the current chunk + if (MoveNextChangedEntity()) + { + return true; // Return true if the next entity is valid + } + + // Move to the next chunk in the query + if (!MoveNextChangedChunk()) + { + return false; // Return false if there's no more chunks + } + } + } +#endif + + while (true) + { + // Move to the next entity in the current chunk + if (MoveNextEntity()) + { + return true; // Return true if the next entity is valid + } + + // Move to the next chunk in the query + if (!MoveNextChunk()) + { + return false; // Return false if there's no more chunks + } + } + } + + public QueryEntityEnumerator GetEnumerator() + { + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool MoveNextChunk() + { + while (_chunkEnumerator.MoveNext()) + { + ref var chunk = ref _chunkEnumerator.Current; + + // Skip empty chunks + if (chunk.Count <= 0) + { + continue; + } + + _chunk = new Ref(ref chunk); + _entityIndex = chunk.Count; + _entityPtr = new Ref(ref chunk.Entity(0)); + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool MoveNextEntity() + { + unchecked + { + return --_entityIndex >= 0; + } + } + +#if CHANGED_FLAGS + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool MoveNextChangedChunk() + { + while (_chunkEnumerator.MoveNext()) + { + ref var chunk = ref _chunkEnumerator.Current; + + // Skip empty and non-changed chunks + if (chunk.Count <= 0 || !chunk.IsAnyChanged(_changedMask)) + { + continue; + } + + _chunk = new Ref(ref chunk); + _entityIndex = chunk.Count; + _entityPtr = new Ref(ref chunk.Entity(0)); + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool MoveNextChangedEntity() + { + unchecked + { + // Skip non-changed entities + return --_entityIndex >= 0 && _chunk.Value.IsChanged(_entityIndex, _changedMask); + } + } + +#endif +} + +[SkipLocalsInit] +public ref struct QueryComponentEnumerator +{ +#if CHANGED_FLAGS + private readonly bool _checkChanged; + private readonly BitSet _changedMask; +#endif + + private QueryChunkEnumerator _chunkEnumerator; + private Ref _cmpPtr; + private Ref _chunkPtr; + private int _entityIndex = -1; + + public QueryComponentEnumerator(Query query) + { +#if CHANGED_FLAGS + _checkChanged = query.HasChangedFilter; + _changedMask = query.Changed; +#endif + _chunkEnumerator = query.GetEnumerator(); + } + + public readonly ref T Current + { + get => ref Unsafe.Add(ref _cmpPtr.Value, _entityIndex); + } + + public bool MoveNext() + { +#if CHANGED_FLAGS + if (_checkChanged) + { + while (true) + { + // Move to the next entity in the current chunk + if (MoveNextChangedEntity()) + { + return true; // Return true if the next entity is valid + } + + // Move to the next chunk in the query + if (!MoveNextChangedChunk()) + { + return false; // Return false if there's no more chunks + } + } + } +#endif + + while (true) + { + // Move to the next entity in the current chunk + if (MoveNextEntity()) + { + return true; // Return true if the next entity is valid + } + + // Move to the next chunk in the query + if (!MoveNextChunk()) + { + return false; // Return false if there's no more chunks + } + } + } + + public QueryComponentEnumerator GetEnumerator() + { + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool MoveNextChunk() + { + while (_chunkEnumerator.MoveNext()) + { + ref var chunk = ref _chunkEnumerator.Current; + + // Skip empty chunks + if (chunk.Count <= 0) + { + continue; + } + + _chunkPtr = new Ref(ref chunk); + _entityIndex = chunk.Count; + _cmpPtr = new Ref(ref chunk.GetFirst()); + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool MoveNextEntity() + { + unchecked + { + return --_entityIndex >= 0; + } + } + +#if CHANGED_FLAGS + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool MoveNextChangedChunk() + { + while (_chunkEnumerator.MoveNext()) + { + ref var chunk = ref _chunkEnumerator.Current; + + // Skip empty and non-changed chunks + if (chunk.Count <= 0 || !chunk.IsAnyChanged(_changedMask)) + { + continue; + } + + _chunkPtr = new Ref(ref chunk); + _entityIndex = chunk.Count; + _cmpPtr = new Ref(ref chunk.GetFirst()); + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool MoveNextChangedEntity() + { + unchecked + { + // Skip non-changed entities + return --_entityIndex >= 0 && _chunkPtr.Value.IsChanged(_entityIndex, _changedMask); + } + } + +#endif +} + + diff --git a/src/Arch/Core/Utils/AssertionUtils.cs b/src/Arch/Core/Utils/AssertionUtils.cs new file mode 100644 index 00000000..a5f19123 --- /dev/null +++ b/src/Arch/Core/Utils/AssertionUtils.cs @@ -0,0 +1,10 @@ +namespace Arch.Core.Utils; + +public static class AssertionUtils +{ + [Conditional("DEBUG"), Conditional("CHANGED_FLAGS")] + public static void AssertNoChangedFilter(QueryDescription queryDescription, [CallerMemberName] string? callerName = null) + { + Debug.Assert(queryDescription.Changed.Count == 0, $"The method {callerName} does not support queries with dirty filters."); + } +} diff --git a/src/Arch/Core/Utils/BitSet.cs b/src/Arch/Core/Utils/BitSet.cs index a82c593a..e39f7c9f 100644 --- a/src/Arch/Core/Utils/BitSet.cs +++ b/src/Arch/Core/Utils/BitSet.cs @@ -24,7 +24,7 @@ public sealed class BitSet public static int RequiredLength(int id) { -#if NET7_0 +#if NET7_0_OR_GREATER return (id >> 5) + int.Sign(id & BitSize); #else return (int)Math.Ceiling((float)id / BitSize); @@ -53,7 +53,22 @@ public static int RequiredLength(int id) /// public BitSet() { - _bits = new uint[_padding]; + _bits = []; + } + + /// + /// Initializes a new instance of the class with the required capacity. + /// + /// The initial capacity. + public BitSet(int capacity) + { + // Calculate the number of uint blocks needed for 'capacity' bits + // Each block holds BitSize + 1 bits (usually 32) + int requiredBlocks = (capacity + BitSize) / (BitSize + 1); + + // Round up to nearest multiple of _padding + int size = (requiredBlocks + _padding - 1) / _padding * _padding; + _bits = new uint[size]; } /// @@ -169,6 +184,16 @@ public void ClearAll() [SkipLocalsInit] public bool All(BitSet other) { + if (Length == 0) + { + return true; + } + + if (other.Length == 0) + { + return false; + } + var min = Math.Min(Math.Min(Length, other.Length), _max); if (!Vector.IsHardwareAccelerated || min < _padding) { @@ -230,6 +255,16 @@ public bool All(BitSet other) /// True if they match, false if not. public bool Any(BitSet other) { + if (Length == 0) + { + return true; + } + + if (other.Length == 0) + { + return false; + } + var min = Math.Min(Math.Min(Length, other.Length), _max); if (!Vector.IsHardwareAccelerated || min < _padding) { @@ -291,6 +326,11 @@ public bool Any(BitSet other) /// True if none match, false if not. public bool None(BitSet other) { + if (Length == 0 || other.Length == 0) + { + return true; + } + var min = Math.Min(Math.Min(Length, other.Length), _max); if (!Vector.IsHardwareAccelerated || min < _padding) { @@ -333,8 +373,17 @@ public bool None(BitSet other) /// True if they match, false if not. public bool Exclusive(BitSet other) { - var min = Math.Min(Math.Min(Length, other.Length), _max); + if (Length == 0 && other.Length == 0) + { + return true; + } + if (Length == 0 || other.Length == 0) + { + return false; + } + + var min = Math.Min(Math.Min(Length, other.Length), _max); if (!Vector.IsHardwareAccelerated || min < _padding) { var bits = _bits.AsSpan(); diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index 5af4c0ba..1c7b1fe5 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -407,6 +407,8 @@ public Query Query(in QueryDescription queryDescription) [Pure] public int CountEntities(in QueryDescription queryDescription) { + AssertionUtils.AssertNoChangedFilter(queryDescription); + var counter = 0; var query = Query(in queryDescription); foreach (var archetype in query.GetArchetypeIterator()) @@ -424,20 +426,19 @@ public int CountEntities(in QueryDescription queryDescription) /// The which specifies the components or s for which to search. /// The receiving the found s. /// The start index inside the . Default is 0. - public void GetEntities(in QueryDescription queryDescription, Span list, int start = 0) + /// The amount of entities matched by the query. + public int GetEntities(in QueryDescription queryDescription, Span list, int start = 0) { var index = 0; var query = Query(in queryDescription); - foreach (ref var chunk in query) + + foreach (ref var entity in query.GetEntityEnumerator()) { - ref var entityFirstElement = ref chunk.Entity(0); - foreach (var entityIndex in chunk) - { - var entity = Unsafe.Add(ref entityFirstElement, entityIndex); - list[start + index] = entity; - index++; - } + list[start + index] = entity; + index++; } + + return index; } /// @@ -446,8 +447,10 @@ public void GetEntities(in QueryDescription queryDescription, Span list, /// The which specifies the components for which to search. /// The receiving s containing s with the matching components. /// The start index inside the . Default is 0. - public void GetArchetypes(in QueryDescription queryDescription, Span archetypes, int start = 0) + public int GetArchetypes(in QueryDescription queryDescription, Span archetypes, int start = 0) { + AssertionUtils.AssertNoChangedFilter(queryDescription); + var index = 0; var query = Query(in queryDescription); foreach (var archetype in query.GetArchetypeIterator()) @@ -455,6 +458,8 @@ public void GetArchetypes(in QueryDescription queryDescription, Span archetypes[start + index] = archetype; index++; } + + return index; } /// @@ -463,15 +468,37 @@ public void GetArchetypes(in QueryDescription queryDescription, Span /// The which specifies which components are searched for. /// The receiving s containing s with the matching components. /// The start index inside the . Default is 0. - public void GetChunks(in QueryDescription queryDescription, Span chunks, int start = 0) + public int GetChunks(in QueryDescription queryDescription, Span chunks, int start = 0) { var index = 0; var query = Query(in queryDescription); + +#if CHANGED_FLAGS + if (query.HasChangedFilter) + { + var changedMask = query.Changed; + foreach (ref var chunk in query) + { + if (!chunk.IsAnyChanged(changedMask)) + { + continue; + } + + chunks[start + index] = chunk; + index++; + } + + return index; + } +#endif + foreach (ref var chunk in query) { chunks[start + index] = chunk; index++; } + + return index; } /// @@ -735,14 +762,9 @@ public partial class World public void Query(in QueryDescription queryDescription, ForEach forEntity) { var query = Query(in queryDescription); - foreach (ref var chunk in query) + foreach (ref var entity in query.GetEntityEnumerator()) { - ref var entityLastElement = ref chunk.Entity(0); - foreach (var entityIndex in chunk) - { - var entity = Unsafe.Add(ref entityLastElement, entityIndex); - forEntity(entity); - } + forEntity(entity); } } @@ -755,16 +777,10 @@ public void Query(in QueryDescription queryDescription, ForEach forEntity) public void InlineQuery(in QueryDescription queryDescription) where T : struct, IForEach { var t = new T(); - var query = Query(in queryDescription); - foreach (ref var chunk in query) + foreach (ref var entity in query.GetEntityEnumerator()) { - ref var entityFirstElement = ref chunk.Entity(0); - foreach (var entityIndex in chunk) - { - var entity = Unsafe.Add(ref entityFirstElement, entityIndex); - t.Update(entity); - } + t.Update(entity); } } @@ -778,14 +794,9 @@ public void InlineQuery(in QueryDescription queryDescription) where T : struc public void InlineQuery(in QueryDescription queryDescription, ref T iForEach) where T : struct, IForEach { var query = Query(in queryDescription); - foreach (ref var chunk in query) + foreach (ref var entity in query.GetEntityEnumerator()) { - ref var entityFirstElement = ref chunk.Entity(0); - foreach (var entityIndex in chunk) - { - var entity = Unsafe.Add(ref entityFirstElement, entityIndex); - iForEach.Update(entity); - } + iForEach.Update(entity); } } } @@ -808,6 +819,8 @@ public partial class World [StructuralChange] public void Destroy(in QueryDescription queryDescription) { + AssertionUtils.AssertNoChangedFilter(queryDescription); + var query = Query(in queryDescription); foreach (var archetype in query.GetArchetypeIterator()) { @@ -847,6 +860,8 @@ public void Destroy(in QueryDescription queryDescription) /// The value of the component to set. public void Set(in QueryDescription queryDescription, in T? value = default) { + AssertionUtils.AssertNoChangedFilter(queryDescription); + var query = Query(in queryDescription); foreach (ref var chunk in query) { @@ -876,13 +891,15 @@ public void Set(in QueryDescription queryDescription, in T? value = default) [StructuralChange] public void Add(in QueryDescription queryDescription, in T? component = default) { + AssertionUtils.AssertNoChangedFilter(queryDescription); + // BitSet to stack/span bitset, size big enough to contain ALL registered components. Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; var query = Query(in queryDescription); foreach (var archetype in query.GetArchetypeIterator()) { - // Archetype with T shouldnt be skipped to prevent undefined behaviour. + // Archetype with T shouldn't be skipped to prevent undefined behaviour. if (archetype.EntityCount == 0 || archetype.Has()) { continue; @@ -933,6 +950,8 @@ public void Add(in QueryDescription queryDescription, in T? component = defau [StructuralChange] public void Remove(in QueryDescription queryDescription) { + AssertionUtils.AssertNoChangedFilter(queryDescription); + // BitSet to stack/span bitset, size big enough to contain ALL registered components. Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; @@ -1745,3 +1764,90 @@ public Signature GetSignature(Entity entity) } #endregion + +#if CHANGED_FLAGS + +public partial class World +{ + + /// + /// Checks whether the component of an has been flagged changed. + /// + /// The component type. + /// The . + public bool IsChanged(Entity entity) + { + var componentType = Component.ComponentType; + var entityData = EntityInfo.GetEntityData(entity.Id); + return entityData.Archetype.IsChanged(ref entityData.Slot, componentType); + } + + /// + /// Checks whether the component of an has been flagged changed. + /// + /// The . + /// The component . + public bool IsChanged(Entity entity, ComponentType type) + { + var entityData = EntityInfo.GetEntityData(entity.Id); + return entityData.Archetype.IsChanged(ref entityData.Slot, type); + } + + /// + /// Flags the component of type an as changed. + /// + /// The component type. + /// The . + public void MarkChanged(Entity entity) + { + var componentType = Component.ComponentType; + var entityData = EntityInfo.GetEntityData(entity.Id); + entityData.Archetype.MarkChanged(ref entityData.Slot, componentType); + } + + /// + /// Flags the component of type an as changed. + /// + /// The . + /// The component . + public void MarkChanged(Entity entity, ComponentType type) + { + var entityData = EntityInfo.GetEntityData(entity.Id); + entityData.Archetype.MarkChanged(ref entityData.Slot, type); + } + + /// + /// Clears the changed flag of the component of an . + /// + /// The component type. + /// The . + public void ClearChanged(Entity entity) + { + var componentType = Component.ComponentType; + var entityData = EntityInfo.GetEntityData(entity.Id); + entityData.Archetype.ClearChanged(ref entityData.Slot, componentType); + } + + /// + /// Clears the changed flag of the component of an . + /// + /// The . + /// The component . + public void ClearChanged(Entity entity, ComponentType type) + { + var entityData = EntityInfo.GetEntityData(entity.Id); + entityData.Archetype.ClearChanged(ref entityData.Slot, type); + } + + /// + /// Clears the changed flag for all components of an . + /// + /// The . + public void ClearChanged(Entity entity) + { + var entityData = EntityInfo.GetEntityData(entity.Id); + entityData.Archetype.ClearChanged(ref entityData.Slot); + } +} + +#endif diff --git a/src/Arch/Templates/QueryDescription.WithDirty.cs b/src/Arch/Templates/QueryDescription.WithDirty.cs new file mode 100644 index 00000000..c40f9737 --- /dev/null +++ b/src/Arch/Templates/QueryDescription.WithDirty.cs @@ -0,0 +1,205 @@ +#if CHANGED_FLAGS + +using System; +using System.Diagnostics.Contracts; +using Arch.Core; +using Arch.Core.Utils; + +namespace Arch.Core; +public partial struct QueryDescription +{ + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } + + [UnscopedRef] + public ref QueryDescription WithChanged() + { + Changed = Component.Signature; + Build(); + return ref this; + } +} + +#endif diff --git a/src/Arch/Templates/QueryDescription.WithDirty.tt b/src/Arch/Templates/QueryDescription.WithDirty.tt new file mode 100644 index 00000000..6b07f2e3 --- /dev/null +++ b/src/Arch/Templates/QueryDescription.WithDirty.tt @@ -0,0 +1,35 @@ +<#@ template language="C#" #> +<#@ output extension=".cs" #> +<#@ import namespace="System.Text" #> +<#@ include file="Helpers.ttinclude" #> + +#if CHANGED_FLAGS + +using System; +using System.Diagnostics.Contracts; +using Arch.Core; +using Arch.Core.Utils; + +namespace Arch.Core; +public partial struct QueryDescription +{ + <# + for (var index = 2; index <= Amount; index++) + { + var generics = AppendGenerics(index); + #> + + [UnscopedRef] + public ref QueryDescription WithChanged<<#= generics #>>() + { + All = Component<<#= generics #>>.Signature; + Build(); + return ref this; + } + <# + } + #> +} + +#endif +