From 6544052bb7ad9dabdc0a827a9910830e26e40f70 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Fri, 14 Jun 2024 19:56:00 +0200 Subject: [PATCH] tried more variants: writepixel+pointer+int, which is not much better --- various-tests/writepixel-pointers-int/go.mod | 13 + various-tests/writepixel-pointers-int/go.sum | 14 + various-tests/writepixel-pointers-int/main.go | 306 ++++++++++++++++++ various-tests/writepixel-pointers/go.mod | 13 + various-tests/writepixel-pointers/go.sum | 14 + various-tests/writepixel-pointers/main.go | 306 ++++++++++++++++++ various-tests/writepixel/main.go | 32 +- 7 files changed, 668 insertions(+), 30 deletions(-) create mode 100644 various-tests/writepixel-pointers-int/go.mod create mode 100644 various-tests/writepixel-pointers-int/go.sum create mode 100644 various-tests/writepixel-pointers-int/main.go create mode 100644 various-tests/writepixel-pointers/go.mod create mode 100644 various-tests/writepixel-pointers/go.sum create mode 100644 various-tests/writepixel-pointers/main.go diff --git a/various-tests/writepixel-pointers-int/go.mod b/various-tests/writepixel-pointers-int/go.mod new file mode 100644 index 0000000..cde79f5 --- /dev/null +++ b/various-tests/writepixel-pointers-int/go.mod @@ -0,0 +1,13 @@ +module testgol + +go 1.22 + +require ( + github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect + github.com/ebitengine/hideconsole v1.0.0 // indirect + github.com/ebitengine/purego v0.7.0 // indirect + github.com/hajimehoshi/ebiten/v2 v2.7.4 // indirect + github.com/jezek/xgb v1.1.1 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect +) diff --git a/various-tests/writepixel-pointers-int/go.sum b/various-tests/writepixel-pointers-int/go.sum new file mode 100644 index 0000000..d3c38a0 --- /dev/null +++ b/various-tests/writepixel-pointers-int/go.sum @@ -0,0 +1,14 @@ +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU= +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M= +github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= +github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= +github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc= +github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8= +github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY= +github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= +github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/various-tests/writepixel-pointers-int/main.go b/various-tests/writepixel-pointers-int/main.go new file mode 100644 index 0000000..5ea5e23 --- /dev/null +++ b/various-tests/writepixel-pointers-int/main.go @@ -0,0 +1,306 @@ +package main + +import ( + "fmt" + "log" + "math/rand" + "os" + "os/exec" + "runtime/pprof" + "unsafe" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" +) + +type Images struct { + Black, White *ebiten.Image +} + +type Cell struct { + State uint8 + Neighbors [8]*Cell + NeighborCount int +} + +func bool2int(b bool) int { + return int(*(*byte)(unsafe.Pointer(&b))) +} + +func (cell *Cell) Count(x, y int) uint8 { + var sum uint8 + + for idx := 0; idx < cell.NeighborCount; idx++ { + sum += cell.Neighbors[idx].State + } + + return sum +} + +func SetNeighbors(grid [][]*Cell, x, y, width, height int) { + idx := 0 + for nbgX := -1; nbgX < 2; nbgX++ { + for nbgY := -1; nbgY < 2; nbgY++ { + var col, row int + + if x+nbgX < 0 || x+nbgX >= width || y+nbgY < 0 || y+nbgY >= height { + continue + } + + col = x + nbgX + row = y + nbgY + + if col == x && row == y { + continue + } + + grid[y][x].Neighbors[idx] = grid[row][col] + grid[y][x].NeighborCount++ + idx++ + } + } +} + +type Grid struct { + Data [][]*Cell + Width, Height, Density int +} + +// Create new empty grid and allocate Data according to provided dimensions +func NewGrid(width, height, density int) *Grid { + grid := &Grid{ + Height: height, + Width: width, + Density: density, + Data: make([][]*Cell, height), + } + + for y := 0; y < height; y++ { + grid.Data[y] = make([]*Cell, width) + for x := 0; x < width; x++ { + grid.Data[y][x] = &Cell{} + + if rand.Intn(density) == 1 { + grid.Data[y][x].State = 1 + } + } + } + + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + SetNeighbors(grid.Data, x, y, width, height) + } + } + + return grid +} + +type Game struct { + Width, Height, Cellsize, Density int + ScreenWidth, ScreenHeight int + Grids []*Grid + Index int + Elapsed int64 + TPG int64 // adjust game speed independently of TPS + Pause, Debug, Profile, Gridlines bool + Pixels []byte + OffScreen *ebiten.Image +} + +func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) { + return game.ScreenWidth, game.ScreenHeight +} + +// live console output of the grid +func (game *Game) DebugDump() { + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + cmd.Run() + + if game.Debug { + for y := 0; y < game.Height; y++ { + for x := 0; x < game.Width; x++ { + if game.Grids[game.Index].Data[y][x].State == 1 { + fmt.Print("XX") + } else { + fmt.Print(" ") + } + } + fmt.Println() + } + } + fmt.Printf("FPS: %0.2f\n", ebiten.ActualTPS()) +} + +func (game *Game) Init() { + // setup two grids, one for display, one for next state + grida := NewGrid(game.Width, game.Height, game.Density) + gridb := NewGrid(game.Width, game.Height, game.Density) + + game.Grids = []*Grid{ + grida, + gridb, + } + + game.Pixels = make([]byte, game.ScreenWidth*game.ScreenHeight*4) + + game.OffScreen = ebiten.NewImage(game.ScreenWidth, game.ScreenHeight) +} + +// count the living neighbors of a cell +func (game *Game) CountNeighbors(x, y int) uint8 { + return game.Grids[game.Index].Data[y][x].Count(x, y) +} + +// the heart of the game +func (game *Game) CheckRule(state uint8, neighbors uint8) uint8 { + var nextstate uint8 + + if state == 1 && neighbors == 3 { + nextstate = 1 + } else if state == 1 && (neighbors == 2 || neighbors == 3) { + nextstate = 1 + } else { + nextstate = 0 + } + + return nextstate +} + +// we only update the cells if we are not in pause state or if the +// game timer (TPG) is elapsed. +func (game *Game) UpdateCells() { + if game.Pause { + return + } + + if game.Elapsed < game.TPG { + game.Elapsed++ + return + } + + // next grid index. we only have to, so we just xor it + next := game.Index ^ 1 + + // calculate cell life state, this is the actual game of life + for y := 0; y < game.Height; y++ { + for x := 0; x < game.Width; x++ { + state := game.Grids[game.Index].Data[y][x].State + neighbors := game.CountNeighbors(x, y) + + // actually apply the current rules + nextstate := game.CheckRule(state, neighbors) + + // change state of current cell in next grid + game.Grids[next].Data[y][x].State = nextstate + } + } + + // switch grid for rendering + game.Index ^= 1 + + game.Elapsed = 0 + + game.UpdatePixels() +} + +func (game *Game) Update() error { + game.UpdateCells() + + if inpututil.IsKeyJustPressed(ebiten.KeySpace) { + game.Pause = !game.Pause + } + + return nil +} + +/* +* +r, g, b := color(it) + + 78 p := 4 * (i + j*screenWidth) + 79 gm.offscreenPix[p] = r + 80 gm.offscreenPix[p+1] = g + 81 gm.offscreenPix[p+2] = b + 82 gm.offscreenPix[p+3] = 0xff +*/ +func (game *Game) UpdatePixels() { + var col byte + + gridx := 0 + gridy := 0 + idx := 0 + + for y := 0; y < game.ScreenHeight; y++ { + for x := 0; x < game.ScreenWidth; x++ { + gridx = x / game.Cellsize + gridy = y / game.Cellsize + + col = 0xff + if game.Grids[game.Index].Data[gridy][gridx].State == 1 { + col = 0x0 + } + + if game.Gridlines { + if x%game.Cellsize == 0 || y%game.Cellsize == 0 { + col = 128 + } + } + + idx = 4 * (x + y*game.ScreenWidth) + + game.Pixels[idx] = col + game.Pixels[idx+1] = col + game.Pixels[idx+2] = col + game.Pixels[idx+3] = 0xff + + idx++ + } + } + + game.OffScreen.WritePixels(game.Pixels) +} + +func (game *Game) Draw(screen *ebiten.Image) { + screen.DrawImage(game.OffScreen, nil) + game.DebugDump() +} + +func main() { + size := 1500 + + game := &Game{ + Width: size, + Height: size, + Cellsize: 4, + Density: 8, + TPG: 10, + Debug: false, + Profile: true, + Gridlines: false, + } + + game.ScreenWidth = game.Width * game.Cellsize + game.ScreenHeight = game.Height * game.Cellsize + + game.Init() + + ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight) + ebiten.SetWindowTitle("triangle conway's game of life") + ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled) + + if game.Profile { + fd, err := os.Create("cpu.profile") + if err != nil { + log.Fatal(err) + } + defer fd.Close() + + pprof.StartCPUProfile(fd) + defer pprof.StopCPUProfile() + } + + if err := ebiten.RunGame(game); err != nil { + log.Fatal(err) + } +} diff --git a/various-tests/writepixel-pointers/go.mod b/various-tests/writepixel-pointers/go.mod new file mode 100644 index 0000000..cde79f5 --- /dev/null +++ b/various-tests/writepixel-pointers/go.mod @@ -0,0 +1,13 @@ +module testgol + +go 1.22 + +require ( + github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect + github.com/ebitengine/hideconsole v1.0.0 // indirect + github.com/ebitengine/purego v0.7.0 // indirect + github.com/hajimehoshi/ebiten/v2 v2.7.4 // indirect + github.com/jezek/xgb v1.1.1 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect +) diff --git a/various-tests/writepixel-pointers/go.sum b/various-tests/writepixel-pointers/go.sum new file mode 100644 index 0000000..d3c38a0 --- /dev/null +++ b/various-tests/writepixel-pointers/go.sum @@ -0,0 +1,14 @@ +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU= +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M= +github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= +github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= +github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc= +github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8= +github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY= +github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= +github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/various-tests/writepixel-pointers/main.go b/various-tests/writepixel-pointers/main.go new file mode 100644 index 0000000..20ed058 --- /dev/null +++ b/various-tests/writepixel-pointers/main.go @@ -0,0 +1,306 @@ +package main + +import ( + "fmt" + "log" + "math/rand" + "os" + "os/exec" + "runtime/pprof" + "unsafe" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" +) + +type Images struct { + Black, White *ebiten.Image +} + +type Cell struct { + State bool + Neighbors [8]*Cell + NeighborCount int +} + +func bool2int(b bool) int { + return int(*(*byte)(unsafe.Pointer(&b))) +} + +func (cell *Cell) Count(x, y int) int { + sum := 0 + + for idx := 0; idx < cell.NeighborCount; idx++ { + sum += bool2int(cell.Neighbors[idx].State) + } + + return sum +} + +func SetNeighbors(grid [][]*Cell, x, y, width, height int) { + idx := 0 + for nbgX := -1; nbgX < 2; nbgX++ { + for nbgY := -1; nbgY < 2; nbgY++ { + var col, row int + + if x+nbgX < 0 || x+nbgX >= width || y+nbgY < 0 || y+nbgY >= height { + continue + } + + col = x + nbgX + row = y + nbgY + + if col == x && row == y { + continue + } + + grid[y][x].Neighbors[idx] = grid[row][col] + grid[y][x].NeighborCount++ + idx++ + } + } +} + +type Grid struct { + Data [][]*Cell + Width, Height, Density int +} + +// Create new empty grid and allocate Data according to provided dimensions +func NewGrid(width, height, density int) *Grid { + grid := &Grid{ + Height: height, + Width: width, + Density: density, + Data: make([][]*Cell, height), + } + + for y := 0; y < height; y++ { + grid.Data[y] = make([]*Cell, width) + for x := 0; x < width; x++ { + grid.Data[y][x] = &Cell{} + + if rand.Intn(density) == 1 { + grid.Data[y][x].State = true + } + } + } + + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + SetNeighbors(grid.Data, x, y, width, height) + } + } + + return grid +} + +type Game struct { + Width, Height, Cellsize, Density int + ScreenWidth, ScreenHeight int + Grids []*Grid + Index int + Elapsed int64 + TPG int64 // adjust game speed independently of TPS + Pause, Debug, Profile, Gridlines bool + Pixels []byte + OffScreen *ebiten.Image +} + +func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) { + return game.ScreenWidth, game.ScreenHeight +} + +// live console output of the grid +func (game *Game) DebugDump() { + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + cmd.Run() + + if game.Debug { + for y := 0; y < game.Height; y++ { + for x := 0; x < game.Width; x++ { + if game.Grids[game.Index].Data[y][x].State { + fmt.Print("XX") + } else { + fmt.Print(" ") + } + } + fmt.Println() + } + } + fmt.Printf("FPS: %0.2f\n", ebiten.ActualTPS()) +} + +func (game *Game) Init() { + // setup two grids, one for display, one for next state + grida := NewGrid(game.Width, game.Height, game.Density) + gridb := NewGrid(game.Width, game.Height, game.Density) + + game.Grids = []*Grid{ + grida, + gridb, + } + + game.Pixels = make([]byte, game.ScreenWidth*game.ScreenHeight*4) + + game.OffScreen = ebiten.NewImage(game.ScreenWidth, game.ScreenHeight) +} + +// count the living neighbors of a cell +func (game *Game) CountNeighbors(x, y int) int { + return game.Grids[game.Index].Data[y][x].Count(x, y) +} + +// the heart of the game +func (game *Game) CheckRule(state bool, neighbors int) bool { + var nextstate bool + + if state && neighbors == 3 { + nextstate = true + } else if state && (neighbors == 2 || neighbors == 3) { + nextstate = true + } else { + nextstate = false + } + + return nextstate +} + +// we only update the cells if we are not in pause state or if the +// game timer (TPG) is elapsed. +func (game *Game) UpdateCells() { + if game.Pause { + return + } + + if game.Elapsed < game.TPG { + game.Elapsed++ + return + } + + // next grid index. we only have to, so we just xor it + next := game.Index ^ 1 + + // calculate cell life state, this is the actual game of life + for y := 0; y < game.Height; y++ { + for x := 0; x < game.Width; x++ { + state := game.Grids[game.Index].Data[y][x].State + neighbors := game.CountNeighbors(x, y) + + // actually apply the current rules + nextstate := game.CheckRule(state, neighbors) + + // change state of current cell in next grid + game.Grids[next].Data[y][x].State = nextstate + } + } + + // switch grid for rendering + game.Index ^= 1 + + game.Elapsed = 0 + + game.UpdatePixels() +} + +func (game *Game) Update() error { + game.UpdateCells() + + if inpututil.IsKeyJustPressed(ebiten.KeySpace) { + game.Pause = !game.Pause + } + + return nil +} + +/* +* +r, g, b := color(it) + + 78 p := 4 * (i + j*screenWidth) + 79 gm.offscreenPix[p] = r + 80 gm.offscreenPix[p+1] = g + 81 gm.offscreenPix[p+2] = b + 82 gm.offscreenPix[p+3] = 0xff +*/ +func (game *Game) UpdatePixels() { + var col byte + + gridx := 0 + gridy := 0 + idx := 0 + + for y := 0; y < game.ScreenHeight; y++ { + for x := 0; x < game.ScreenWidth; x++ { + gridx = x / game.Cellsize + gridy = y / game.Cellsize + + col = 0xff + if game.Grids[game.Index].Data[gridy][gridx].State { + col = 0x0 + } + + if game.Gridlines { + if x%game.Cellsize == 0 || y%game.Cellsize == 0 { + col = 128 + } + } + + idx = 4 * (x + y*game.ScreenWidth) + + game.Pixels[idx] = col + game.Pixels[idx+1] = col + game.Pixels[idx+2] = col + game.Pixels[idx+3] = 0xff + + idx++ + } + } + + game.OffScreen.WritePixels(game.Pixels) +} + +func (game *Game) Draw(screen *ebiten.Image) { + screen.DrawImage(game.OffScreen, nil) + game.DebugDump() +} + +func main() { + size := 1500 + + game := &Game{ + Width: size, + Height: size, + Cellsize: 4, + Density: 8, + TPG: 10, + Debug: false, + Profile: true, + Gridlines: false, + } + + game.ScreenWidth = game.Width * game.Cellsize + game.ScreenHeight = game.Height * game.Cellsize + + game.Init() + + ebiten.SetWindowSize(game.ScreenWidth, game.ScreenHeight) + ebiten.SetWindowTitle("triangle conway's game of life") + ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled) + + if game.Profile { + fd, err := os.Create("cpu.profile") + if err != nil { + log.Fatal(err) + } + defer fd.Close() + + pprof.StartCPUProfile(fd) + defer pprof.StopCPUProfile() + } + + if err := ebiten.RunGame(game); err != nil { + log.Fatal(err) + } +} diff --git a/various-tests/writepixel/main.go b/various-tests/writepixel/main.go index dabe5b7..0c61d9f 100644 --- a/various-tests/writepixel/main.go +++ b/various-tests/writepixel/main.go @@ -238,35 +238,7 @@ func (game *Game) Draw(screen *ebiten.Image) { } func main() { - //x := 1 - //y := 0 - col := 1 >> 0xff - - fmt.Printf("col: %d\n", col) - - x := 1 - y := 2 - c := 4 - - xm := x & (c - 1) - ym := y & (c - 1) - - fmt.Println(xm & ym) - - a := 1 - b := 1 - //gen := 100 - hist := 0 - for gen := 0; gen < 50; gen++ { - fmt.Println((a ^ (1 ^ b)) * (gen - hist)) - if gen == 25 { - a = 0 - } - } -} - -func _main() { - size := 800 + size := 1500 game := &Game{ Width: size, @@ -275,7 +247,7 @@ func _main() { Density: 8, TPG: 10, Debug: false, - Profile: false, + Profile: true, Gridlines: false, }