DragonECS/src/EcsMask.cs

413 lines
14 KiB
C#
Raw Normal View History

2024-01-07 18:52:54 +08:00
using DCFApixels.DragonECS.Internal;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
namespace DCFApixels.DragonECS
{
[DebuggerTypeProxy(typeof(DebuggerProxy))]
2024-01-07 19:32:16 +08:00
public sealed class EcsMask : IEquatable<EcsMask>
2024-01-07 18:52:54 +08:00
{
internal readonly int id;
internal readonly int worldID;
internal readonly EcsMaskChunck[] incChunckMasks;
internal readonly EcsMaskChunck[] excChunckMasks;
internal readonly int[] inc;
internal readonly int[] exc;
2024-01-07 23:19:18 +08:00
#region Properties
public int ID => id;
2024-01-07 18:52:54 +08:00
public int WorldID => worldID;
public EcsWorld World => EcsWorld.GetWorld(worldID);
/// <summary>Including constraints</summary>
public ReadOnlySpan<int> Inc => inc;
/// <summary>Excluding constraints</summary>
public ReadOnlySpan<int> Exc => exc;
2024-01-07 23:19:18 +08:00
#endregion
#region Constructors
2024-01-11 00:48:39 +08:00
public static Builder New(EcsWorld world)
{
return new Builder(world);
}
2024-01-07 19:32:16 +08:00
internal EcsMask(int id, int worldID, int[] inc, int[] exc)
2024-01-07 18:52:54 +08:00
{
#if DEBUG
CheckConstraints(inc, exc);
#endif
this.id = id;
this.inc = inc;
this.exc = exc;
this.worldID = worldID;
2024-01-07 19:32:16 +08:00
2024-01-07 20:44:11 +08:00
incChunckMasks = MakeMaskChuncsArray(inc);
excChunckMasks = MakeMaskChuncsArray(exc);
}
private unsafe EcsMaskChunck[] MakeMaskChuncsArray(int[] sortedArray)
{
EcsMaskChunck* buffer = stackalloc EcsMaskChunck[sortedArray.Length];
int resultLength = 0;
for (int i = 0; i < sortedArray.Length;)
2024-01-07 19:32:16 +08:00
{
2024-01-07 20:44:11 +08:00
int chankIndexX = sortedArray[i] >> EcsMaskChunck.DIV_SHIFT;
int maskX = 0;
do
{
EcsMaskChunck bitJ = EcsMaskChunck.FromID(sortedArray[i]);
if (bitJ.chankIndex != chankIndexX)
{
break;
}
maskX |= bitJ.mask;
i++;
} while (i < sortedArray.Length);
buffer[resultLength++] = new EcsMaskChunck(chankIndexX, maskX);
2024-01-07 19:32:16 +08:00
}
2024-01-07 20:44:11 +08:00
EcsMaskChunck[] result = new EcsMaskChunck[resultLength];
for (int i = 0; i < resultLength; i++)
2024-01-07 19:32:16 +08:00
{
2024-01-07 20:44:11 +08:00
result[i] = buffer[i];
2024-01-07 19:32:16 +08:00
}
2024-01-07 20:44:11 +08:00
return result;
2024-01-07 18:52:54 +08:00
}
2024-01-07 23:19:18 +08:00
#endregion
2024-01-07 18:52:54 +08:00
#region Object
public override string ToString() => CreateLogString(worldID, inc, exc);
2024-01-07 19:32:16 +08:00
public bool Equals(EcsMask mask)
{
return id == mask.id && worldID == mask.worldID;
}
public override bool Equals(object obj)
{
return obj is EcsMask mask && id == mask.id && Equals(mask);
}
public override int GetHashCode()
{
return unchecked(id ^ (worldID * EcsConsts.MAGIC_PRIME));
}
2024-01-07 18:52:54 +08:00
#endregion
#region Debug utils
#if DEBUG
private static HashSet<int> _dummyHashSet = new HashSet<int>();
private void CheckConstraints(int[] inc, int[] exc)
{
lock (_dummyHashSet)
{
if (CheckRepeats(inc)) throw new EcsFrameworkException("The values in the Include constraints are repeated.");
if (CheckRepeats(exc)) throw new EcsFrameworkException("The values in the Exclude constraints are repeated.");
_dummyHashSet.Clear();
_dummyHashSet.UnionWith(inc);
if (_dummyHashSet.Overlaps(exc)) throw new EcsFrameworkException("Conflicting Include and Exclude constraints.");
}
}
private bool CheckRepeats(int[] array)
{
_dummyHashSet.Clear();
foreach (var item in array)
{
if (_dummyHashSet.Contains(item)) return true;
_dummyHashSet.Add(item);
}
return false;
}
#endif
private static string CreateLogString(int worldID, int[] inc, int[] exc)
{
#if (DEBUG && !DISABLE_DEBUG)
string converter(int o) => EcsDebugUtility.GetGenericTypeName(EcsWorld.GetWorld(worldID).AllPools[o].ComponentType, 1);
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
}
2024-01-07 19:32:16 +08:00
2024-01-07 18:52:54 +08:00
internal class DebuggerProxy
{
2024-01-07 19:32:16 +08:00
public readonly int ID;
2024-01-07 18:52:54 +08:00
public readonly EcsWorld world;
2024-01-07 19:32:16 +08:00
private readonly int _worldID;
2024-01-07 18:52:54 +08:00
public readonly EcsMaskChunck[] includedChunkMasks;
public readonly EcsMaskChunck[] excludedChunkMasks;
public readonly int[] included;
public readonly int[] excluded;
public readonly Type[] includedTypes;
public readonly Type[] excludedTypes;
public DebuggerProxy(EcsMask mask)
{
2024-01-07 19:32:16 +08:00
ID = mask.id;
2024-01-07 18:52:54 +08:00
world = EcsWorld.GetWorld(mask.worldID);
2024-01-07 19:32:16 +08:00
_worldID = mask.worldID;
2024-01-07 18:52:54 +08:00
includedChunkMasks = mask.incChunckMasks;
excludedChunkMasks = mask.excChunckMasks;
included = mask.inc;
excluded = mask.exc;
Type converter(int o) => world.GetComponentType(o);
includedTypes = included.Select(converter).ToArray();
excludedTypes = excluded.Select(converter).ToArray();
}
2024-01-07 19:32:16 +08:00
public override string ToString() => CreateLogString(_worldID, included, excluded);
2024-01-07 18:52:54 +08:00
}
#endregion
2024-01-07 19:32:16 +08:00
#region Builder
2024-01-11 00:48:39 +08:00
private readonly struct WorldMaskComponent : IEcsWorldComponent<WorldMaskComponent>
{
private readonly EcsWorld _world;
private readonly Dictionary<Key, EcsMask> _masks;
#region Constructor/Destructor
public WorldMaskComponent(EcsWorld world, Dictionary<Key, EcsMask> masks)
{
_world = world;
_masks = masks;
}
public void Init(ref WorldMaskComponent component, EcsWorld world)
{
component = new WorldMaskComponent(world, new Dictionary<Key, EcsMask>(256));
}
public void OnDestroy(ref WorldMaskComponent component, EcsWorld world)
{
component._masks.Clear();
component = default;
}
#endregion
#region GetMask
internal EcsMask GetMask(Key maskKey)
{
if (!_masks.TryGetValue(maskKey, out EcsMask result))
{
result = new EcsMask(_masks.Count, _world.id, maskKey.inc, maskKey.exc);
_masks.Add(maskKey, result);
}
return result;
}
#endregion
}
private readonly struct Key : IEquatable<Key>
2024-01-07 18:52:54 +08:00
{
public readonly int[] inc;
public readonly int[] exc;
public readonly int hash;
2024-01-11 00:48:39 +08:00
#region Constructors
public Key(int[] inc, int[] exc)
2024-01-07 18:52:54 +08:00
{
this.inc = inc;
this.exc = exc;
2024-01-11 00:48:39 +08:00
unchecked
{
hash = inc.Length + exc.Length;
for (int i = 0, iMax = inc.Length; i < iMax; i++)
{
hash = hash * EcsConsts.MAGIC_PRIME + inc[i];
}
for (int i = 0, iMax = exc.Length; i < iMax; i++)
{
hash = hash * EcsConsts.MAGIC_PRIME - exc[i];
}
}
2024-01-07 18:52:54 +08:00
}
2024-01-11 00:48:39 +08:00
#endregion
#region Object
2024-01-07 18:52:54 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-01-11 00:48:39 +08:00
public bool Equals(Key other)
2024-01-07 18:52:54 +08:00
{
if (inc.Length != other.inc.Length)
{
return false;
}
if (exc.Length != other.exc.Length)
{
return false;
}
for (int i = 0; i < inc.Length; i++)
{
if (inc[i] != other.inc[i])
{
return false;
}
}
for (int i = 0; i < exc.Length; i++)
{
if (exc[i] != other.exc[i])
{
return false;
}
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => hash;
2024-01-11 00:48:39 +08:00
#endregion
2024-01-07 18:52:54 +08:00
}
public class Builder
{
2024-01-07 19:32:16 +08:00
private readonly EcsWorld _world;
private readonly HashSet<int> _inc = new HashSet<int>();
private readonly HashSet<int> _exc = new HashSet<int>();
private readonly List<Combined> _combined = new List<Combined>();
2024-01-07 18:52:54 +08:00
2024-01-11 00:48:39 +08:00
#region Constrcutors
2024-01-07 18:52:54 +08:00
internal Builder(EcsWorld world)
{
_world = world;
}
2024-01-11 00:48:39 +08:00
#endregion
2024-01-07 18:52:54 +08:00
2024-01-11 00:48:39 +08:00
#region Include/Exclude/Combine
public Builder Include<T>()
2024-01-07 18:52:54 +08:00
{
int id = _world.GetComponentID<T>();
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (_inc.Contains(id) || _exc.Contains(id)) Throw.ConstraintIsAlreadyContainedInMask(typeof(T));
#endif
_inc.Add(id);
2024-01-11 00:48:39 +08:00
return this;
2024-01-07 18:52:54 +08:00
}
2024-01-11 00:48:39 +08:00
public Builder Exclude<T>()
2024-01-07 18:52:54 +08:00
{
int id = _world.GetComponentID<T>();
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (_inc.Contains(id) || _exc.Contains(id)) Throw.ConstraintIsAlreadyContainedInMask(typeof(T));
#endif
_exc.Add(id);
2024-01-11 00:48:39 +08:00
return this;
2024-01-07 18:52:54 +08:00
}
2024-01-11 00:48:39 +08:00
public Builder Include(Type type)
2024-01-07 18:52:54 +08:00
{
int id = _world.GetComponentID(type);
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (_inc.Contains(id) || _exc.Contains(id)) Throw.ConstraintIsAlreadyContainedInMask(type);
#endif
_inc.Add(id);
2024-01-11 00:48:39 +08:00
return this;
2024-01-07 18:52:54 +08:00
}
2024-01-11 00:48:39 +08:00
public Builder Exclude(Type type)
2024-01-07 18:52:54 +08:00
{
int id = _world.GetComponentID(type);
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (_inc.Contains(id) || _exc.Contains(id)) Throw.ConstraintIsAlreadyContainedInMask(type);
#endif
_exc.Add(id);
2024-01-11 00:48:39 +08:00
return this;
2024-01-07 18:52:54 +08:00
}
2024-01-11 00:48:39 +08:00
public Builder Combine(EcsMask mask, int order = 0)
2024-01-07 18:52:54 +08:00
{
_combined.Add(new Combined(mask, order));
2024-01-11 00:48:39 +08:00
return this;
2024-01-07 18:52:54 +08:00
}
2024-01-11 00:48:39 +08:00
#endregion
2024-01-07 18:52:54 +08:00
2024-01-11 00:48:39 +08:00
#region Build
2024-01-07 18:52:54 +08:00
public EcsMask Build()
{
HashSet<int> combinedInc;
HashSet<int> combinedExc;
if (_combined.Count > 0)
{
combinedInc = new HashSet<int>();
combinedExc = new HashSet<int>();
_combined.Sort((a, b) => a.order - b.order);
foreach (var item in _combined)
{
EcsMask submask = item.mask;
combinedInc.ExceptWith(submask.exc);//удаляю конфликтующие ограничения
combinedExc.ExceptWith(submask.inc);//удаляю конфликтующие ограничения
combinedInc.UnionWith(submask.inc);
combinedExc.UnionWith(submask.exc);
}
combinedInc.ExceptWith(_exc);//удаляю конфликтующие ограничения
combinedExc.ExceptWith(_inc);//удаляю конфликтующие ограничения
combinedInc.UnionWith(_inc);
combinedExc.UnionWith(_exc);
}
else
{
combinedInc = _inc;
combinedExc = _exc;
}
var inc = combinedInc.ToArray();
Array.Sort(inc);
2024-01-11 00:48:39 +08:00
_inc.Clear();
2024-01-07 18:52:54 +08:00
var exc = combinedExc.ToArray();
Array.Sort(exc);
2024-01-11 00:48:39 +08:00
_exc.Clear();
2024-01-07 18:52:54 +08:00
2024-01-11 00:48:39 +08:00
_combined.Clear();
return _world.Get<WorldMaskComponent>().GetMask(new Key(inc, exc));
2024-01-07 18:52:54 +08:00
}
2024-01-11 00:48:39 +08:00
#endregion
2024-01-07 18:52:54 +08:00
}
2024-01-07 19:32:16 +08:00
2024-01-07 18:52:54 +08:00
private readonly struct Combined
{
public readonly EcsMask mask;
public readonly int order;
public Combined(EcsMask mask, int order)
{
this.mask = mask;
this.order = order;
}
}
2024-01-07 19:32:16 +08:00
#endregion
2024-01-07 18:52:54 +08:00
}
2024-01-07 23:19:18 +08:00
#region EcsMaskChunck
2024-01-07 18:52:54 +08:00
[DebuggerTypeProxy(typeof(DebuggerProxy))]
public readonly struct EcsMaskChunck
{
internal const int BITS = 32;
internal const int DIV_SHIFT = 5;
internal const int MOD_MASK = BITS - 1;
public readonly int chankIndex;
public readonly int mask;
public EcsMaskChunck(int chankIndex, int mask)
{
this.chankIndex = chankIndex;
this.mask = mask;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsMaskChunck FromID(int id)
{
return new EcsMaskChunck(id >> DIV_SHIFT, 1 << (id & MOD_MASK));
}
public override string ToString()
{
return $"mask({chankIndex}, {mask}, {BitsUtility.CountBits(mask)})";
}
internal class DebuggerProxy
{
public int chunk;
public uint mask;
public int[] values = Array.Empty<int>();
public string bits;
public DebuggerProxy(EcsMaskChunck maskbits)
{
chunk = maskbits.chankIndex;
mask = (uint)maskbits.mask;
BitsUtility.GetBitNumbersNoAlloc(mask, ref values);
for (int i = 0; i < values.Length; i++)
{
values[i] += (chunk) << 5;
}
bits = BitsUtility.ToBitsString(mask, '_', 8);
}
}
}
2024-01-07 23:19:18 +08:00
#endregion
2024-01-07 18:52:54 +08:00
}