Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp of the library with state support #6

Merged
merged 7 commits into from
Dec 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 83 additions & 69 deletions README.md

Large diffs are not rendered by default.

422 changes: 264 additions & 158 deletions grid.go

Large diffs are not rendered by default.

241 changes: 202 additions & 39 deletions grid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
package tile

import (
"fmt"
"io"
"sync"
"testing"
"unsafe"

Expand All @@ -12,24 +15,24 @@ import (

/*
cpu: Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz
BenchmarkGrid/each-8 698 1694002 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/neighbors-8 19652220 66.16 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/within-8 26200 47040 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/at-8 59190276 16.96 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/write-8 100000000 15.40 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/merge-8 52110926 23.06 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/each-8 862 1365740 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/neighbors-8 66562384 17.94 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/within-8 30012 40112 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/at-8 396362580 3.025 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/write-8 127712601 9.256 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/merge-8 125377372 9.410 ns/op 0 B/op 0 allocs/op
*/
func BenchmarkGrid(b *testing.B) {
var d [6]byte
var d Tile[uint32]
var p Point
defer assert.NotNil(b, d)
m := NewGrid(768, 768)
m := NewGridOf[uint32](768, 768)

b.Run("each", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
m.Each(func(point Point, tile Tile) {
m.Each(func(point Point, tile Tile[uint32]) {
p = point
d = tile
})
Expand All @@ -40,7 +43,7 @@ func BenchmarkGrid(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
m.Neighbors(300, 300, func(point Point, tile Tile) {
m.Neighbors(300, 300, func(point Point, tile Tile[uint32]) {
p = point
d = tile
})
Expand All @@ -51,7 +54,7 @@ func BenchmarkGrid(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
m.Within(At(100, 100), At(200, 200), func(point Point, tile Tile) {
m.Within(At(100, 100), At(200, 200), func(point Point, tile Tile[uint32]) {
p = point
d = tile
})
Expand All @@ -71,28 +74,85 @@ func BenchmarkGrid(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
m.WriteAt(100, 100, Tile{})
m.WriteAt(100, 100, Value(0))
}
})

b.Run("merge", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
m.MergeAt(100, 100, Tile{}, Tile{0, 1, 0, 0, 0})
m.MergeAt(100, 100, func(v Value) Value {
v += 1
return v
})
}
})

b.Run("mask", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
m.MaskAt(100, 100, Value(0), Value(1))
}
})
}

/*
cpu: Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz
BenchmarkState/range-8 11211600 103.4 ns/op 0 B/op 0 allocs/op
BenchmarkState/add-8 41380593 29.00 ns/op 0 B/op 0 allocs/op
BenchmarkState/del-8 54474884 21.79 ns/op 0 B/op 0 allocs/op
*/
func BenchmarkState(b *testing.B) {
m := NewGridOf[int](768, 768)
m.Each(func(p Point, c Tile[int]) {
for i := 0; i < 10; i++ {
c.Add(i)
}
})

b.Run("range", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
cursor, _ := m.At(100, 100)
cursor.Range(func(v int) error {
return nil
})
}
})

b.Run("add", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
cursor, _ := m.At(100, 100)
cursor.Add(100)
}
})

b.Run("del", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
cursor, _ := m.At(100, 100)
cursor.Del(100)
}
})
}

func TestPageSize(t *testing.T) {
assert.LessOrEqual(t, int(unsafe.Sizeof(page{})), 64)
assert.Equal(t, 8, int(unsafe.Sizeof(map[uintptr]Point{})))
assert.Equal(t, 64, int(unsafe.Sizeof(page[string]{})))
assert.Equal(t, 36, int(unsafe.Sizeof([9]Value{})))
}

func TestWithin(t *testing.T) {
m := NewGrid(9, 9)

var path []string
m.Within(At(1, 1), At(5, 5), func(p Point, tile Tile) {
m.Within(At(1, 1), At(5, 5), func(p Point, tile Tile[string]) {
path = append(path, p.String())
})
assert.Equal(t, 25, len(path))
Expand All @@ -109,7 +169,7 @@ func TestWithinCorner(t *testing.T) {
m := NewGrid(9, 9)

var path []string
m.Within(At(7, 6), At(10, 10), func(p Point, tile Tile) {
m.Within(At(7, 6), At(10, 10), func(p Point, tile Tile[string]) {
path = append(path, p.String())
})
assert.Equal(t, 6, len(path))
Expand All @@ -119,10 +179,25 @@ func TestWithinCorner(t *testing.T) {
}, path)
}

func TestWithinOneSide(t *testing.T) {
m := NewGrid(9, 9)

var path []string
m.Within(At(1, 6), At(4, 10), func(p Point, tile Tile[string]) {
path = append(path, p.String())
})
assert.Equal(t, 12, len(path))
assert.ElementsMatch(t, []string{
"1,6", "2,6", "3,6", "4,6",
"1,7", "2,7", "3,7", "4,7",
"1,8", "2,8", "3,8", "4,8",
}, path)
}

func TestWithinInvalid(t *testing.T) {
m := NewGrid(9, 9)
count := 0
m.Within(At(10, 10), At(20, 20), func(p Point, tile Tile) {
m.Within(At(10, 10), At(20, 20), func(p Point, tile Tile[string]) {
count++
})
assert.Equal(t, 0, count)
Expand All @@ -132,7 +207,7 @@ func TestEach(t *testing.T) {
m := NewGrid(9, 9)

var path []string
m.Each(func(p Point, tile Tile) {
m.Each(func(p Point, tile Tile[string]) {
path = append(path, p.String())
})
assert.Equal(t, 81, len(path))
Expand Down Expand Up @@ -163,16 +238,16 @@ func TestNeighbors(t *testing.T) {

// Create a 9x9 map with labeled tiles
m := NewGrid(9, 9)
m.Each(func(p Point, tile Tile) {
copy(tile[:], p.String()[:3])
m.WriteAt(p.X, p.Y, tile)
m.Each(func(p Point, tile Tile[string]) {
m.WriteAt(p.X, p.Y, Value(p.Integer()))
})

// Run all the tests
for _, tc := range tests {
var out []string
m.Neighbors(tc.x, tc.y, func(_ Point, tile Tile) {
out = append(out, string(tile[:3]))
m.Neighbors(tc.x, tc.y, func(_ Point, tile Tile[string]) {
loc := unpackPoint(uint32(tile.Value()))
out = append(out, loc.String())
})
assert.ElementsMatch(t, tc.expect, out)
}
Expand All @@ -182,22 +257,21 @@ func TestAt(t *testing.T) {

// Create a 9x9 map with labeled tiles
m := NewGrid(9, 9)
m.Each(func(p Point, tile Tile) {
copy(tile[:], p.String()[:3])
m.WriteAt(p.X, p.Y, tile)
m.Each(func(p Point, tile Tile[string]) {
m.WriteAt(p.X, p.Y, Value(p.Integer()))
})

// Make sure our At() and the position matches
m.Each(func(p Point, tile Tile) {
m.Each(func(p Point, tile Tile[string]) {
at, _ := m.At(p.X, p.Y)
assert.Equal(t, p.String(), string(at[:3]))
assert.Equal(t, p.String(), unpackPoint(uint32(at.Value())).String())
})

// Make sure that points match
for y := int16(0); y < 9; y++ {
for x := int16(0); x < 9; x++ {
at, _ := m.At(x, y)
assert.Equal(t, At(x, y).String(), string(at[:3]))
assert.Equal(t, At(x, y).String(), unpackPoint(uint32(at.Value())).String())
}
}
}
Expand All @@ -207,22 +281,111 @@ func TestUpdate(t *testing.T) {
// Create a 9x9 map with labeled tiles
m := NewGrid(9, 9)
i := 0
m.Each(func(p Point, tile Tile) {
m.Each(func(p Point, _ Tile[string]) {
i++
tile[0] = byte(i)
m.WriteAt(p.X, p.Y, tile)
m.WriteAt(p.X, p.Y, Value(i))
})

// Assert the update
tile, _ := m.At(8, 8)
assert.Equal(t, 81, int(tile[0]))
cursor, _ := m.At(8, 8)
assert.Equal(t, 81, int(cursor.Value()))

// 81 = 0b01010001
tile[0] = 0b00101110 // change last 2 bits and should ignore other bits
m.MergeAt(8, 8, tile, Tile{
0b00000011, 0, 0, 0, 0, 0,
delta := Value(0b00101110) // change last 2 bits and should ignore other bits
m.MaskAt(8, 8, delta, Value(0b00000011))

// original: 0101 0001
// delta: 0010 1110
// mask: 0000 0011
// result: 0101 0010
cursor, _ = m.At(8, 8)
assert.Equal(t, 0b01010010, int(cursor.Value()))
}

func TestState(t *testing.T) {
m := NewGrid(9, 9)
m.Each(func(p Point, c Tile[string]) {
c.Add(p.String())
c.Add(p.String()) // duplicate
})

m.Each(func(p Point, c Tile[string]) {
assert.Equal(t, 1, c.Count())
assert.NoError(t, c.Range(func(s string) error {
assert.Equal(t, p.String(), s)
return nil
}))

c.Del(p.String())
assert.Equal(t, 0, c.Count())
})
}

func TestStateRangeErr(t *testing.T) {
m := NewGrid(9, 9)
m.Each(func(p Point, c Tile[string]) {
c.Add(p.String())
})

m.Each(func(p Point, c Tile[string]) {
assert.Error(t, c.Range(func(s string) error {
return io.EOF
}))
})
}

func TestPointOf(t *testing.T) {
truthTable := func(x, y int16, idx uint8) (int16, int16) {
switch idx {
case 0:
return x, y
case 1:
return x + 1, y
case 2:
return x + 2, y
case 3:
return x, y + 1
case 4:
return x + 1, y + 1
case 5:
return x + 2, y + 1
case 6:
return x, y + 2
case 7:
return x + 1, y + 2
case 8:
return x + 2, y + 2
default:
return x, y
}
}

for i := 0; i < 9; i++ {
at := pointOf(At(0, 0), uint8(i))
x, y := truthTable(0, 0, uint8(i))
assert.Equal(t, x, at.X, fmt.Sprintf("idx=%v", i))
assert.Equal(t, y, at.Y, fmt.Sprintf("idx=%v", i))
}
}

func TestConcurrentMerge(t *testing.T) {
const count = 10000
var wg sync.WaitGroup
wg.Add(count)

m := NewGrid(9, 9)
for i := 0; i < count; i++ {
go func() {
m.MergeAt(1, 1, func(v Value) Value {
v += 1
return v
})
wg.Done()
}()
}

tile, _ = m.At(8, 8)
assert.Equal(t, 0b01010010, int(tile[0]))
wg.Wait()
tile, ok := m.At(1, 1)
assert.True(t, ok)
assert.Equal(t, uint32(count), tile.Value())
}
Loading