uidriver/js: Ensure that the checking-audio loop is finished when the game is finished

After the game loop is finished, any goroutines should not exist.
Otherwise, 'Go program has already exited' error can happen on
Wasm.

This change ensures that the goroutines are finished when the game
is finished. Note that time.Sleep was required to ensure that the
(*time.Ticker) ends.

Fixes #1027
This commit is contained in:
Hajime Hoshi 2020-02-09 03:39:04 +09:00
parent 0092a05eb7
commit 32471af18f

View File

@ -178,7 +178,10 @@ func (u *UserInterface) update() error {
func (u *UserInterface) loop(context driver.UIContext) <-chan error {
u.context = context
ch := make(chan error)
errCh := make(chan error)
reqStopAudioCh := make(chan struct{})
resStopAudioCh := make(chan struct{})
var cf js.Func
f := func(this js.Value, args []js.Value) interface{} {
if u.contextLost {
@ -187,8 +190,11 @@ func (u *UserInterface) loop(context driver.UIContext) <-chan error {
}
if err := u.update(); err != nil {
ch <- err
close(ch)
close(reqStopAudioCh)
<-resStopAudioCh
errCh <- err
close(errCh)
return nil
}
if u.vsync {
@ -209,18 +215,37 @@ func (u *UserInterface) loop(context driver.UIContext) <-chan error {
// To check the document's visiblity, visibilitychange event should usually be used. However, this event is
// not reliable and sometimes it is not fired (#961). Then, watch the state regularly instead.
go func() {
t := time.NewTicker(100 * time.Millisecond)
defer t.Stop()
for range t.C {
if u.suspended() {
hooks.SuspendAudio()
} else {
hooks.ResumeAudio()
defer close(resStopAudioCh)
const interval = 100 * time.Millisecond
t := time.NewTicker(interval)
defer func() {
t.Stop()
// This is a dirty hack. (*time.Ticker).Stop() just marks the timer 'deleted' [1] and
// something might run even after Stop. On Wasm, this causes an issue to execute Go program
// even after finishing (#1027). Sleep for the interval time duration to ensure that
// everything related to the timer is finished.
//
// [1] runtime.deltimer
time.Sleep(interval)
}()
for {
select {
case <-t.C:
if u.suspended() {
hooks.SuspendAudio()
} else {
hooks.ResumeAudio()
}
case <-reqStopAudioCh:
return
}
}
}()
return ch
return errCh
}
func init() {