Noah Petherbridge
c5353df211
Instead of the loadscreen eager-loading ALL level chunks to Go Images, only load the chunks within the "LoadingViewport" - which is the on-screen Viewport plus a margin of chunks off the screen edges. During gameplay, every few ticks, reevaluate which chunks are inside or outside the LoadingViewport; for chunks outside, free their SDL2 textures and free their cached bitmaps to keep overall memory usage down. The AzulianTag-Forest level now stays under 200 Textures at any given time and the loadscreen goes faster as it doesn't have to load every chunk's images up front. The LoadUnloadChunk feature can be turned on/off with feature flags. If disabled the old behavior is restored: loadscreen loads all images and the LoadUnloadChunks function is not run. Other changes: * loadscreen: do not free textures in the Hide() function as this runs on a different goroutine and may break. The 4 wallpaper textures are OK to keep in memory anyway, the loadscreen is reused often! * Free more leaked textures: on the Inventory frame and when an actor calls Self.Destroy() * Stop leaking goroutines in the PubSub feature of the doodad script engine; scripting.Supervisor.Teardown() sends a stop signal to all scripts to clean up neatly. Canvas.Destroy() tears down its scripting supervisor automatically.
261 lines
6.1 KiB
Go
261 lines
6.1 KiB
Go
// Package loadscreen implements a modal "Loading" screen for the game, which
|
|
// can be shown or hidden by gameplay scenes as needed.
|
|
package loadscreen
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"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"
|
|
"git.kirsle.net/go/ui"
|
|
)
|
|
|
|
// Configuration values.
|
|
const (
|
|
ProgressWidth = 300
|
|
ProgressHeight = 16
|
|
)
|
|
|
|
// State variables for the loading screen.
|
|
var (
|
|
visible bool
|
|
withProgress bool
|
|
subtitle string // custom subtitle text, SetSubtitle().
|
|
|
|
// Animated title bar
|
|
titleBase = "Loading"
|
|
animState = 0
|
|
animation = []string{
|
|
". ",
|
|
".. ",
|
|
"...",
|
|
" ..",
|
|
" .",
|
|
" ",
|
|
}
|
|
animSpeed uint64 = 32
|
|
titleVar string
|
|
|
|
// UI widgets.
|
|
window *ui.Frame
|
|
canvas *uix.Canvas
|
|
secondary *ui.Label // subtitle text
|
|
progressTrough *ui.Frame
|
|
progressBar *ui.Frame
|
|
progressText *ui.Label
|
|
)
|
|
|
|
// Show the basic loading screen without a progress bar.
|
|
func Show() {
|
|
setup()
|
|
visible = true
|
|
withProgress = false
|
|
subtitle = ""
|
|
}
|
|
|
|
// ShowWithProgress initializes the loading screen with a progress bar starting at zero.
|
|
func ShowWithProgress() {
|
|
setup()
|
|
visible = true
|
|
withProgress = true
|
|
subtitle = ""
|
|
SetProgress(0)
|
|
}
|
|
|
|
// SetSubtitle specifies secondary text beneath the Loading banner.
|
|
// The subtitle is blanked on Show() and ShowWithProgress() and must
|
|
// be specified by the caller if desired. Pass multiple values for
|
|
// multiple lines of text.
|
|
func SetSubtitle(value ...string) {
|
|
subtitle = strings.Join(value, "\n")
|
|
}
|
|
|
|
// IsActive returns whether the loading screen is currently visible.
|
|
func IsActive() bool {
|
|
return visible
|
|
}
|
|
|
|
// Resized the window.
|
|
func Resized() {
|
|
if visible {
|
|
size := render.NewRect(shmem.CurrentRenderEngine.WindowSize())
|
|
window.Resize(size)
|
|
}
|
|
}
|
|
|
|
/*
|
|
Hide the loading screen.
|
|
|
|
NOTICE: the loadscreen is hidden on an async goroutine and it is NOT SAFE to clean up
|
|
textures used by the wallpaper images, but this is OK because the loadscreen uses the
|
|
same wallpaper every time and is called many times during gameplay, it can hold its
|
|
textures.
|
|
*/
|
|
func Hide() {
|
|
visible = false
|
|
}
|
|
|
|
// SetProgress sets the current progress value for loading screens having a progress bar.
|
|
func SetProgress(v float64) {
|
|
// Resize the progress bar in the trough.
|
|
if progressTrough != nil {
|
|
var (
|
|
troughSize = progressTrough.Size()
|
|
height = progressBar.Size().H
|
|
)
|
|
progressBar.Resize(render.Rect{
|
|
W: int(float64(troughSize.W-4) * v),
|
|
H: height,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Common function to initialize the loading screen.
|
|
func setup() {
|
|
if window != nil {
|
|
return
|
|
}
|
|
|
|
titleVar = titleBase + animation[animState]
|
|
|
|
// Create the parent container that will stretch full screen.
|
|
window = ui.NewFrame("Loadscreen Window")
|
|
window.SetBackground(render.RGBA(0, 0, 1, 40)) // makes the wallpaper darker? :/
|
|
|
|
// "Loading" text.
|
|
label := ui.NewLabel(ui.Label{
|
|
TextVariable: &titleVar,
|
|
Font: balance.LoadScreenFont,
|
|
})
|
|
label.Compute(shmem.CurrentRenderEngine)
|
|
window.Place(label, ui.Place{
|
|
Top: 128,
|
|
Center: true,
|
|
})
|
|
|
|
// Subtitle text.
|
|
secondary = ui.NewLabel(ui.Label{
|
|
TextVariable: &subtitle,
|
|
Font: balance.LoadScreenSecondaryFont,
|
|
})
|
|
window.Place(secondary, ui.Place{
|
|
Top: 128 + label.Size().H + 64,
|
|
Center: true,
|
|
})
|
|
|
|
// Progress bar.
|
|
progressTrough = ui.NewFrame("Progress Trough")
|
|
progressTrough.Configure(ui.Config{
|
|
Width: ProgressWidth,
|
|
Height: ProgressHeight,
|
|
BorderSize: 2,
|
|
BorderStyle: ui.BorderSunken,
|
|
Background: render.DarkGrey,
|
|
})
|
|
window.Place(progressTrough, ui.Place{
|
|
// Nestle it between the Title and Subtitle.
|
|
Center: true,
|
|
Top: 128 + label.Size().H + 16,
|
|
})
|
|
|
|
progressBar = ui.NewFrame("Progress Bar")
|
|
progressBar.Configure(ui.Config{
|
|
Width: 0,
|
|
Height: ProgressHeight - 4,
|
|
Background: render.Green,
|
|
})
|
|
progressTrough.Pack(progressBar, ui.Pack{
|
|
Side: ui.W,
|
|
})
|
|
}
|
|
|
|
// Loop is called on every game loop. If the loadscreen is not active, nothing happens.
|
|
// Otherwise the loading screen UI is drawn to screen.
|
|
func Loop(windowSize render.Rect, e render.Engine) {
|
|
if !visible {
|
|
return
|
|
}
|
|
|
|
if window != nil {
|
|
// Initialize the wallpaper canvas?
|
|
if canvas == nil {
|
|
canvas = uix.NewCanvas(128, false)
|
|
canvas.LoadLevel(&level.Level{
|
|
Chunker: level.NewChunker(100),
|
|
Palette: level.NewPalette(),
|
|
PageType: level.Bounded,
|
|
Wallpaper: "blueprint.png",
|
|
})
|
|
}
|
|
canvas.Resize(windowSize)
|
|
canvas.Compute(e)
|
|
canvas.Present(e, render.Origin)
|
|
|
|
window.Resize(windowSize)
|
|
window.Compute(e)
|
|
window.Present(e, render.Origin)
|
|
|
|
// Show/hide the progress bar.
|
|
progressTrough.Compute(e)
|
|
if withProgress && progressTrough.Hidden() {
|
|
progressTrough.Show()
|
|
} else if !withProgress && !progressTrough.Hidden() {
|
|
progressTrough.Hide()
|
|
}
|
|
|
|
// Show/hide the subtitle text.
|
|
if len(subtitle) > 0 && secondary.Hidden() {
|
|
secondary.Show()
|
|
} else if subtitle == "" && !secondary.Hidden() {
|
|
secondary.Hide()
|
|
}
|
|
|
|
// Animate the ellipses.
|
|
if shmem.Tick%animSpeed == 0 {
|
|
titleVar = titleBase + animation[animState]
|
|
animState++
|
|
if animState >= len(animation) {
|
|
animState = 0
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// PreloadAllChunkBitmaps is a helper function to eager cache all bitmap
|
|
// images of the chunks in a level drawing. It is designed to work with the
|
|
// loading screen and will set the Progress percent based on the total number
|
|
// of chunks vs. chunks remaining to pre-cache bitmaps from.
|
|
func PreloadAllChunkBitmaps(chunker *level.Chunker) {
|
|
// If we're using the smarter (experimental) chunk loader, return.
|
|
if balance.Feature.LoadUnloadChunk {
|
|
return
|
|
}
|
|
|
|
loadChunksTarget := len(chunker.Chunks)
|
|
|
|
// Skipping the eager rendering of chunks?
|
|
if !balance.EagerRenderLevelChunks {
|
|
log.Info("PreloadAllChunkBitmaps: skipping eager render")
|
|
return
|
|
}
|
|
|
|
for {
|
|
remaining := chunker.PrerenderN(10)
|
|
|
|
// Set the load screen progress % based on number of chunks to render.
|
|
if loadChunksTarget > 0 {
|
|
percent := float64(loadChunksTarget-remaining) / float64(loadChunksTarget)
|
|
SetProgress(percent)
|
|
}
|
|
|
|
if remaining == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|