Skip to content

Commit

Permalink
filters match against pointer to mask, to avoid copying
Browse files Browse the repository at this point in the history
  • Loading branch information
mlange-42 committed Jan 9, 2024
1 parent f665e06 commit 7fa6024
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 78 deletions.
2 changes: 1 addition & 1 deletion ecs/archetype_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func newArchNode(mask Mask, data *nodeData, relation int8, capacityIncrement int
// Matches the archetype node against a filter.
// Ignores the relation target.
func (a *archNode) Matches(f Filter) bool {
return f.Matches(a.Mask)
return f.Matches(&a.Mask)
}

// Archetypes of the node.
Expand Down
28 changes: 12 additions & 16 deletions ecs/bitmask.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ func All(ids ...ID) Mask {
}

// Matches the mask as filter against another mask.
func (b Mask) Matches(bits Mask) bool {
return bits.Contains(b)
func (b Mask) Matches(bits *Mask) bool {
return bits.Contains(&b)
}

// Without creates a [MaskFilter] which filters for including the mask's components,
Expand Down Expand Up @@ -95,23 +95,19 @@ func (b *Mask) Reset() {
}

// Contains reports if the other mask is a subset of this mask.
func (b *Mask) Contains(other Mask) bool {
for i := range b.Bits {
if b.Bits[i]&other.Bits[i] != other.Bits[i] {
return false
}
}
return true
func (b *Mask) Contains(other *Mask) bool {
return b.Bits[0]&other.Bits[0] == other.Bits[0] &&
b.Bits[1]&other.Bits[1] == other.Bits[1] &&
b.Bits[2]&other.Bits[2] == other.Bits[2] &&
b.Bits[3]&other.Bits[3] == other.Bits[3]
}

// ContainsAny reports if any bit of the other mask is in this mask.
func (b *Mask) ContainsAny(other Mask) bool {
for i := range b.Bits {
if b.Bits[i]&other.Bits[i] != 0 {
return true
}
}
return false
func (b *Mask) ContainsAny(other *Mask) bool {
return b.Bits[0]&other.Bits[0] != 0 ||
b.Bits[1]&other.Bits[1] != 0 ||
b.Bits[2]&other.Bits[2] != 0 ||
b.Bits[3]&other.Bits[3] != 0
}

// TotalBitsSet returns how many bits are set in this mask.
Expand Down
63 changes: 35 additions & 28 deletions ecs/bitmask_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import (
"github.com/stretchr/testify/assert"
)

func all(ids ...ecs.ID) *ecs.Mask {
mask := ecs.All(ids...)
return &mask
}

func TestBitMask(t *testing.T) {
mask := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27), ecs.ID(200))

Expand All @@ -33,8 +38,8 @@ func TestBitMask(t *testing.T) {
other1 := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(32))
other2 := ecs.All(ecs.ID(0), ecs.ID(2))

assert.False(t, mask.Contains(other1))
assert.True(t, mask.Contains(other2))
assert.False(t, mask.Contains(&other1))
assert.True(t, mask.Contains(&other2))

mask.Reset()
assert.Equal(t, 0, mask.TotalBitsSet())
Expand All @@ -43,30 +48,31 @@ func TestBitMask(t *testing.T) {
other1 = ecs.All(ecs.ID(1), ecs.ID(32))
other2 = ecs.All(ecs.ID(0), ecs.ID(32))

assert.True(t, mask.ContainsAny(other1))
assert.False(t, mask.ContainsAny(other2))
assert.True(t, mask.ContainsAny(&other1))
assert.False(t, mask.ContainsAny(&other2))
}

func TestBitMaskWithoutExclusive(t *testing.T) {
mask := ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))
assert.True(t, mask.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
assert.True(t, mask.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))

assert.False(t, mask.Matches(ecs.All(ecs.ID(1), ecs.ID(2))))
assert.True(t, mask.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
assert.True(t, mask.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))

assert.False(t, mask.Matches(all(ecs.ID(1), ecs.ID(2))))

without := mask.Without(ecs.ID(3))

assert.True(t, without.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
assert.True(t, without.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
assert.True(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
assert.True(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))

assert.False(t, without.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
assert.False(t, without.Matches(ecs.All(ecs.ID(1), ecs.ID(2))))
assert.False(t, without.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
assert.False(t, without.Matches(all(ecs.ID(1), ecs.ID(2))))

excl := mask.Exclusive()

assert.True(t, excl.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
assert.False(t, excl.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
assert.False(t, excl.Matches(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
assert.True(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13))))
assert.False(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27))))
assert.False(t, excl.Matches(all(ecs.ID(1), ecs.ID(2), ecs.ID(3), ecs.ID(13))))
}

func TestBitMask256(t *testing.T) {
Expand All @@ -86,11 +92,11 @@ func TestBitMask256(t *testing.T) {

mask = ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(13), ecs.ID(27), ecs.ID(63), ecs.ID(64), ecs.ID(65))

assert.True(t, mask.Contains(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(64))))
assert.False(t, mask.Contains(ecs.All(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(90))))
assert.True(t, mask.Contains(all(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(64))))
assert.False(t, mask.Contains(all(ecs.ID(1), ecs.ID(2), ecs.ID(63), ecs.ID(90))))

assert.True(t, mask.ContainsAny(ecs.All(ecs.ID(6), ecs.ID(65), ecs.ID(111))))
assert.False(t, mask.ContainsAny(ecs.All(ecs.ID(6), ecs.ID(66), ecs.ID(90))))
assert.True(t, mask.ContainsAny(all(ecs.ID(6), ecs.ID(65), ecs.ID(111))))
assert.False(t, mask.ContainsAny(all(ecs.ID(6), ecs.ID(66), ecs.ID(90))))
}

func TestBitMask64(t *testing.T) {
Expand Down Expand Up @@ -124,7 +130,7 @@ func BenchmarkBitmask64Get(b *testing.B) {
_ = v
}

func BenchmarkBitmask128Get(b *testing.B) {
func BenchmarkBitmask256Get(b *testing.B) {
b.StopTimer()
mask := ecs.All()
for i := 0; i < ecs.MaskTotalBits; i++ {
Expand Down Expand Up @@ -158,7 +164,7 @@ func BenchmarkBitmaskContains(b *testing.B) {

var v bool
for i := 0; i < b.N; i++ {
v = mask.Contains(filter)
v = mask.Contains(&filter)
}

b.StopTimer()
Expand All @@ -179,7 +185,7 @@ func BenchmarkBitmaskContainsAny(b *testing.B) {

var v bool
for i := 0; i < b.N; i++ {
v = mask.ContainsAny(filter)
v = mask.ContainsAny(&filter)
}

b.StopTimer()
Expand All @@ -189,12 +195,12 @@ func BenchmarkBitmaskContainsAny(b *testing.B) {

func BenchmarkMaskFilter(b *testing.B) {
b.StopTimer()
mask := ecs.All(0, 1, 2).Without()
mask := ecs.All(0, 1, 2).Without(3)
bits := ecs.All(0, 1, 2)
b.StartTimer()
var v bool
for i := 0; i < b.N; i++ {
v = mask.Matches(bits)
v = mask.Matches(&bits)
}
b.StopTimer()
v = !v
Expand All @@ -203,7 +209,7 @@ func BenchmarkMaskFilter(b *testing.B) {

func BenchmarkMaskFilterNoPointer(b *testing.B) {
b.StopTimer()
mask := maskFilterPointer{ecs.All(0, 1, 2), ecs.All()}
mask := maskFilterPointer{ecs.All(0, 1, 2), ecs.All(3)}
bits := ecs.All(0, 1, 2)
b.StartTimer()
var v bool
Expand Down Expand Up @@ -236,7 +242,7 @@ func BenchmarkMask(b *testing.B) {
b.StartTimer()
var v bool
for i := 0; i < b.N; i++ {
v = mask.Matches(bits)
v = mask.Matches(&bits)
}
b.StopTimer()
v = !v
Expand Down Expand Up @@ -273,15 +279,16 @@ type maskFilterPointer struct {

// Matches a filter against a mask.
func (f maskFilterPointer) Matches(bits ecs.Mask) bool {
return bits.Contains(f.Mask) &&
(f.Exclude.IsZero() || !bits.ContainsAny(f.Exclude))
return bits.Contains(&f.Mask) &&
(f.Exclude.IsZero() || !bits.ContainsAny(&f.Exclude))
}

type maskPointer ecs.Mask

// Matches a filter against a mask.
func (f *maskPointer) Matches(bits ecs.Mask) bool {
return bits.Contains(ecs.Mask(*f))
m := ecs.Mask(*f)
return bits.Contains(&m)
}

func ExampleMask() {
Expand Down
6 changes: 3 additions & 3 deletions ecs/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (c *Cache) addArchetype(arch *archetype) {
if !arch.HasRelation() {
for i := range c.filters {
e := &c.filters[i]
if !e.Filter.Matches(arch.Mask) {
if !e.Filter.Matches(&arch.Mask) {
continue
}
e.Archetypes.Add(arch)
Expand All @@ -108,7 +108,7 @@ func (c *Cache) addArchetype(arch *archetype) {

for i := range c.filters {
e := &c.filters[i]
if !e.Filter.Matches(arch.Mask) {
if !e.Filter.Matches(&arch.Mask) {
continue
}
if rf, ok := e.Filter.(*RelationFilter); ok {
Expand All @@ -135,7 +135,7 @@ func (c *Cache) removeArchetype(arch *archetype) {
for i := range c.filters {
e := &c.filters[i]

if e.Indices == nil && e.Filter.Matches(arch.Mask) {
if e.Indices == nil && e.Filter.Matches(&arch.Mask) {
c.mapArchetypes(e)
}

Expand Down
10 changes: 5 additions & 5 deletions ecs/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package ecs
// For advanced filtering, see package [github.com/mlange-42/arche/filter].
type Filter interface {
// Matches the filter against a mask, i.e. a component composition.
Matches(bits Mask) bool
Matches(bits *Mask) bool
}

// MaskFilter is a [Filter] for including and excluding certain components.
Expand All @@ -20,8 +20,8 @@ type MaskFilter struct {
}

// Matches the filter against a mask.
func (f *MaskFilter) Matches(bits Mask) bool {
return bits.Contains(f.Include) && (f.Exclude.IsZero() || !bits.ContainsAny(f.Exclude))
func (f *MaskFilter) Matches(bits *Mask) bool {
return bits.Contains(&f.Include) && (f.Exclude.IsZero() || !bits.ContainsAny(&f.Exclude))
}

// RelationFilter is a [Filter] for a [Relation] target, in addition to components.
Expand All @@ -42,7 +42,7 @@ func NewRelationFilter(filter Filter, target Entity) RelationFilter {
}

// Matches the filter against a mask.
func (f *RelationFilter) Matches(bits Mask) bool {
func (f *RelationFilter) Matches(bits *Mask) bool {
return f.Filter.Matches(bits)
}

Expand All @@ -56,6 +56,6 @@ type CachedFilter struct {
}

// Matches the filter against a mask.
func (f *CachedFilter) Matches(bits Mask) bool {
func (f *CachedFilter) Matches(bits *Mask) bool {
return f.filter.Matches(bits)
}
12 changes: 6 additions & 6 deletions ecs/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import (
func TestCachedMaskFilter(t *testing.T) {
f := ecs.All(1, 2, 3).Without(4)

assert.True(t, f.Matches(ecs.All(1, 2, 3)))
assert.True(t, f.Matches(ecs.All(1, 2, 3, 5)))
assert.True(t, f.Matches(all(1, 2, 3)))
assert.True(t, f.Matches(all(1, 2, 3, 5)))

assert.False(t, f.Matches(ecs.All(1, 2)))
assert.False(t, f.Matches(ecs.All(1, 2, 3, 4)))
assert.False(t, f.Matches(all(1, 2)))
assert.False(t, f.Matches(all(1, 2, 3, 4)))
}

func TestCachedFilter(t *testing.T) {
Expand All @@ -23,8 +23,8 @@ func TestCachedFilter(t *testing.T) {
f := ecs.All(1, 2, 3)
fc := w.Cache().Register(f)

assert.Equal(t, f.Matches(ecs.All(1, 2, 3)), fc.Matches(ecs.All(1, 2, 3)))
assert.Equal(t, f.Matches(ecs.All(1, 2)), fc.Matches(ecs.All(1, 2)))
assert.Equal(t, f.Matches(all(1, 2, 3)), fc.Matches(all(1, 2, 3)))
assert.Equal(t, f.Matches(all(1, 2)), fc.Matches(all(1, 2)))

w.Cache().Unregister(&fc)
}
Expand Down
9 changes: 5 additions & 4 deletions ecs/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ func TestMask(t *testing.T) {
filter := All(0, 2, 4)
other := All(0, 1, 2)

assert.False(t, filter.Matches(other))
assert.False(t, filter.Matches(&other))

other = All(0, 1, 2, 3, 4)
assert.True(t, filter.Matches(other))
assert.True(t, filter.Matches(&other))
}

func TestQuery(t *testing.T) {
Expand Down Expand Up @@ -260,7 +260,7 @@ func TestQueryCount(t *testing.T) {

type testFilter struct{}

func (f testFilter) Matches(bits Mask) bool {
func (f testFilter) Matches(bits *Mask) bool {
return true
}

Expand All @@ -282,7 +282,8 @@ func TestQueryInterface(t *testing.T) {
w.Add(e3, posID, rotID)
w.Add(e4, rotID)

q := w.Query(testFilter{})
filter := testFilter{}
q := w.Query(&filter)

cnt := 0
for q.Next() {
Expand Down
Loading

0 comments on commit 7fa6024

Please sign in to comment.