From 121d37c509c11756bbb5139b6ff01fab8db871d4 Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Sat, 29 Jul 2023 23:54:41 +0200 Subject: [PATCH 1/6] bump golangci/golangci-lint-action to v3.6.0 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e973dd11..39bbfec9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: run: GOOS=js GOARCH=wasm go test -v ./... - name: Lint - uses: golangci/golangci-lint-action@v3.4.0 + uses: golangci/golangci-lint-action@v3.6.0 with: args: "-v" version: v1.52.1 From 5691a52e002a4d3ed123785280268e0d3bd52b7f Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Sat, 29 Jul 2023 23:55:19 +0200 Subject: [PATCH 2/6] bump golangci-lint to v1.54.1 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39bbfec9..26bdd0da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,7 +60,7 @@ jobs: uses: golangci/golangci-lint-action@v3.6.0 with: args: "-v" - version: v1.52.1 + version: v1.54.1 - name: Generate coverage report run: go test -v -coverprofile=coverage.txt -covermode=atomic ./... From e8b8127cb642cf7fc5f57f18947de3eca0ee6d4a Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Sat, 29 Jul 2023 23:41:32 +0200 Subject: [PATCH 3/6] Require Go 1.21 as minimal version of Go --- .github/workflows/build.yml | 2 +- README.md | 2 +- go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26bdd0da..36921b69 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - go: [ "1.20" ] + go: [ 1.21 ] env: DISPLAY: ':99.0' steps: diff --git a/README.md b/README.md index 284e201d..12be78f5 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Pi is under development. Only limited functionality is provided. API is not stab ## How to get started? 1. Install dependencies - * [Go 1.20+](https://go.dev/dl/) + * [Go 1.21+](https://go.dev/dl/) * If not on Windows, please install additional dependencies for [Linux](docs/install-linux.md) or [macOS](docs/install-macos.md). 2. Try examples from [examples](examples) directory. 3. Create a new game using provided [Github template](https://github.com/elgopher/pi-template). diff --git a/go.mod b/go.mod index 3bd7df1f..30fb379c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/elgopher/pi -go 1.20 +go 1.21 require ( github.com/hajimehoshi/ebiten/v2 v2.5.6 From 09a50976350c8274079e15af1cec056edb742f1f Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Sat, 29 Jul 2023 23:48:30 +0200 Subject: [PATCH 4/6] Remove pi.MaxInt and pi.MinInt Because Go 1.21 supports generic max and min functions which work for integers. --- .../internal/lib/github_com-elgopher-pi.go | 2 -- docs/ROADMAP.md | 2 +- examples/shapes/main.go | 2 +- internal/bench/math_bench_test.go | 28 ------------------- internal/fuzz/math_test.go | 12 -------- math.go | 18 ------------ math_test.go | 18 ------------ pixmap.go | 12 ++++---- 8 files changed, 8 insertions(+), 86 deletions(-) diff --git a/devtools/internal/lib/github_com-elgopher-pi.go b/devtools/internal/lib/github_com-elgopher-pi.go index 16b336ec..54d17e89 100644 --- a/devtools/internal/lib/github_com-elgopher-pi.go +++ b/devtools/internal/lib/github_com-elgopher-pi.go @@ -39,10 +39,8 @@ func init() { "Left": reflect.ValueOf(pi.Left), "Line": reflect.ValueOf(pi.Line), "Load": reflect.ValueOf(pi.Load), - "MaxInt": reflect.ValueOf(pi.MaxInt[int]), // TODO Generic functions not supported by Yaegi yet "Mid": reflect.ValueOf(pi.Mid), "MidInt": reflect.ValueOf(pi.MidInt[int]), // TODO Generic functions not supported by Yaegi yet - "MinInt": reflect.ValueOf(pi.MinInt[int]), // TODO Generic functions not supported by Yaegi yet "MouseBtn": reflect.ValueOf(pi.MouseBtn), "MouseBtnDuration": reflect.ValueOf(&pi.MouseBtnDuration).Elem(), "MouseBtnp": reflect.ValueOf(pi.MouseBtnp), diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index e6dd083c..d0380607 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -21,7 +21,7 @@ * [ ] map API * [ ] math API * [x] Cos, Sin, Atan2 - * [x] Min, Max, Mid for integers + * [x] Mid for integers * [x] Mid for float64 * [x] Game controller support: gamepad and keyboard * [x] Mouse support diff --git a/examples/shapes/main.go b/examples/shapes/main.go index 8a0588ef..f348d400 100644 --- a/examples/shapes/main.go +++ b/examples/shapes/main.go @@ -100,7 +100,7 @@ func drawMousePointer() { func radius(x0, y0, x1, y1 int) int { dx := math.Abs(float64(x0 - x1)) dy := math.Abs(float64(y0 - y1)) - return int(math.Max(dx, dy)) + return int(max(dx, dy)) } func printCmd(command string) { diff --git a/internal/bench/math_bench_test.go b/internal/bench/math_bench_test.go index 84c036cc..d04d5719 100644 --- a/internal/bench/math_bench_test.go +++ b/internal/bench/math_bench_test.go @@ -9,34 +9,6 @@ import ( "github.com/elgopher/pi" ) -func BenchmarkMinInt(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - for j := 0; j < 60; j++ { - pi.MinInt(j, j+1) - } - for j := 0; j < 60; j++ { - pi.MinInt(j+1, j) - } - } -} - -func BenchmarkMaxInt(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - for j := 0; j < 60; j++ { - pi.MaxInt(j, j+1) - } - for j := 0; j < 60; j++ { - pi.MaxInt(j+1, j) - } - } -} - func BenchmarkMidInt(b *testing.B) { b.ReportAllocs() b.ResetTimer() diff --git a/internal/fuzz/math_test.go b/internal/fuzz/math_test.go index b221336f..715ba603 100644 --- a/internal/fuzz/math_test.go +++ b/internal/fuzz/math_test.go @@ -11,18 +11,6 @@ import ( "github.com/elgopher/pi" ) -func FuzzMinInt(f *testing.F) { - f.Fuzz(func(t *testing.T, x, y int) { - pi.MinInt(x, y) - }) -} - -func FuzzMaxInt(f *testing.F) { - f.Fuzz(func(t *testing.T, x, y int) { - pi.MaxInt(x, y) - }) -} - func FuzzMidInt(f *testing.F) { f.Fuzz(func(t *testing.T, x, y, z int) { pi.MidInt(x, y, z) diff --git a/math.go b/math.go index e1d2c70d..965edce1 100644 --- a/math.go +++ b/math.go @@ -41,24 +41,6 @@ type Int interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } -// MinInt returns minimum of two integer numbers. -func MinInt[T Int](x, y T) T { - if x < y { - return x - } - - return y -} - -// MaxInt returns maximum of two integer numbers. -func MaxInt[T Int](x, y T) T { - if x > y { - return x - } - - return y -} - // MidInt returns the middle of three integer numbers. Very useful for clamping. func MidInt[T Int](x, y, z T) T { if x > y { diff --git a/math_test.go b/math_test.go index 8b9acdc4..942b7250 100644 --- a/math_test.go +++ b/math_test.go @@ -114,24 +114,6 @@ func TestAtan2(t *testing.T) { } } -func TestMinInt(t *testing.T) { - assert.Equal(t, 0, pi.MinInt(0, 0)) - assert.Equal(t, 1, pi.MinInt(1, 2)) - assert.Equal(t, 1, pi.MinInt(1, 1)) - assert.Equal(t, 1, pi.MinInt(2, 1)) - assert.Equal(t, -2, pi.MinInt(-1, -2)) - assert.Equal(t, -2, pi.MinInt(-2, 2)) -} - -func TestMaxInt(t *testing.T) { - assert.Equal(t, 0, pi.MaxInt(0, 0)) - assert.Equal(t, 2, pi.MaxInt(2, 1)) - assert.Equal(t, 1, pi.MaxInt(1, 1)) - assert.Equal(t, 2, pi.MaxInt(1, 2)) - assert.Equal(t, -1, pi.MaxInt(-1, -2)) - assert.Equal(t, 2, pi.MaxInt(-2, 2)) -} - func TestMidInt(t *testing.T) { assert.Equal(t, 0, pi.MidInt(0, 0, 0)) assert.Equal(t, 1, pi.MidInt(0, 1, 2)) diff --git a/pixmap.go b/pixmap.go index 6ba8f469..f192137a 100644 --- a/pixmap.go +++ b/pixmap.go @@ -212,8 +212,8 @@ func (p PixMap) Pointer(x, y, w, h int) (ptr Pointer, ok bool) { DeltaX: dx, DeltaY: dy, Pix: pix, - RemainingPixels: MinInt(w, clip.X+clip.W-x), - RemainingLines: MinInt(h, clip.Y+clip.H-y), + RemainingPixels: min(w, clip.X+clip.W-x), + RemainingLines: min(h, clip.Y+clip.H-y), }, true } @@ -230,13 +230,13 @@ type Pointer struct { func (p PixMap) Copy(x, y, w, h int, dst PixMap, dstX, dstY int) { dstPtr, srcPtr := p.pointersForCopy(x, y, w, h, dst, dstX, dstY) - remainingLines := MinInt(dstPtr.RemainingLines, srcPtr.RemainingLines) + remainingLines := min(dstPtr.RemainingLines, srcPtr.RemainingLines) if remainingLines == 0 { return } - remainingPixels := MinInt(dstPtr.RemainingPixels, srcPtr.RemainingPixels) + remainingPixels := min(dstPtr.RemainingPixels, srcPtr.RemainingPixels) copy(dstPtr.Pix[:remainingPixels], srcPtr.Pix) for i := 1; i < remainingLines; i++ { @@ -250,13 +250,13 @@ func (p PixMap) Copy(x, y, w, h int, dst PixMap, dstX, dstY int) { func (p PixMap) Merge(x, y, w, h int, dst PixMap, dstX, dstY int, merge func(dst, src []byte)) { dstPtr, srcPtr := p.pointersForCopy(x, y, w, h, dst, dstX, dstY) - remainingLines := MinInt(dstPtr.RemainingLines, srcPtr.RemainingLines) + remainingLines := min(dstPtr.RemainingLines, srcPtr.RemainingLines) if remainingLines == 0 { return } - remainingPixels := MinInt(dstPtr.RemainingPixels, srcPtr.RemainingPixels) + remainingPixels := min(dstPtr.RemainingPixels, srcPtr.RemainingPixels) merge(dstPtr.Pix[:remainingPixels], srcPtr.Pix) for i := 1; i < remainingLines; i++ { From c475d6449ea8a0b8911a5dd1faa1e39f786eed79 Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Sun, 30 Jul 2023 09:03:24 +0200 Subject: [PATCH 5/6] Use Go's 1.21 clear function to clear pixels with 0 --- pixmap.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pixmap.go b/pixmap.go index f192137a..aa9cda61 100644 --- a/pixmap.go +++ b/pixmap.go @@ -23,7 +23,6 @@ type PixMap struct { height int clip Region - zeroPix []byte wholeLinePix []byte } @@ -44,7 +43,6 @@ func NewPixMap(width, height int) PixMap { width: width, height: height, clip: Region{W: width, H: height}, - zeroPix: make([]byte, len(pixels)), wholeLinePix: make([]byte, width), } } @@ -84,7 +82,6 @@ func NewPixMapWithPixels(pixels []byte, lineWidth int) PixMap { width: lineWidth, height: height, clip: Region{W: lineWidth, H: height}, - zeroPix: make([]byte, len(pixels)), wholeLinePix: make([]byte, lineWidth), } } @@ -147,7 +144,7 @@ func (p PixMap) WithClip(x, y, w, h int) PixMap { // Clear clears the entire PixMap with color 0. It does not take into account the clipping region. func (p PixMap) Clear() { - copy(p.pix, p.zeroPix) + clear(p.pix) } // ClearCol clears the entire PixMap with specified color. It does not take into account the clipping region. From 5e5c6e44beaef1d6efa8e0607a530cb49065e773 Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Sun, 30 Jul 2023 11:26:20 +0200 Subject: [PATCH 6/6] Replace Mid and MidInt with one generic function Mid This new function accepts integers, floats and even strings --- .../internal/lib/github_com-elgopher-pi.go | 12 +---- docs/ROADMAP.md | 3 +- internal/bench/math_bench_test.go | 12 ++--- internal/fuzz/math_test.go | 2 +- math.go | 46 ++++--------------- math_test.go | 40 ++++++++-------- 6 files changed, 37 insertions(+), 78 deletions(-) diff --git a/devtools/internal/lib/github_com-elgopher-pi.go b/devtools/internal/lib/github_com-elgopher-pi.go index 54d17e89..fc7bd14b 100644 --- a/devtools/internal/lib/github_com-elgopher-pi.go +++ b/devtools/internal/lib/github_com-elgopher-pi.go @@ -39,8 +39,7 @@ func init() { "Left": reflect.ValueOf(pi.Left), "Line": reflect.ValueOf(pi.Line), "Load": reflect.ValueOf(pi.Load), - "Mid": reflect.ValueOf(pi.Mid), - "MidInt": reflect.ValueOf(pi.MidInt[int]), // TODO Generic functions not supported by Yaegi yet + "Mid": reflect.ValueOf(pi.Mid[float64]), // TODO Generic functions not supported by Yaegi yet "MouseBtn": reflect.ValueOf(pi.MouseBtn), "MouseBtnDuration": reflect.ValueOf(&pi.MouseBtnDuration).Elem(), "MouseBtnp": reflect.ValueOf(pi.MouseBtnp), @@ -85,7 +84,6 @@ func init() { "Button": reflect.ValueOf((*pi.Button)(nil)), "Controller": reflect.ValueOf((*pi.Controller)(nil)), "Font": reflect.ValueOf((*pi.Font)(nil)), - //"Int": reflect.ValueOf((*pi.Int)(nil)), // TODO Generic constraints not supported by Yaegi yet "MouseButton": reflect.ValueOf((*pi.MouseButton)(nil)), "PixMap": reflect.ValueOf((*pi.PixMap)(nil)), "Pointer": reflect.ValueOf((*pi.Pointer)(nil)), @@ -93,13 +91,5 @@ func init() { "Position": reflect.ValueOf((*pi.Position)(nil)), "Region": reflect.ValueOf((*pi.Region)(nil)), "Transparency": reflect.ValueOf((*pi.Transparency)(nil)), - - // interface wrapper definitions - "_Int": reflect.ValueOf((*_github_com_elgopher_pi_Int)(nil)), } } - -// _github_com_elgopher_pi_Int is an interface wrapper for Int type -type _github_com_elgopher_pi_Int struct { - IValue interface{} -} diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d0380607..0aa3de29 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -21,8 +21,7 @@ * [ ] map API * [ ] math API * [x] Cos, Sin, Atan2 - * [x] Mid for integers - * [x] Mid for float64 + * [x] Generic Mid for float64 and integers * [x] Game controller support: gamepad and keyboard * [x] Mouse support * [ ] Add mouse wheel support diff --git a/internal/bench/math_bench_test.go b/internal/bench/math_bench_test.go index d04d5719..b0ae7068 100644 --- a/internal/bench/math_bench_test.go +++ b/internal/bench/math_bench_test.go @@ -15,12 +15,12 @@ func BenchmarkMidInt(b *testing.B) { for i := 0; i < b.N; i++ { for j := 0; j < 20; j++ { - pi.MidInt(j, j+1, j+2) // y - pi.MidInt(j+2, j+1, j) // y - pi.MidInt(j+1, j, j+2) // x - pi.MidInt(j+1, j+2, j) // x - pi.MidInt(j, j+2, j+1) // z - pi.MidInt(j+2, j, j+1) // z + pi.Mid(j, j+1, j+2) // y + pi.Mid(j+2, j+1, j) // y + pi.Mid(j+1, j, j+2) // x + pi.Mid(j+1, j+2, j) // x + pi.Mid(j, j+2, j+1) // z + pi.Mid(j+2, j, j+1) // z } } } diff --git a/internal/fuzz/math_test.go b/internal/fuzz/math_test.go index 715ba603..8ff0e4b4 100644 --- a/internal/fuzz/math_test.go +++ b/internal/fuzz/math_test.go @@ -13,7 +13,7 @@ import ( func FuzzMidInt(f *testing.F) { f.Fuzz(func(t *testing.T, x, y, z int) { - pi.MidInt(x, y, z) + pi.Mid(x, y, z) }) } diff --git a/math.go b/math.go index 965edce1..415efd08 100644 --- a/math.go +++ b/math.go @@ -3,7 +3,10 @@ package pi -import "math" +import ( + "cmp" + "math" +) // Sin returns the sine of angle which is in the range of 0.0-1.0 measured clockwise. // @@ -36,42 +39,11 @@ func Atan2(dx, dy float64) float64 { return math.Mod(0.75+v/(math.Pi*2), 1) } -// Int is a generic type for all integer types -type Int interface { - ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr -} - -// MidInt returns the middle of three integer numbers. Very useful for clamping. -func MidInt[T Int](x, y, z T) T { - if x > y { - x, y = y, x - } - - if y > z { - y = z - } - - if x > y { - y = x - } - - return y -} - -// Mid returns the middle of three float64 numbers. Very useful for clamping. -// NaNs are always put at the beginning (are the smallest ones). -func Mid(x, y, z float64) float64 { - if x > y || math.IsNaN(y) { - x, y = y, x - } - - if y > z || math.IsNaN(z) { - y = z - } - - if x > y || math.IsNaN(y) { - y = x - } +// Mid returns the middle of three ordered values (numbers or strings). Very useful for clamping. +func Mid[T cmp.Ordered](x, y, z T) T { + x, y = min(x, y), max(x, y) + y = min(y, z) + y = max(x, y) return y } diff --git a/math_test.go b/math_test.go index 942b7250..50f40fec 100644 --- a/math_test.go +++ b/math_test.go @@ -114,33 +114,31 @@ func TestAtan2(t *testing.T) { } } -func TestMidInt(t *testing.T) { - assert.Equal(t, 0, pi.MidInt(0, 0, 0)) - assert.Equal(t, 1, pi.MidInt(0, 1, 2)) - assert.Equal(t, 1, pi.MidInt(2, 1, 0)) - assert.Equal(t, 1, pi.MidInt(1, 0, 2)) - assert.Equal(t, 1, pi.MidInt(1, 2, 0)) - assert.Equal(t, 1, pi.MidInt(2, 0, 1)) - assert.Equal(t, 1, pi.MidInt(0, 2, 1)) - assert.Equal(t, -1, pi.MidInt(0, -1, -2)) -} - func TestMid(t *testing.T) { - assert.Equal(t, 0.0, pi.Mid(0, 0, 0)) - assert.Equal(t, 1.0, pi.Mid(0, 1, 2)) - assert.Equal(t, 1.0, pi.Mid(2, 1, 0)) - assert.Equal(t, 1.0, pi.Mid(1, 0, 2)) - assert.Equal(t, 1.0, pi.Mid(1, 2, 0)) - assert.Equal(t, 1.0, pi.Mid(2, 0, 1)) - assert.Equal(t, 1.0, pi.Mid(0, 2, 1)) - assert.Equal(t, -1.0, pi.Mid(0, -1, -2)) + assert.Equal(t, 0, pi.Mid(0, 0, 0)) + assert.Equal(t, 1, pi.Mid(0, 1, 2)) + assert.Equal(t, 1, pi.Mid(2, 1, 0)) + assert.Equal(t, 1, pi.Mid(1, 0, 2)) + assert.Equal(t, 1, pi.Mid(1, 2, 0)) + assert.Equal(t, 1, pi.Mid(2, 0, 1)) + assert.Equal(t, 1, pi.Mid(0, 2, 1)) + assert.Equal(t, -1, pi.Mid(0, -1, -2)) + + assert.Equal(t, 0.0, pi.Mid(0.0, 0.0, 0.0)) + assert.Equal(t, 1.0, pi.Mid(0.0, 1.0, 2.0)) + assert.Equal(t, 1.0, pi.Mid(2.0, 1.0, 0.0)) + assert.Equal(t, 1.0, pi.Mid(1.0, 0.0, 2.0)) + assert.Equal(t, 1.0, pi.Mid(1.0, 2.0, 0.0)) + assert.Equal(t, 1.0, pi.Mid(2.0, 0.0, 1.0)) + assert.Equal(t, 1.0, pi.Mid(0.0, 2.0, 1.0)) + assert.Equal(t, -1.0, pi.Mid(0.0, -1.0, -2.0)) assertNaN(t, pi.Mid(math.NaN(), math.NaN(), math.NaN())) assertInf(t, pi.Mid(math.Inf(1), math.Inf(1), math.Inf(1)), 1) - assert.Equal(t, 1.0, pi.Mid(1.0, math.NaN(), 2.0)) // NaNs always go to the beginning + assertNaN(t, pi.Mid(1.0, math.NaN(), 2.0)) assertNaN(t, pi.Mid(math.NaN(), math.NaN(), 1.0)) assertNaN(t, pi.Mid(1.0, math.NaN(), math.NaN())) - assert.Equal(t, 1.0, pi.Mid(1.0, 2.0, math.NaN())) + assertNaN(t, pi.Mid(1.0, 2.0, math.NaN())) assert.Equal(t, 1.0, pi.Mid(math.Inf(1), math.Inf(-1), 1.0)) }