Replace GopherWasm with syscall/js

Fixes #857
This commit is contained in:
Hajime Hoshi 2019-05-01 02:15:28 +09:00
parent ab84184b4f
commit 10fb5e33be
12 changed files with 113 additions and 68 deletions

View File

@ -22,9 +22,9 @@ import (
"io"
"io/ioutil"
"math"
"syscall/js"
"time"
"github.com/gopherjs/gopherwasm/js"
"github.com/hajimehoshi/ebiten/audio"
)
@ -198,7 +198,7 @@ func decode(context *audio.Context, buf []byte, try int) (*Stream, error) {
u8 := js.TypedArrayOf(buf)
a := u8.Get("buffer").Call("slice", u8.Get("byteOffset"), u8.Get("byteOffset").Int()+u8.Get("byteLength").Int())
oc.Call("decodeAudioData", a, js.NewCallback(func(args []js.Value) {
succeeded := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
buf := args[0]
s.leftData = float32ArrayToSlice(buf.Call("getChannelData", 0))
switch n := buf.Get("numberOfChannels").Int(); n {
@ -211,7 +211,11 @@ func decode(context *audio.Context, buf []byte, try int) (*Stream, error) {
default:
ch <- fmt.Errorf("audio/mp3: number of channels must be 1 or 2 but %d", n)
}
}), js.NewCallback(func(args []js.Value) {
return nil
})
defer succeeded.Release()
failed := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
err := args[0]
if err != js.Null() || err != js.Undefined() {
ch <- fmt.Errorf("audio/mp3: decodeAudioData failed: %v", err)
@ -220,7 +224,11 @@ func decode(context *audio.Context, buf []byte, try int) (*Stream, error) {
// from the next frame (#438).
ch <- errTryAgain
}
}))
return nil
})
defer failed.Release()
oc.Call("decodeAudioData", a, succeeded, failed)
u8.Release()
timeout := time.Duration(math.Pow(2, float64(try))) * time.Second

View File

@ -17,8 +17,7 @@ package stb
import (
"fmt"
"io"
"github.com/gopherjs/gopherwasm/js"
"syscall/js"
)
var flatten = js.Global().Get("window").Call("eval", `(function(arr) {
@ -90,21 +89,18 @@ func DecodeVorbis(buf []byte) (*Samples, int, int, error) {
samples := &Samples{}
sampleRate := 0
var f js.Callback
f = js.NewCallback(func(args []js.Value) {
f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
r := args[0]
if e := r.Get("error"); e != js.Null() {
ch <- fmt.Errorf("audio/vorbis/internal/stb: decode error: %s", e.String())
close(ch)
f.Release()
return
return nil
}
if r.Get("eof").Bool() {
close(ch)
f.Release()
return
return nil
}
if samples.channels == 0 {
@ -117,7 +113,7 @@ func DecodeVorbis(buf []byte) (*Samples, int, int, error) {
flattened := flatten.Invoke(r.Get("data"))
if flattened.Length() == 0 {
return
return nil
}
s := make([]float32, flattened.Length())
@ -127,7 +123,9 @@ func DecodeVorbis(buf []byte) (*Samples, int, int, error) {
samples.samples = append(samples.samples, s)
samples.lengthInSamples += int64(len(s)) / int64(samples.channels)
return nil
})
defer f.Release()
arr := js.TypedArrayOf(buf)
js.Global().Get("stbvorbis").Call("decode", arr, f)

View File

@ -20,8 +20,7 @@ import (
"bytes"
"errors"
"fmt"
"github.com/gopherjs/gopherwasm/js"
"syscall/js"
)
type file struct {
@ -39,23 +38,25 @@ func OpenFile(path string) (ReadSeekCloser, error) {
req := js.Global().Get("XMLHttpRequest").New()
req.Call("open", "GET", path, true)
req.Set("responseType", "arraybuffer")
loadCallback := js.NewCallback(func([]js.Value) {
loadf := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
defer close(ch)
status := req.Get("status").Int()
if 200 <= status && status < 400 {
content = req.Get("response")
return
return nil
}
err = errors.New(fmt.Sprintf("http error: %d", status))
return nil
})
defer loadCallback.Release()
req.Call("addEventListener", "load", loadCallback)
errorCallback := js.NewCallback(func([]js.Value) {
defer loadf.Release()
req.Call("addEventListener", "load", loadf)
errorf := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
defer close(ch)
err = errors.New(fmt.Sprintf("XMLHttpRequest error: %s", req.Get("statusText").String()))
return nil
})
req.Call("addEventListener", "error", errorCallback)
defer errorCallback.Release()
req.Call("addEventListener", "error", errorf)
defer errorf.Release()
req.Call("send")
<-ch
if err != nil {

View File

@ -23,10 +23,9 @@ import (
_ "image/jpeg"
"log"
"math"
"syscall/js"
"time"
"github.com/gopherjs/gopherwasm/js"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
"github.com/hajimehoshi/ebiten/examples/resources/images"

4
go.mod
View File

@ -4,10 +4,10 @@ require (
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f
github.com/gofrs/flock v0.7.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/gopherjs/gopherwasm v1.1.0
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c
github.com/hajimehoshi/bitmapfont v1.1.1
github.com/hajimehoshi/go-mp3 v0.2.0
github.com/hajimehoshi/oto v0.3.4-0.20190430120619-1c8ecbb2424a
github.com/hajimehoshi/oto v0.3.4-0.20190501045152-031fb1b9274d
github.com/jakecoffman/cp v0.1.0
github.com/jfreymuth/oggvorbis v1.0.0
github.com/jfreymuth/vorbis v1.0.0 // indirect

6
go.sum
View File

@ -5,8 +5,8 @@ github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ=
@ -18,8 +18,8 @@ github.com/hajimehoshi/go-mp3 v0.1.2/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6g
github.com/hajimehoshi/go-mp3 v0.2.0 h1:isy34iDg+96PsNuFbTdRRXzKr6a1gc2nhsPuFSfXacY=
github.com/hajimehoshi/go-mp3 v0.2.0/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
github.com/hajimehoshi/oto v0.3.4-0.20190430120619-1c8ecbb2424a h1:mNlGg4s1p2aifTjhMFn9dtTPYW+MTxKFQjWvm9DZ11U=
github.com/hajimehoshi/oto v0.3.4-0.20190430120619-1c8ecbb2424a/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM=
github.com/hajimehoshi/oto v0.3.4-0.20190501045152-031fb1b9274d h1:C2A7WySl23l4SD7FL1wnr2fk7TWtTf7W30pDDmpsZB4=
github.com/hajimehoshi/oto v0.3.4-0.20190501045152-031fb1b9274d/go.mod h1:rj1gPEbAKUBdaV4JiWw2bqUpHtYxBemL7pC5U8rxkO4=
github.com/jakecoffman/cp v0.1.0 h1:sgSYEGUgfwiT447fRjloa2c5b6UyYP+7muR3gQK+Ep0=
github.com/jakecoffman/cp v0.1.0/go.mod h1:a3xPx9N8RyFAACD644t2dj/nK4SuLg1v+jL61m2yVo4=
github.com/jfreymuth/oggvorbis v1.0.0 h1:aOpiihGrFLXpsh2osOlEvTcg5/aluzGQeC7m3uYWOZ0=

View File

@ -17,9 +17,8 @@
package clock
import (
"syscall/js"
"time"
"github.com/gopherjs/gopherwasm/js"
)
func now() int64 {

View File

@ -17,7 +17,7 @@
package devicescale
import (
"github.com/gopherjs/gopherwasm/js"
"syscall/js"
)
func impl(x, y int) float64 {

View File

@ -19,8 +19,7 @@ package opengl
import (
"errors"
"fmt"
"github.com/gopherjs/gopherwasm/js"
"syscall/js"
"github.com/hajimehoshi/ebiten/internal/graphics"
)

View File

@ -17,10 +17,9 @@
package js
import (
"syscall/js"
"unicode"
"github.com/gopherjs/gopherwasm/js"
"github.com/hajimehoshi/ebiten/internal/driver"
)

View File

@ -21,8 +21,7 @@ import (
"log"
"runtime"
"strconv"
"github.com/gopherjs/gopherwasm/js"
"syscall/js"
"github.com/hajimehoshi/ebiten/internal/devicescale"
"github.com/hajimehoshi/ebiten/internal/driver"
@ -226,28 +225,30 @@ func (u *UserInterface) loop(context driver.UIContext) <-chan error {
u.context = context
ch := make(chan error)
var cf js.Callback
f := func([]js.Value) {
var cf js.Func
f := func(this js.Value, args []js.Value) interface{} {
if u.contextLost {
requestAnimationFrame.Invoke(cf)
return
return nil
}
if err := u.update(); err != nil {
ch <- err
close(ch)
return
return nil
}
if u.vsync {
requestAnimationFrame.Invoke(cf)
} else {
setTimeout.Invoke(cf, 0)
}
return nil
}
cf = js.NewCallback(f)
// TODO: Should cf be released after the game ends?
cf = js.FuncOf(f)
// Call f asyncly to be async since ch is used in f.
go func() {
f(nil)
f(js.Value{}, nil)
}()
return ch
}
@ -255,38 +256,43 @@ func (u *UserInterface) loop(context driver.UIContext) <-chan error {
func init() {
if document.Get("body") == js.Null() {
ch := make(chan struct{})
window.Call("addEventListener", "load", js.NewCallback(func([]js.Value) {
window.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
close(ch)
return nil
}))
<-ch
}
window.Call("addEventListener", "focus", js.NewCallback(func([]js.Value) {
window.Call("addEventListener", "focus", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
theUI.windowFocus = true
if theUI.suspended() {
theUI.context.SuspendAudio()
} else {
theUI.context.ResumeAudio()
}
return nil
}))
window.Call("addEventListener", "blur", js.NewCallback(func([]js.Value) {
window.Call("addEventListener", "blur", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
theUI.windowFocus = false
if theUI.suspended() {
theUI.context.SuspendAudio()
} else {
theUI.context.ResumeAudio()
}
return nil
}))
document.Call("addEventListener", "visibilitychange", js.NewCallback(func([]js.Value) {
document.Call("addEventListener", "visibilitychange", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
theUI.pageVisible = !document.Get("hidden").Bool()
if theUI.suspended() {
theUI.context.SuspendAudio()
} else {
theUI.context.ResumeAudio()
}
return nil
}))
window.Call("addEventListener", "resize", js.NewCallback(func([]js.Value) {
window.Call("addEventListener", "resize", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
theUI.updateScreenSize()
return nil
}))
// Adjust the initial scale to 1.
@ -318,8 +324,9 @@ func init() {
// TODO: This is OK as long as the game is in an independent iframe.
// What if the canvas is embedded in a HTML directly?
document.Get("body").Call("addEventListener", "click", js.NewCallback(func([]js.Value) {
document.Get("body").Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
canvas.Call("focus")
return nil
}))
canvasStyle := canvas.Get("style")
@ -330,57 +337,93 @@ func init() {
canvas.Get("style").Set("outline", "none")
// Keyboard
// Don't 'preventDefault' on keydown events or keypress events wouldn't work (#715).
canvas.Call("addEventListener", "keydown", js.NewEventCallback(0, func(e js.Value) {
canvas.Call("addEventListener", "keydown", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
// Don't 'preventDefault' on keydown events or keypress events wouldn't work (#715).
theUI.input.Update(e)
return nil
}))
canvas.Call("addEventListener", "keypress", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "keypress", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
canvas.Call("addEventListener", "keyup", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "keyup", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
// Mouse
canvas.Call("addEventListener", "mousedown", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "mousedown", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
canvas.Call("addEventListener", "mouseup", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "mouseup", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
canvas.Call("addEventListener", "mousemove", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "mousemove", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
canvas.Call("addEventListener", "wheel", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "wheel", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
// Touch
canvas.Call("addEventListener", "touchstart", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "touchstart", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
canvas.Call("addEventListener", "touchend", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "touchend", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
canvas.Call("addEventListener", "touchmove", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
canvas.Call("addEventListener", "touchmove", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.input.Update(e)
return nil
}))
// Gamepad
window.Call("addEventListener", "gamepadconnected", js.NewCallback(func(e []js.Value) {
window.Call("addEventListener", "gamepadconnected", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// Do nothing.
return nil
}))
canvas.Call("addEventListener", "contextmenu", js.NewEventCallback(js.PreventDefault, func(js.Value) {
// Do nothing.
canvas.Call("addEventListener", "contextmenu", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
return nil
}))
// Context
canvas.Call("addEventListener", "webglcontextlost", js.NewEventCallback(js.PreventDefault, func(js.Value) {
canvas.Call("addEventListener", "webglcontextlost", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
e := args[0]
e.Call("preventDefault")
theUI.contextLost = true
return nil
}))
canvas.Call("addEventListener", "webglcontextrestored", js.NewCallback(func(e []js.Value) {
canvas.Call("addEventListener", "webglcontextrestored", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
theUI.contextLost = false
return nil
}))
}

View File

@ -19,8 +19,7 @@ package web
import (
"runtime"
"strings"
"github.com/gopherjs/gopherwasm/js"
"syscall/js"
)
func IsGopherJS() bool {