Stabilize Load Screen by Deferring SDL2 Calls
* The loading screen for Edit and Play modes is stable and the risk of game crash is removed. The root cause was the setupAsync() functions running on a background goroutine, and running SDL2 draw functions while NOT on the main thread, which causes problems. * The fix is all SDL2 Texture draws become lazy loaded: when the main thread is presenting, any Wallpaper or ui.Image that has no texture yet gets one created at that time from the cached image.Image. * All internal game logic then uses image.Image types, to cache bitmaps of Level Chunks, Wallpaper images, Sprite icons, etc. and the game is free to prepare these asynchronously; only the main thread ever Presents and the SDL2 textures initialize on first appearance. * Several functions had arguments cleaned up: Canvas.LoadLevel() does not need the render.Engine as (e.g. wallpaper) textures don't render at that stage.
This commit is contained in:
parent
2d1b926e4f
commit
215ed5c847
|
@ -101,7 +101,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
|
|||
"Opening: "+s.Level.Title,
|
||||
"by "+s.Level.Author,
|
||||
)
|
||||
s.UI.Canvas.LoadLevel(d.Engine, s.Level)
|
||||
s.UI.Canvas.LoadLevel(s.Level)
|
||||
s.UI.Canvas.InstallActors(s.Level.Actors)
|
||||
} else if s.filename != "" && s.OpenFile {
|
||||
log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename)
|
||||
|
@ -132,7 +132,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
|
|||
log.Debug("EditorScene.Setup: initializing a new Level")
|
||||
s.Level = level.New()
|
||||
s.Level.Palette = level.DefaultPalette()
|
||||
s.UI.Canvas.LoadLevel(d.Engine, s.Level)
|
||||
s.UI.Canvas.LoadLevel(s.Level)
|
||||
s.UI.Canvas.ScrollTo(render.Origin)
|
||||
s.UI.Canvas.Scrollable = true
|
||||
}
|
||||
|
@ -360,7 +360,7 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
|||
|
||||
s.DrawingType = enum.LevelDrawing
|
||||
s.Level = level
|
||||
s.UI.Canvas.LoadLevel(s.d.Engine, s.Level)
|
||||
s.UI.Canvas.LoadLevel(s.Level)
|
||||
|
||||
log.Info("Installing %d actors into the drawing", len(level.Actors))
|
||||
if err := s.UI.Canvas.InstallActors(level.Actors); err != nil {
|
||||
|
|
|
@ -131,7 +131,7 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
|||
log.Info("OnChangePageTypeAndWallpaper called: %+v, %+v", pageType, wallpaper)
|
||||
scene.Level.PageType = pageType
|
||||
scene.Level.Wallpaper = wallpaper
|
||||
u.Canvas.LoadLevel(d.Engine, scene.Level)
|
||||
u.Canvas.LoadLevel(scene.Level)
|
||||
},
|
||||
OnCancel: func() {
|
||||
u.levelSettingsWindow.Hide()
|
||||
|
@ -261,7 +261,7 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
|||
// Reload the level.
|
||||
if scene.Level != nil {
|
||||
log.Warn("RELOAD LEVEL")
|
||||
u.Canvas.LoadLevel(d.Engine, scene.Level)
|
||||
u.Canvas.LoadLevel(scene.Level)
|
||||
scene.Level.Chunker.Redraw()
|
||||
} else if scene.Doodad != nil {
|
||||
log.Warn("RELOAD DOODAD")
|
||||
|
|
|
@ -244,7 +244,7 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error {
|
|||
|
||||
// Title screen level to load.
|
||||
if lvl, err := level.LoadFile(balance.DemoLevelName); err == nil {
|
||||
s.canvas.LoadLevel(d.Engine, lvl)
|
||||
s.canvas.LoadLevel(lvl)
|
||||
s.canvas.InstallActors(lvl.Actors)
|
||||
|
||||
// Load all actor scripts.
|
||||
|
|
|
@ -96,7 +96,7 @@ func (s *MenuScene) Setup(d *Doodle) error {
|
|||
W: d.width,
|
||||
H: d.height,
|
||||
})
|
||||
s.canvas.LoadLevel(d.Engine, &level.Level{
|
||||
s.canvas.LoadLevel(&level.Level{
|
||||
Chunker: level.NewChunker(100),
|
||||
Palette: level.NewPalette(),
|
||||
PageType: level.Bounded,
|
||||
|
@ -135,8 +135,8 @@ func (s *MenuScene) Setup(d *Doodle) error {
|
|||
|
||||
// configureCanvas updates the settings of the background canvas, so a live
|
||||
// preview of the wallpaper and wrapping type can be shown.
|
||||
func (s *MenuScene) configureCanvas(e render.Engine, pageType level.PageType, wallpaper string) {
|
||||
s.canvas.LoadLevel(e, &level.Level{
|
||||
func (s *MenuScene) configureCanvas(pageType level.PageType, wallpaper string) {
|
||||
s.canvas.LoadLevel(&level.Level{
|
||||
Chunker: level.NewChunker(100),
|
||||
Palette: level.NewPalette(),
|
||||
PageType: pageType,
|
||||
|
@ -151,7 +151,7 @@ func (s *MenuScene) setupNewWindow(d *Doodle) error {
|
|||
Engine: d.Engine,
|
||||
OnChangePageTypeAndWallpaper: func(pageType level.PageType, wallpaper string) {
|
||||
log.Info("OnChangePageTypeAndWallpaper called: %+v, %+v", pageType, wallpaper)
|
||||
s.configureCanvas(d.Engine, pageType, wallpaper)
|
||||
s.configureCanvas(pageType, wallpaper)
|
||||
},
|
||||
OnCreateNewLevel: func(lvl *level.Level) {
|
||||
d.Goto(&EditorScene{
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/apps/doodle/pkg/uix"
|
||||
"git.kirsle.net/go/render"
|
||||
|
@ -108,7 +109,7 @@ func setup() {
|
|||
|
||||
// Create the parent container that will stretch full screen.
|
||||
window = ui.NewFrame("Loadscreen Window")
|
||||
window.SetBackground(render.RGBA(0, 0, 1, 40))
|
||||
window.SetBackground(render.RGBA(0, 0, 1, 40)) // makes the wallpaper darker? :/
|
||||
|
||||
// "Loading" text.
|
||||
label := ui.NewLabel(ui.Label{
|
||||
|
@ -167,7 +168,7 @@ func Loop(windowSize render.Rect, e render.Engine) {
|
|||
// Initialize the wallpaper canvas?
|
||||
if canvas == nil {
|
||||
canvas = uix.NewCanvas(128, false)
|
||||
canvas.LoadLevel(e, &level.Level{
|
||||
canvas.LoadLevel(&level.Level{
|
||||
Chunker: level.NewChunker(100),
|
||||
Palette: level.NewPalette(),
|
||||
PageType: level.Bounded,
|
||||
|
@ -215,8 +216,13 @@ func Loop(windowSize render.Rect, e render.Engine) {
|
|||
// of chunks vs. chunks remaining to pre-cache bitmaps from.
|
||||
func PreloadAllChunkBitmaps(chunker *level.Chunker) {
|
||||
loadChunksTarget := len(chunker.Chunks)
|
||||
// if loadChunksTarget == 0 {
|
||||
// return
|
||||
// }
|
||||
|
||||
for {
|
||||
remaining := chunker.PrerenderN(10)
|
||||
log.Info("Remain: %d", remaining)
|
||||
|
||||
// Set the load screen progress % based on number of chunks to render.
|
||||
if loadChunksTarget > 0 {
|
||||
|
|
|
@ -171,7 +171,7 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
|
|||
// Given a filename or map data to play?
|
||||
if s.Level != nil {
|
||||
log.Debug("PlayScene.Setup: received level from scene caller")
|
||||
s.drawing.LoadLevel(d.Engine, s.Level)
|
||||
s.drawing.LoadLevel(s.Level)
|
||||
s.drawing.InstallActors(s.Level.Actors)
|
||||
} else if s.Filename != "" {
|
||||
loadscreen.SetSubtitle("Opening: " + s.Filename)
|
||||
|
@ -183,7 +183,7 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
|
|||
if s.Level == nil {
|
||||
log.Debug("PlayScene.Setup: no grid given, initializing empty grid")
|
||||
s.Level = level.New()
|
||||
s.drawing.LoadLevel(d.Engine, s.Level)
|
||||
s.drawing.LoadLevel(s.Level)
|
||||
s.drawing.InstallActors(s.Level.Actors)
|
||||
}
|
||||
|
||||
|
@ -611,7 +611,7 @@ func (s *PlayScene) LoadLevel(filename string) error {
|
|||
}
|
||||
|
||||
s.Level = level
|
||||
s.drawing.LoadLevel(s.d.Engine, s.Level)
|
||||
s.drawing.LoadLevel(s.Level)
|
||||
s.drawing.InstallActors(s.Level.Actors)
|
||||
|
||||
return nil
|
||||
|
|
|
@ -29,12 +29,7 @@ func LoadImage(e render.Engine, filename string) (*ui.Image, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tex, err := e.StoreTexture(filename, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ui.ImageFromTexture(tex), nil
|
||||
return ui.ImageFromImage(img)
|
||||
}
|
||||
|
||||
// WASM: try the file over HTTP ajax request.
|
||||
|
@ -49,12 +44,7 @@ func LoadImage(e render.Engine, filename string) (*ui.Image, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tex, err := e.StoreTexture(filename, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ui.ImageFromTexture(tex), nil
|
||||
return ui.ImageFromImage(img)
|
||||
}
|
||||
|
||||
// Then try the file system.
|
||||
|
@ -71,12 +61,7 @@ func LoadImage(e render.Engine, filename string) (*ui.Image, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tex, err := e.StoreTexture(filename, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ui.ImageFromTexture(tex), nil
|
||||
return ui.ImageFromImage(img)
|
||||
}
|
||||
|
||||
return nil, errors.New("no such sprite found")
|
||||
|
|
|
@ -48,7 +48,7 @@ func (s *StoryScene) Setup(d *Doodle) error {
|
|||
// Set up the background wallpaper canvas.
|
||||
s.canvas = uix.NewCanvas(100, false)
|
||||
s.canvas.Resize(render.NewRect(d.width, d.height))
|
||||
s.canvas.LoadLevel(d.Engine, &level.Level{
|
||||
s.canvas.LoadLevel(&level.Level{
|
||||
Chunker: level.NewChunker(100),
|
||||
Palette: level.NewPalette(),
|
||||
PageType: level.Bounded,
|
||||
|
|
|
@ -145,7 +145,7 @@ func (w *Canvas) Load(p *level.Palette, g *level.Chunker) {
|
|||
}
|
||||
|
||||
// LoadLevel initializes a Canvas from a Level object.
|
||||
func (w *Canvas) LoadLevel(e render.Engine, level *level.Level) {
|
||||
func (w *Canvas) LoadLevel(level *level.Level) {
|
||||
w.level = level
|
||||
w.Load(level.Palette, level.Chunker)
|
||||
|
||||
|
@ -159,14 +159,14 @@ func (w *Canvas) LoadLevel(e render.Engine, level *level.Level) {
|
|||
}
|
||||
}
|
||||
|
||||
wp, err := wallpaper.FromFile(e, filename, level)
|
||||
wp, err := wallpaper.FromFile(filename, level)
|
||||
if err != nil {
|
||||
log.Error("wallpaper FromFile(%s): %s", filename, err)
|
||||
}
|
||||
|
||||
w.wallpaper.maxWidth = level.MaxWidth
|
||||
w.wallpaper.maxHeight = level.MaxHeight
|
||||
err = w.wallpaper.Load(e, level.PageType, wp)
|
||||
err = w.wallpaper.Load(level.PageType, wp)
|
||||
if err != nil {
|
||||
log.Error("wallpaper Load: %s", err)
|
||||
}
|
||||
|
|
|
@ -11,16 +11,15 @@ type Wallpaper struct {
|
|||
pageType level.PageType
|
||||
maxWidth int64
|
||||
maxHeight int64
|
||||
corner render.Texturer
|
||||
top render.Texturer
|
||||
left render.Texturer
|
||||
repeat render.Texturer
|
||||
|
||||
// Pointer to the Wallpaper datum.
|
||||
WP *wallpaper.Wallpaper
|
||||
}
|
||||
|
||||
// Valid returns whether the Wallpaper is configured. Only Levels should
|
||||
// have wallpapers and Doodads will have nil ones.
|
||||
func (wp *Wallpaper) Valid() bool {
|
||||
return wp.repeat != nil
|
||||
return wp.WP != nil && wp.WP.Repeat() != nil
|
||||
}
|
||||
|
||||
// Canvas Loop() task that keeps mobile actors constrained inside the borders
|
||||
|
@ -76,8 +75,8 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
|||
var (
|
||||
wp = w.wallpaper
|
||||
S = w.Size()
|
||||
size = wp.corner.Size()
|
||||
sizeOrig = wp.corner.Size()
|
||||
size = wp.WP.QuarterRect()
|
||||
sizeOrig = wp.WP.QuarterRect()
|
||||
|
||||
// Get the relative viewport of world coordinates looked at by the canvas.
|
||||
// The X,Y values are the negative Scroll value
|
||||
|
@ -202,7 +201,9 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
|||
src.H = sizeOrig.H
|
||||
}
|
||||
|
||||
e.Copy(wp.repeat, src, dst)
|
||||
if tex, err := wp.WP.RepeatTexture(e); err == nil {
|
||||
e.Copy(tex, src, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,7 +228,9 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
|||
}
|
||||
|
||||
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||
e.Copy(wp.left, src, dst)
|
||||
if tex, err := wp.WP.LeftTexture(e); err == nil {
|
||||
e.Copy(tex, src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
// The top edge tiled along the top edge.
|
||||
|
@ -250,7 +253,9 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
|||
}
|
||||
|
||||
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||
e.Copy(wp.top, src, dst)
|
||||
if tex, err := wp.WP.TopTexture(e); err == nil {
|
||||
e.Copy(tex, src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
// The top left corner for all page types except Unbounded.
|
||||
|
@ -273,38 +278,17 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
|||
}
|
||||
|
||||
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||
e.Copy(wp.corner, src, dst)
|
||||
if tex, err := wp.WP.CornerTexture(e); err == nil {
|
||||
e.Copy(tex, src, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load the wallpaper settings from a level.
|
||||
func (wp *Wallpaper) Load(e render.Engine, pageType level.PageType, v *wallpaper.Wallpaper) error {
|
||||
func (wp *Wallpaper) Load(pageType level.PageType, v *wallpaper.Wallpaper) error {
|
||||
wp.pageType = pageType
|
||||
if tex, err := v.CornerTexture(e); err == nil {
|
||||
wp.corner = tex
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
if tex, err := v.TopTexture(e); err == nil {
|
||||
wp.top = tex
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
if tex, err := v.LeftTexture(e); err == nil {
|
||||
wp.left = tex
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
if tex, err := v.RepeatTexture(e); err == nil {
|
||||
wp.repeat = tex
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
wp.WP = v
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -48,19 +48,19 @@ type Wallpaper struct {
|
|||
// FromImage creates a Wallpaper from an image.Image.
|
||||
// If the renger.Engine is nil it will compute images but not pre-cache any
|
||||
// textures yet.
|
||||
func FromImage(e render.Engine, img *image.RGBA, name string) (*Wallpaper, error) {
|
||||
func FromImage(img *image.RGBA, name string) (*Wallpaper, error) {
|
||||
wp := &Wallpaper{
|
||||
Name: name,
|
||||
Image: img,
|
||||
}
|
||||
wp.cache(e)
|
||||
wp.cache()
|
||||
return wp, nil
|
||||
}
|
||||
|
||||
// FromFile creates a Wallpaper from a file on disk.
|
||||
// If the renger.Engine is nil it will compute images but not pre-cache any
|
||||
// textures yet.
|
||||
func FromFile(e render.Engine, filename string, embeddable filesystem.Embeddable) (*Wallpaper, error) {
|
||||
func FromFile(filename string, embeddable filesystem.Embeddable) (*Wallpaper, error) {
|
||||
// Default object to return on errors.
|
||||
var defaultWP = &Wallpaper{
|
||||
Name: strings.Split(filepath.Base(filename), ".")[0],
|
||||
|
@ -118,12 +118,12 @@ func FromFile(e render.Engine, filename string, embeddable filesystem.Embeddable
|
|||
Image: rgba,
|
||||
ready: true,
|
||||
}
|
||||
wp.cache(e)
|
||||
wp.cache()
|
||||
return wp, nil
|
||||
}
|
||||
|
||||
// cache the bitmap images.
|
||||
func (wp *Wallpaper) cache(e render.Engine) {
|
||||
func (wp *Wallpaper) cache() {
|
||||
// Zero-bound the rect cuz an image.Rect doesn't necessarily contain 0,0
|
||||
var rect = wp.Image.Bounds()
|
||||
if rect.Min.X < 0 {
|
||||
|
@ -164,6 +164,11 @@ func (wp *Wallpaper) QuarterSize() (int, int) {
|
|||
return wp.quarterWidth, wp.quarterHeight
|
||||
}
|
||||
|
||||
// QuarterRect returns a Rect of the size of the quarter images.
|
||||
func (wp *Wallpaper) QuarterRect() render.Rect {
|
||||
return render.NewRect(wp.QuarterSize())
|
||||
}
|
||||
|
||||
// Corner returns the top left corner image.
|
||||
func (wp *Wallpaper) Corner() *image.RGBA {
|
||||
return wp.corner
|
||||
|
|
Loading…
Reference in New Issue
Block a user