Skip to content

Commit 130a814

Browse files
committed
+ fix hashing
1 parent 6e4dc08 commit 130a814

File tree

4 files changed

+79
-129
lines changed

4 files changed

+79
-129
lines changed

src/Archetype.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,13 @@ ComponentComparer comparer
168168
_chunks = new ArchetypeChunk[ARCHETYPE_INITIAL_CAPACITY];
169169

170170

171-
var roll = new RollingHash();
171+
var hash = 0ul;
172172
var dict = new Dictionary<EcsID, int>();
173173
var allDict = new Dictionary<EcsID, int>();
174174
var maxId = -1;
175175
for (int i = 0, cur = 0; i < sign.Length; ++i)
176176
{
177-
roll.Add(sign[i].ID);
177+
hash = UnorderedSetHasher.Combine(hash, sign[i].ID);
178178

179179
if (sign[i].Size > 0)
180180
{
@@ -185,7 +185,7 @@ ComponentComparer comparer
185185
allDict.Add(sign[i].ID, i);
186186
}
187187

188-
Id = roll.Hash;
188+
Id = hash;
189189

190190
_fastLookup = new int[maxId + 1];
191191
_fastLookup.AsSpan().Fill(-1);

src/Hashing.cs

Lines changed: 20 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,37 @@
11
namespace TinyEcs;
22

3-
4-
public struct RollingHash
3+
internal static class UnorderedSetHasher
54
{
6-
private const ulong Base = 31; // A prime base for hashing
7-
private const ulong Modulus = 1_000_000_007; // A large prime modulus
8-
9-
private ulong _hash;
10-
private ulong _product;
11-
12-
private static readonly ulong _inverseCache = ModInverse2(Base, Modulus);
13-
14-
15-
public RollingHash()
16-
{
17-
_hash = 0;
18-
_product = 1;
19-
}
20-
21-
public readonly ulong Hash => _hash;
22-
23-
24-
25-
public void Add(ulong value)
26-
{
27-
_hash = (_hash + value * _product) % Modulus;
28-
_product = (_product * Base) % Modulus;
29-
}
30-
31-
32-
public void Remove(ulong value)
33-
{
34-
var inverseBase = _inverseCache;
35-
_product = (_product * inverseBase) % Modulus;
36-
_hash = (_hash + Modulus - (value * _product % Modulus)) % Modulus;
37-
}
38-
39-
40-
// Compute modular inverse of a with respect to m using Extended Euclidean Algorithm
5+
private const ulong Prime = 0x9E3779B185EBCA87UL; // Large 64-bit prime (Golden ratio)
416

42-
private static ulong ModInverse2(ulong a, ulong m)
43-
{
44-
ulong m0 = m, x0 = 0, x1 = 1;
45-
46-
while (a > 1)
47-
{
48-
ulong q = a / m;
49-
ulong t = m;
50-
51-
m = a % m;
52-
a = t;
53-
t = x0;
54-
55-
x0 = x1 - q * x0;
56-
x1 = t;
57-
}
58-
59-
return (x1 + m0) % m0;
60-
}
61-
62-
public static ulong Calculate(params ReadOnlySpan<EcsID> values)
7+
public static ulong HashUnordered(Span<ulong> values)
638
{
64-
var hash = 0ul;
65-
var product = 1ul;
9+
ulong hash = 0;
6610

6711
foreach (ref readonly var value in values)
6812
{
69-
hash = (hash + value * product) % Modulus;
70-
product = (product * Base) % Modulus;
13+
hash ^= Mix(value);
14+
hash *= Prime;
7115
}
7216

7317
return hash;
7418
}
7519

76-
public static ulong Calculate(params ReadOnlySpan<ComponentInfo> values)
20+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
21+
public static ulong Combine(ulong currentHash, ulong mixed)
7722
{
78-
var hash = 0ul;
79-
var product = 1ul;
80-
81-
foreach (ref readonly var value in values)
82-
{
83-
hash = (hash + value.ID * product) % Modulus;
84-
product = (product * Base) % Modulus;
85-
}
23+
return (currentHash ^ mixed) * Prime;
24+
}
8625

87-
return hash;
26+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
27+
private static ulong Mix(ulong x)
28+
{
29+
// A simple mixer (variant of MurmurHash3 finalizer)
30+
x ^= x >> 30;
31+
x *= 0xbf58476d1ce4e5b9UL;
32+
x ^= x >> 27;
33+
x *= 0x94d049bb133111ebUL;
34+
x ^= x >> 31;
35+
return x;
8836
}
8937
}

src/World.Public.cs

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ public sealed partial class World
1313
/// </summary>
1414
/// <param name="maxComponentId"></param>
1515
public World(ulong maxComponentId = 256)
16-
{
17-
_comparer = new ComponentComparer(this);
18-
_archRoot = new Archetype(
19-
this,
20-
[],
21-
_comparer
22-
);
16+
{
17+
_comparer = new ComponentComparer(this);
18+
_archRoot = new Archetype(
19+
this,
20+
[],
21+
_comparer
22+
);
2323
_typeIndex.Add(_archRoot.Id, _archRoot);
2424
LastArchetypeId = _archRoot.Id;
2525

2626
_maxCmpId = maxComponentId;
27-
_entities.MaxID = maxComponentId;
27+
_entities.MaxID = maxComponentId;
2828

2929
#if USE_PAIR
3030
_ = Component<Rule>();
@@ -69,7 +69,7 @@ static EntityView setCommon(EntityView entity, string name)
6969
NamingEntityMapper = new(this);
7070

7171
OnPluginInitialization?.Invoke(this);
72-
}
72+
}
7373

7474

7575

@@ -92,10 +92,10 @@ static EntityView setCommon(EntityView entity, string name)
9292
/// Cleanup the world.
9393
/// </summary>
9494
public void Dispose()
95-
{
96-
_entities.Clear();
97-
_archRoot.Clear();
98-
_typeIndex.Clear();
95+
{
96+
_entities.Clear();
97+
_archRoot.Clear();
98+
_typeIndex.Clear();
9999
_cachedComponents.Clear();
100100
RelationshipEntityMapper.Clear();
101101
NamingEntityMapper.Clear();
@@ -127,7 +127,9 @@ public Archetype Archetype(params Span<ComponentInfo> ids)
127127

128128
ids.SortNoAlloc(_comparisonCmps);
129129

130-
var hash = RollingHash.Calculate(ids);
130+
var hash = 0ul;
131+
foreach (ref readonly var cmp in ids)
132+
hash = UnorderedSetHasher.Combine(hash, cmp.ID);
131133
if (!_typeIndex.TryGetValue(hash, out var archetype))
132134
{
133135
var archLessOne = Archetype(ids[..^1]);
@@ -164,7 +166,7 @@ public EntityView Entity(Archetype arch)
164166
/// </summary>
165167
/// <param name="id"></param>
166168
/// <returns></returns>
167-
public EntityView Entity(ulong id = 0)
169+
public EntityView Entity(ulong id = 0)
168170
{
169171
lock (_newEntLock)
170172
{
@@ -243,8 +245,8 @@ public EntityView Entity(string name)
243245
/// Associated children are deleted too.
244246
/// </summary>
245247
/// <param name="entity"></param>
246-
public void Delete(EcsID entity)
247-
{
248+
public void Delete(EcsID entity)
249+
{
248250
if (IsDeferred)
249251
{
250252
if (Exists(entity))
@@ -314,15 +316,15 @@ static void applyDeleteRules(World world, EcsID entity, params Span<IQueryTerm>
314316
EcsAssert.Assert(removedId == entity);
315317
_entities.Remove(removedId);
316318
}
317-
}
319+
}
318320

319321
/// <summary>
320322
/// Check if the entity is valid and alive.
321323
/// </summary>
322324
/// <param name="entity"></param>
323325
/// <returns></returns>
324-
public bool Exists(EcsID entity)
325-
{
326+
public bool Exists(EcsID entity)
327+
{
326328
#if USE_PAIR
327329
if (entity.IsPair())
328330
{
@@ -331,8 +333,8 @@ public bool Exists(EcsID entity)
331333
}
332334
#endif
333335

334-
return _entities.Contains(entity);
335-
}
336+
return _entities.Contains(entity);
337+
}
336338

337339
/// <summary>
338340
/// Use this function to analyze pairs members.<br/>
@@ -363,21 +365,21 @@ public EcsID GetAlive(EcsID id)
363365
/// </summary>
364366
/// <param name="id"></param>
365367
/// <returns></returns>
366-
public ReadOnlySpan<ComponentInfo> GetType(EcsID id)
367-
{
368-
ref var record = ref GetRecord(id);
369-
return record.Archetype.All.AsSpan();
370-
}
368+
public ReadOnlySpan<ComponentInfo> GetType(EcsID id)
369+
{
370+
ref var record = ref GetRecord(id);
371+
return record.Archetype.All.AsSpan();
372+
}
371373

372374
/// <summary>
373375
/// Add a Tag to the entity.
374376
/// </summary>
375377
/// <typeparam name="T"></typeparam>
376378
/// <param name="entity"></param>
377-
public void Add<T>(EcsID entity) where T : struct
379+
public void Add<T>(EcsID entity) where T : struct
378380
{
379-
ref readonly var cmp = ref Component<T>();
380-
EcsAssert.Panic(cmp.Size <= 0, "this is not a tag");
381+
ref readonly var cmp = ref Component<T>();
382+
EcsAssert.Panic(cmp.Size <= 0, "this is not a tag");
381383

382384
if (IsDeferred && !Has(entity, cmp.ID))
383385
{
@@ -386,19 +388,19 @@ public void Add<T>(EcsID entity) where T : struct
386388
return;
387389
}
388390

389-
_ = Attach(entity, cmp.ID, cmp.Size);
390-
}
391+
_ = Attach(entity, cmp.ID, cmp.Size);
392+
}
391393

392394
/// <summary>
393395
/// Set a Component to the entity.
394396
/// </summary>
395397
/// <typeparam name="T"></typeparam>
396398
/// <param name="entity"></param>
397399
/// <param name="component"></param>
398-
public void Set<T>(EcsID entity, T component) where T : struct
400+
public void Set<T>(EcsID entity, T component) where T : struct
399401
{
400402
ref readonly var cmp = ref Component<T>();
401-
EcsAssert.Panic(cmp.Size > 0, "this is not a component");
403+
EcsAssert.Panic(cmp.Size > 0, "this is not a component");
402404

403405
if (IsDeferred && !Has(entity, cmp.ID))
404406
{
@@ -407,9 +409,9 @@ public void Set<T>(EcsID entity, T component) where T : struct
407409
return;
408410
}
409411

410-
(var raw, var row) = Attach(entity, cmp.ID, cmp.Size);
411-
var array = (T[])raw!;
412-
array[row & TinyEcs.Archetype.CHUNK_THRESHOLD] = component;
412+
(var raw, var row) = Attach(entity, cmp.ID, cmp.Size);
413+
var array = (T[])raw!;
414+
array[row & TinyEcs.Archetype.CHUNK_THRESHOLD] = component;
413415
}
414416

415417
/// <summary>
@@ -434,7 +436,7 @@ public void Add(EcsID entity, EcsID id)
434436
/// </summary>
435437
/// <typeparam name="T"></typeparam>
436438
/// <param name="entity"></param>
437-
public void Unset<T>(EcsID entity) where T : struct
439+
public void Unset<T>(EcsID entity) where T : struct
438440
=> Unset(entity, Component<T>().ID);
439441

440442
/// <summary>
@@ -460,7 +462,7 @@ public void Unset(EcsID entity, EcsID id)
460462
/// <typeparam name="T"></typeparam>
461463
/// <param name="entity"></param>
462464
/// <returns></returns>
463-
public bool Has<T>(EcsID entity) where T : struct
465+
public bool Has<T>(EcsID entity) where T : struct
464466
=> Has(entity, Component<T>().ID);
465467

466468
/// <summary>
@@ -471,21 +473,21 @@ public bool Has<T>(EcsID entity) where T : struct
471473
/// <param name="id"></param>
472474
/// <returns></returns>
473475
public bool Has(EcsID entity, EcsID id)
474-
{
476+
{
475477
return IsAttached(ref GetRecord(entity), id);
476-
}
478+
}
477479

478480
/// <summary>
479481
/// Get a component from the entity.
480482
/// </summary>
481483
/// <typeparam name="T"></typeparam>
482484
/// <param name="entity"></param>
483485
/// <returns></returns>
484-
public ref T Get<T>(EcsID entity) where T : struct
486+
public ref T Get<T>(EcsID entity) where T : struct
485487
{
486488
ref readonly var cmp = ref Component<T>();
487489
return ref GetUntrusted<T>(entity, cmp.ID, cmp.Size);
488-
}
490+
}
489491

490492
/// <summary>
491493
/// Get the name associated to the entity.
@@ -537,9 +539,9 @@ public void Rule(EcsID entity, EcsID ruleId)
537539
/// Print the archetype graph.
538540
/// </summary>
539541
public void PrintGraph()
540-
{
541-
_archRoot.Print(0);
542-
}
542+
{
543+
_archRoot.Print(0);
544+
}
543545

544546
/// <summary>
545547
///

0 commit comments

Comments
 (0)