From c590e645f6636f3995968ff95b631d8f06edd156 Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 23 Jul 2024 00:10:35 +0200 Subject: [PATCH] Breaking changes, introduced `Signature`. Renamed `Group` to `Component`, moved methods around and several other small changes for improved performance. --- src/Arch.SourceGen/Fundamentals/Component.cs | 52 ++++ src/Arch.SourceGen/Fundamentals/Create.cs | 4 +- src/Arch.SourceGen/Fundamentals/Group.cs | 52 ---- .../Queries/QueryDescription.cs | 8 +- src/Arch.SourceGen/QueryGenerator.cs | 4 +- src/Arch.Tests/ArchetypeTest.cs | 6 +- src/Arch.Tests/EnumeratorTest.cs | 1 + src/Arch/Core/Archetype.cs | 32 +-- src/Arch/Core/Chunk.cs | 5 +- src/Arch/Core/EntityInfo.cs | 2 +- .../Extensions/Internal/BitSetExtensions.cs | 21 +- .../Internal/ComponentTypeExtensions.cs | 4 +- src/Arch/Core/Extensions/WorldExtensions.cs | 19 +- src/Arch/Core/Query.cs | 244 ++++++++++++++++-- src/Arch/Core/Utils/CompileTimeStatics.cs | 75 +++--- src/Arch/Core/World.cs | 63 ++--- 16 files changed, 376 insertions(+), 216 deletions(-) create mode 100644 src/Arch.SourceGen/Fundamentals/Component.cs delete mode 100644 src/Arch.SourceGen/Fundamentals/Group.cs diff --git a/src/Arch.SourceGen/Fundamentals/Component.cs b/src/Arch.SourceGen/Fundamentals/Component.cs new file mode 100644 index 00000000..ac9edfd4 --- /dev/null +++ b/src/Arch.SourceGen/Fundamentals/Component.cs @@ -0,0 +1,52 @@ +namespace Arch.SourceGen; + +public static class ComponentExtensions +{ + public static StringBuilder AppendComponents(this StringBuilder sb, int amount) + { + for (var index = 1; index < amount; index++) + { + sb.AppendComponent(index); + } + + return sb; + } + + public static StringBuilder AppendComponent(this StringBuilder sb, int amount) + { + var generics = new StringBuilder().GenericWithoutBrackets(amount); + var types = new StringBuilder(); + for (var index = 0; index <= amount; index++) + { + types.Append($"Component.ComponentType,"); + } + + var template = + $$""" + /// + public static class Component<{{generics}}> + { + internal static readonly int Id; + + /// + /// An for this given set of components. + /// + public static readonly Signature Signature; + + /// + /// The hash code for this given set of components. + /// + public static readonly int Hash; + + static Component() + { + Id = Interlocked.Increment(ref Component.Id); + Signature = new Signature(new [] { {{types}} }); + Hash = Signature.GetHashCode(); + } + } + """; + + return sb.AppendLine(template); + } +} diff --git a/src/Arch.SourceGen/Fundamentals/Create.cs b/src/Arch.SourceGen/Fundamentals/Create.cs index 54198b38..69fba299 100644 --- a/src/Arch.SourceGen/Fundamentals/Create.cs +++ b/src/Arch.SourceGen/Fundamentals/Create.cs @@ -30,7 +30,7 @@ public static StringBuilder AppendCreate(this StringBuilder sb, int amount) [StructuralChange] public Entity Create<{{generics}}>({{parameters}}) { - var types = Group<{{generics}}>.Types; + var signature = Component<{{generics}}>.Signature; // Recycle id or increase var recycle = RecycledIds.TryDequeue(out var recycledId); @@ -40,7 +40,7 @@ public static StringBuilder AppendCreate(this StringBuilder sb, int amount) var entity = new Entity(recycled.Id, Id); // Add to archetype & mapping - var archetype = GetOrCreate(types); + var archetype = GetOrCreate(signature); var createdChunk = archetype.Add(entity, out var slot); archetype.Set<{{generics}}>(ref slot, {{inParameters}}); diff --git a/src/Arch.SourceGen/Fundamentals/Group.cs b/src/Arch.SourceGen/Fundamentals/Group.cs deleted file mode 100644 index 52bd6e1d..00000000 --- a/src/Arch.SourceGen/Fundamentals/Group.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Arch.SourceGen; - -public static class GroupExtensions -{ - public static StringBuilder AppendGroups(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendGroup(index); - } - - return sb; - } - - public static StringBuilder AppendGroup(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var types = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - types.Append($"Component.ComponentType,"); - } - - var template = - $$""" - /// - public static class Group<{{generics}}> - { - internal static readonly int Id; - - /// - /// The global array of for this given type group. Must not be modified in any way. - /// - public static readonly ComponentType[] Types; - - /// - /// The hash code for this given type group. - /// - public static readonly int Hash; - - static Group() - { - Id = Interlocked.Increment(ref Group.Id); - Types = new ComponentType[] { {{types}} }; - Hash = Component.GetHashCode(Types); - } - } - """; - - return sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Queries/QueryDescription.cs b/src/Arch.SourceGen/Queries/QueryDescription.cs index a35598e6..d2c38694 100644 --- a/src/Arch.SourceGen/Queries/QueryDescription.cs +++ b/src/Arch.SourceGen/Queries/QueryDescription.cs @@ -22,7 +22,7 @@ public static StringBuilder AppendQueryDescriptionWithAll(this StringBuilder sb, [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref QueryDescription WithAll<{{generics}}>() { - All = Group<{{generics}}>.Types; + All = Component<{{generics}}>.Signature; _hashCode = -1; return ref this; } @@ -52,7 +52,7 @@ public static StringBuilder AppendQueryDescriptionWithAny(this StringBuilder sb, [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref QueryDescription WithAny<{{generics}}>() { - Any = Group<{{generics}}>.Types; + Any = Component<{{generics}}>.Signature; _hashCode = -1; return ref this; } @@ -82,7 +82,7 @@ public static StringBuilder AppendQueryDescriptionWithNone(this StringBuilder sb [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref QueryDescription WithNone<{{generics}}>() { - None = Group<{{generics}}>.Types; + None = Component<{{generics}}>.Signature; _hashCode = -1; return ref this; } @@ -112,7 +112,7 @@ public static StringBuilder AppendQueryDescriptionWithExclusive(this StringBuild [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref QueryDescription WithExclusive<{{generics}}>() { - Exclusive = Group<{{generics}}>.Types; + Exclusive = Component<{{generics}}>.Signature; _hashCode = -1; return ref this; } diff --git a/src/Arch.SourceGen/QueryGenerator.cs b/src/Arch.SourceGen/QueryGenerator.cs index 68425ec8..1a32dfab 100644 --- a/src/Arch.SourceGen/QueryGenerator.cs +++ b/src/Arch.SourceGen/QueryGenerator.cs @@ -20,7 +20,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) compileTimeStatics.AppendLine("using System;"); compileTimeStatics.AppendLine("using System.Threading;"); compileTimeStatics.AppendLine("namespace Arch.Core.Utils;"); - compileTimeStatics.AppendGroups(25); + compileTimeStatics.AppendComponents(25); var delegates = new StringBuilder(); delegates.AppendLine("using System;"); @@ -41,7 +41,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) references.AppendLine("using CommunityToolkit.HighPerformance;"); references.AppendLine("using Arch.Core.Utils;"); references.AppendLine("namespace Arch.Core;"); - references.AppendComponents(25); + ReferencesExtensions.AppendComponents(references, 25); references.AppendEntityComponents(25); var jobs = new StringBuilder(); diff --git a/src/Arch.Tests/ArchetypeTest.cs b/src/Arch.Tests/ArchetypeTest.cs index 136155ed..e18350bb 100644 --- a/src/Arch.Tests/ArchetypeTest.cs +++ b/src/Arch.Tests/ArchetypeTest.cs @@ -15,9 +15,9 @@ internal unsafe struct HeavyComponent [TestFixture] public sealed class ArchetypeTest { - private static readonly ComponentType[] _group = { typeof(Transform), typeof(Rotation) }; - private static readonly ComponentType[] _otherGroup = { typeof(Transform), typeof(Rotation), typeof(Ai) }; - private static readonly ComponentType[] _heavyGroup = { typeof(Transform), typeof(Rotation), typeof(HeavyComponent) }; + private static readonly Signature _group = new(typeof(Transform), typeof(Rotation)); + private static readonly Signature _otherGroup = new(typeof(Transform), typeof(Rotation), typeof(Ai)); + private static readonly Signature _heavyGroup = new(typeof(Transform), typeof(Rotation), typeof(HeavyComponent)); /// /// Tests if s and their are created correctly. diff --git a/src/Arch.Tests/EnumeratorTest.cs b/src/Arch.Tests/EnumeratorTest.cs index 1ef2e508..535af6f6 100644 --- a/src/Arch.Tests/EnumeratorTest.cs +++ b/src/Arch.Tests/EnumeratorTest.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; using Arch.Core; +using Arch.Core.Extensions; using Arch.Core.Utils; using static NUnit.Framework.Assert; diff --git a/src/Arch/Core/Archetype.cs b/src/Arch/Core/Archetype.cs index 51e356e6..54fec96b 100644 --- a/src/Arch/Core/Archetype.cs +++ b/src/Arch/Core/Archetype.cs @@ -123,7 +123,7 @@ public sealed partial class Archetype /// /// The minimum size of a regular L1 cache. /// - internal const int BaseSize = 16000; // 16KB Chunk size + internal const int BaseSize = 16_384; // 16KB Chunk size /// /// A lookup array that maps the component id to an index within the component array of a to quickly find the correct array for the component type. @@ -134,22 +134,22 @@ public sealed partial class Archetype /// /// Initializes a new instance of the class by a group of components. /// - /// The component structure of the 's that can be stored in this . - internal Archetype(ComponentType[] types) + /// The component structure of the 's that can be stored in this . + internal Archetype(Signature signature) { - Types = types; + Types = signature; // Calculations - ChunkSizeInBytes = MinimumRequiredChunkSize(types); - EntitiesPerChunk = CalculateEntitiesPerChunk(types); + ChunkSizeInBytes = MinimumRequiredChunkSize(signature); + EntitiesPerChunk = CalculateEntitiesPerChunk(signature); // The bitmask/set - BitSet = types.ToBitSet(); - _componentIdToArrayIndex = types.ToLookupArray(); + BitSet = signature; + _componentIdToArrayIndex = signature.Components.ToLookupArray(); // Setup arrays and mappings Chunks = ArrayPool.Shared.Rent(1); - Chunks[0] = new Chunk(EntitiesPerChunk, _componentIdToArrayIndex, types); + Chunks[0] = new Chunk(EntitiesPerChunk, _componentIdToArrayIndex, signature); ChunkCount = 1; ChunkCapacity = 1; @@ -163,6 +163,11 @@ internal Archetype(ComponentType[] types) /// public ComponentType[] Types { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } + /// + /// A bitset representation of the array for fast lookups and queries. + /// + public BitSet BitSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } + /// /// The lookup array used by this , is being passed to all its to save memory. /// @@ -172,11 +177,6 @@ internal int[] LookupArray get => _componentIdToArrayIndex; } - /// - /// A bitset representation of the array for fast lookups and queries. - /// - public BitSet BitSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } - /// /// The number of entities that are stored per . /// @@ -523,7 +523,7 @@ public sealed unsafe partial class Archetype /// /// The component structure of the 's. /// The amount of 's required. - public int MinimumRequiredChunkSize(ComponentType[] types) + public int MinimumRequiredChunkSize(Span types) { var minimumEntities = (sizeof(Entity) + types.ToByteSize()) * MinimumAmountOfEntitiesPerChunk; return (int)Math.Ceiling((float)minimumEntities / BaseSize) * BaseSize; @@ -534,7 +534,7 @@ public int MinimumRequiredChunkSize(ComponentType[] types) /// /// The component structure of the 's. /// The amount of 's. - public int CalculateEntitiesPerChunk(ComponentType[] types) + public int CalculateEntitiesPerChunk(Span types) { return ChunkSizeInBytes / (sizeof(Entity) + types.ToByteSize()); } diff --git a/src/Arch/Core/Chunk.cs b/src/Arch/Core/Chunk.cs index 48eabbb6..1ebaaad9 100644 --- a/src/Arch/Core/Chunk.cs +++ b/src/Arch/Core/Chunk.cs @@ -15,13 +15,14 @@ namespace Arch.Core; [SkipLocalsInit] // Really a speed improvements? The benchmark only showed a slight improvement public partial struct Chunk { + /// /// Initializes a new instance of the struct. /// Automatically creates a lookup array for quick access to internal components. /// /// How many entities of the respective component structure fit into this . /// The respective component structure of all entities in this . - internal Chunk(int capacity, params ComponentType[] types) + internal Chunk(int capacity, Span types) : this(capacity, types.ToLookupArray(), types) { } /// @@ -30,7 +31,7 @@ internal Chunk(int capacity, params ComponentType[] types) /// How many entities of the respective component structure fit into this . /// A lookup array which maps the component id to the array index of the component array. /// The respective component structure of all entities in this . - internal Chunk(int capacity, int[] componentIdToArrayIndex, params ComponentType[] types) + internal Chunk(int capacity, int[] componentIdToArrayIndex, Span types) { // Calculate capacity and init arrays. Size = 0; diff --git a/src/Arch/Core/EntityInfo.cs b/src/Arch/Core/EntityInfo.cs index 2108a1a8..af4a2f64 100644 --- a/src/Arch/Core/EntityInfo.cs +++ b/src/Arch/Core/EntityInfo.cs @@ -101,7 +101,7 @@ internal class EntityInfoStorage /// internal EntityInfoStorage() { - var cpuL1CacheSize = 16_000; + var cpuL1CacheSize = 16_384; Versions = new JaggedArray( cpuL1CacheSize / Unsafe.SizeOf(), diff --git a/src/Arch/Core/Extensions/Internal/BitSetExtensions.cs b/src/Arch/Core/Extensions/Internal/BitSetExtensions.cs index 8bc8bed1..c9e151e2 100644 --- a/src/Arch/Core/Extensions/Internal/BitSetExtensions.cs +++ b/src/Arch/Core/Extensions/Internal/BitSetExtensions.cs @@ -9,25 +9,6 @@ namespace Arch.Core.Extensions.Internal; /// internal static class BitSetExtensions { - // NOTE: Should this be in `TypeExtensions`? - /// - /// Converts an array of 's to its . - /// - /// The array of 's. - /// Their newly created . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static BitSet ToBitSet(this ComponentType[] types) - { - if (types.Length == 0) - { - return new BitSet(); - } - - var bitSet = new BitSet(); - bitSet.SetBits(types); - - return bitSet; - } /// /// Sets bits in a from the ids. @@ -35,7 +16,7 @@ internal static BitSet ToBitSet(this ComponentType[] types) /// The . /// The 's array. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void SetBits(this BitSet bitSet, ComponentType[] types) + internal static void SetBits(this BitSet bitSet, Span types) { foreach (var type in types) { diff --git a/src/Arch/Core/Extensions/Internal/ComponentTypeExtensions.cs b/src/Arch/Core/Extensions/Internal/ComponentTypeExtensions.cs index 562fbe84..45ee4e6e 100644 --- a/src/Arch/Core/Extensions/Internal/ComponentTypeExtensions.cs +++ b/src/Arch/Core/Extensions/Internal/ComponentTypeExtensions.cs @@ -16,7 +16,7 @@ internal static class ComponentTypeExtensions /// The array. /// Their combined byte size. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int ToByteSize(this ComponentType[] types) + internal static int ToByteSize(this Span types) { var size = 0; foreach (var type in types) @@ -35,7 +35,7 @@ internal static int ToByteSize(this ComponentType[] types) /// The array. /// The lookup array. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int[] ToLookupArray(this ComponentType[] types) + internal static int[] ToLookupArray(this Span types) { // Get maximum component ID. var max = 0; diff --git a/src/Arch/Core/Extensions/WorldExtensions.cs b/src/Arch/Core/Extensions/WorldExtensions.cs index 5b7df391..cd0c5be7 100644 --- a/src/Arch/Core/Extensions/WorldExtensions.cs +++ b/src/Arch/Core/Extensions/WorldExtensions.cs @@ -10,21 +10,26 @@ namespace Arch.Core.Extensions; [WorldExtensions] public static class WorldExtensions { - + /* /// - /// Reserves space for a certain number of 's of a given component structure/. + /// Reserves space for a certain number of s of a given component structure/. /// /// /// Causes a structural change. /// - /// The . /// The component structure/. - /// The amount. + /// The amount of s to reserve space for. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Reserve(this World world, ComponentType[] types, int amount) + [StructuralChange] + public static void Reserve(this World world, Span types, int amount) { - world.Reserve(types, amount); - } + var archetype = world.GetOrCreate(types); + archetype.Reserve(amount); + + var requiredCapacity = world.Capacity + amount; + EntityInfo.EnsureCapacity(requiredCapacity); + Capacity = requiredCapacity; + }*/ /// /// Search all matching 's and put them into the given . diff --git a/src/Arch/Core/Query.cs b/src/Arch/Core/Query.cs index 0fd30d4b..aedeef09 100644 --- a/src/Arch/Core/Query.cs +++ b/src/Arch/Core/Query.cs @@ -2,9 +2,197 @@ using Arch.Core.Extensions.Internal; using Arch.Core.Utils; using Collections.Pooled; +using CommunityToolkit.HighPerformance; namespace Arch.Core; + +/// +/// The struct +/// describes a combination of different s and caches their hash. Its basically just a list of s. +/// This is then used for describing an aswell as identification to find the correct or a suitable . +/// +[SkipLocalsInit] +public struct Signature : IEquatable +{ + /// + /// A null reference, basically an empty . + /// + public static readonly Signature Null = new(); + + /// + /// Its cached hashcode, because its incredible expensive to calculate a new hashcode everytime. + /// + private int _hashCode; + + /// + /// Initializes a new instance of the struct. + /// + public Signature() + { + ComponentsArray = Array.Empty(); + _hashCode = -1; + } + + /// + /// Initializes a new instance of the struct. + /// + /// An array of s. + public Signature(params ComponentType[] components) + { + ComponentsArray = components; + _hashCode = -1; + _hashCode = GetHashCode(); + } + + /// + /// An array of s. + /// + internal ComponentType[] ComponentsArray + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + set; + } = Array.Empty(); + + /// + /// An array of s. + /// + public Span Components + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => MemoryMarshal.CreateSpan(ref ComponentsArray.DangerousGetReferenceAt(0), Count); + } + + /// + /// The amount of s in this instance. + /// + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ComponentsArray.Length; + } + + /// + /// Checks for indifference, if the internal arrays have equal elements true is returned. Otherwise false. + /// + /// The other to compare with. + /// True if elements of the arrays are equal, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Signature other) + { + return GetHashCode() == other.GetHashCode(); + } + + /// + /// Checks for indifference, if the internal arrays have equal elements true is returned. Otherwise false. + /// + /// The other to compare with. + /// True if elements of the arrays are equal, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object? obj) + { + return obj is Signature other && Equals(other); + } + + /// + /// Calculates the hash. + /// + /// The hash. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() + { + // Cache hashcode since the calculation is expensive. + if (_hashCode != -1) + { + return _hashCode; + } + + unchecked + { + _hashCode = Component.GetHashCode(Components); + return _hashCode; + } + } + + /// + /// Checks for indifference, if the internal arrays have equal elements true is returned. Otherwise false. + /// + /// The left . + /// The right . + /// True if their internal arrays are equal, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Signature left, Signature right) + { + return left.Equals(right); + } + + /// + /// Checks for difference, if the internal arrays have equal elements false is returned. Otherwise true. + /// + /// The left . + /// The right . + /// True if their internal arrays are unequal, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Signature left, Signature right) + { + return !left.Equals(right); + } + + /// + /// Converts a array into a . + /// + /// The passed s. + /// A new . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Signature(ComponentType[] components) + { + return new Signature(components); + } + + /// + /// Converts a into a s array. + /// + /// The passed . + /// The s array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ComponentType[](Signature signature) + { + return signature.ComponentsArray; + } + + /// + /// Converts a into a s array. + /// + /// The passed . + /// The s array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Span(Signature signature) + { + return signature.Components; + } + + /// + /// Converts a into a . + /// + /// The passed . + /// A new s. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator BitSet(Signature signature) + { + if (signature.Count == 0) + { + return new BitSet(); + } + + var bitSet = new BitSet(); + bitSet.SetBits(signature.Components); + + return bitSet; + } +} + + /// /// The struct /// represents a description of the 's or components we want to address by means of a query. @@ -23,34 +211,37 @@ public partial struct QueryDescription : IEquatable private int _hashCode; /// - /// An array of all components that an should have mandatory. + /// An of all components that an should have mandatory. /// If the content of the array is subsequently changed, a should be carried out. /// - public ComponentType[] All = Array.Empty(); + public Signature All { get; set; } = new(); /// /// An array of all components of which an should have at least one. /// If the content of the array is subsequently changed, a should be carried out. /// - public ComponentType[] Any = Array.Empty(); + public Signature Any { get; set; } = new(); /// /// An array of all components of which an should not have any. /// If the content of the array is subsequently changed, a should be carried out. /// - public ComponentType[] None = Array.Empty(); + public Signature None { get; set; } = new(); /// /// An array of all components that exactly match the structure of an . /// 's with more or less components than those defined in the array are not addressed. /// If the content of the array is subsequently changed, a should be carried out. /// - public ComponentType[] Exclusive = Array.Empty(); + public Signature Exclusive { get; set; } = new(); /// /// Initializes a new instance of the struct. /// - public QueryDescription() { } + public QueryDescription() + { + _hashCode = -1; + } /// /// Initializes a new instance of the struct. @@ -61,10 +252,12 @@ public QueryDescription() { } /// All components that an should have mandatory. public QueryDescription(ComponentType[]? all = null, ComponentType[]? any = null, ComponentType[]? none = null, ComponentType[]? exclusive = null) { - All = all ?? Array.Empty(); - Any = any ?? Array.Empty(); - None = none ?? Array.Empty(); - Exclusive = exclusive ?? Array.Empty(); + All = all ?? All; + Any = any ?? Any; + None = none ?? None; + Exclusive = exclusive ?? Exclusive; + + _hashCode = -1; _hashCode = GetHashCode(); } @@ -88,7 +281,7 @@ public void Rebuild() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref QueryDescription WithAll() { - All = Group.Types; + All = Component.Signature; _hashCode = -1; return ref this; } @@ -102,7 +295,7 @@ public ref QueryDescription WithAll() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref QueryDescription WithAny() { - Any = Group.Types; + Any = Component.Signature; _hashCode = -1; return ref this; } @@ -116,7 +309,7 @@ public ref QueryDescription WithAny() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref QueryDescription WithNone() { - None = Group.Types; + None = Component.Signature; _hashCode = -1; return ref this; } @@ -131,7 +324,7 @@ public ref QueryDescription WithNone() [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref QueryDescription WithExclusive() { - Exclusive = Group.Types; + Exclusive = Component.Signature; _hashCode = -1; return ref this; } @@ -141,6 +334,7 @@ public ref QueryDescription WithExclusive() /// /// The other to compare with. /// True if elements of the arrays are equal, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(QueryDescription other) { return GetHashCode() == other.GetHashCode(); @@ -151,6 +345,7 @@ public bool Equals(QueryDescription other) /// /// The other to compare with. /// True if elements of the arrays are equal, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object? obj) { return obj is QueryDescription other && Equals(other); @@ -161,6 +356,7 @@ public override bool Equals(object? obj) /// Calculates the hash. /// /// The hash. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { // Cache hashcode since the calculation is expensive. @@ -188,6 +384,7 @@ public override int GetHashCode() /// The left . /// The right . /// True if their internal arrays are equal, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(QueryDescription left, QueryDescription right) { return left.Equals(right); @@ -199,6 +396,7 @@ public override int GetHashCode() /// The left . /// The right . /// True if their internal arrays are unequal, otherwhise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(QueryDescription left, QueryDescription right) { return !left.Equals(right); @@ -233,21 +431,21 @@ internal Query(PooledList archetypes, QueryDescription description) : _archetypes = archetypes; Debug.Assert( - !((description.Any.Length != 0 || - description.All.Length != 0 || - description.None.Length != 0) && - description.Exclusive.Length != 0), + !((description.Any.Count != 0 || + description.All.Count != 0 || + description.None.Count != 0) && + description.Exclusive.Count != 0), "If Any, All or None have items then Exclusive may not have any items" ); // Convert to `BitSet`s. - _all = description.All.ToBitSet(); - _any = description.Any.ToBitSet(); - _none = description.None.ToBitSet(); - _exclusive = description.Exclusive.ToBitSet(); + _all = description.All; + _any = description.Any; + _none = description.None; + _exclusive = description.Exclusive; // Handle exclusive. - if (description.Exclusive.Length != 0) + if (description.Exclusive.Count != 0) { _isExclusive = true; } diff --git a/src/Arch/Core/Utils/CompileTimeStatics.cs b/src/Arch/Core/Utils/CompileTimeStatics.cs index 970e6e43..0eeb1209 100644 --- a/src/Arch/Core/Utils/CompileTimeStatics.cs +++ b/src/Arch/Core/Utils/CompileTimeStatics.cs @@ -374,30 +374,6 @@ private static class ArrayFactory } } -/// -/// The class, provides compile time static information about a component. -/// -/// Its generic type. -/// -/// A is created once during its first use. -/// Subsequent uses access statically stored information. -/// -public static class Component -{ - /// - /// Creates the compile time static class for acessing its information. - /// Registers the component. - /// - static Component() - { - ComponentType = ComponentRegistry.Add(); - } - - /// - /// A static reference to information about the compile time static registered class. - /// - public static readonly ComponentType ComponentType; -} /// /// The class provides information about a component during runtime. @@ -408,6 +384,8 @@ static Component() /// public static class Component { + internal static int Id; + /// /// Searches a by its . If it does not exist, it will be added. /// @@ -456,6 +434,11 @@ public static int GetHashCode(Span obj) return GetHashCode(stack); } + /// + /// Calculates the hash code of a array, which is unique for the elements contained in the array. + /// + /// The . + /// A unique hashcode for the contained elements. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetHashCode(Span span) { @@ -465,6 +448,38 @@ public static int GetHashCode(Span span) } } +/// +/// The class, provides compile time static information about a component. +/// +/// Its generic type. +/// +/// A is created once during its first use. +/// Subsequent uses access statically stored information. +/// +public static class Component +{ + + /// + /// A static reference to information about the compile time static registered class. + /// + public static readonly ComponentType ComponentType; + + /// + /// An for this given set of components. + /// + public static readonly Signature Signature; + + /// + /// Creates the compile time static class for acessing its information. + /// Registers the component. + /// + static Component() + { + ComponentType = ComponentRegistry.Add(); + Signature = new Signature(ComponentType); + } +} + // NOTE: Rename or reimplement this? An entire class just for counting something seems overkill. /// /// The class counts Id's for internally registered jobs during compile time. @@ -511,15 +526,3 @@ static JobMeta() /// public static readonly DefaultObjectPool Pool; } - -// TODO: Based on the hash of each `Group` we can easily Map a `Group` to another `Group`. -// E.g.: `Group` to `Group`, as they return the same hash. -/// -/// The class counts the IDs of registered groups in an compile-time static way, -/// and stores an underlying array for dynamic access. In this way, its related classes (, ...) -/// can be used to statically track sets of components from generic calls with zero overhead. -/// -public static class Group -{ - internal static int Id; -} diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index 75db9f7d..e559d64c 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -230,21 +230,19 @@ private World(int id) /// internal PooledDictionary QueryCache { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; set; } - private ReaderWriterLockSlim _queryCacheLock = new(); - /// /// Reserves space for a certain number of s of a given component structure/. /// /// /// Causes a structural change. /// - /// The component structure/. + /// The component structure/. /// The amount of s to reserve space for. [MethodImpl(MethodImplOptions.AggressiveInlining)] [StructuralChange] - public void Reserve(Span types, int amount) + public void Reserve(in Signature signature, int amount) { - var archetype = GetOrCreate(types); + var archetype = GetOrCreate(signature); archetype.Reserve(amount); var requiredCapacity = Capacity + amount; @@ -265,7 +263,7 @@ public void Reserve(Span types, int amount) [StructuralChange] public Entity Create(params ComponentType[] types) { - return Create(types.AsSpan()); + return Create((Signature)types); } // TODO: Find cleaner way to resize the EntityInfo? Let archetype.Create return an amount which is added to Capacity or whatever? @@ -280,7 +278,7 @@ public Entity Create(params ComponentType[] types) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [StructuralChange] - public Entity Create(Span types) + public Entity Create(in Signature types) { // Recycle id or increase var recycle = RecycledIds.TryDequeue(out var recycledId); @@ -290,7 +288,7 @@ public Entity Create(Span types) var entity = new Entity(recycled.Id, Id); // Add to archetype & mapping - var archetype = GetOrCreate(types); + var archetype = GetOrCreate(in types); var createdChunk = archetype.Add(entity, out var slot); // Resize map & Array to fit all potential new entities @@ -594,28 +592,28 @@ public override string ToString() public partial class World { /// - /// Maps a hash to its . + /// Maps a hash to its . /// internal PooledDictionary GroupToArchetype { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; set; } /// /// Returns an based on its components. If it does not exist, it will be created. /// - /// Its s. + /// Its s. /// An existing or new . [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Archetype GetOrCreate(Span types) + internal Archetype GetOrCreate(in Signature signature) { - if (TryGetArchetype(types, out var archetype)) + var hashCode = signature.GetHashCode(); + if (TryGetArchetype(hashCode, out var archetype)) { return archetype; } // Create archetype - archetype = new Archetype(types.ToArray()); - var hash = Component.GetHashCode(types); + archetype = new Archetype(signature); - GroupToArchetype[hash] = archetype; + GroupToArchetype[hashCode] = archetype; Archetypes.Add(archetype); // Archetypes always allocate one single chunk upon construction @@ -639,43 +637,16 @@ internal bool TryGetArchetype(int hash, [MaybeNullWhen(false)] out Archetype arc } /// - /// Tries to find an by a . - /// - /// A indicating the structure. - /// The found . - /// True if found, otherwise false. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool TryGetArchetype(BitSet bitset, [MaybeNullWhen(false)] out Archetype archetype) - { - return TryGetArchetype(bitset.GetHashCode(), out archetype); - } - - /// - /// Tries to find an by a . - /// - /// A indicating the structure. - /// The found . - /// True if found, otherwise false. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool TryGetArchetype(SpanBitSet bitset, [MaybeNullWhen(false)] out Archetype archetype) - { - return TryGetArchetype(bitset.GetHashCode(), out archetype); - } - - /// - /// Tries to find an by the hash of its components. + /// Tries to find an by a provided . /// - /// Its s. + /// Its . /// The found . /// True if found, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] [Pure] - public bool TryGetArchetype(Span types, [MaybeNullWhen(false)] out Archetype archetype) + public bool TryGetArchetype(in Signature signature, [MaybeNullWhen(false)] out Archetype archetype) { - var hash = Component.GetHashCode(types); - return TryGetArchetype(hash, out archetype); + return TryGetArchetype(signature.GetHashCode(), out archetype); } ///