diff --git a/encompass-cs/Collections/BitSet1024.cs b/encompass-cs/Collections/BitSet1024.cs new file mode 100644 index 0000000..239cd79 --- /dev/null +++ b/encompass-cs/Collections/BitSet1024.cs @@ -0,0 +1,178 @@ +using System.Numerics; + +namespace Encompass.Collections +{ + public static class BitSet1024Builder + { + public static BitSet1024 Zeroes() + { + return new BitSet1024(Vector256Builder.Zeroes(), Vector256Builder.Zeroes(), Vector256Builder.Zeroes(), Vector256Builder.Zeroes()); + } + + public static BitSet1024 Ones() + { + return new BitSet1024(Vector256Builder.Ones(), Vector256Builder.Ones(), Vector256Builder.Ones(), Vector256Builder.Ones()); + } + } + + public static class Vector256Builder + { + static readonly uint[] zeroes = new uint[8]; // max size of a Vector is 8 uints + static readonly uint[] ones = new uint[8] { uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue }; + static uint[] builderInts = new uint[8]; + + private static void ResetBuilder() + { + for (var i = 0; i < 8; i++) + { + builderInts[i] = 0; + } + } + + public static Vector Zeroes() + { + return new Vector(zeroes); + } + + public static Vector Ones() + { + return new Vector(ones); + } + + public static Vector Build(int index) + { + if (index > 255) { throw new System.ArgumentOutOfRangeException(nameof(index)); } + ResetBuilder(); + builderInts[index / 32] |= (uint)(1 << (index % 32)); + return new Vector(builderInts); + } + } + + public struct BitSet1024 + { + public Vector A { get; } + public Vector B { get; } + public Vector C { get; } + public Vector D { get; } + + internal BitSet1024(Vector a, Vector b, Vector c, Vector d) + { + A = a; + B = b; + C = c; + D = d; + } + + public BitSet1024 And(BitSet1024 other) + { + return new BitSet1024(A & other.A, B & other.B, C & other.C, D & other.D); + } + + public BitSet1024 Or(BitSet1024 other) + { + return new BitSet1024(A | other.A, B | other.B, C | other.C, D | other.D); + } + + public BitSet1024 Not() + { + return new BitSet1024(~A, ~B, ~C, ~D); + } + + public BitSet1024 Set(int index) + { + if (index < 256) + { + return new BitSet1024(A | Vector256Builder.Build(index % 256), B, C, D); + } + else if (index < 512) + { + return new BitSet1024(A, B | Vector256Builder.Build(index % 256), C, D); + } + else if (index < 768) + { + return new BitSet1024(A, B, C | Vector256Builder.Build(index % 256), D); + } + else if (index < 1024) + { + return new BitSet1024(A, B, C, D | Vector256Builder.Build(index % 256)); + } + else + { + throw new System.ArgumentOutOfRangeException(nameof(index)); + } + } + + public BitSet1024 UnSet(int index) + { + if (index < 256) + { + return new BitSet1024(A & ~Vector256Builder.Build(index % 256), B, C, D); + } + else if (index < 512) + { + return new BitSet1024(A, B & ~Vector256Builder.Build(index % 256), C, D); + } + else if (index < 768) + { + return new BitSet1024(A, B, C & ~Vector256Builder.Build(index % 256), D); + } + else if (index < 1024) + { + return new BitSet1024(A, B, C, D & ~Vector256Builder.Build(index % 256)); + } + else + { + throw new System.ArgumentOutOfRangeException(nameof(index)); + } + } + + public bool Get(int index) + { + var vectorIndex = index % 256; + if (index < 256) + { + return (A[vectorIndex / 32] & (uint)(1 << vectorIndex % 32)) != 0; + } + else if (index < 512) + { + return (B[vectorIndex / 32] & (uint)(1 << vectorIndex % 32)) != 0; + } + else if (index < 768) + { + return (C[vectorIndex / 32] & (uint)(1 << vectorIndex % 32)) != 0; + } + else if (index < 1024) + { + return (D[vectorIndex / 32] & (uint)(1 << vectorIndex % 32)) != 0; + } + else + { + throw new System.ArgumentOutOfRangeException(nameof(index)); + } + } + + public bool AllTrue() + { + for (var i = 0; i < 8; i++) + { + if (A[i] != uint.MaxValue) { return false; } + if (B[i] != uint.MaxValue) { return false; } + if (C[i] != uint.MaxValue) { return false; } + if (D[i] != uint.MaxValue) { return false; } + } + return true; + } + + public bool AllFalse() + { + for (var i = 0; i < 8; i++) + { + if (A[i] != 0) { return false; } + if (B[i] != 0) { return false; } + if (C[i] != 0) { return false; } + if (D[i] != 0) { return false; } + } + return true; + } + } +} diff --git a/encompass-cs/Collections/ComponentBitSet.cs b/encompass-cs/Collections/ComponentBitSet.cs new file mode 100644 index 0000000..63433dd --- /dev/null +++ b/encompass-cs/Collections/ComponentBitSet.cs @@ -0,0 +1,60 @@ +using Encompass.Collections; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Encompass +{ + internal class ComponentBitSet + { + Dictionary entities = new Dictionary(); + Dictionary TypeToIndex { get; } + + public ComponentBitSet(Dictionary typeToIndex) + { + TypeToIndex = typeToIndex; + } + + public void FinishRegistering() + { + + } + + public void Clear() + { + entities.Clear(); + } + + public void AddEntity(Entity entity) + { + entities.Add(entity, BitSet1024Builder.Zeroes()); + } + + public void Set(Entity entity) where TComponent : struct, IComponent + { + if (!entities.ContainsKey(entity)) { AddEntity(entity); } + entities[entity] = entities[entity].Set(TypeToIndex[typeof(TComponent)]); + } + + public void RemoveComponent(Entity entity) where TComponent : struct, IComponent + { + if (entities.ContainsKey(entity)) + { + entities[entity] = entities[entity].UnSet(TypeToIndex[typeof(TComponent)]); + } + } + + public void RemoveEntity(Entity entity) + { + if (entities.ContainsKey(entity)) + { + entities.Remove(entity); + } + } + + public BitSet1024 EntityBitArray(Entity entity) + { + return entities.ContainsKey(entity) ? entities[entity] : BitSet1024Builder.Zeroes(); + } + } +} diff --git a/encompass-cs/Collections/ComponentStore.cs b/encompass-cs/Collections/ComponentStore.cs index 4a95184..6e44131 100644 --- a/encompass-cs/Collections/ComponentStore.cs +++ b/encompass-cs/Collections/ComponentStore.cs @@ -1,5 +1,5 @@ -using System; -using System.Collections; +using Encompass.Collections; +using System; using System.Collections.Generic; namespace Encompass @@ -52,7 +52,7 @@ namespace Encompass return Stores.ContainsKey(type) && Stores[type].Has(entity); } - public BitArray EntityBitArray(Entity entity) + public BitSet1024 EntityBitArray(Entity entity) { return componentBitSet.EntityBitArray(entity); } diff --git a/encompass-cs/ComponentBitSet.cs b/encompass-cs/ComponentBitSet.cs deleted file mode 100644 index 3b40794..0000000 --- a/encompass-cs/ComponentBitSet.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Encompass -{ - internal class ComponentBitSet - { - BitArrayPool bitArrayPool = new BitArrayPool(32768); // todo: set entity cap - Dictionary entities = new Dictionary(); - Dictionary TypeToIndex { get; set; } - BitArray withQueryArray; - BitArray withoutQueryArray; - BitArray emptyArray; - - public ComponentBitSet(Dictionary typeToIndex) - { - TypeToIndex = typeToIndex; - } - - public void FinishRegistering() - { - withQueryArray = new BitArray(TypeToIndex.Count); - withoutQueryArray = new BitArray(TypeToIndex.Count); - emptyArray = new BitArray(TypeToIndex.Count); - foreach (var kvp in entities) - { - kvp.Value.Length = TypeToIndex.Count; - } - } - - public void Clear() - { - foreach (var kvp in entities) - { - bitArrayPool.Free(kvp.Value); - } - entities.Clear(); - } - - public void AddEntity(Entity entity) - { - var bitArray = bitArrayPool.Obtain(TypeToIndex.Count); - entities.Add(entity, bitArray); - } - - public void Set(Entity entity) where TComponent : struct, IComponent - { - if (!entities.ContainsKey(entity)) { AddEntity(entity); } - entities[entity].Set(TypeToIndex[typeof(TComponent)], true); - } - - public void RemoveComponent(Entity entity) where TComponent : struct, IComponent - { - if (entities.ContainsKey(entity)) - { - entities[entity].Set(TypeToIndex[typeof(TComponent)], false); - } - } - - public void RemoveEntity(Entity entity) - { - if (entities.ContainsKey(entity)) - { - bitArrayPool.Free(entities[entity]); - entities.Remove(entity); - } - } - - public BitArray EntityBitArray(Entity entity) - { - return entities.ContainsKey(entity) ? entities[entity] : emptyArray; - } - } -} diff --git a/encompass-cs/ComponentUpdateManager.cs b/encompass-cs/ComponentUpdateManager.cs index f8f38d5..95dae3c 100644 --- a/encompass-cs/ComponentUpdateManager.cs +++ b/encompass-cs/ComponentUpdateManager.cs @@ -1,4 +1,5 @@ -using System; +using Encompass.Collections; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -14,16 +15,6 @@ namespace Encompass private Dictionary typeToIndex; public ComponentStore UpToDateComponentStore { get; private set; } - // bitarray references to avoid garbage collection - BitArray pendingMask; - BitArray readMask; - BitArray withMask; - BitArray withoutMask; - BitArray notWithMask; - BitArray solverArrayA; - BitArray solverArrayB; - BitArray solverArrayC; - public ComponentUpdateManager(Dictionary typeToIndex) { existingAndPendingComponentStore = new ComponentStore(typeToIndex); @@ -47,15 +38,6 @@ namespace Encompass existingComponentStore.FinishRegistering(); pendingComponentStore.FinishRegistering(); UpToDateComponentStore.FinishRegistering(); - - pendingMask = new BitArray(typeToIndex.Count); - readMask = new BitArray(typeToIndex.Count); - withMask = new BitArray(typeToIndex.Count); - withoutMask = new BitArray(typeToIndex.Count); - notWithMask = new BitArray(typeToIndex.Count); - solverArrayA = new BitArray(typeToIndex.Count); - solverArrayB = new BitArray(typeToIndex.Count); - solverArrayC = new BitArray(typeToIndex.Count); } internal void Clear() @@ -216,32 +198,31 @@ namespace Encompass internal IEnumerable QueryEntities(IEnumerable entities, HashSet readTypes, HashSet readPendingTypes, IEnumerable withTypes, IEnumerable withoutTypes) { - pendingMask.SetAll(false); + var pendingMask = BitSet1024Builder.Zeroes(); foreach (var type in readPendingTypes) { - pendingMask.Set(typeToIndex[type], true); + pendingMask = pendingMask.Set(typeToIndex[type]); } - readMask.SetAll(false); + var readMask = BitSet1024Builder.Zeroes(); foreach (var type in readTypes) { - readMask.Set(typeToIndex[type], true); + readMask = readMask.Set(typeToIndex[type]); } - withMask.SetAll(false); + var withMask = BitSet1024Builder.Zeroes(); foreach (var type in withTypes) { - withMask.Set(typeToIndex[type], true); + withMask = withMask.Set(typeToIndex[type]); } - withoutMask.SetAll(false); + var withoutMask = BitSet1024Builder.Zeroes(); foreach (var type in withoutTypes) { - withoutMask.Set(typeToIndex[type], true); + withoutMask = withoutMask.Set(typeToIndex[type]); } - notWithMask.SetAll(false); - notWithMask.Or(withMask).Not(); + var notWithMask = withMask.Not(); foreach (var entity in entities) { @@ -252,24 +233,17 @@ namespace Encompass } } - internal bool VerifyTypes(BitArray pendingEntity, BitArray existingEntity, BitArray readMask, BitArray pendingMask, BitArray withMask, BitArray withoutMask, BitArray notWithMask) + internal bool VerifyTypes(BitSet1024 pendingEntity, BitSet1024 existingEntity, BitSet1024 readMask, BitSet1024 pendingMask, BitSet1024 withMask, BitSet1024 withoutMask, BitSet1024 notWithMask) { - solverArrayA.SetAll(false); - solverArrayB.SetAll(false); - solverArrayC.SetAll(false); + var pending = pendingMask.And(withMask).And(pendingEntity); + var existing = readMask.And(withMask).And(existingEntity); + var withCheck = pending.Or(existing).Or(notWithMask); - solverArrayA.Or(pendingMask).And(withMask).And(pendingEntity); - solverArrayB.Or(readMask).And(withMask).And(existingEntity); - solverArrayA.Or(solverArrayB).Or(notWithMask); + var pendingForbidden = pendingMask.And(withoutMask).And(pendingEntity).Not(); + var existingForbidden = readMask.And(withoutMask).And(existingEntity).Not(); + var withoutCheck = pendingForbidden.And(existingForbidden); - solverArrayB.SetAll(false); - solverArrayB.Or(pendingMask).And(withoutMask).And(pendingEntity).Not(); - solverArrayC.Or(readMask).And(withoutMask).And(existingEntity).Not(); - solverArrayB.And(solverArrayC); - - solverArrayA.And(solverArrayB); - - return !solverArrayA.Cast().Contains(false); + return withCheck.And(withoutCheck).AllTrue(); } } } diff --git a/test/BitSet1024Test.cs b/test/BitSet1024Test.cs new file mode 100644 index 0000000..81e1a4b --- /dev/null +++ b/test/BitSet1024Test.cs @@ -0,0 +1,62 @@ +using Encompass.Collections; +using FluentAssertions; +using NUnit.Framework; + +namespace Tests +{ + public class BitSet1024Test + { + [Test] + public void Zeroes() + { + var bitSet = BitSet1024Builder.Zeroes(); + bitSet.AllFalse().Should().BeTrue(); + } + + [Test] + public void Ones() + { + var bitSet = BitSet1024Builder.Ones(); + bitSet.AllTrue().Should().BeTrue(); + } + + [Test] + public void Set() + { + var bitSet = BitSet1024Builder.Zeroes().Set(5); + bitSet.AllFalse().Should().BeFalse(); + + bitSet = BitSet1024Builder.Zeroes().Set(278); + bitSet.AllFalse().Should().BeFalse(); + + bitSet = BitSet1024Builder.Zeroes().Set(569); + bitSet.AllFalse().Should().BeFalse(); + + bitSet = BitSet1024Builder.Zeroes().Set(1023); + bitSet.AllFalse().Should().BeFalse(); + } + + [Test] + public void UnSet() + { + var bitSet = BitSet1024Builder.Ones().UnSet(562); + bitSet.Get(562).Should().BeFalse(); + bitSet.Set(562).AllTrue().Should().BeTrue(); + } + + [Test] + public void Get() + { + var bitSet = BitSet1024Builder.Zeroes().Set(359); + bitSet.Get(359).Should().BeTrue(); + bitSet.UnSet(359).AllFalse().Should().BeTrue(); + } + + [Test] + public void Not() + { + var bitSet = BitSet1024Builder.Ones().Not(); + bitSet.AllFalse().Should().BeTrue(); + } + } +}