Wrap SDL function with sdl.Do() for goroutine thread-safety.

main
Sean Hickey 2022-09-29 15:53:02 -07:00
parent c13ba6359e
commit fcd11a4e53
7 changed files with 160 additions and 74 deletions

View File

@ -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
}

60
internal/game/sdl.go Normal file
View File

@ -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()
})
}

46
internal/game/window.go Normal file
View File

@ -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()
})
}
}

101
main.go
View File

@ -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
}