using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace Svelto.ECS { [StructLayout(LayoutKind.Explicit, Size = 4)] //the type doesn't implement IEqualityComparer, what implements it is a custom comparer public readonly struct ExclusiveGroupStruct : IEquatable, IComparable { public static readonly ExclusiveGroupStruct Invalid = default; //must stay here because of Burst public ExclusiveGroupStruct(byte[] data, uint pos):this() { _idInternal = (uint)( data[pos] | data[++pos] << 8 | data[++pos] << 16 ); _bytemask = (byte) (data[++pos] << 24); DBC.ECS.Check.Ensure(id < _globalId, "Invalid group ID deserialiased"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { return obj is ExclusiveGroupStruct other && Equals(other); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return (int) id; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) { return c1.Equals(c2); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) { return c1.Equals(c2) == false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ExclusiveGroupStruct other) { #if DEBUG && !PROFILE_SVELTO if ((other.id != this.id || other._bytemask == this._bytemask) == false) throw new ECSException( "if the groups are correctly initialised, two groups with the same ID and different bitmask cannot exist"); #endif return other.id == this.id; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(ExclusiveGroupStruct other) { return other.id.CompareTo(id); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsEnabled() { return (_bytemask & (byte)ExclusiveGroupBitmask.DISABLED_BIT) == 0; } public override string ToString() { return this.ToName(); } public bool isInvalid => this == Invalid; public uint id => _idInternal & 0xFFFFFF; public uint ToIDAndBitmask() => _idInternal; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b) { var aID = a.id + b; #if DEBUG && !PROFILE_SVELTO if (aID >= 0xFFFFFF) throw new IndexOutOfRangeException(); #endif var group = new ExclusiveGroupStruct(aID); return @group; } internal static ExclusiveGroupStruct Generate() { var newValue = Interlocked.Increment(ref _staticGlobalID); ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint) newValue - (uint) 1); DBC.ECS.Check.Require(_globalId < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); return groupStruct; } internal static ExclusiveGroupStruct Generate(byte bitmask) { var newValue = Interlocked.Increment(ref _staticGlobalID); ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint) newValue - (uint) 1, bitmask); DBC.ECS.Check.Require(_globalId < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); return groupStruct; } /// /// Use this to reserve N groups. We of course assign the current ID and then increment the index /// by range so that the next reserved index will take the range in consideration. This method is used /// internally by ExclusiveGroup. /// internal static ExclusiveGroupStruct GenerateWithRange(ushort range) { var newValue = Interlocked.Add(ref _staticGlobalID, (int)range); ExclusiveGroupStruct groupStruct = new ExclusiveGroupStruct((uint) newValue - (uint)range); DBC.ECS.Check.Require(_globalId < ExclusiveGroup.MaxNumberOfExclusiveGroups, "too many exclusive groups created"); return groupStruct; } /// /// used internally only by the framework to convert uint in to groups. ID must be generated by the framework /// so only the framework can assure that this method is not being abused /// internal ExclusiveGroupStruct(uint groupID):this() { #if DEBUG && !PROFILE_SVELTO if (groupID >= 0xFFFFFF) throw new IndexOutOfRangeException(); #endif _idInternal = groupID; } ExclusiveGroupStruct(uint groupID, byte bytemask):this() { #if DEBUG && !PROFILE_SVELTO if (groupID >= 0xFFFFFF) throw new IndexOutOfRangeException(); #endif _idInternal = groupID; _bytemask = bytemask; } static ExclusiveGroupStruct() { _staticGlobalID = 1; } [FieldOffset(0)] readonly uint _idInternal; //byte mask can be used to add special flags to specific groups that can be checked for example when swapping groups //however at the moment we are not letting the user access it, because if we do so we should give access only to //4 bits are the other 4 bits will stay reserved for Svelto use (at the moment of writing using only the disable //bit) [FieldOffset(3)] readonly byte _bytemask; static int _staticGlobalID; static uint _globalId => (uint) _staticGlobalID; } }