diff --git a/config/sdl.go b/config/sdl.go deleted file mode 100644 index a977085..0000000 --- a/config/sdl.go +++ /dev/null @@ -1,27 +0,0 @@ -package config - -import ( - "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_FULLSCREEN_WINDOW_FLAGS uint32 = SDL_WINDOW_FLAGS | sdl.WINDOW_FULLSCREEN_DESKTOP - SDL_WINDOW_WIDTH int32 = 800 - SDL_WINDOW_HEIGHT int32 = 600 -) - -func SdlSettings() error { - // 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/config/config.go b/internal/config/config.go similarity index 100% rename from config/config.go rename to internal/config/config.go diff --git a/config/logging.go b/internal/config/logging.go similarity index 100% rename from config/logging.go rename to internal/config/logging.go diff --git a/config/random.go b/internal/config/random.go similarity index 100% rename from config/random.go rename to internal/config/random.go diff --git a/internal/game/sdl.go b/internal/game/sdl.go new file mode 100644 index 0000000..0d5580a --- /dev/null +++ b/internal/game/sdl.go @@ -0,0 +1,60 @@ +package game + +import ( + "fmt" + + "github.com/veandco/go-sdl2/sdl" +) + +/* +NOTE: We're using the SDL with goroutines example. +Every call to an SDL function must be passed through the sdl.Do() function so that it joins the queue of thread-sensitive calls. +You'll see this a lot below, but basically every SDL function must do something like this: + sdl.Do(func() { + err = sdl.SomeFunction() + }) +This is for thread safety because SDL is not intended to be thread-safe. +However, the rest of the code can do whatever it wants with threads so long as the SDL calls all use this method. +*/ + +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_FULLSCREEN_WINDOW_FLAGS uint32 = SDL_WINDOW_FLAGS | sdl.WINDOW_FULLSCREEN_DESKTOP + SDL_WINDOW_WIDTH int32 = 800 + SDL_WINDOW_HEIGHT int32 = 600 +) + +func SdlInit() error { + var err error + + // Initialize SDL + sdl.Do(func() { + err = sdl.Init(SDL_INIT_FLAGS) + }) + if err != nil { + err = fmt.Errorf("failed initializing SDL: %w", err) + return err + } + + // Set some base SDL settings + sdl.Do(func() { + // 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 +} + +func SdlQuit() { + sdl.Do(func() { + sdl.Quit() + }) +} diff --git a/internal/game/window.go b/internal/game/window.go new file mode 100644 index 0000000..53ed66e --- /dev/null +++ b/internal/game/window.go @@ -0,0 +1,46 @@ +package game + +import ( + "github.com/veandco/go-sdl2/sdl" +) + +type window struct { + SdlWindow *sdl.Window + KeyStates map[sdl.Keycode]bool +} + +func NewWindow(title string) (*window, error) { + var ( + err error + sdlWindow *sdl.Window + ) + + sdl.Do(func() { + sdlWindow, err = sdl.CreateWindow( + title, + sdl.WINDOWPOS_UNDEFINED, + sdl.WINDOWPOS_UNDEFINED, + SDL_WINDOW_WIDTH, + SDL_WINDOW_HEIGHT, + SDL_WINDOW_FLAGS) + }) + if err != nil { + return nil, err + } + + keyStates := make(map[sdl.Keycode]bool) + gw := &window{ + SdlWindow: sdlWindow, + KeyStates: keyStates, + } + + return gw, nil +} + +func (g *window) Destroy() { + if g.SdlWindow != nil { + sdl.Do(func() { + g.SdlWindow.Destroy() + }) + } +} diff --git a/main.go b/main.go index 288e7c6..305de6d 100644 --- a/main.go +++ b/main.go @@ -3,9 +3,10 @@ package main import ( "fmt" "log" - "runtime" + "os" - "gitea.wisellama.rocks/Project-Ely/project-ely/config" + "gitea.wisellama.rocks/Project-Ely/project-ely/internal/config" + "gitea.wisellama.rocks/Project-Ely/project-ely/internal/game" "gitea.wisellama.rocks/Wisellama/gosimpleconf" "github.com/veandco/go-sdl2/sdl" ) @@ -28,43 +29,50 @@ func main() { } defer logWriter.Cleanup() - // Start everything - log.Printf("=== Starting %v", configMap["game.title"]) - err = run(configMap) + // Start everything with the SDL goroutine context + log.Printf("=== Starting %v ===", configMap["game.title"]) + sdl.Main(func() { + err = run(configMap) + }) + + exitcode := 0 if err != nil { - log.Fatalf("error running: %v\n", err) + log.Printf("ERROR: %v\n", err) + exitcode = 1 } + + os.Exit(exitcode) } func run(configMap gosimpleconf.ConfigMap) error { - runtime.LockOSThread() + var err error - err := sdl.Init(config.SDL_INIT_FLAGS) + err = game.SdlInit() if err != nil { - err = fmt.Errorf("failed initializing SDL: %w", err) return err } - defer sdl.Quit() + defer game.SdlQuit() - err = config.SdlSettings() + gameWindow, err := game.NewWindow(configMap["game.title"]) if err != nil { - err = fmt.Errorf("failed in SDL settings: %w", err) - return err - } - - gameWindow, err := createWindow(configMap["game.title"]) - if err != nil { - err = fmt.Errorf("failed creating SDL window: %w", err) + err = fmt.Errorf("failed creating GameWindow: %w", err) return err } defer gameWindow.Destroy() - renderer, err := sdl.CreateRenderer(gameWindow, -1, sdl.RENDERER_ACCELERATED) + var renderer *sdl.Renderer + sdl.Do(func() { + renderer, err = sdl.CreateRenderer(gameWindow.SdlWindow, -1, sdl.RENDERER_ACCELERATED) + }) if err != nil { err = fmt.Errorf("failed creating SDL renderer: %w", err) return err } - defer renderer.Destroy() + defer func() { + sdl.Do(func() { + renderer.Destroy() + }) + }() var rect *sdl.Rect var x int32 = 0 @@ -74,7 +82,11 @@ func run(configMap gosimpleconf.ConfigMap) error { running := true for running { - for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { + var event sdl.Event + sdl.Do(func() { + event = sdl.PollEvent() + }) + for event != nil { switch e := event.(type) { case *sdl.QuitEvent: running = false @@ -91,6 +103,10 @@ func run(configMap gosimpleconf.ConfigMap) error { keystates[e.Keysym.Sym] = false } } + + sdl.Do(func() { + event = sdl.PollEvent() + }) } if keystates[sdl.K_a] { @@ -101,7 +117,7 @@ func run(configMap gosimpleconf.ConfigMap) error { } if keystates[sdl.K_d] { x += 1 - w, _ := gameWindow.GetSize() + w, _ := gameWindow.SdlWindow.GetSize() if x+rect.W > w { x = w - rect.W } @@ -115,41 +131,32 @@ func run(configMap gosimpleconf.ConfigMap) error { } if keystates[sdl.K_s] { y += 1 - _, h := gameWindow.GetSize() + _, h := gameWindow.SdlWindow.GetSize() if y+rect.H > h { y = h - rect.H } } // Black background - renderer.SetDrawColor(0, 0, 0, 255) - renderer.Clear() + sdl.Do(func() { + renderer.SetDrawColor(0, 0, 0, 255) + renderer.Clear() + }) // Blue square - renderer.SetDrawColor(0, 0, 255, 255) - rect = &sdl.Rect{X: x, Y: y, W: 200, H: 200} - renderer.DrawRect(rect) - renderer.FillRect(rect) + sdl.Do(func() { + renderer.SetDrawColor(0, 0, 255, 255) + rect = &sdl.Rect{X: x, Y: y, W: 200, H: 200} + renderer.DrawRect(rect) + renderer.FillRect(rect) + }) - renderer.Present() - sdl.Delay(16) + // Draw + sdl.Do(func() { + renderer.Present() + sdl.Delay(16) + }) } return nil } - -func createWindow(title string) (*sdl.Window, error) { - window, err := sdl.CreateWindow( - title, - sdl.WINDOWPOS_UNDEFINED, - sdl.WINDOWPOS_UNDEFINED, - config.SDL_WINDOW_WIDTH, - config.SDL_WINDOW_HEIGHT, - config.SDL_WINDOW_FLAGS) - if err != nil { - log.Println("Failed creating SDL window") - return nil, err - } - - return window, nil -}