Skip to content

Commit 87f66d2

Browse files
committed
Replaced chunk internal dictionary with a lookup table. Its purpose is to map the component id to the chunk array index for quickly receiving the underlying arrays.
Since the dictionary is no longer involved, it actually increased the speed... #18
1 parent de37e79 commit 87f66d2

File tree

5 files changed

+74
-85
lines changed

5 files changed

+74
-85
lines changed

Arch.Benchmark/Benchmark.cs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,8 @@ private static void Main(string[] args)
1515
.AddValidator(JitOptimizationsValidator.DontFailOnError)
1616
.AddLogger(ConsoleLogger.Default)
1717
.AddColumnProvider(DefaultColumnProviders.Instance);
18-
19-
/*
20-
var it = new QueryBenchmark { amount = 10000 };
21-
it.Setup();
22-
for (var index = 0; index < 10000; index++) {
23-
it.StructEntityQuery();
24-
Console.WriteLine(index);
25-
}
26-
it.Cleanup();*/
27-
18+
2819
// Use : dotnet run -c Release --framework net7.0 -- --job short --filter *IterationBenchmark*
2920
BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args, config);
30-
31-
/*
32-
var it = new ArchetypeIterationBenchmark { amount = 100000 };
33-
it.Setup();
34-
for (var index = 0; index < 100000; index++) {
35-
it.IterationJobSchedulerUnsafeAdd();
36-
Console.WriteLine(index);
37-
}
38-
Console.WriteLine("Cleanup");
39-
it.Cleanup();
40-
Console.WriteLine("YES");*/
4121
}
4222
}

Arch/Core/Archetype.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,32 @@ internal Archetype(params Type[] types)
2222

2323
// The bitmask/set
2424
BitSet = types.ToBitSet();
25+
ComponentIdToArrayIndex = types.ToLookupArray();
26+
2527
EntityIdToChunkIndex = new PooledDictionary<int, int>(EntitiesPerChunk);
2628
Chunks = Array.Empty<Chunk>();
2729
}
2830

2931
/// <summary>
3032
/// The types with which the <see cref="BitSet" /> was created.
3133
/// </summary>
32-
public Type[] Types { get; set; }
34+
public Type[] Types { get; }
3335

3436
/// <summary>
3537
/// The bitmask for querying, contains the component flags set for this archetype.
3638
/// </summary>
37-
public BitSet BitSet { get; set; }
39+
public BitSet BitSet { get; }
40+
41+
/// <summary>
42+
/// A lookup table which maps a component-id to its array index in the <see cref="Chunk"/>.
43+
/// Stored here since it reduces the amount of memory, better than every chunk having one of these.
44+
/// </summary>
45+
public int[] ComponentIdToArrayIndex { get; }
3846

3947
/// <summary>
4048
/// For mapping the entity id to the chunk it is in.
4149
/// </summary>
42-
public PooledDictionary<int, int> EntityIdToChunkIndex { get; set; }
50+
public PooledDictionary<int, int> EntityIdToChunkIndex { get; }
4351

4452
/// <summary>
4553
/// A array of active chunks within this archetype.
@@ -97,6 +105,7 @@ internal int NextChunkIndex()
97105
public bool Add(in Entity entity)
98106
{
99107

108+
// Get next available chunk which has free space...
100109
var nextChunkIndex = NextChunkIndex();
101110
if (nextChunkIndex != -1)
102111
{
@@ -114,7 +123,7 @@ public bool Add(in Entity entity)
114123
}
115124

116125
// Create new chunk
117-
var newChunk = new Chunk(EntitiesPerChunk, Types);
126+
var newChunk = new Chunk(EntitiesPerChunk, ComponentIdToArrayIndex, Types);
118127
newChunk.Add(in entity);
119128

120129
// Resize chunks
@@ -295,7 +304,7 @@ public void Reserve(in int amount)
295304

296305
for (var index = 0; index < neededChunks; index++)
297306
{
298-
var newChunk = new Chunk(EntitiesPerChunk, Types);
307+
var newChunk = new Chunk(EntitiesPerChunk, ComponentIdToArrayIndex, Types);
299308
Chunks[Capacity + index] = newChunk;
300309
}
301310

@@ -309,7 +318,7 @@ public void Reserve(in int amount)
309318

310319
for (var index = 0; index < neededChunks; index++)
311320
{
312-
var newChunk = new Chunk(EntitiesPerChunk, Types);
321+
var newChunk = new Chunk(EntitiesPerChunk, ComponentIdToArrayIndex, Types);
313322
Chunks[Capacity + index] = newChunk;
314323
}
315324

Arch/Core/Chunk.cs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Diagnostics.Contracts;
44
using System.Runtime.CompilerServices;
5+
using Arch.Core.Extensions;
56
using Arch.Core.Utils;
67
using CommunityToolkit.HighPerformance;
78

@@ -22,6 +23,7 @@ namespace Arch.Core;
2223
/// </summary>
2324
public partial struct Chunk
2425
{
26+
2527
/// <summary>
2628
/// Allocates enough space for the passed amount of entities with all its components.
2729
/// </summary>
@@ -37,16 +39,39 @@ internal Chunk(int capacity, params Type[] types)
3739
Components = new Array[types.Length];
3840

3941
// Init mapping
40-
ComponentIdToArrayIndex = new Dictionary<int, int>(types.Length);
42+
ComponentIdToArrayIndex = types.ToLookupArray();
4143
EntityIdToIndex = new Dictionary<int, int>(Capacity);
4244

4345
// Allocate arrays and map
4446
for (var index = 0; index < types.Length; index++)
4547
{
4648
var type = types[index];
47-
var componentId = ComponentMeta.Id(type);
49+
Components[index] = Array.CreateInstance(type, Capacity);
50+
}
51+
}
52+
53+
/// <summary>
54+
/// Allocates enough space for the passed amount of entities with all its components.
55+
/// </summary>
56+
/// <param name="capacity"></param>
57+
/// <param name="types"></param>
58+
internal Chunk(int capacity, int[] componentIdToArrayIndex, params Type[] types)
59+
{
60+
// Calculate capacity & init arrays
61+
Size = 0;
62+
Capacity = capacity;
63+
64+
Entities = new Entity[Capacity];
65+
Components = new Array[types.Length];
66+
67+
// Init mapping
68+
ComponentIdToArrayIndex = componentIdToArrayIndex;
69+
EntityIdToIndex = new Dictionary<int, int>(Capacity);
4870

49-
ComponentIdToArrayIndex[componentId] = index;
71+
// Allocate arrays and map
72+
for (var index = 0; index < types.Length; index++)
73+
{
74+
var type = types[index];
5075
Components[index] = Array.CreateInstance(type, Capacity);
5176
}
5277
}
@@ -104,7 +129,8 @@ public void Set<T>(in Entity entity, in T cmp)
104129
public bool Has<T>()
105130
{
106131
var id = ComponentMeta<T>.Id;
107-
return ComponentIdToArrayIndex.ContainsKey(id);
132+
if (id >= ComponentIdToArrayIndex.Length) return false;
133+
return ComponentIdToArrayIndex[id] != 1;
108134
}
109135

110136
/// <summary>
@@ -192,7 +218,7 @@ public void Remove(in Entity entity)
192218
/// <summary>
193219
/// A map to get the index of a component array inside <see cref="Components" />.
194220
/// </summary>
195-
public readonly Dictionary<int, int> ComponentIdToArrayIndex { [Pure] get; }
221+
public readonly int[] ComponentIdToArrayIndex { [Pure] get; }
196222

197223
/// <summary>
198224
/// A map used to get the array indexes of a certain <see cref="Entity" />.
@@ -226,10 +252,8 @@ public partial struct Chunk
226252
private int Index<T>()
227253
{
228254
var id = ComponentMeta<T>.Id;
229-
if (ComponentIdToArrayIndex.TryGetValue(id, out var index))
230-
return index;
231-
232-
return -1;
255+
if (id >= ComponentIdToArrayIndex.Length) return -1;
256+
return ComponentIdToArrayIndex[id];
233257
}
234258

235259
/// <summary>
@@ -326,7 +350,8 @@ public partial struct Chunk
326350
public bool Has(Type t)
327351
{
328352
var id = ComponentMeta.Id(t);
329-
return ComponentIdToArrayIndex.ContainsKey(id);
353+
if (id >= ComponentIdToArrayIndex.Length) return false;
354+
return ComponentIdToArrayIndex[id] != -1;
330355
}
331356

332357
/// <summary>
@@ -339,10 +364,8 @@ public bool Has(Type t)
339364
private int Index(Type type)
340365
{
341366
var id = ComponentMeta.Id(type);
342-
if (ComponentIdToArrayIndex.TryGetValue(id, out var index))
343-
return index;
344-
345-
return -1;
367+
if (id >= ComponentIdToArrayIndex.Length) return -1;
368+
return ComponentIdToArrayIndex[id];
346369
}
347370

348371
/// <summary>

Arch/Core/Extensions/TypeExtensions.cs

Lines changed: 19 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@ public static void WriteComponentIds(this Type[] types, Span<int> ids)
3434
/// <returns></returns>
3535
/// <exception cref="ArgumentException">Throws an exception if one type is not a value type</exception>
3636
[MethodImpl(MethodImplOptions.AggressiveInlining)]
37-
public static int ToByteSize(this IEnumerable<Type> types)
37+
public static int ToByteSize(this Type[] types)
3838
{
3939
var size = 0;
40-
4140
foreach (var type in types)
4241
{
4342
if (!type.IsValueType)
@@ -48,58 +47,34 @@ public static int ToByteSize(this IEnumerable<Type> types)
4847

4948
return size;
5049
}
51-
50+
5251
/// <summary>
5352
/// Calculates the byte sum of the types.
5453
/// </summary>
5554
/// <param name="types">The types array</param>
5655
/// <returns></returns>
5756
/// <exception cref="ArgumentException">Throws an exception if one type is not a value type</exception>
5857
[MethodImpl(MethodImplOptions.AggressiveInlining)]
59-
public static int OffsetTo(this IEnumerable<Type> types, Type type, int capacity)
58+
public static int[] ToLookupArray(this Type[] types)
6059
{
61-
var offset = 0;
62-
63-
foreach (var currentType in types)
60+
61+
// Get max component id
62+
var max = 0;
63+
foreach (var type in types)
6464
{
65-
if (!currentType.IsValueType)
66-
throw new ArgumentException("Cant determine size of non value type.");
67-
68-
if (currentType == type) return offset;
69-
offset += Marshal.SizeOf(currentType) * capacity;
65+
var componentId = ComponentMeta.Id(type);
66+
if (componentId >= max) max = componentId;
7067
}
7168

72-
return offset;
73-
}
74-
75-
/// <summary>
76-
/// Checks wether a passed type is managed or not using a cached type version of <see cref="RuntimeHelpers.IsReferenceOrContainsReferences{T}" />
77-
/// </summary>
78-
/// <param name="type">The type, must be struct.</param>
79-
/// <returns></returns>
80-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
81-
public static bool IsManaged(Type type)
82-
{
83-
// Use cache
84-
if (IsReferenceOrContainsReferenceCache.TryGetValue(type, out var func))
85-
return func();
86-
87-
// Cache for type
88-
var methodInfo = typeof(RuntimeHelpers).GetMethod("IsReferenceOrContainsReferences");
89-
var genericMethod = methodInfo.MakeGenericMethod(type);
90-
var funcDelegate = (Func<bool>)genericMethod.CreateDelegate(typeof(Func<bool>));
91-
IsReferenceOrContainsReferenceCache[type] = funcDelegate;
92-
93-
return funcDelegate();
94-
}
95-
96-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
97-
public static void Categorize(this Type[] types, out List<Type> managed, out List<Type> unmanaged)
98-
{
99-
managed = new List<Type>(types.Length);
100-
unmanaged = new List<Type>(types.Length);
101-
foreach (var type in types)
102-
if (!IsManaged(type)) unmanaged.Add(type);
103-
else managed.Add(type);
69+
// Create lookup table where the component-id points to the component index.
70+
var array = new int[max+1];
71+
Array.Fill(array, -1); // -1 Since that indicates no component is in that index since components start at zero we can not use zero here.
72+
for(var index = 0; index < types.Length; index++)
73+
{
74+
var type = types[index];
75+
var componentId = ComponentMeta.Id(type);
76+
array[componentId] = index;
77+
}
78+
return array;
10479
}
10580
}

Arch/Core/Utils/ComponentRegistry.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace Arch.Core.Utils;
66

77
/// <summary>
88
/// A class which tracks all used components in this project.
9+
/// Component-Ids start at 0 and each new used component will get an increased id.
10+
/// TODO : Probably components should start at 1 instead, since the hash and chunk.Has would work way smoother with it.
911
/// </summary>
1012
public static class ComponentRegistry
1113
{

0 commit comments

Comments
 (0)