Skip to content

Commit

Permalink
Revamp of the library with state support (#6)
Browse files Browse the repository at this point in the history
* Changed iterator

* generic state

* add state notification

* tests

* faster store

* final refactoring

* update readme
  • Loading branch information
kelindar committed Dec 17, 2022
1 parent 545865a commit 28d6c88
Show file tree
Hide file tree
Showing 11 changed files with 727 additions and 518 deletions.
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

0 comments on commit 28d6c88

Please sign in to comment.