From 95d58d452599f5a86c5e3d1a70e9e3b2995eab13 Mon Sep 17 00:00:00 2001 From: Sean Hickey Date: Sat, 18 Jun 2022 13:27:50 -0700 Subject: [PATCH] Use gosimpleconf. Pull stuff out of main.go --- Makefile | 4 +- game.conf | 7 ++ go.mod | 1 + go.sum | 2 + main.go | 197 +++++-------------------------------- pkg/breakout/gamewindow.go | 20 ++-- pkg/config/config.go | 23 +++++ pkg/config/logging.go | 26 +++++ pkg/config/opengl.go | 13 +++ pkg/config/random.go | 24 +++++ pkg/config/sdl.go | 43 ++++++++ pkg/game/opengl.go | 123 +++++++++++++++++++++++ 12 files changed, 294 insertions(+), 189 deletions(-) create mode 100644 game.conf create mode 100644 pkg/config/config.go create mode 100644 pkg/config/logging.go create mode 100644 pkg/config/opengl.go create mode 100644 pkg/config/random.go create mode 100644 pkg/config/sdl.go create mode 100644 pkg/game/opengl.go diff --git a/Makefile b/Makefile index c17ecb7..00d834f 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ all: release dependencies: go mod tidy -build: linter dependencies - go build -x -v +debug: linter dependencies + go build -x -v -gcflags=all="-N -l" release: linter dependencies go build -x -v -ldflags "-s -w" diff --git a/game.conf b/game.conf new file mode 100644 index 0000000..a0d7397 --- /dev/null +++ b/game.conf @@ -0,0 +1,7 @@ +game.title = Carpy Breakout + +cpuprofile.enabled = false +cpuprofile.file = cpu.prof + +log.writeToFile = false +log.file = output.log diff --git a/go.mod b/go.mod index 4740c8e..da4d8c9 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gitea.wisellama.rocks/Wisellama/carpy-breakout go 1.17 require ( + gitea.wisellama.rocks/Wisellama/gosimpleconf v0.0.3 github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784 github.com/go-gl/mathgl v1.0.0 github.com/veandco/go-sdl2 v0.4.10 diff --git a/go.sum b/go.sum index 98f400c..e0e5b50 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +gitea.wisellama.rocks/Wisellama/gosimpleconf v0.0.3 h1:g4hcc7TjodjMNrSEQ3UO96HXvltHc2EOiudS+Wfle60= +gitea.wisellama.rocks/Wisellama/gosimpleconf v0.0.3/go.mod h1:kY9gQL8laVTe+tW0ue5bYb6QThw78d7mx6AHwQ5CIzc= github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784 h1:1Zi56D0LNfvkzM+BdoxKryvUEdyWO7LP8oRT+oSYJW0= github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= diff --git a/main.go b/main.go index d129711..5d3b62b 100644 --- a/main.go +++ b/main.go @@ -1,195 +1,42 @@ package main import ( - "flag" "log" "os" - "runtime" "runtime/pprof" - "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/breakout" - "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/globjects" - gl "github.com/go-gl/gl/v3.1/gles2" - "github.com/go-gl/mathgl/mgl32" - "github.com/veandco/go-sdl2/sdl" + "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/config" + "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/game" + "gitea.wisellama.rocks/Wisellama/gosimpleconf" ) -var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") - -const gameTitle = "Carpy Breakout" - func main() { - flag.Parse() + // Initialize the random number generator + err := config.InitRNG() + if err != nil { + log.Fatalf("error initializing RNG: %v", err) + } - if *cpuprofile != "" { - f, err := os.Create(*cpuprofile) + // Parse the config and setup logging + configMap, err := config.Configure() + if err != nil { + log.Fatalf("error in Configure: %v", err) + } + + // Setup cpu profiling if configured + cpuProfile := gosimpleconf.Bool(configMap["cpuprofile.enabled"]) + if cpuProfile { + f, err := os.Create(configMap["cpuprofile.file"]) if err != nil { - log.Fatal(err) + log.Fatalf("error creating file for cpuprofile: %v", err) } err = pprof.StartCPUProfile(f) if err != nil { - log.Fatal(err) + log.Fatalf("error starting cpuprofile: %v", err) } defer pprof.StopCPUProfile() } - setupLogging() - - // TODO can potentially rework stuff to not need LockOSThread() - // here. SDL has the built-in sdl.Do(), but I would need to create - // my own similar thing for OpenGL. Essentially they both need to - // be locked to the main thread, but anything else can be in a - // goroutine. - runtime.LockOSThread() - - runOpenGL() -} - -func runOpenGL() { - var sdlInitFlags uint32 = sdl.INIT_TIMER | sdl.INIT_AUDIO | sdl.INIT_VIDEO | sdl.INIT_EVENTS | sdl.INIT_JOYSTICK | sdl.INIT_HAPTIC | sdl.INIT_GAMECONTROLLER // ignore sensor subsystem - err := sdl.Init(sdlInitFlags) - if err != nil { - log.Fatal(err) - } - defer sdl.Quit() - - err = sdlSettings() - if err != nil { - log.Fatal(err) - } - - gameWindow, err := breakout.NewGameWindow(gameTitle) - if err != nil { - log.Fatal(err) - } - defer gameWindow.Destroy() - - // GL Init requires an active OpenGL context (e.g. the game window) - err = gl.Init() - if err != nil { - log.Fatalf("Failed to initialize OpenGL: %v", err) - } - glVersion := gl.GetString(gl.VERSION) - if glVersion == nil { - log.Printf("Error getting OpenGL version.\n") - } - log.Printf("OpenGL Version: %v\n", gl.GoStr(glVersion)) - - // Then we can setup the OpenGL specific stuff in the game window - err = gameWindow.GLInitDefault() - if err != nil { - log.Printf("error in gameWindow GLInitDefault: %v", err) - } - - sunLight := globjects.NewPointLight(mgl32.Vec3{-10, 10, 30}) - sunLight.GLInit(gameWindow.GLProgram) - - camera := globjects.NewCamera(gameWindow.GLProgram) - camera.Position = mgl32.Vec3{0, 0, 30} - gameWindow.SetCamera(camera) - gameWindow.AddObject(camera) - - // textureId, err := glhelpers.NewTexture("square.png") - // if err != nil { - // log.Fatal(err) - // } - - // Brick Targets - targets := breakout.NewTargets(4, 5, mgl32.Vec3{0, 6, 0}) - targets.GLInit(gameWindow.GLProgram) - gameWindow.SetTargets(targets) - for _, brick := range targets.Bricks { - gameWindow.AddObject(brick.Box) - } - - gameWindowAABB := globjects.NewAABB(targets.TopRight, targets.TopRight.Mul(-1)) - gameWindow.AABB = gameWindowAABB - - // Side Walls - sideWallsMaterial := globjects.NewMaterial() - sideWallsMaterial.Color = mgl32.Vec4{0.3, 0.3, 0.3, 1} - sideWalls := breakout.NewSideWalls(gameWindowAABB, sideWallsMaterial) - sideWalls.GLInit(gameWindow.GLProgram) - gameWindow.SetSideWalls(sideWalls) - for _, wall := range sideWalls.Boxes { - gameWindow.AddObject(wall) - } - - // Paddle - paddleMaterial := globjects.NewMaterial() - paddleMaterial.Color = mgl32.Vec4{0, 1, 0, 1} - paddleBox := globjects.NewBox(6.0, 1.0, 4.0, mgl32.Vec3{0, -8, 0}, paddleMaterial) - paddle := breakout.NewPaddle(paddleBox) - paddle.GLInit(gameWindow.GLProgram) - gameWindow.SetPaddle(paddle) - gameWindow.AddObject(paddle) - - // Ball - ballMaterial := globjects.NewMaterial() - ballMaterial.Color = mgl32.Vec4{1, 1, 1, 1} - ballBox := globjects.NewBox(1, 1, 1, mgl32.Vec3{-8, 0, 0}, ballMaterial) - ball := breakout.NewBall(ballBox) - ball.GLInit(gameWindow.GLProgram) - gameWindow.SetBall(ball) - gameWindow.AddObject(ball) - ball.Box.Velocity = mgl32.Vec3{.1, -.1, 0} - - glSettings() - for gameWindow.IsRunning() { - gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) - - gameWindow.Update() - gameWindow.GLDraw() - - gameWindow.SDLWindow.GLSwap() - - gameWindow.HandleEvents() - sdl.Delay(16) - } -} - -func setupLogging() { - // const logFile = "output.log" - // file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - // if err != nil { - // log.Fatal(err) - // } - - // log.SetOutput(file) - log.Println("=== START ===") -} - -func glSettings() { - // Global options after setup is all done - gl.ClearColor(0.0, 0.0, 0.0, 1.0) - gl.Enable(gl.DEPTH_TEST) - gl.DepthFunc(gl.LESS) - gl.LineWidth(2.0) -} - -func sdlSettings() error { - var err error - - // Smooth - err = sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1) - if err != nil { - log.Printf("error setting GL_MULTISAMPLEBUFFERS: %v", err) - return err - } - err = sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4) - if err != nil { - log.Printf("error setting GL_MULTISAMPLESAMPLES: %v", err) - return err - } - - // Disable the Linux compositor flicker. - // https://github.com/mosra/magnum/issues/184#issuecomment-425952900 - sdl.SetHint(sdl.HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0") - - sdl.DisableScreenSaver() - - // Capture the mouse for movement - sdl.SetRelativeMouseMode(true) - - return nil + // Start the game + game.Run(configMap) } diff --git a/pkg/breakout/gamewindow.go b/pkg/breakout/gamewindow.go index 77ed990..66f41d6 100644 --- a/pkg/breakout/gamewindow.go +++ b/pkg/breakout/gamewindow.go @@ -4,6 +4,7 @@ import ( "fmt" "log" + "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/config" "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/glhelpers" "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/globjects" gl "github.com/go-gl/gl/v3.1/gles2" @@ -11,11 +12,6 @@ import ( "github.com/veandco/go-sdl2/sdl" ) -const defaultWindowWidth int32 = 800 -const defaultWindowHeight int32 = 600 -const defaultWindowFlags uint32 = sdl.WINDOW_SHOWN | sdl.WINDOW_RESIZABLE | sdl.WINDOW_OPENGL -const fullscreenWindowFlags uint32 = defaultWindowFlags | sdl.WINDOW_FULLSCREEN_DESKTOP - type GameWindow struct { SDLWindow *sdl.Window GLContext *sdl.GLContext @@ -47,9 +43,9 @@ func NewGameWindow(title string) (*GameWindow, error) { title, sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, - defaultWindowWidth, - defaultWindowHeight, - defaultWindowFlags) + config.SDL_WINDOW_WIDTH, + config.SDL_WINDOW_HEIGHT, + config.SDL_WINDOW_FLAGS) if err != nil { log.Println("Failed creating SDL window") return nil, err @@ -101,14 +97,14 @@ func (w *GameWindow) Destroy() { func (w *GameWindow) ToggleFullscreen() { var err error if w.fullscreen { - err = w.SDLWindow.SetFullscreen(defaultWindowFlags) + err = w.SDLWindow.SetFullscreen(config.SDL_WINDOW_FLAGS) if err != nil { - log.Printf("error setting defaultWindowFlags: %v", err) + log.Printf("error setting default window flags: %v", err) } } else { - err = w.SDLWindow.SetFullscreen(fullscreenWindowFlags) + err = w.SDLWindow.SetFullscreen(config.SDL_FULLSCREEN_WINDOW_FLAGS) if err != nil { - log.Printf("error setting fullscreenWindowFlags: %v", err) + log.Printf("error setting fullscreen window flags: %v", err) } } diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..14e9834 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,23 @@ +package config + +import ( + "gitea.wisellama.rocks/Wisellama/gosimpleconf" +) + +func Configure() (gosimpleconf.ConfigMap, error) { + var err error + configMap, err := gosimpleconf.Load("game.conf") + if err != nil { + return nil, err + } + + flagMap := gosimpleconf.SetupFlagOverrides(configMap) + configMap = gosimpleconf.ParseFlags(configMap, flagMap) + + err = SetupLogging(configMap) + if err != nil { + return nil, err + } + + return configMap, nil +} diff --git a/pkg/config/logging.go b/pkg/config/logging.go new file mode 100644 index 0000000..bd78a2a --- /dev/null +++ b/pkg/config/logging.go @@ -0,0 +1,26 @@ +package config + +import ( + "log" + "os" + + "gitea.wisellama.rocks/Wisellama/gosimpleconf" +) + +func SetupLogging(configMap gosimpleconf.ConfigMap) error { + + writeToFile := gosimpleconf.Bool(configMap["log.writeToFile"]) + + if writeToFile { + logFile := configMap["log.file"] + file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + + log.SetOutput(file) + } + + log.Printf("=== START %v ===", configMap["game.title"]) + return nil +} diff --git a/pkg/config/opengl.go b/pkg/config/opengl.go new file mode 100644 index 0000000..10fd385 --- /dev/null +++ b/pkg/config/opengl.go @@ -0,0 +1,13 @@ +package config + +import ( + gl "github.com/go-gl/gl/v3.1/gles2" +) + +func GlSettings() { + // Global options after setup is all done + gl.ClearColor(0.0, 0.0, 0.0, 1.0) + gl.Enable(gl.DEPTH_TEST) + gl.DepthFunc(gl.LESS) + gl.LineWidth(2.0) +} diff --git a/pkg/config/random.go b/pkg/config/random.go new file mode 100644 index 0000000..0207429 --- /dev/null +++ b/pkg/config/random.go @@ -0,0 +1,24 @@ +package config + +import ( + crypto_rand "crypto/rand" + "encoding/binary" + "fmt" + math_rand "math/rand" +) + +// InitRNG will grab some cryptographically secure bytes to use as the +// seed for the non-crypto random number generator. Suggested on +// StackOverflow to avoid time-based seeds: +// https://stackoverflow.com/a/54491783 +func InitRNG() error { + var b [8]byte + _, err := crypto_rand.Read(b[:]) + if err != nil { + err = fmt.Errorf("error seeding random number generator: %w", err) + return err + } + math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:]))) + + return nil +} diff --git a/pkg/config/sdl.go b/pkg/config/sdl.go new file mode 100644 index 0000000..ff6f58e --- /dev/null +++ b/pkg/config/sdl.go @@ -0,0 +1,43 @@ +package config + +import ( + "log" + + "github.com/veandco/go-sdl2/sdl" +) + +const ( + SDL_INIT_FLAGS uint32 = sdl.INIT_TIMER | sdl.INIT_AUDIO | sdl.INIT_VIDEO | sdl.INIT_EVENTS | sdl.INIT_JOYSTICK | sdl.INIT_HAPTIC | sdl.INIT_GAMECONTROLLER // ignore sensor subsystem + + SDL_WINDOW_FLAGS uint32 = sdl.WINDOW_SHOWN | sdl.WINDOW_RESIZABLE | sdl.WINDOW_OPENGL + SDL_FULLSCREEN_WINDOW_FLAGS uint32 = SDL_WINDOW_FLAGS | sdl.WINDOW_FULLSCREEN_DESKTOP + SDL_WINDOW_WIDTH int32 = 800 + SDL_WINDOW_HEIGHT int32 = 600 +) + +func SdlSettings() error { + var err error + + // Smooth + err = sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1) + if err != nil { + log.Printf("error setting GL_MULTISAMPLEBUFFERS: %v", err) + return err + } + err = sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4) + if err != nil { + log.Printf("error setting GL_MULTISAMPLESAMPLES: %v", err) + return err + } + + // Disable the Linux compositor flicker. + // https://github.com/mosra/magnum/issues/184#issuecomment-425952900 + sdl.SetHint(sdl.HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0") + + sdl.DisableScreenSaver() + + // Capture the mouse for movement + sdl.SetRelativeMouseMode(true) + + return nil +} diff --git a/pkg/game/opengl.go b/pkg/game/opengl.go new file mode 100644 index 0000000..9f3fa0f --- /dev/null +++ b/pkg/game/opengl.go @@ -0,0 +1,123 @@ +package game + +import ( + "log" + "runtime" + + "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/breakout" + "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/config" + "gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/globjects" + "gitea.wisellama.rocks/Wisellama/gosimpleconf" + gl "github.com/go-gl/gl/v3.1/gles2" + "github.com/go-gl/mathgl/mgl32" + "github.com/veandco/go-sdl2/sdl" +) + +func Run(configMap gosimpleconf.ConfigMap) { + // TODO can potentially rework stuff to not need LockOSThread() + // here. SDL has the built-in sdl.Do(), but I would need to create + // my own similar thing for OpenGL. Essentially they both need to + // be locked to the main thread, but anything else can be in a + // goroutine. + runtime.LockOSThread() + + err := sdl.Init(config.SDL_INIT_FLAGS) + if err != nil { + log.Fatal(err) + } + defer sdl.Quit() + + err = config.SdlSettings() + if err != nil { + log.Fatal(err) + } + + gameWindow, err := breakout.NewGameWindow(configMap["game.title"]) + if err != nil { + log.Fatal(err) + } + defer gameWindow.Destroy() + + // GL Init requires an active OpenGL context (e.g. the game window) + err = gl.Init() + if err != nil { + log.Fatalf("Failed to initialize OpenGL: %v", err) + } + glVersion := gl.GetString(gl.VERSION) + if glVersion == nil { + log.Printf("Error getting OpenGL version.\n") + } + log.Printf("OpenGL Version: %v\n", gl.GoStr(glVersion)) + + // Then we can setup the OpenGL specific stuff in the game window + err = gameWindow.GLInitDefault() + if err != nil { + log.Printf("error in gameWindow GLInitDefault: %v", err) + } + + sunLight := globjects.NewPointLight(mgl32.Vec3{-10, 10, 30}) + sunLight.GLInit(gameWindow.GLProgram) + + camera := globjects.NewCamera(gameWindow.GLProgram) + camera.Position = mgl32.Vec3{0, 0, 30} + gameWindow.SetCamera(camera) + gameWindow.AddObject(camera) + + // textureId, err := glhelpers.NewTexture("square.png") + // if err != nil { + // log.Fatal(err) + // } + + // Brick Targets + targets := breakout.NewTargets(4, 5, mgl32.Vec3{0, 6, 0}) + targets.GLInit(gameWindow.GLProgram) + gameWindow.SetTargets(targets) + for _, brick := range targets.Bricks { + gameWindow.AddObject(brick.Box) + } + + gameWindowAABB := globjects.NewAABB(targets.TopRight, targets.TopRight.Mul(-1)) + gameWindow.AABB = gameWindowAABB + + // Side Walls + sideWallsMaterial := globjects.NewMaterial() + sideWallsMaterial.Color = mgl32.Vec4{0.3, 0.3, 0.3, 1} + sideWalls := breakout.NewSideWalls(gameWindowAABB, sideWallsMaterial) + sideWalls.GLInit(gameWindow.GLProgram) + gameWindow.SetSideWalls(sideWalls) + for _, wall := range sideWalls.Boxes { + gameWindow.AddObject(wall) + } + + // Paddle + paddleMaterial := globjects.NewMaterial() + paddleMaterial.Color = mgl32.Vec4{0, 1, 0, 1} + paddleBox := globjects.NewBox(6.0, 1.0, 4.0, mgl32.Vec3{0, -8, 0}, paddleMaterial) + paddle := breakout.NewPaddle(paddleBox) + paddle.GLInit(gameWindow.GLProgram) + gameWindow.SetPaddle(paddle) + gameWindow.AddObject(paddle) + + // Ball + ballMaterial := globjects.NewMaterial() + ballMaterial.Color = mgl32.Vec4{1, 1, 1, 1} + ballBox := globjects.NewBox(1, 1, 1, mgl32.Vec3{-8, 0, 0}, ballMaterial) + ball := breakout.NewBall(ballBox) + ball.GLInit(gameWindow.GLProgram) + gameWindow.SetBall(ball) + gameWindow.AddObject(ball) + ball.Box.Velocity = mgl32.Vec3{.1, -.1, 0} + + config.GlSettings() + for gameWindow.IsRunning() { + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + + gameWindow.Update() + gameWindow.GLDraw() + + gameWindow.SDLWindow.GLSwap() + + gameWindow.HandleEvents() + sdl.Delay(16) + } +}