using MessagePack; namespace OECS; /// /// An opaque handle to an entity in the ECS world. /// Combines a 24-bit identifier with an 8-bit version to prevent /// use-after-free bugs when entity IDs are recycled. /// [MessagePackObject(AllowPrivate = true)] [MessagePackFormatter(typeof(EntityFormatter))] public readonly partial struct Entity : IEquatable { private const uint IdMask = 0x00FF_FFFF; // lower 24 bits private const uint VersionMask = 0xFF00_0000; // upper 8 bits private const int VersionShift = 24; private const uint MaxId = IdMask; private const uint MaxVersion = 0xFF; /// /// A sentinel value representing a null or invalid entity. /// public static readonly Entity Null = new(0); [Key(0)] private readonly uint _value; internal Entity(uint id, uint version) { _value = (id & IdMask) | ((version & MaxVersion) << VersionShift); } private Entity(uint value) { _value = value; } /// /// The 24-bit entity identifier. /// [IgnoreMember] public uint Id => _value & IdMask; /// /// The 8-bit generation version. Incremented each time the ID is recycled. /// [IgnoreMember] public uint Version => (_value & VersionMask) >> VersionShift; /// /// Returns true if this entity is the null sentinel. /// [IgnoreMember] public bool IsNull => _value == 0; /// /// Creates a new entity with the given version, keeping the same ID. /// internal Entity WithVersion(uint version) => new(Id, version); /// /// Converts the entity to its raw 32-bit packed representation. /// public static explicit operator uint(Entity entity) => entity._value; /// /// Creates an entity from its raw 32-bit packed representation. /// public static explicit operator Entity(uint value) => new(value); public bool Equals(Entity other) => _value == other._value; public override bool Equals(object? obj) => obj is Entity other && Equals(other); public override int GetHashCode() => _value.GetHashCode(); public override string ToString() => $"Entity({Id}:v{Version})"; public static bool operator ==(Entity left, Entity right) => left.Equals(right); public static bool operator !=(Entity left, Entity right) => !left.Equals(right); }