351 - add progress handle and helper functions to ScreenLoadHandler:OnLoad to provide ability to asynchronously load data and animate the loading screen (#445)

Co-authored-by: carrelda@Davids-MacBook-Pro.local <carrelda@Davids-MacBook-Pro.local>
This commit is contained in:
David Carrell 2020-06-24 17:46:03 -05:00 committed by GitHub
parent b60465fd6c
commit 390f6a1234
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 117 additions and 37 deletions

View File

@ -44,6 +44,7 @@ func SetLayout(layout *Layout) {
singleton.SetLayout(layout)
}
// ShowLoadScreen renders the loading progress screen. The provided progress argument defines the loading animation's state in the range `[0, 1]`, where `0` is initial frame and `1` is the final frame
func ShowLoadScreen(progress float64) {
verifyWasInit()
singleton.showLoadScreen(progress)

View File

@ -1,6 +1,8 @@
package d2screen
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
@ -9,7 +11,10 @@ import (
type Screen interface{}
type ScreenLoadHandler interface {
OnLoad() error
// OnLoad performs all necessary loading to prepare a screen to be shown such as loading assets, placing and binding
// of ui elements, etc. This loading is done asynchronously. The provided channel will allow implementations to
// provide progress via Error, Progress, or Done
OnLoad(loading LoadingState)
}
type ScreenUnloadHandler interface {
@ -27,6 +32,7 @@ type ScreenAdvanceHandler interface {
var singleton struct {
nextScreen Screen
loadingScreen Screen
loadingState LoadingState
currentScreen Screen
}
@ -35,7 +41,25 @@ func SetNextScreen(screen Screen) {
}
func Advance(elapsed float64) error {
if singleton.nextScreen != nil {
switch {
case singleton.loadingScreen != nil:
// this call blocks execution and could lead to deadlock if a screen implements OnLoad incorreclty
load, ok := <-singleton.loadingState.updates
if !ok {
log.Println("loadingState chan should not be closed while in a loading screen")
}
if load.err != nil {
log.Printf("PROBLEM LOADING THE SCREEN: %v", load.err)
return load.err
}
d2gui.ShowLoadScreen(load.progress)
if load.done {
singleton.currentScreen = singleton.loadingScreen
singleton.loadingScreen = nil
d2gui.ShowCursor()
d2gui.HideLoadScreen()
}
case singleton.nextScreen != nil:
if handler, ok := singleton.currentScreen.(ScreenUnloadHandler); ok {
if err := handler.OnUnload(); err != nil {
return err
@ -45,28 +69,19 @@ func Advance(elapsed float64) error {
d2ui.Reset()
d2gui.SetLayout(nil)
if _, ok := singleton.nextScreen.(ScreenLoadHandler); ok {
if handler, ok := singleton.nextScreen.(ScreenLoadHandler); ok {
d2gui.ShowLoadScreen(0)
d2gui.HideCursor()
singleton.loadingState = LoadingState{updates: make(chan loadingUpdate)}
go handler.OnLoad(singleton.loadingState)
singleton.currentScreen = nil
singleton.loadingScreen = singleton.nextScreen
} else {
singleton.currentScreen = singleton.nextScreen
singleton.loadingScreen = nil
}
singleton.nextScreen = nil
} else if singleton.loadingScreen != nil {
handler := singleton.loadingScreen.(ScreenLoadHandler)
if err := handler.OnLoad(); err != nil {
return err
}
singleton.currentScreen = singleton.loadingScreen
singleton.loadingScreen = nil
d2gui.ShowCursor()
d2gui.HideLoadScreen()
} else if singleton.currentScreen != nil {
case singleton.currentScreen != nil:
if handler, ok := singleton.currentScreen.(ScreenAdvanceHandler); ok {
if err := handler.Advance(elapsed); err != nil {
return err
@ -86,3 +101,32 @@ func Render(surface d2render.Surface) error {
return nil
}
type LoadingState struct {
updates chan loadingUpdate
}
type loadingUpdate struct {
progress float64
err error
done bool
}
// Error provides a way for callers to report an error during loading.
// This is meant to be delivered via the progress channel in OnLoad implementations.
func (l *LoadingState) Error(err error) {
l.updates <- loadingUpdate{err: err}
}
// Progress provides a way for callers to report the ratio between `0` and `1` of the progress made loading a screen.
// This is meant to be delivered via the progress channel in OnLoad implementations.
func (l *LoadingState) Progress(ratio float64) {
l.updates <- loadingUpdate{progress: ratio}
}
// Done provides a way for callers to report that screen loading has been completed.
// This is meant to be delivered via the progress channel in OnLoad implementations.
func (l *LoadingState) Done() {
l.updates <- loadingUpdate{progress: 1.0}
l.updates <- loadingUpdate{done: true}
}

View File

@ -3,6 +3,7 @@ package d2gamescreen
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2video"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
)
type BlizzardIntro struct {
@ -13,12 +14,15 @@ func CreateBlizzardIntro() *BlizzardIntro {
return &BlizzardIntro{}
}
func (v *BlizzardIntro) OnLoad() error {
func (v *BlizzardIntro) OnLoad(loading d2screen.LoadingState) {
videoBytes, err := d2asset.LoadFile("/data/local/video/BlizNorth640x480.bik")
if err != nil {
return err
loading.Error(err)
return
}
loading.Progress(0.5)
v.videoDecoder = d2video.CreateBinkDecoder(videoBytes)
return nil
loading.Done()
return
}

View File

@ -55,9 +55,10 @@ func CreateCharacterSelect(connectionType d2clientconnectiontype.ClientConnectio
}
}
func (v *CharacterSelect) OnLoad() error {
func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) {
d2audio.PlayBGM(d2resource.BGMTitle)
d2input.BindHandler(v)
loading.Progress(0.1)
animation, _ := d2asset.LoadAnimation(d2resource.CharacterSelectionBackground, d2resource.PaletteSky)
v.background, _ = d2ui.LoadSprite(animation)
@ -82,6 +83,7 @@ func (v *CharacterSelect) OnLoad() error {
v.exitButton.SetPosition(33, 537)
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
d2ui.AddWidget(&v.exitButton)
loading.Progress(0.2)
v.deleteCharCancelButton = d2ui.CreateButton(d2ui.ButtonTypeOkCancel, "NO")
v.deleteCharCancelButton.SetPosition(282, 308)
@ -103,6 +105,7 @@ func (v *CharacterSelect) OnLoad() error {
v.d2HeroTitle = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits)
v.d2HeroTitle.SetPosition(320, 23)
v.d2HeroTitle.Alignment = d2ui.LabelAlignCenter
loading.Progress(0.3)
v.deleteCharConfirmLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
lines := d2common.SplitIntoLinesWithMaxWidth("Are you sure that you want to delete this character? Take note: this will delete all versions of this Character.", 29)
@ -121,6 +124,7 @@ func (v *CharacterSelect) OnLoad() error {
v.charScrollbar = d2ui.CreateScrollbar(586, 87, 369)
v.charScrollbar.OnActivated(func() { v.onScrollUpdate() })
d2ui.AddWidget(&v.charScrollbar)
loading.Progress(0.5)
for i := 0; i < 8; i++ {
xOffset := 115
@ -138,7 +142,7 @@ func (v *CharacterSelect) OnLoad() error {
}
v.refreshGameStates()
return nil
loading.Done()
}
func (v *CharacterSelect) onScrollUpdate() {

View File

@ -63,28 +63,35 @@ func (v *Credits) LoadContributors() []string {
return contributors
}
// Load is called to load the resources for the credits screen
func (v *Credits) OnLoad() error {
// OnLoad is called to load the resources for the credits screen
func (v *Credits) OnLoad(loading d2screen.LoadingState) {
animation, _ := d2asset.LoadAnimation(d2resource.CreditsBackground, d2resource.PaletteSky)
v.creditsBackground, _ = d2ui.LoadSprite(animation)
v.creditsBackground.SetPosition(0, 0)
loading.Progress(0.2)
v.exitButton = d2ui.CreateButton(d2ui.ButtonTypeMedium, "EXIT")
v.exitButton.SetPosition(33, 543)
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
d2ui.AddWidget(&v.exitButton)
loading.Progress(0.4)
fileData, err := d2asset.LoadFile(d2resource.CreditsText)
if err != nil {
return err
loading.Error(err)
return
}
loading.Progress(0.6)
creditData, _ := d2common.Utf16BytesToString(fileData[2:])
v.creditsText = strings.Split(creditData, "\r\n")
for i := range v.creditsText {
v.creditsText[i] = strings.Trim(v.creditsText[i], " ")
}
loading.Progress(0.8)
v.creditsText = append(v.LoadContributors(), v.creditsText...)
return nil
loading.Done()
}
// Render renders the credits screen

View File

@ -4,6 +4,8 @@ import (
"fmt"
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
@ -40,9 +42,9 @@ func CreateGame(gameClient *d2client.GameClient) *Game {
return result
}
func (v *Game) OnLoad() error {
func (v *Game) OnLoad(loading d2screen.LoadingState) {
d2audio.PlayBGM("")
return nil
loading.Done()
}
func (v *Game) OnUnload() error {

View File

@ -3,6 +3,7 @@ package d2gamescreen
import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
)
type GuiTestMain struct{}
@ -11,8 +12,9 @@ func CreateGuiTestMain() *GuiTestMain {
return &GuiTestMain{}
}
func (g *GuiTestMain) OnLoad() error {
func (g *GuiTestMain) OnLoad(loading d2screen.LoadingState) {
layout := d2gui.CreateLayout(d2gui.PositionTypeHorizontal)
loading.Progress(0.3)
//
layoutLeft := layout.AddLayout(d2gui.PositionTypeVertical)
layoutLeft.SetHorizontalAlign(d2gui.HorizontalAlignCenter)
@ -23,6 +25,7 @@ func (g *GuiTestMain) OnLoad() error {
layoutLeft.AddLabel("FontStyleFormal10Static", d2gui.FontStyleFormal10Static)
layoutLeft.AddLabel("FontStyleFormal11Units", d2gui.FontStyleFormal11Units)
layoutLeft.AddLabel("FontStyleFormal12Static", d2gui.FontStyleFormal12Static)
loading.Progress(0.6)
layout.AddSpacerDynamic()
@ -33,11 +36,12 @@ func (g *GuiTestMain) OnLoad() error {
layoutRight.AddButton("OkCancel", d2gui.ButtonStyleOkCancel)
layoutRight.AddButton("Short", d2gui.ButtonStyleShort)
layoutRight.AddButton("Wide", d2gui.ButtonStyleWide)
loading.Progress(0.9)
layout.SetVerticalAlign(d2gui.VerticalAlignMiddle)
d2gui.SetLayout(layout)
return nil
loading.Done()
}
func (g *GuiTestMain) Render(screen d2render.Surface) error {

View File

@ -80,8 +80,9 @@ func CreateMainMenu() *MainMenu {
}
// Load is called to load the resources for the main menu
func (v *MainMenu) OnLoad() error {
func (v *MainMenu) OnLoad(loading d2screen.LoadingState) {
d2audio.PlayBGM(d2resource.BGMTitle)
loading.Progress(0.2)
v.versionLabel = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.versionLabel.Alignment = d2ui.LabelAlignRight
@ -100,6 +101,7 @@ func (v *MainMenu) OnLoad() error {
v.copyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment")
v.copyrightLabel.Color = color.RGBA{R: 188, G: 168, B: 140, A: 255}
v.copyrightLabel.SetPosition(400, 500)
loading.Progress(0.3)
v.copyrightLabel2 = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic)
v.copyrightLabel2.Alignment = d2ui.LabelAlignCenter
@ -112,6 +114,7 @@ func (v *MainMenu) OnLoad() error {
v.openDiabloLabel.SetText("OpenDiablo2 is neither developed by, nor endorsed by Blizzard or its parent company Activision")
v.openDiabloLabel.Color = color.RGBA{R: 255, G: 255, B: 140, A: 255}
v.openDiabloLabel.SetPosition(400, 580)
loading.Progress(0.5)
animation, _ := d2asset.LoadAnimation(d2resource.GameSelectScreen, d2resource.PaletteSky)
v.background, _ = d2ui.LoadSprite(animation)
@ -130,6 +133,7 @@ func (v *MainMenu) OnLoad() error {
v.diabloLogoLeft.SetBlend(true)
v.diabloLogoLeft.PlayForward()
v.diabloLogoLeft.SetPosition(400, 120)
loading.Progress(0.6)
animation, _ = d2asset.LoadAnimation(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits)
v.diabloLogoRight, _ = d2ui.LoadSprite(animation)
@ -158,6 +162,7 @@ func (v *MainMenu) OnLoad() error {
v.cinematicsButton = d2ui.CreateButton(d2ui.ButtonTypeShort, "CINEMATICS")
v.cinematicsButton.SetPosition(401, 505)
d2ui.AddWidget(&v.cinematicsButton)
loading.Progress(0.7)
v.singlePlayerButton = d2ui.CreateButton(d2ui.ButtonTypeWide, "SINGLE PLAYER")
v.singlePlayerButton.SetPosition(264, 290)
@ -203,6 +208,7 @@ func (v *MainMenu) OnLoad() error {
v.btnTcpIpJoinGame.SetPosition(264, 320)
v.btnTcpIpJoinGame.OnActivated(func() { v.onTcpIpJoinGameClicked() })
d2ui.AddWidget(&v.btnTcpIpJoinGame)
loading.Progress(0.8)
v.tcpIpOptionsLabel = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits)
v.tcpIpOptionsLabel.SetPosition(400, 23)
@ -224,6 +230,7 @@ func (v *MainMenu) OnLoad() error {
v.tcpJoinGameEntry.SetPosition(318, 245)
v.tcpJoinGameEntry.SetFilter("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._:")
d2ui.AddWidget(&v.tcpJoinGameEntry)
loading.Progress(0.9)
v.btnServerIpCancel = d2ui.CreateButton(d2ui.ButtonTypeOkCancel, "CANCEL")
v.btnServerIpCancel.SetPosition(285, 305)
@ -243,7 +250,7 @@ func (v *MainMenu) OnLoad() error {
d2input.BindHandler(v)
return nil
loading.Done()
}
func (v *MainMenu) onMapTestClicked() {

View File

@ -143,13 +143,16 @@ func (met *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) {
met.mapRenderer.MoveCameraTo(met.mapRenderer.WorldToOrtho(met.mapEngine.GetCenterPosition()))
}
func (met *MapEngineTest) OnLoad() error {
func (met *MapEngineTest) OnLoad(loading d2screen.LoadingState) {
d2input.BindHandler(met)
loading.Progress(0.2)
met.mapEngine = d2mapengine.CreateMapEngine()
loading.Progress(0.5)
met.mapRenderer = d2maprenderer.CreateMapRenderer(met.mapEngine)
loading.Progress(0.7)
met.LoadRegionByIndex(met.currentRegion, met.levelPreset, met.fileIndex)
return nil
loading.Done()
}
func (met *MapEngineTest) OnUnload() error {

View File

@ -78,8 +78,9 @@ func CreateSelectHeroClass(connectionType d2clientconnectiontype.ClientConnectio
return result
}
func (v *SelectHeroClass) OnLoad() error {
func (v *SelectHeroClass) OnLoad(loading d2screen.LoadingState) {
d2audio.PlayBGM(d2resource.BGMTitle)
loading.Progress(0.1)
v.bgImage = loadSprite(d2resource.CharacterSelectBackground, d2resource.PaletteFechar)
v.bgImage.SetPosition(0, 0)
@ -97,6 +98,7 @@ func (v *SelectHeroClass) OnLoad() error {
v.heroDesc1Label = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc1Label.Alignment = d2ui.LabelAlignCenter
v.heroDesc1Label.SetPosition(400, 100)
loading.Progress(0.3)
v.heroDesc2Label = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc2Label.Alignment = d2ui.LabelAlignCenter
@ -128,6 +130,7 @@ func (v *SelectHeroClass) OnLoad() error {
v.heroNameLabel.Color = color.RGBA{R: 216, G: 196, B: 128, A: 255}
v.heroNameLabel.SetText("Character Name")
v.heroNameLabel.SetPosition(321, 475)
loading.Progress(0.4)
v.heroNameTextbox = d2ui.CreateTextbox()
v.heroNameTextbox.SetPosition(318, 493)
@ -155,6 +158,7 @@ func (v *SelectHeroClass) OnLoad() error {
v.hardcoreCharLabel.Color = color.RGBA{R: 216, G: 196, B: 128, A: 255}
v.hardcoreCharLabel.SetText("Hardcore")
v.hardcoreCharLabel.SetPosition(339, 548)
loading.Progress(0.5)
v.heroRenderInfo[d2enum.HeroBarbarian] = &HeroRenderInfo{
d2enum.HeroStanceIdle,
@ -234,6 +238,7 @@ func (v *SelectHeroClass) OnLoad() error {
v.heroRenderInfo[d2enum.HeroSorceress].BackWalkSpriteOverlay.PlayForward()
v.heroRenderInfo[d2enum.HeroSorceress].BackWalkSpriteOverlay.SetPlayLengthMs(1200)
v.heroRenderInfo[d2enum.HeroSorceress].BackWalkSpriteOverlay.SetPlayLoop(false)
loading.Progress(0.6)
v.heroRenderInfo[d2enum.HeroNecromancer] = &HeroRenderInfo{
d2enum.HeroStanceIdle,
@ -314,6 +319,7 @@ func (v *SelectHeroClass) OnLoad() error {
v.heroRenderInfo[d2enum.HeroPaladin].BackWalkSprite.PlayForward()
v.heroRenderInfo[d2enum.HeroPaladin].BackWalkSprite.SetPlayLengthMs(1300)
v.heroRenderInfo[d2enum.HeroPaladin].BackWalkSprite.SetPlayLoop(false)
loading.Progress(0.7)
v.heroRenderInfo[d2enum.HeroAmazon] = &HeroRenderInfo{
d2enum.HeroStanceIdle,
@ -378,6 +384,7 @@ func (v *SelectHeroClass) OnLoad() error {
v.heroRenderInfo[d2enum.HeroAssassin].BackWalkSprite.PlayForward()
v.heroRenderInfo[d2enum.HeroAssassin].BackWalkSprite.SetPlayLengthMs(1500)
v.heroRenderInfo[d2enum.HeroAssassin].BackWalkSprite.SetPlayLoop(false)
loading.Progress(0.8)
v.heroRenderInfo[d2enum.HeroDruid] = &HeroRenderInfo{
d2enum.HeroStanceIdle,
@ -411,7 +418,7 @@ func (v *SelectHeroClass) OnLoad() error {
v.heroRenderInfo[d2enum.HeroDruid].BackWalkSprite.SetPlayLengthMs(1500)
v.heroRenderInfo[d2enum.HeroDruid].BackWalkSprite.SetPlayLoop(false)
return nil
loading.Done()
}
func (v *SelectHeroClass) OnUnload() error {

View File

@ -49,7 +49,6 @@ func NewEscapeMenu() *EscapeMenu {
}
}
// ScreenLoadHandler
func (m *EscapeMenu) OnLoad() error {
m.labels = []d2ui.Label{
d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteSky),
@ -82,7 +81,6 @@ func (m *EscapeMenu) OnLoad() error {
return nil
}
// ScreenRenderHandler
func (m *EscapeMenu) Render(target d2render.Surface) error {
if !m.isOpen {
return nil
@ -113,7 +111,6 @@ func (m *EscapeMenu) Render(target d2render.Surface) error {
return nil
}
// ScreenAdvanceHandler
func (m *EscapeMenu) Advance(elapsed float64) error {
if !m.isOpen {
return nil