Skip to content

Commit

Permalink
Fix move view subscribers
Browse files Browse the repository at this point in the history
  • Loading branch information
kelindar committed Nov 8, 2024
1 parent 28d6c88 commit cc43f19
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 83 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module github.com/kelindar/tile

go 1.19
go 1.23

require (
github.com/kelindar/iostream v1.3.0
github.com/stretchr/testify v1.8.1
github.com/kelindar/iostream v1.4.0
github.com/stretchr/testify v1.9.0
)

require (
Expand Down
15 changes: 4 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kelindar/iostream v1.3.0 h1:Bz2qQabipZlF1XCk64bnxsGLete+iHtayGPeWVpbwbo=
github.com/kelindar/iostream v1.3.0/go.mod h1:MkjMuVb6zGdPQVdwLnFRO0xOTOdDvBWTztFmjRDQkXk=
github.com/kelindar/iostream v1.4.0 h1:ELKlinnM/K3GbRp9pYhWuZOyBxMMlYAfsOP+gauvZaY=
github.com/kelindar/iostream v1.4.0/go.mod h1:MkjMuVb6zGdPQVdwLnFRO0xOTOdDvBWTztFmjRDQkXk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
46 changes: 25 additions & 21 deletions grid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import (
)

/*
cpu: Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz
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
cpu: 13th Gen Intel(R) Core(TM) i7-13700K
BenchmarkGrid/each-24 1452 830268 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/neighbors-24 121583491 9.861 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/within-24 49360 24477 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/at-24 687659378 1.741 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/write-24 191272338 6.307 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/merge-24 162536985 7.332 ns/op 0 B/op 0 allocs/op
BenchmarkGrid/mask-24 158258084 7.601 ns/op 0 B/op 0 allocs/op
*/
func BenchmarkGrid(b *testing.B) {
var d Tile[uint32]
Expand Down Expand Up @@ -99,10 +100,10 @@ func BenchmarkGrid(b *testing.B) {
}

/*
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
cpu: 13th Gen Intel(R) Core(TM) i7-13700K
BenchmarkState/range-24 17017800 71.14 ns/op 0 B/op 0 allocs/op
BenchmarkState/add-24 72639224 16.32 ns/op 0 B/op 0 allocs/op
BenchmarkState/del-24 82469125 13.65 ns/op 0 B/op 0 allocs/op
*/
func BenchmarkState(b *testing.B) {
m := NewGridOf[int](768, 768)
Expand Down Expand Up @@ -155,13 +156,12 @@ func TestWithin(t *testing.T) {
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))
assert.Equal(t, 16, len(path))
assert.ElementsMatch(t, []string{
"1,1", "2,1", "1,2", "2,2", "3,1",
"4,1", "5,1", "3,2", "4,2", "5,2",
"1,3", "2,3", "1,4", "2,4", "1,5",
"2,5", "3,3", "4,3", "5,3", "3,4",
"4,4", "5,4", "3,5", "4,5", "5,5",
"1,1", "2,1", "1,2", "2,2",
"3,1", "4,1", "3,2", "4,2",
"1,3", "2,3", "1,4", "2,4",
"3,3", "4,3", "3,4", "4,4",
}, path)
}

Expand All @@ -179,18 +179,22 @@ func TestWithinCorner(t *testing.T) {
}, path)
}

func TestWithinXY(t *testing.T) {
assert.False(t, At(4, 8).WithinRect(NewRect(1, 6, 4, 10)))
}

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.Equal(t, 9, 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",
"1,6", "2,6", "3,6",
"1,7", "2,7", "3,7",
"1,8", "2,8", "3,8",
}, path)
}

Expand Down
74 changes: 65 additions & 9 deletions point.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ func (p Point) DivideScalar(s int16) Point {

// Within checks if the point is within the specified bounding box.
func (p Point) Within(nw, se Point) bool {
return p.X >= nw.X && p.Y >= nw.Y && p.X <= se.X && p.Y <= se.Y
return Rect{Min: nw, Max: se}.Contains(p)
}

// WithinRect checks if the point is within the specified bounding box.
func (p Point) WithinRect(box Rect) bool {
return p.X >= box.Min.X && p.Y >= box.Min.Y && p.X <= box.Max.X && p.Y <= box.Max.Y
return box.Contains(p)
}

// WithinSize checks if the point is within the specified bounding box
Expand Down Expand Up @@ -143,23 +143,74 @@ func NewRect(left, top, right, bottom int16) Rect {
}

// Contains returns whether a point is within the rectangle or not.
func (r *Rect) Contains(p Point) bool {
return p.X >= r.Min.X && p.Y >= r.Min.Y && p.X <= r.Max.X && p.Y <= r.Max.Y
func (a Rect) Contains(p Point) bool {
return a.Min.X <= p.X && p.X < a.Max.X && a.Min.Y <= p.Y && p.Y < a.Max.Y
}

// Intersects returns whether a rectangle intersects with another rectangle or not.
func (r *Rect) Intersects(box Rect) bool {
return !(box.Max.X < r.Min.X || box.Min.X > r.Max.X || box.Max.Y < r.Min.Y || box.Min.Y > r.Max.Y)
func (a Rect) Intersects(b Rect) bool {
return b.Min.X < a.Max.X && a.Min.X < b.Max.X && b.Min.Y < a.Max.Y && a.Min.Y < b.Max.Y
}

// Size returns the size of the rectangle
func (r *Rect) Size() Point {
func (a *Rect) Size() Point {
return Point{
X: r.Max.X - r.Min.X,
Y: r.Max.Y - r.Min.Y,
X: a.Max.X - a.Min.X,
Y: a.Max.Y - a.Min.Y,
}
}

// IsZero returns true if the rectangle is zero-value
func (a Rect) IsZero() bool {
return a.Min.X == a.Max.X && a.Min.Y == a.Max.Y
}

// Difference calculates up to four non-overlapping regions in a that are not covered by b.
// If there are fewer than four distinct regions, the remaining Rects will be zero-value.
func (a Rect) Difference(b Rect) (result [4]Rect) {
if b.Contains(a.Min) && b.Contains(a.Max) {
return // Fully covered, return zero-value result
}

// Check for non-overlapping cases
if !a.Intersects(b) {
result[0] = a // No overlap, return A as is
return
}

left := min(a.Min.X, b.Min.X)
right := max(a.Max.X, b.Max.X)
top := min(a.Min.Y, b.Min.Y)
bottom := max(a.Max.Y, b.Max.Y)

result[0].Min = Point{X: left, Y: top}
result[0].Max = Point{X: right, Y: max(a.Min.Y, b.Min.Y)}

result[1].Min = Point{X: left, Y: min(a.Max.Y, b.Max.Y)}
result[1].Max = Point{X: right, Y: bottom}

result[2].Min = Point{X: left, Y: top}
result[2].Max = Point{X: max(a.Min.X, b.Min.X), Y: bottom}

result[3].Min = Point{X: min(a.Max.X, b.Max.X), Y: top}
result[3].Max = Point{X: right, Y: bottom}

if result[0].Size().X == 0 || result[0].Size().Y == 0 {
result[0] = Rect{}
}
if result[1].Size().X == 0 || result[1].Size().Y == 0 {
result[1] = Rect{}
}
if result[2].Size().X == 0 || result[2].Size().Y == 0 {
result[2] = Rect{}
}
if result[3].Size().X == 0 || result[3].Size().Y == 0 {
result[3] = Rect{}
}

return
}

// -----------------------------------------------------------------------------

// Diretion represents a direction
Expand Down Expand Up @@ -200,3 +251,8 @@ func (v Direction) String() string {
return ""
}
}

// Vector returns a direction vector with a given scale
func (v Direction) Vector(scale int16) Point {
return Point{}.MoveBy(v, scale)
}
71 changes: 65 additions & 6 deletions point_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import (
)

/*
cpu: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
BenchmarkPoint/within-8 1000000000 0.2697 ns/op 0 B/op 0 allocs/op
BenchmarkPoint/within-rect-8 1000000000 0.2928 ns/op 0 B/op 0 allocs/op
BenchmarkPoint/interleave-8 1000000000 0.8242 ns/op 0 B/op 0 allocs/op
cpu: 13th Gen Intel(R) Core(TM) i7-13700K
BenchmarkPoint/within-24 1000000000 0.09854 ns/op 0 B/op 0 allocs/op
BenchmarkPoint/within-rect-24 1000000000 0.09966 ns/op 0 B/op 0 allocs/op
*/
func BenchmarkPoint(b *testing.B) {
p := At(10, 20)
Expand Down Expand Up @@ -50,12 +49,18 @@ func TestPoint(t *testing.T) {
assert.Equal(t, "8,18", p.Subtract(p2).String())
assert.Equal(t, "20,40", p.Multiply(p2).String())
assert.Equal(t, "5,10", p.Divide(p2).String())
assert.True(t, p.Within(At(1, 1), At(10, 20)))
assert.True(t, p.WithinRect(NewRect(1, 1, 10, 20)))
assert.True(t, p.Within(At(1, 1), At(20, 30)))
assert.True(t, p.WithinRect(NewRect(1, 1, 20, 30)))
assert.False(t, p.WithinSize(At(10, 20)))
assert.True(t, p.WithinSize(At(20, 30)))
}

func TestIntersects(t *testing.T) {
assert.True(t, NewRect(0, 0, 2, 2).Intersects(NewRect(1, 0, 3, 2)))
assert.False(t, NewRect(0, 0, 2, 2).Intersects(NewRect(2, 0, 4, 2)))
assert.False(t, NewRect(10, 10, 12, 12).Intersects(NewRect(9, 12, 11, 14)))
}

func TestDirection(t *testing.T) {
for i := 0; i < 8; i++ {
dir := Direction(i)
Expand Down Expand Up @@ -88,3 +93,57 @@ func TestMove(t *testing.T) {
assert.Equal(t, tc.out, Point{}.Move(tc.dir), tc.dir.String())
}
}

func TestContains(t *testing.T) {
tests := map[Point]bool{
{X: 0, Y: 0}: true,
{X: 1, Y: 0}: true,
{X: 0, Y: 1}: true,
{X: 1, Y: 1}: true,
{X: 2, Y: 2}: false,
{X: 3, Y: 3}: false,
{X: 1, Y: 2}: false,
{X: 2, Y: 1}: false,
}

for point, expect := range tests {
r := NewRect(0, 0, 2, 2)
assert.Equal(t, expect, r.Contains(point), point.String())
}
}

func TestDiff_Right(t *testing.T) {
a := Rect{At(0, 0), At(2, 2)}
b := Rect{At(1, 0), At(3, 2)}

diff := a.Difference(b)
assert.Equal(t, Rect{At(0, 0), At(1, 2)}, diff[2])
assert.Equal(t, Rect{At(2, 0), At(3, 2)}, diff[3])
}

func TestDiff_Left(t *testing.T) {
a := Rect{At(0, 0), At(2, 2)}
b := Rect{At(-1, 0), At(1, 2)}

diff := a.Difference(b)
assert.Equal(t, Rect{At(-1, 0), At(0, 2)}, diff[2])
assert.Equal(t, Rect{At(1, 0), At(2, 2)}, diff[3])
}

func TestDiff_Up(t *testing.T) {
a := Rect{At(0, 0), At(2, 2)}
b := Rect{At(0, -1), At(2, 1)}

diff := a.Difference(b)
assert.Equal(t, Rect{At(0, -1), At(2, 0)}, diff[0])
assert.Equal(t, Rect{At(0, 1), At(2, 2)}, diff[1])
}

func TestDiff_Down(t *testing.T) {
a := Rect{At(0, 0), At(2, 2)}
b := Rect{At(0, 1), At(2, 3)}

diff := a.Difference(b)
assert.Equal(t, Rect{At(0, 0), At(2, 1)}, diff[0])
assert.Equal(t, Rect{At(0, 2), At(2, 3)}, diff[1])
}
59 changes: 36 additions & 23 deletions view.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,52 @@ type View[T comparable] struct {
}

// Resize resizes the viewport.
func (v *View[T]) Resize(box Rect, fn func(Point, Tile[T])) {
func (v *View[T]) Resize(view Rect, fn func(Point, Tile[T])) {
owner := v.Grid // The parent map
prev := v.rect // Previous bounding box
v.rect = box // New bounding box
v.rect = view // New bounding box

// Unsubscribe from the pages which are not required anymore
if prev.Min.X >= 0 || prev.Min.Y >= 0 || prev.Max.X >= 0 || prev.Max.Y >= 0 {
owner.pagesWithin(prev.Min, prev.Max, func(page *page[T]) {
if bounds := page.Bounds(); !bounds.Intersects(box) {
for _, diff := range view.Difference(prev) {
if diff.IsZero() {
continue // Skip zero-value rectangles
}

owner.pagesWithin(diff.Min, diff.Max, func(page *page[T]) {
r := page.Bounds()
switch {

// Page is now in view
case view.Intersects(r) && !prev.Intersects(r):
if owner.observers.Subscribe(page.point, v) {
page.SetObserved(true) // Mark the page as being observed
}

// Page is no longer in view
case !view.Intersects(r) && prev.Intersects(r):
if owner.observers.Unsubscribe(page.point, v) {
page.SetObserved(false) // Mark the page as not being observed
}
}
})
}

// Subscribe to every page which we have not previously subscribed
owner.pagesWithin(box.Min, box.Max, func(page *page[T]) {
if bounds := page.Bounds(); !bounds.Intersects(prev) {
if owner.observers.Subscribe(page.point, v) {
page.SetObserved(true) // Mark the page as being observed
// Callback for each new tile in the view
if fn != nil {
page.Each(v.Grid, func(p Point, tile Tile[T]) {
if view.Contains(p) && !prev.Contains(p) {
fn(p, tile)
}
})
}
}
})
}
}

// Callback for each new tile in the view
if fn != nil {
page.Each(v.Grid, func(p Point, v Tile[T]) {
if !prev.Contains(p) && box.Contains(p) {
fn(p, v)
}
})
}
})
// MoveTo moves the viewport towards a particular direction.
func (v *View[T]) MoveTo(angle Direction, distance int16, fn func(Point, Tile[T])) {
p := angle.Vector(distance)
v.Resize(Rect{
Min: v.rect.Min.Add(p),
Max: v.rect.Max.Add(p),
}, fn)
}

// MoveBy moves the viewport towards a particular direction.
Expand Down
Loading

0 comments on commit cc43f19

Please sign in to comment.