mirror of
https://codeberg.org/scip/golsky.git
synced 2025-12-16 20:20:57 +01:00
add profiling support and window geom options to force geometry
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
golsky
|
golsky
|
||||||
bak
|
bak
|
||||||
dump*
|
dump*
|
||||||
|
rect*
|
||||||
|
*profile
|
||||||
|
|||||||
160
config.go
160
config.go
@@ -1,9 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
"runtime/pprof"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"github.com/tlinden/golsky/rle"
|
"github.com/tlinden/golsky/rle"
|
||||||
@@ -22,42 +25,136 @@ type Config struct {
|
|||||||
StateGrid *Grid // a grid from a statefile
|
StateGrid *Grid // a grid from a statefile
|
||||||
Wrap bool // wether wraparound mode is in place or not
|
Wrap bool // wether wraparound mode is in place or not
|
||||||
ShowVersion bool
|
ShowVersion bool
|
||||||
|
|
||||||
|
// for internal profiling
|
||||||
|
ProfileFile string
|
||||||
|
ProfileDraw bool
|
||||||
|
ProfileMaxLoops int64
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VERSION = "v0.0.6"
|
VERSION = "v0.0.7"
|
||||||
Alive = 1
|
Alive = 1
|
||||||
Dead = 0
|
Dead = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetRLE(filename string) *rle.RLE {
|
// parse given window geometry and adjust game settings according to it
|
||||||
|
func (config *Config) ParseGeom(geom string) error {
|
||||||
|
if geom == "" {
|
||||||
|
config.ScreenWidth = config.Cellsize * config.Width
|
||||||
|
config.ScreenHeight = config.Cellsize * config.Height
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// force a geom
|
||||||
|
geometry := strings.Split(geom, "x")
|
||||||
|
if len(geometry) != 2 {
|
||||||
|
return errors.New("failed to parse -g parameters, expecting WIDTHxHEIGHT")
|
||||||
|
}
|
||||||
|
|
||||||
|
width, err := strconv.Atoi(geometry[0])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to parse width, expecting integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
height, err := strconv.Atoi(geometry[1])
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to parse height, expecting integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust dimensions, account for grid width+height so that cells
|
||||||
|
// fit into window
|
||||||
|
config.ScreenWidth = width - (width % config.Width)
|
||||||
|
config.ScreenHeight = height - (height % config.Height)
|
||||||
|
config.Cellsize = config.ScreenWidth / config.Width
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we have been given an RLE file to load, then load it and
|
||||||
|
// adjust game settings accordingly
|
||||||
|
func (config *Config) ParseRLE(rlefile string) error {
|
||||||
|
if rlefile == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rleobj, err := rle.GetRLE(rlefile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rleobj == nil {
|
||||||
|
return errors.New("failed to load RLE file (uncatched module error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
config.RLE = rleobj
|
||||||
|
|
||||||
|
// adjust geometry if needed
|
||||||
|
if config.RLE.Width > config.Width || config.RLE.Height > config.Height {
|
||||||
|
config.Width = config.RLE.Width * 2
|
||||||
|
config.Height = config.RLE.Height * 2
|
||||||
|
config.Cellsize = config.ScreenWidth / config.Width
|
||||||
|
}
|
||||||
|
|
||||||
|
// RLE needs an empty grid
|
||||||
|
config.Empty = true
|
||||||
|
|
||||||
|
// it may come with its own rule
|
||||||
|
if config.RLE.Rule != "" {
|
||||||
|
config.Rule = ParseGameRule(config.RLE.Rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse a state file, if given, and adjust game settings accordingly
|
||||||
|
func (config *Config) ParseStatefile(statefile string) error {
|
||||||
|
if config.Statefile == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
grid, err := LoadState(config.Statefile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load game state: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Width = grid.Width
|
||||||
|
config.Height = grid.Height
|
||||||
|
config.Cellsize = config.ScreenWidth / config.Width
|
||||||
|
config.StateGrid = grid
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *Config) EnableCPUProfiling(filename string) error {
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := os.ReadFile(filename)
|
fd, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedRle, err := rle.Parse(string(content))
|
pprof.StartCPUProfile(fd)
|
||||||
if err != nil {
|
defer pprof.StopCPUProfile()
|
||||||
log.Fatalf("failed to load RLE pattern file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &parsedRle
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseCommandline() *Config {
|
func ParseCommandline() (*Config, error) {
|
||||||
config := Config{}
|
config := Config{}
|
||||||
|
|
||||||
var rule string
|
var (
|
||||||
var rlefile string
|
rule, rlefile, geom string
|
||||||
|
)
|
||||||
|
|
||||||
// commandline params, most configure directly config flags
|
// commandline params, most configure directly config flags
|
||||||
pflag.IntVarP(&config.Width, "width", "W", 40, "grid width in cells")
|
pflag.IntVarP(&config.Width, "width", "W", 40, "grid width in cells")
|
||||||
pflag.IntVarP(&config.Height, "height", "H", 40, "grid height in cells")
|
pflag.IntVarP(&config.Height, "height", "H", 40, "grid height in cells")
|
||||||
pflag.IntVarP(&config.Cellsize, "cellsize", "c", 8, "cell size in pixels")
|
pflag.IntVarP(&config.Cellsize, "cellsize", "c", 8, "cell size in pixels")
|
||||||
|
pflag.StringVarP(&geom, "geom", "g", "", "window geometry in WxH in pixels, overturns -c")
|
||||||
|
|
||||||
pflag.IntVarP(&config.Density, "density", "D", 10, "density of random cells")
|
pflag.IntVarP(&config.Density, "density", "D", 10, "density of random cells")
|
||||||
pflag.IntVarP(&config.TPG, "ticks-per-generation", "t", 10,
|
pflag.IntVarP(&config.TPG, "ticks-per-generation", "t", 10,
|
||||||
"game speed: the higher the slower (default: 10)")
|
"game speed: the higher the slower (default: 10)")
|
||||||
@@ -75,44 +172,27 @@ func ParseCommandline() *Config {
|
|||||||
pflag.BoolVarP(&config.ShowEvolution, "show-evolution", "s", false, "show evolution tracks")
|
pflag.BoolVarP(&config.ShowEvolution, "show-evolution", "s", false, "show evolution tracks")
|
||||||
pflag.BoolVarP(&config.Wrap, "wrap-around", "w", false, "wrap around grid mode")
|
pflag.BoolVarP(&config.Wrap, "wrap-around", "w", false, "wrap around grid mode")
|
||||||
|
|
||||||
|
pflag.StringVarP(&config.ProfileFile, "profile-file", "", "", "enable profiling")
|
||||||
|
pflag.BoolVarP(&config.ProfileDraw, "profile-draw", "", false, "profile draw method (default false)")
|
||||||
|
pflag.Int64VarP(&config.ProfileMaxLoops, "profile-max-loops", "", 10, "how many loops to execute (default 10)")
|
||||||
|
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
// check if we have been given an RLE file to load
|
err := config.ParseGeom(geom)
|
||||||
config.RLE = GetRLE(rlefile)
|
|
||||||
if config.RLE != nil {
|
|
||||||
if config.RLE.Width > config.Width || config.RLE.Height > config.Height {
|
|
||||||
config.Width = config.RLE.Width * 2
|
|
||||||
config.Height = config.RLE.Height * 2
|
|
||||||
fmt.Printf("rlew: %d, rleh: %d, w: %d, h: %d\n",
|
|
||||||
config.RLE.Width, config.RLE.Height, config.Width, config.Height)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RLE needs an empty grid
|
|
||||||
config.Empty = true
|
|
||||||
|
|
||||||
// it may come with its own rule
|
|
||||||
if config.RLE.Rule != "" {
|
|
||||||
config.Rule = ParseGameRule(config.RLE.Rule)
|
|
||||||
}
|
|
||||||
} else if config.Statefile != "" {
|
|
||||||
grid, err := LoadState(config.Statefile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to load game state: %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Width = grid.Width
|
err = config.ParseRLE(rlefile)
|
||||||
config.Height = grid.Height
|
if err != nil {
|
||||||
config.StateGrid = grid
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.ScreenWidth = config.Cellsize * config.Width
|
|
||||||
config.ScreenHeight = config.Cellsize * config.Height
|
|
||||||
|
|
||||||
// load rule from commandline when no rule came from RLE file,
|
// load rule from commandline when no rule came from RLE file,
|
||||||
// default is B3/S23, aka conways game of life
|
// default is B3/S23, aka conways game of life
|
||||||
if config.Rule == nil {
|
if config.Rule == nil {
|
||||||
config.Rule = ParseGameRule(rule)
|
config.Rule = ParseGameRule(rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &config
|
return &config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
70
main.go
70
main.go
@@ -4,35 +4,77 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime/pprof"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "net/http/pprof"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config := ParseCommandline()
|
config, err := ParseCommandline()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
if config.ShowVersion {
|
if config.ShowVersion {
|
||||||
fmt.Printf("This is golsky version %s\n", VERSION)
|
fmt.Printf("This is golsky version %s\n", VERSION)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// grid := [][]int64{
|
|
||||||
// {0, 1, 1},
|
|
||||||
// {0, 1, 0},
|
|
||||||
// {1, 1, 0},
|
|
||||||
// }
|
|
||||||
|
|
||||||
// err := rle.StoreGridToRLE(grid, "test.rle", "B3/S23", 3, 3)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// os.Exit(0)
|
|
||||||
|
|
||||||
game := NewGame(config, Play)
|
game := NewGame(config, Play)
|
||||||
|
|
||||||
|
if config.ProfileFile != "" {
|
||||||
|
// enable cpu profiling and use fake game loop
|
||||||
|
fd, err := os.Create(config.ProfileFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
pprof.StartCPUProfile(fd)
|
||||||
|
defer pprof.StopCPUProfile()
|
||||||
|
|
||||||
|
Ebitfake(game)
|
||||||
|
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
fd.Close()
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
// main loop
|
// main loop
|
||||||
if err := ebiten.RunGame(game); err != nil {
|
if err := ebiten.RunGame(game); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fake game loop, required to be able to profile the program using
|
||||||
|
// pprof. Otherwise any kind of program exit leads to an empty profile
|
||||||
|
// file.
|
||||||
|
func Ebitfake(game *Game) {
|
||||||
|
screen := ebiten.NewImage(game.ScreenWidth, game.ScreenHeight)
|
||||||
|
|
||||||
|
var loops int64
|
||||||
|
|
||||||
|
for {
|
||||||
|
err := game.Update()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if game.Config.ProfileDraw {
|
||||||
|
game.Draw(screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(".")
|
||||||
|
time.Sleep(16 * time.Millisecond) // around 60 TPS
|
||||||
|
|
||||||
|
if loops >= game.Config.ProfileMaxLoops {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
loops++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
19
rle/rle.go
19
rle/rle.go
@@ -20,6 +20,25 @@ type RLE struct {
|
|||||||
patternLineIndex int
|
patternLineIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wrapper to load a RLE file
|
||||||
|
func GetRLE(filename string) (*RLE, error) {
|
||||||
|
if filename == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedRle, err := Parse(string(content))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load RLE pattern file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &parsedRle, nil
|
||||||
|
}
|
||||||
|
|
||||||
func Parse(input string) (RLE, error) {
|
func Parse(input string) (RLE, error) {
|
||||||
rle := RLE{
|
rle := RLE{
|
||||||
inputLines: strings.Split(input, "\n"),
|
inputLines: strings.Split(input, "\n"),
|
||||||
|
|||||||
Reference in New Issue
Block a user