Skip to content
This repository has been archived by the owner on Oct 22, 2022. It is now read-only.

Commit

Permalink
Add EcsId.Entity / Pair and related changes
Browse files Browse the repository at this point in the history
  • Loading branch information
copygirl committed May 3, 2022
1 parent 3422479 commit 29abbda
Show file tree
Hide file tree
Showing 17 changed files with 316 additions and 262 deletions.
4 changes: 2 additions & 2 deletions src/gaemstone.Bloxel/Client/ChunkMeshGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ public void OnUnload()

public void OnUpdate(double delta)
{
var generated = new List<(EcsId, Mesh?)>();
var generated = new List<(EcsId.Entity, Mesh?)>();
Game.Queries.Run(without: Game.Type(typeof(Mesh)), action:
(EcsId entity, Chunk chunk, ChunkPaletteStorage<Prototype> storage) =>
(EcsId.Entity entity, Chunk chunk, ChunkPaletteStorage<Prototype> storage) =>
generated.Add((entity, Generate(chunk.Position, storage))));
foreach (var (entity, mesh) in generated) {
if (mesh is Mesh m) Game.Set(entity, m);
Expand Down
7 changes: 3 additions & 4 deletions src/gaemstone.Bloxel/Prototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ namespace gaemstone.Bloxel
{
public readonly struct Prototype
{
public readonly EcsId Value { get; }
public Prototype(EcsId value) => Value = value;
public static implicit operator Prototype(in EcsId value) => new(value);
public static implicit operator EcsId(in Prototype prototype) => prototype.Value;
public readonly EcsId.Entity Value { get; }
public Prototype(EcsId.Entity value) => Value = value;
public static implicit operator Prototype(in EcsId.Entity value) => new(value);
}
}
70 changes: 70 additions & 0 deletions src/gaemstone.Common/ECS/EcsId+Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Runtime.InteropServices;

namespace gaemstone.ECS
{
public readonly partial struct EcsId
{
[StructLayout(LayoutKind.Explicit)]
public readonly struct Entity
: IEquatable<Entity>
, IComparable<Entity>
{
/// <summary>
/// A special entity value that represents "no entity".
/// There may never be an entity alive with this value.
/// This is equal to using <c>default(EcsEntity)</c>.
/// </summary>
public static readonly Entity None = default;


/// <summary>
/// The internal 64-bit value representing this entity.
/// </summary>
[FieldOffset(0)] public readonly ulong Value;

/// <summary>
/// The unique 32-bit integer identifier for this entity.
/// Only one (alive) entity may have this identifier at a time.
/// </summary>
[FieldOffset(0)] public readonly uint ID;

/// <summary>
/// The generation of this entitiy.
/// This is increased each time an entity is destroyed.
/// </summary>
[FieldOffset(4)] public readonly ushort Generation;


public bool IsNone => ID == 0;


public Entity(ulong value) : this()
{
const ulong UNUSED_MASK = 0x00FF_0000_0000_0000;
const ulong ROLE_MASK = 0xFF00_0000_0000_0000;
if ((value & UNUSED_MASK) != 0L) throw new ArgumentException("Value has unused bits set", nameof(value));
if ((value & ROLE_MASK) != 0L) throw new ArgumentException("Value has role bits set", nameof(value));
Value = value;
}

public Entity(uint id, ushort generation) : this()
{
if (id == 0) throw new ArgumentException("ID must be greater than 0", nameof(id));
ID = id; Generation = generation;
}


public bool Equals(Entity other) => Value == other.Value;
public override bool Equals(object? obj) => (obj is Entity other) && Equals(other);
public override int GetHashCode() => HashCode.Combine(Value);

public static bool operator ==(Entity left, Entity right) => left.Equals(right);
public static bool operator !=(Entity left, Entity right) => !left.Equals(right);

public int CompareTo(Entity other) => Value.CompareTo(other.Value);

public override string ToString() => $"Entity(Id=0x{ID:X},Generation=0x{Generation:X})";
}
}
}
44 changes: 44 additions & 0 deletions src/gaemstone.Common/ECS/EcsId+Pair.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;

namespace gaemstone.ECS
{
public readonly partial struct EcsId
{
public readonly struct Pair
: IEquatable<Pair>
, IComparable<Pair>
{
public readonly ulong Value;

public uint Target => (uint)Value;
public uint Relation => (uint)((Value >> 32) & 0xFFFFFF);


public Pair(ulong value) : this()
{
if (Value >> 56 != (ulong)EcsRole.Pair) throw new ArgumentException(
"Role bits must be set to EcsRole.Pair", nameof(value));
Value = value;
}

public Pair(uint relation, uint target)
{
if (relation > 0xFFFFFF) throw new ArgumentOutOfRangeException(nameof(relation),
relation, "Relation must fit into 24 bits (be smaller or equal to 0xFFFFFF");
Value = (ulong)target | (relation << 32) | ((ulong)EcsRole.Pair << 56);
}


public bool Equals(Pair other) => Value == other.Value;
public override bool Equals(object? obj) => (obj is Pair other) && Equals(other);
public override int GetHashCode() => HashCode.Combine(Value);

public static bool operator ==(Pair left, Pair right) => left.Equals(right);
public static bool operator !=(Pair left, Pair right) => !left.Equals(right);

public int CompareTo(Pair other) => Value.CompareTo(other.Value);

public override string ToString() => $"Pair(Target=0x{Target:X},Relation=0x{Relation:X})";
}
}
}
102 changes: 17 additions & 85 deletions src/gaemstone.Common/ECS/EcsId.cs
Original file line number Diff line number Diff line change
@@ -1,119 +1,51 @@
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace gaemstone.ECS
{
// ID Generation Role
// |-------32-------|---16---|###|-8-|
// ===================================
// | Low 32 bits | High 32 bits |
// ===================================

// When Role == Pair, this is the layout:
// Target Relation Role
// |-------32-------|-----24-----|###|

[StructLayout(LayoutKind.Explicit)]
public readonly struct EcsId
public readonly partial struct EcsId
: IEquatable<EcsId>
, IComparable<EcsId>
{
public static readonly EcsId None = default;

[FieldOffset(0)] public readonly ulong Value;

[FieldOffset(0)] public readonly uint Low;
[FieldOffset(4)] public readonly uint High;

[FieldOffset(0)] public readonly uint ID;
[FieldOffset(4)] public readonly ushort Generation;
[FieldOffset(7)] public readonly EcsRole Role;

public bool IsNone => (ID == 0);

[FieldOffset(0)] readonly Entity _asEntity;
[FieldOffset(0)] readonly Pair _asPair;


EcsId(ulong value) : this() => Value = value;
EcsId(uint low, uint high) : this() { Low = low; High = high; }

public EcsId(uint id) : this(id, 0, EcsRole.None) { }
public EcsId(uint id, EcsRole role) : this(id, 0, role) { }
public EcsId(uint id, ushort generation) : this(id, generation, EcsRole.None) { }
public EcsId(uint id, ushort generation, EcsRole role) : this()
{
if (id == 0) throw new ArgumentOutOfRangeException(nameof(id), "ID must be greater than 0 to be valid.");
ID = id; Generation = generation; Role = role;
}

public static EcsId Pair(uint relation, uint target)
=> new(target | (relation & 0xFFFFFF) << 32 | (ulong)EcsRole.Pair << 56);
public static EcsId Pair(EcsId relation, EcsId target)
=> Pair(target.ID, relation.ID);
public (uint Relation, uint Target) ToPair()
=> (High & 0xFFFFFF, Low);
public (EcsId Relation, EcsId Target) ToPair(Universe context)
{
var (relationId, targetId) = ToPair();
if (!context.Entities.TryLookup(relationId, out var relation)) throw new EntityNotFoundException(relationId);
if (!context.Entities.TryLookup(targetId , out var target )) throw new EntityNotFoundException(targetId);
return (relation, target);
}
public static implicit operator EcsId(Entity entity) => new(entity.Value);
public static implicit operator EcsId(Pair pair ) => new(pair.Value);

public Entity? AsEntity() => (Role == EcsRole.Entity) ? _asEntity : null;
public Pair? AsPair () => (Role == EcsRole.Pair ) ? _asPair : null;


public bool Equals(EcsId other) => Value == other.Value;
public override bool Equals(object? obj) => (obj is EcsId other) && Equals(other);
public override int GetHashCode() => Value.GetHashCode();

public static bool operator ==(EcsId left, EcsId right) => left.Equals(right);
public static bool operator ==(EcsId left, EcsId right) => left.Equals(right);
public static bool operator !=(EcsId left, EcsId right) => !left.Equals(right);

public int CompareTo(EcsId other) => Value.CompareTo(other.Value);


public override string ToString()
{
var builder = new StringBuilder();
AppendString(builder);
return builder.ToString();
}
public void AppendString(StringBuilder builder)
{
if (Role == EcsRole.Pair) {
builder.Append($"EcsId.Pair(relation: 0x{High & 0xFFFFFF:X}, target: 0x{Low:X})");
} else {
builder.Append($"EcsId(id: 0x{ID:X}");
if (Generation != 0) builder.Append($", generation: {Generation}");
if (Role != EcsRole.None) builder.Append($", role: {Role}");
builder.Append(')');
}
}

public string ToString(Universe context)
{
var builder = new StringBuilder();
AppendString(builder, context);
return builder.ToString();
}
public void AppendString(StringBuilder builder, Universe context)
{
if (Role == EcsRole.Pair) {
var (relationId, targetId) = ToPair();
builder.Append('(');
if (context.Entities.TryLookup(relationId, out var relation)) relation.AppendString(builder);
else builder.Append($"0x{relationId:X}");
builder.Append(",");
if (context.Entities.TryLookup(targetId, out var target)) target.AppendString(builder);
else builder.Append($"0x{targetId:X}");
builder.Append(')');
} else if (context.TryGet<Identifier>(this, out var identifier))
builder.Append(identifier);
else
AppendString(builder);
}
public override string ToString() => Role switch {
EcsRole.Entity => _asEntity.ToString(),
EcsRole.Pair => _asPair.ToString(),
_ => $"EcsId(Value=0x{Value:X},Role={Role})"
};
}

public enum EcsRole : byte
{
None,
Entity,
Pair,
}
}
96 changes: 0 additions & 96 deletions src/gaemstone.Common/ECS/EcsType.cs

This file was deleted.

Loading

0 comments on commit 29abbda

Please sign in to comment.