Skip to content

Commit

Permalink
+ removed EcsID as struct. Use ulong extention
Browse files Browse the repository at this point in the history
+ improved deferred op
  • Loading branch information
andreakarasho committed Jul 13, 2024
1 parent 66926b3 commit deb2b7a
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 106 deletions.
14 changes: 9 additions & 5 deletions src/Archetype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ public struct ArchetypeChunk

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref EntityView EntityAt(int row)
=> ref Entities[row & Archetype.CHUNK_THRESHOLD];
#if NET
=> ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(Entities), row & Archetype.CHUNK_THRESHOLD);
#else
=> ref Unsafe.Add(ref MemoryMarshal.GetReference(Entities.AsSpan()), row & Archetype.CHUNK_THRESHOLD);
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref T GetReference<T>(int column) where T : struct
Expand All @@ -24,7 +28,7 @@ public readonly ref T GetReference<T>(int column) where T : struct
#if NET
return ref MemoryMarshal.GetArrayDataReference(array);
#else
return ref array[0];
return ref MemoryMarshal.GetReference(array.AsSpan());
#endif
}

Expand Down Expand Up @@ -89,7 +93,7 @@ ComponentComparer comparer
_edgesLeft = new List<EcsEdge>();
_edgesRight = new List<EcsEdge>();
Components = [ .. components];
Pairs = Components.Where(x => x.ID.IsPair).ToImmutableArray();
Pairs = Components.Where(x => x.ID.IsPair()).ToImmutableArray();
Id = Hashing.Calculate(Components.AsSpan());
_chunks = new ArchetypeChunk[ARCHETYPE_INITIAL_CAPACITY];
_lookup = new Dictionary<ulong, int>(/*_comparer*/);
Expand Down Expand Up @@ -141,7 +145,7 @@ public ChunkEnumerator GetEnumerator()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int GetComponentIndex(EcsID id)
{
return _lookup.TryGetValue(id.Value, out var v) ? v : -1;
return _lookup.TryGetValue(id, out var v) ? v : -1;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -169,7 +173,7 @@ private EcsID RemoveByRow(int row)

if (row < _count)
{
EcsAssert.Assert(lastChunk.EntityAt(_count).ID.IsValid, "Entity is invalid. This should never happen!");
EcsAssert.Assert(lastChunk.EntityAt(_count).ID.IsValid(), "Entity is invalid. This should never happen!");

chunk.EntityAt(row) = lastChunk.EntityAt(_count);

Expand Down
124 changes: 74 additions & 50 deletions src/EcsID.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,84 @@
namespace TinyEcs;

/// <summary>
/// The ecs entity rappresentation which is a wrapper around an ulong
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public readonly struct EcsID : IEquatable<ulong>, IComparable<ulong>, IEquatable<EcsID>, IComparable<EcsID>
public static class EcsIdEx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal EcsID(ulong value) => Value = value;


/// <summary>
/// ID + Generation
/// </summary>
[FieldOffset(0)]
public readonly ulong Value;

/// <summary>
/// ID only
/// </summary>
[FieldOffset(0)]
internal readonly int ID;

/// <summary>
/// Generation count.<br/>
/// This number rappresent how many times the real ID has been recycled.
/// </summary>
[FieldOffset(4)]
internal readonly int Generation;
public static bool IsPair(this ref readonly EcsID id)
=> IDOp.IsPair(id);

public static EcsID First(this ref readonly EcsID id)
=> id.IsPair() ? IDOp.GetPairFirst(id) : 0;

public readonly bool IsValid => Value != 0;
public readonly bool IsPair => IDOp.IsPair(Value);
public readonly EcsID First => IsPair ? IDOp.GetPairFirst(Value) : 0;
public readonly EcsID Second => IsPair ? IDOp.GetPairSecond(Value) : 0;
public readonly (EcsID, EcsID) Pair => (First, Second);
public static EcsID Second(this ref readonly EcsID id)
=> id.IsPair() ? IDOp.GetPairSecond(id) : 0;

public static (EcsID, EcsID) Pair(this ref readonly EcsID id)
=> (id.First(), id.Second());

public readonly int CompareTo(ulong other) => Value.CompareTo(other);
public readonly int CompareTo(EcsID other) => Value.CompareTo(other.Value);
public readonly bool Equals(ulong other) => Value == other;
public readonly bool Equals(EcsID other) => Value == other.Value;
public static bool IsValid(this ref readonly EcsID id)
=> id != 0;

public static EcsID RealId(this ref readonly EcsID id)
=> IDOp.RealID(id);

public static implicit operator ulong(EcsID id) => id.Value;
public static implicit operator EcsID(ulong value) => new (value);
public static bool operator ==(EcsID id, EcsID other) => id.Value.Equals(other.Value);
public static bool operator !=(EcsID id, EcsID other) => !id.Value.Equals(other.Value);


public readonly override bool Equals(object? obj) => obj is EcsID ent && Equals(ent);
public readonly override int GetHashCode() => Value.GetHashCode();
public readonly override string ToString()
{
if (IsPair)
return $"({First}, {Second}) | {Value}";
return $"[{ID} - @{Generation} | {Value}]";
}
public static int Generation(this ref readonly EcsID id)
=> (int) IDOp.GetGeneration(id);
}

/// <summary>
/// The ecs entity rappresentation which is a wrapper around an ulong
/// </summary>
// [StructLayout(LayoutKind.Explicit)]
// public readonly struct EcsID : IEquatable<ulong>, IComparable<ulong>, IEquatable<EcsID>, IComparable<EcsID>
// {
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// internal EcsID(ulong value) => Value = value;
//
//
// /// <summary>
// /// ID + Generation
// /// </summary>
// [FieldOffset(0)]
// public readonly ulong Value;
//
// /// <summary>
// /// ID only
// /// </summary>
// [FieldOffset(0)]
// internal readonly int ID;
//
// /// <summary>
// /// Generation count.<br/>
// /// This number rappresent how many times the real ID has been recycled.
// /// </summary>
// [FieldOffset(4)]
// internal readonly int Generation;
//
//
// public readonly bool IsValid => Value != 0;
// public readonly bool IsPair => IDOp.IsPair(Value);
// public readonly EcsID First => IsPair ? IDOp.GetPairFirst(Value) : 0;
// public readonly EcsID Second => IsPair ? IDOp.GetPairSecond(Value) : 0;
// public readonly (EcsID, EcsID) Pair => (First, Second);
//
//
// public readonly int CompareTo(ulong other) => Value.CompareTo(other);
// public readonly int CompareTo(EcsID other) => Value.CompareTo(other.Value);
// public readonly bool Equals(ulong other) => Value == other;
// public readonly bool Equals(EcsID other) => Value == other.Value;
//
//
// // public static implicit operator ulong(EcsID id) => id.Value;
// // public static implicit operator EcsID(ulong value) => new (value);
// public static bool operator ==(EcsID id, EcsID other) => id.Value.Equals(other.Value);
// public static bool operator !=(EcsID id, EcsID other) => !id.Value.Equals(other.Value);
//
//
// public readonly override bool Equals(object? obj) => obj is EcsID ent && Equals(ent);
// public readonly override int GetHashCode() => Value.GetHashCode();
// public readonly override string ToString()
// {
// if (IsPair)
// return $"({First}, {Second}) | {Value}";
// return $"[{ID} - @{Generation} | {Value}]";
// }
// }
22 changes: 11 additions & 11 deletions src/EcsOp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,67 @@ namespace TinyEcs;

public static class IDOp
{
public static void Toggle(ref EcsID id)
public static void Toggle(ref ulong id)
{
id ^= EcsConst.ECS_TOGGLE;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsID GetGeneration(EcsID id)
public static ulong GetGeneration(ulong id)
{
return ((id & EcsConst.ECS_GENERATION_MASK) >> 32);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsID IncreaseGeneration(EcsID id)
public static ulong IncreaseGeneration(ulong id)
{
return ((id & ~EcsConst.ECS_GENERATION_MASK) | ((0xFFFF & (GetGeneration(id) + 1)) << 32));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsID RealID(EcsID id)
public static ulong RealID(ulong id)
{
return id &= EcsConst.ECS_ENTITY_MASK;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasFlag(EcsID id, byte flag)
public static bool HasFlag(ulong id, byte flag)
{
return (id & flag) != 0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsComponent(EcsID id)
public static bool IsComponent(ulong id)
{
return (id & ~EcsConst.ECS_COMPONENT_MASK) != 0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsID SetAsComponent(EcsID id)
public static ulong SetAsComponent(ulong id)
{
return id |= EcsConst.ECS_ID_FLAGS_MASK;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsID Pair(EcsID first, EcsID second)
public static ulong Pair(ulong first, ulong second)
{
return EcsConst.ECS_PAIR | ((first << 32) + (uint)second);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPair(EcsID id)
public static bool IsPair(ulong id)
{
return ((id) & EcsConst.ECS_ID_FLAGS_MASK) == EcsConst.ECS_PAIR;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsID GetPairFirst(EcsID id)
public static ulong GetPairFirst(ulong id)
{
return (uint)((id & EcsConst.ECS_COMPONENT_MASK) >> 32);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsID GetPairSecond(EcsID id)
public static ulong GetPairSecond(ulong id)
{
return (uint)id;
}
Expand Down
2 changes: 1 addition & 1 deletion src/EntityView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal EntityView(World world, EcsID id)
}

/// <inheritdoc cref="EcsID.Generation"/>
public readonly int Generation => ID.Generation;
public readonly int Generation => ID.Generation();


[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
2 changes: 1 addition & 1 deletion src/Hashing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static ulong Calculate(ReadOnlySpan<IQueryTerm> terms)
var hc = (ulong)terms.Length;
foreach (ref readonly var val in terms)
hc = unchecked(hc * FIXED + (ulong)val.Id + (byte)val.Op +
(val is ContainerQueryTerm container ? container.Terms.Aggregate(0Ul, static (a, b) => a + b.Id.Value) : 0));
(val is ContainerQueryTerm container ? container.Terms.Aggregate(0Ul, static (a, b) => a + b.Id) : 0));
return hc;
}

Expand Down
8 changes: 4 additions & 4 deletions src/Lookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ internal static class Lookup
if (_arrayCreator.TryGetValue(hashcode, out var fn))
return fn(count);

if (hashcode.IsPair)
if (hashcode.IsPair())
{
(var first, var second) = hashcode.Pair;
(var first, var second) = hashcode.Pair();
if (_arrayCreator.TryGetValue(first, out fn) && _components.TryGetValue(first, out var cmp) && cmp.Size > 0)
return fn(count);
if (_arrayCreator.TryGetValue(second, out fn) && _components.TryGetValue(second, out cmp) && cmp.Size > 0)
Expand Down Expand Up @@ -317,8 +317,8 @@ private static QueryTerm CreateUnmatchedTerm(object obj)
targetId = target.ID;
}

EcsAssert.Panic(actionId.IsValid, $"invalid action id {actionId.Value}");
EcsAssert.Panic(targetId.IsValid, $"invalid target id {targetId.Value}");
EcsAssert.Panic(actionId.IsValid(), $"invalid action id {actionId}");
EcsAssert.Panic(targetId.IsValid(), $"invalid target id {targetId}");

idx = IDOp.Pair(actionId, targetId);
}
Expand Down
2 changes: 2 additions & 0 deletions src/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
global using System.Runtime.InteropServices;
global using System.Buffers;
global using System.Linq;

global using EcsID = ulong;
31 changes: 10 additions & 21 deletions src/World.Deferred.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,32 @@ namespace TinyEcs;
public sealed partial class World
{
private readonly ConcurrentQueue<DeferredOp> _operations = new();
private WorldState _worldState = new () { State = WorldStateTypes.Normal, Locks = 0 };
private WorldState _worldState = new () { Locks = 0 };

public bool IsDeferred => _worldState.State == WorldStateTypes.Deferred;
public bool IsDeferred => _worldState.Locks > 0;



public void BeginDeferred()
{
if (_worldState.State == WorldStateTypes.Merging)
if (_worldState.Locks < 0)
return;

_worldState.State = WorldStateTypes.Deferred;
Interlocked.Increment(ref _worldState.Locks);
_worldState.Locks += 1;
}

public void EndDeferred()
{
if (_worldState.State == WorldStateTypes.Merging)
if (_worldState.Locks < 0)
return;

Interlocked.Decrement(ref _worldState.Locks);
EcsAssert.Assert(_worldState.Locks >= 0, "begin/end deferred calls mismatch");
_worldState.Locks -= 1;

if (_worldState.Locks == 0)
{
_worldState.State = WorldStateTypes.Merging;
_worldState.Locks = -1;
Merge();
_worldState.State = WorldStateTypes.Normal;
_worldState.Locks = 0;
}
}

Expand Down Expand Up @@ -137,11 +135,9 @@ private void Merge()

case DeferredOpTypes.SetComponent:
{
if (op.ComponentInfo.ID.IsPair)
if (op.ComponentInfo.ID.IsPair())
{
var first = op.ComponentInfo.ID.First;
var second = op.ComponentInfo.ID.Second;

(var first, var second) = op.ComponentInfo.ID.Pair();
CheckUnique(op.Entity, first);
CheckSymmetric(op.Entity, first, second);
}
Expand All @@ -163,16 +159,9 @@ private void Merge()

}

enum WorldStateTypes
{
Normal,
Deferred,
Merging
}

struct WorldState
{
public WorldStateTypes State;
public int Locks;
}

Expand Down
Loading

0 comments on commit deb2b7a

Please sign in to comment.