using DCFApixels.DragonECS.Core; using DCFApixels.DragonECS.Internal; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; #if ENABLE_IL2CPP using Unity.IL2CPP.CompilerServices; #endif namespace DCFApixels.DragonECS { #if ENABLE_IL2CPP [Il2CppSetOption(Option.NullChecks, false)] [Il2CppSetOption(Option.ArrayBoundsChecks, false)] #endif [DebuggerTypeProxy(typeof(DebuggerProxy))] public sealed class EcsStaticMask : IEquatable, IComponentMask { public static readonly EcsStaticMask Empty; public static readonly EcsStaticMask Broken; private static readonly Stack _buildersPool = new Stack(); private static readonly ConcurrentDictionary _ids = new ConcurrentDictionary(); private static readonly IdDispenser _idDIspenser = new IdDispenser(nullID: 0); private static readonly object _lock = new object(); static EcsStaticMask() { EcsStaticMask createMask(int id, Key key) { EcsStaticMask result = new EcsStaticMask(id, key); _ids[key] = result; return result; } Empty = createMask(0, new Key(new EcsTypeCode[0], new EcsTypeCode[0])); Broken = createMask(_idDIspenser.UseFree(), new Key(new EcsTypeCode[1] { (EcsTypeCode)1 }, new EcsTypeCode[1] { (EcsTypeCode)1 })); } public readonly int ID; /// Sorted private readonly EcsTypeCode[] _incs; /// Sorted private readonly EcsTypeCode[] _excs; #region Properties /// Sorted set including constraints presented as global type codes. public ReadOnlySpan IncTypeCodes { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return _incs; } } /// Sorted set excluding constraints presented as global type codes. public ReadOnlySpan ExcTypeCodes { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return _excs; } } public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return _incs.Length == 0 && _excs.Length == 0; } } public bool IsBroken { get { return (_incs.Length & _excs.Length) == 1 && _incs[0] == _excs[0]; } } #endregion #region Constrcutors private EcsStaticMask(int id, Key key) { ID = id; _incs = key.Incs; _excs = key.Excs; } public static Builder New() { return Builder.New(); } public static Builder Inc() { return Builder.New().Inc(); } public static Builder Exc() { return Builder.New().Exc(); } public static Builder Inc(Type type) { return Builder.New().Inc(type); } public static Builder Exc(Type type) { return Builder.New().Exc(type); } public static Builder Inc(EcsTypeCode typeCode) { return Builder.New().Inc(typeCode); } public static Builder Exc(EcsTypeCode typeCode) { return Builder.New().Exc(typeCode); } private static EcsStaticMask CreateMask(Key key) { if (_ids.TryGetValue(key, out EcsStaticMask result) == false) { lock (_lock) { if (_ids.TryGetValue(key, out result) == false) { #if DEBUG CheckConstraints(key.Incs, key.Excs); #endif result = new EcsStaticMask(_idDIspenser.UseFree(), key); _ids[key] = result; } } } return result; } #endregion #region Checks public bool IsSubmaskOf(EcsStaticMask otherMask) { return IsSubmask(otherMask, this); } public bool IsSupermaskOf(EcsStaticMask otherMask) { return IsSubmask(this, otherMask); } public bool IsConflictWith(EcsStaticMask otherMask) { return OverlapsArray(_incs, otherMask._excs) || OverlapsArray(_excs, otherMask._incs); } private static bool IsSubmask(EcsStaticMask super, EcsStaticMask sub) { return IsSubarray(sub._incs, super._incs) && IsSuperarray(sub._excs, super._excs); } private static bool OverlapsArray(EcsTypeCode[] l, EcsTypeCode[] r) { int li = 0; int ri = 0; while (li < l.Length && ri < r.Length) { if (l[li] == r[ri]) { return true; } else if (l[li] < r[ri]) { li++; } else { ri++; } } return false; } private static bool IsSubarray(EcsTypeCode[] super, EcsTypeCode[] sub) { if (super.Length < sub.Length) { return false; } int superI = 0; int subI = 0; while (superI < super.Length && subI < sub.Length) { if (super[superI] == sub[subI]) { superI++; } subI++; } return subI == sub.Length; } private static bool IsSuperarray(EcsTypeCode[] super, EcsTypeCode[] sub) { if (super.Length < sub.Length) { return false; } int superI = 0; int subI = 0; while (superI < super.Length && subI < sub.Length) { if (super[superI] == sub[subI]) { subI++; } superI++; } return subI == sub.Length; } #endregion #region Methods public EcsMask ToMask(EcsWorld world) { return EcsMask.FromStatic(world, this); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(EcsStaticMask other) { return ID == other.ID; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return ID; } public override bool Equals(object obj) { return Equals((EcsStaticMask)obj); } public override string ToString() { return CreateLogString(_incs, _excs); } #endregion #region Builder private readonly struct Key : IEquatable { public readonly EcsTypeCode[] Incs; public readonly EcsTypeCode[] Excs; public readonly int Hash; #region Constructors public Key(EcsTypeCode[] inc, EcsTypeCode[] exc) { this.Incs = inc; this.Excs = exc; unchecked { Hash = inc.Length + exc.Length; for (int i = 0, iMax = inc.Length; i < iMax; i++) { Hash = Hash * EcsConsts.MAGIC_PRIME + (int)inc[i]; } for (int i = 0, iMax = exc.Length; i < iMax; i++) { Hash = Hash * EcsConsts.MAGIC_PRIME - (int)exc[i]; } } } #endregion #region Object [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Key other) { if (Incs.Length != other.Incs.Length) { return false; } if (Excs.Length != other.Excs.Length) { return false; } for (int i = 0; i < Incs.Length; i++) { if (Incs[i] != other.Incs[i]) { return false; } } for (int i = 0; i < Excs.Length; i++) { if (Excs[i] != other.Excs[i]) { return false; } } return true; } public override bool Equals(object obj) { return Equals((Key)obj); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return Hash; } #endregion } public readonly struct Builder { private readonly BuilderInstance _builder; private readonly int _version; #region Properties public bool IsNull { get { return _builder == null || _builder._version != _version; } } #endregion #region Constrcutors private Builder(BuilderInstance builder) { _builder = builder; _version = builder._version; } public static Builder New() { lock (_lock) { if (_buildersPool.TryPop(out BuilderInstance builderInstance) == false) { builderInstance = new BuilderInstance(); } return new Builder(builderInstance); } } #endregion #region Inc/Exc/Combine/Except public Builder Inc() { return Inc(EcsTypeCodeManager.Get()); } public Builder Exc() { return Exc(EcsTypeCodeManager.Get()); } public Builder Inc(Type type) { return Inc(EcsTypeCodeManager.Get(type)); } public Builder Exc(Type type) { return Exc(EcsTypeCodeManager.Get(type)); } public Builder Inc(EcsTypeCode typeCode) { if (_version != _builder._version) { Throw.CantReuseBuilder(); } _builder.Inc(typeCode); return this; } public Builder Exc(EcsTypeCode typeCode) { if (_version != _builder._version) { Throw.CantReuseBuilder(); } _builder.Exc(typeCode); return this; } public Builder Combine(EcsStaticMask mask) { if (_version != _builder._version) { Throw.CantReuseBuilder(); } _builder.Combine(mask); return this; } public Builder Except(EcsStaticMask mask) { if (_version != _builder._version) { Throw.CantReuseBuilder(); } _builder.Except(mask); return this; } #endregion #region Build/Cancel public EcsStaticMask Build() { if (_version != _builder._version) { Throw.CantReuseBuilder(); } lock (_lock) { _buildersPool.Push(_builder); return _builder.Build(); } } public void Cancel() { if (_version != _builder._version) { Throw.CantReuseBuilder(); } lock (_lock) { _buildersPool.Push(_builder); } } #endregion } private class BuilderInstance { private readonly HashSet _inc = new HashSet(); private readonly HashSet _exc = new HashSet(); private readonly List _combineds = new List(); private bool _sortedCombinedChecker = true; private readonly List _excepteds = new List(); internal int _version; #region Constrcutors internal BuilderInstance() { } #endregion #region Inc/Exc/Combine/Except public void Inc(EcsTypeCode typeCode) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS if (_inc.Contains(typeCode) || _exc.Contains(typeCode)) { Throw.ConstraintIsAlreadyContainedInMask(); } #endif _inc.Add(typeCode); } public void Exc(EcsTypeCode typeCode) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS if (_inc.Contains(typeCode) || _exc.Contains(typeCode)) { Throw.ConstraintIsAlreadyContainedInMask(); } #endif _exc.Add(typeCode); } public void Combine(EcsStaticMask mask, int order = 0) { if (_sortedCombinedChecker && order != 0) { _sortedCombinedChecker = false; } _combineds.Add(new Combined(mask, order)); } public void Except(EcsStaticMask mask, int order = 0) { _excepteds.Add(new Excepted(mask, order)); } #endregion #region Build public EcsStaticMask Build() { HashSet combinedIncs = _inc; HashSet combinedExcs = _exc; if (_combineds.Count > 0) { combinedIncs = new HashSet(); combinedExcs = new HashSet(); if (_sortedCombinedChecker == false) { _combineds.Sort((a, b) => a.order - b.order); } foreach (var item in _combineds) { EcsStaticMask submask = item.mask; combinedIncs.ExceptWith(submask._excs);//удаляю конфликтующие ограничения combinedExcs.ExceptWith(submask._incs);//удаляю конфликтующие ограничения combinedIncs.UnionWith(submask._incs); combinedExcs.UnionWith(submask._excs); } combinedIncs.ExceptWith(_exc);//удаляю конфликтующие ограничения combinedExcs.ExceptWith(_inc);//удаляю конфликтующие ограничения combinedIncs.UnionWith(_inc); combinedExcs.UnionWith(_exc); _combineds.Clear(); } else { combinedIncs = _inc; combinedExcs = _exc; } if (_excepteds.Count > 0) { foreach (var item in _excepteds) { //if (combinedIncs.Overlaps(item.mask._exc) || combinedExcs.Overlaps(item.mask._inc)) //{ // return _world.Get().BrokenMask; //} combinedIncs.ExceptWith(item.mask._incs); combinedExcs.ExceptWith(item.mask._excs); } _excepteds.Clear(); } var inc = combinedIncs.ToArray(); Array.Sort(inc); var exc = combinedExcs.ToArray(); Array.Sort(exc); var key = new Key(inc, exc); EcsStaticMask result = CreateMask(key); _inc.Clear(); _exc.Clear(); _version++; return result; } #endregion #region Utils private readonly struct Combined { public readonly EcsStaticMask mask; public readonly int order; public Combined(EcsStaticMask mask, int order) { this.mask = mask; this.order = order; } } private readonly struct Excepted { public readonly EcsStaticMask mask; public readonly int order; public Excepted(EcsStaticMask mask, int order) { this.mask = mask; this.order = order; } } #endregion } #endregion #region Debug utils private static string CreateLogString(EcsTypeCode[] inc, EcsTypeCode[] exc) { #if (DEBUG && !DISABLE_DEBUG) string converter(EcsTypeCode o) { return EcsTypeCodeManager.FindTypeOfCode(o).ToString(); } return $"Inc({string.Join(", ", inc.Select(converter))}) Exc({string.Join(", ", exc.Select(converter))})"; #else return $"Inc({string.Join(", ", inc)}) Exc({string.Join(", ", exc)})"; // Release optimization #endif } internal class DebuggerProxy { private EcsStaticMask _source; public readonly int ID; public readonly EcsTypeCode[] included; public readonly EcsTypeCode[] excluded; public readonly Type[] includedTypes; public readonly Type[] excludedTypes; public bool IsEmpty { get { return _source.IsEmpty; } } public bool IsBroken { get { return _source.IsBroken; } } public DebuggerProxy(EcsStaticMask mask) { _source = mask; ID = mask.ID; included = mask._incs; excluded = mask._excs; Type converter(EcsTypeCode o) { return EcsTypeCodeManager.FindTypeOfCode(o).Type; } includedTypes = included.Select(converter).ToArray(); excludedTypes = excluded.Select(converter).ToArray(); } public override string ToString() { return CreateLogString(included, excluded); } } #if DEBUG private static void CheckConstraints(EcsTypeCode[] incs, EcsTypeCode[] excs) { if (CheckRepeats(incs)) { throw new EcsFrameworkException("The values in the Include constraints are repeated."); } if (CheckRepeats(excs)) { throw new EcsFrameworkException("The values in the Exclude constraints are repeated."); } if (OverlapsArray(incs, excs)) { throw new EcsFrameworkException("Conflicting Include and Exclude constraints."); } } private static bool CheckRepeats(EcsTypeCode[] array) { if (array.Length <= 1) { return false; } EcsTypeCode lastValue = array[0]; for (int i = 1; i < array.Length; i++) { EcsTypeCode value = array[i]; if (value == lastValue) { return true; } lastValue = value; } return false; } #endif #endregion } }