├── .gitignore ├── bundle.wasm ├── go.mod ├── go.sum ├── makefile ├── README.md ├── index.html ├── main.go ├── gltypes ├── gltypes.go └── arrays.go ├── static └── index.html ├── bundle.go └── wasm_exec.js /.gitignore: -------------------------------------------------------------------------------- 1 | wasm-rotating-cube 2 | .idea 3 | -------------------------------------------------------------------------------- /bundle.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobcob7/wasm-rotating-cube/HEAD/bundle.wasm -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bobcob7/wasm-rotating-cube 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a 7 | golang.org/x/image v0.0.0-20180926015637-991ec62608f3 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a h1:2n5w2v3knlspzjJWyQPC0j88Mwvq0SZV0Jdws34GJwc= 2 | github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a/go.mod h1:dvrdneKbyWbK2skTda0nM4B9zSlS2GZSnjX7itr/skQ= 3 | golang.org/x/image v0.0.0-20180926015637-991ec62608f3 h1:5IfA9fqItkh2alJW94tvQk+6+RF9MW2q9DzwE8DBddQ= 4 | golang.org/x/image v0.0.0-20180926015637-991ec62608f3/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 5 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | GOROOT := $(shell go env GOROOT) 2 | 3 | all: wasm-rotating-cube 4 | 5 | wasm-rotating-cube: output.wasm main.go 6 | go build -o wasm-rotating-cube main.go 7 | 8 | output.wasm: bundle.go wasm_exec.js 9 | GOOS=js GOARCH=wasm go build -o bundle.wasm bundle.go 10 | 11 | run: output.wasm wasm-rotating-cube 12 | ./wasm-rotating-cube 13 | 14 | wasm_exec.js: 15 | cp $(GOROOT)/misc/wasm/wasm_exec.js . 16 | 17 | clean: 18 | rm -f wasm-rotating-cube wasm_exec.js *.wasm 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WASM Rotating Cube 2 | 3 | This project is built on my wasm-basic-triangle project, Check it out for a more basic example. Instead of drawing a 2D triangle, this project draws a 4 | 3D cube and rotates it. 5 | 6 | # Demo 7 | To see this project running check out this link: [https://bobcob7.github.io/wasm-rotating-cube/](https://bobcob7.github.io/wasm-rotating-cube/) 8 | 9 | # Running 10 | To run you can use the makefile with `make run` 11 | 12 | # References: 13 | - https://www.tutorialspoint.com/webgl/webgl_cube_rotation.htm 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 21 | 22 | Your browser doesn't appear to support the canvas tag. 23 | 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "log" 6 | "io/ioutil" 7 | "runtime" 8 | ) 9 | 10 | func main() { 11 | indexData, err := ioutil.ReadFile("static/index.html") 12 | if err != nil { 13 | log.Fatalf("Could not read index file: %s\n", err) 14 | } 15 | 16 | wasmExecLocation := runtime.GOROOT() + "/misc/wasm/wasm_exec.js" 17 | wasmExecData, err := ioutil.ReadFile(wasmExecLocation) 18 | if err != nil { 19 | log.Fatalf("Could not read wasm_exec file: %s\n", err) 20 | } 21 | 22 | wasmData, err := ioutil.ReadFile("bundle.wasm") 23 | if err != nil { 24 | log.Fatalf("Could not read wasm file: %s\n", err) 25 | } 26 | 27 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 28 | w.Write(indexData) 29 | }) 30 | 31 | http.HandleFunc("/wasm_exec.js", func(w http.ResponseWriter, r *http.Request) { 32 | w.Write(wasmExecData) 33 | }) 34 | 35 | http.HandleFunc("/bundle.wasm", func(w http.ResponseWriter, r *http.Request) { 36 | w.Header().Set("Content-Type", "application/wasm") 37 | w.WriteHeader(http.StatusOK) 38 | w.Write(wasmData) 39 | }) 40 | 41 | log.Fatal(http.ListenAndServe(":8080", nil)) 42 | } -------------------------------------------------------------------------------- /gltypes/gltypes.go: -------------------------------------------------------------------------------- 1 | package gltypes 2 | 3 | import "syscall/js" 4 | 5 | // GLTypes provides WebGL bindings. 6 | type GLTypes struct { 7 | StaticDraw js.Value 8 | ArrayBuffer js.Value 9 | ElementArrayBuffer js.Value 10 | VertexShader js.Value 11 | FragmentShader js.Value 12 | Float js.Value 13 | DepthTest js.Value 14 | ColorBufferBit js.Value 15 | DepthBufferBit js.Value 16 | Triangles js.Value 17 | UnsignedShort js.Value 18 | LEqual js.Value 19 | LineLoop js.Value 20 | } 21 | 22 | // New grabs the WebGL bindings from a GL context. 23 | func (types *GLTypes) New(gl js.Value) { 24 | types.StaticDraw = gl.Get("STATIC_DRAW") 25 | types.ArrayBuffer = gl.Get("ARRAY_BUFFER") 26 | types.ElementArrayBuffer = gl.Get("ELEMENT_ARRAY_BUFFER") 27 | types.VertexShader = gl.Get("VERTEX_SHADER") 28 | types.FragmentShader = gl.Get("FRAGMENT_SHADER") 29 | types.Float = gl.Get("FLOAT") 30 | types.DepthTest = gl.Get("DEPTH_TEST") 31 | types.ColorBufferBit = gl.Get("COLOR_BUFFER_BIT") 32 | types.Triangles = gl.Get("TRIANGLES") 33 | types.UnsignedShort = gl.Get("UNSIGNED_SHORT") 34 | types.LEqual = gl.Get("LEQUAL") 35 | types.DepthBufferBit = gl.Get("DEPTH_BUFFER_BIT") 36 | types.LineLoop = gl.Get("LINE_LOOP") 37 | } 38 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | WASM Basic Triangle 4 | 5 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 45 | 46 | -------------------------------------------------------------------------------- /gltypes/arrays.go: -------------------------------------------------------------------------------- 1 | package gltypes 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "syscall/js" 8 | "unsafe" 9 | ) 10 | 11 | // Shamelessly stolen from: https://github.com/golang/go/issues/32402 12 | // Thanks hajimehoshi 13 | func sliceToByteSlice(s interface{}) []byte { 14 | switch s := s.(type) { 15 | case []int8: 16 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 17 | return *(*[]byte)(unsafe.Pointer(h)) 18 | case []int16: 19 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 20 | h.Len *= 2 21 | h.Cap *= 2 22 | return *(*[]byte)(unsafe.Pointer(h)) 23 | case []int32: 24 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 25 | h.Len *= 4 26 | h.Cap *= 4 27 | return *(*[]byte)(unsafe.Pointer(h)) 28 | case []int64: 29 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 30 | h.Len *= 8 31 | h.Cap *= 8 32 | return *(*[]byte)(unsafe.Pointer(h)) 33 | case []uint8: 34 | return s 35 | case []uint16: 36 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 37 | h.Len *= 2 38 | h.Cap *= 2 39 | return *(*[]byte)(unsafe.Pointer(h)) 40 | case []uint32: 41 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 42 | h.Len *= 4 43 | h.Cap *= 4 44 | return *(*[]byte)(unsafe.Pointer(h)) 45 | case []uint64: 46 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 47 | h.Len *= 8 48 | h.Cap *= 8 49 | return *(*[]byte)(unsafe.Pointer(h)) 50 | case []float32: 51 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 52 | h.Len *= 4 53 | h.Cap *= 4 54 | return *(*[]byte)(unsafe.Pointer(h)) 55 | case []float64: 56 | h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 57 | h.Len *= 8 58 | h.Cap *= 8 59 | return *(*[]byte)(unsafe.Pointer(h)) 60 | default: 61 | panic(fmt.Sprintf("jsutil: unexpected value at sliceToBytesSlice: %T", s)) 62 | } 63 | } 64 | func SliceToTypedArray(s interface{}) js.Value { 65 | switch s := s.(type) { 66 | case []int8: 67 | a := js.Global().Get("Uint8Array").New(len(s)) 68 | js.CopyBytesToJS(a, sliceToByteSlice(s)) 69 | runtime.KeepAlive(s) 70 | buf := a.Get("buffer") 71 | return js.Global().Get("Int8Array").New(buf, a.Get("byteOffset"), a.Get("byteLength")) 72 | case []int16: 73 | a := js.Global().Get("Uint8Array").New(len(s) * 2) 74 | js.CopyBytesToJS(a, sliceToByteSlice(s)) 75 | runtime.KeepAlive(s) 76 | buf := a.Get("buffer") 77 | return js.Global().Get("Int16Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/2) 78 | case []int32: 79 | a := js.Global().Get("Uint8Array").New(len(s) * 4) 80 | js.CopyBytesToJS(a, sliceToByteSlice(s)) 81 | runtime.KeepAlive(s) 82 | buf := a.Get("buffer") 83 | return js.Global().Get("Int32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4) 84 | case []uint8: 85 | a := js.Global().Get("Uint8Array").New(len(s)) 86 | js.CopyBytesToJS(a, s) 87 | runtime.KeepAlive(s) 88 | return a 89 | case []uint16: 90 | a := js.Global().Get("Uint8Array").New(len(s) * 2) 91 | js.CopyBytesToJS(a, sliceToByteSlice(s)) 92 | runtime.KeepAlive(s) 93 | buf := a.Get("buffer") 94 | return js.Global().Get("Uint16Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/2) 95 | case []uint32: 96 | a := js.Global().Get("Uint8Array").New(len(s) * 4) 97 | js.CopyBytesToJS(a, sliceToByteSlice(s)) 98 | runtime.KeepAlive(s) 99 | buf := a.Get("buffer") 100 | return js.Global().Get("Uint32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4) 101 | case []float32: 102 | a := js.Global().Get("Uint8Array").New(len(s) * 4) 103 | js.CopyBytesToJS(a, sliceToByteSlice(s)) 104 | runtime.KeepAlive(s) 105 | buf := a.Get("buffer") 106 | return js.Global().Get("Float32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4) 107 | case []float64: 108 | a := js.Global().Get("Uint8Array").New(len(s) * 8) 109 | js.CopyBytesToJS(a, sliceToByteSlice(s)) 110 | runtime.KeepAlive(s) 111 | buf := a.Get("buffer") 112 | return js.Global().Get("Float64Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/8) 113 | default: 114 | panic(fmt.Sprintf("jsutil: unexpected value at SliceToTypedArray: %T", s)) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /bundle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall/js" 5 | "unsafe" 6 | 7 | "github.com/bobcob7/wasm-rotating-cube/gltypes" 8 | "github.com/go-gl/mathgl/mgl32" 9 | ) 10 | 11 | var ( 12 | width int 13 | height int 14 | gl js.Value 15 | glTypes gltypes.GLTypes 16 | ) 17 | 18 | //// BUFFERS + SHADERS //// 19 | // Shamelessly copied from https://www.tutorialspoint.com/webgl/webgl_cube_rotation.htm // 20 | var verticesNative = []float32{ 21 | -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 22 | -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 23 | -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 24 | 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 25 | -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 26 | -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 27 | } 28 | var colorsNative = []float32{ 29 | 5, 3, 7, 5, 3, 7, 5, 3, 7, 5, 3, 7, 30 | 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 31 | 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 32 | 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 33 | 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 34 | 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 35 | } 36 | var indicesNative = []uint16{ 37 | 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 38 | 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 39 | 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23, 40 | } 41 | 42 | const vertShaderCode = ` 43 | attribute vec3 position; 44 | uniform mat4 Pmatrix; 45 | uniform mat4 Vmatrix; 46 | uniform mat4 Mmatrix; 47 | attribute vec3 color; 48 | varying vec3 vColor; 49 | 50 | void main(void) { 51 | gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.); 52 | vColor = color; 53 | } 54 | ` 55 | const fragShaderCode = ` 56 | precision mediump float; 57 | varying vec3 vColor; 58 | void main(void) { 59 | gl_FragColor = vec4(vColor, 1.); 60 | } 61 | ` 62 | 63 | func main() { 64 | // Init Canvas stuff 65 | doc := js.Global().Get("document") 66 | canvasEl := doc.Call("getElementById", "gocanvas") 67 | width = doc.Get("body").Get("clientWidth").Int() 68 | height = doc.Get("body").Get("clientHeight").Int() 69 | canvasEl.Set("width", width) 70 | canvasEl.Set("height", height) 71 | 72 | gl = canvasEl.Call("getContext", "webgl") 73 | if gl.IsUndefined() { 74 | gl = canvasEl.Call("getContext", "experimental-webgl") 75 | } 76 | // once again 77 | if gl.IsUndefined() { 78 | js.Global().Call("alert", "browser might not support webgl") 79 | return 80 | } 81 | 82 | // Get some WebGL bindings 83 | glTypes.New(gl) 84 | 85 | // Convert buffers to JS TypedArrays 86 | var colors = gltypes.SliceToTypedArray(colorsNative) 87 | var vertices = gltypes.SliceToTypedArray(verticesNative) 88 | var indices = gltypes.SliceToTypedArray(indicesNative) 89 | 90 | // Create vertex buffer 91 | vertexBuffer := gl.Call("createBuffer") 92 | gl.Call("bindBuffer", glTypes.ArrayBuffer, vertexBuffer) 93 | gl.Call("bufferData", glTypes.ArrayBuffer, vertices, glTypes.StaticDraw) 94 | 95 | // Create color buffer 96 | colorBuffer := gl.Call("createBuffer") 97 | gl.Call("bindBuffer", glTypes.ArrayBuffer, colorBuffer) 98 | gl.Call("bufferData", glTypes.ArrayBuffer, colors, glTypes.StaticDraw) 99 | 100 | // Create index buffer 101 | indexBuffer := gl.Call("createBuffer") 102 | gl.Call("bindBuffer", glTypes.ElementArrayBuffer, indexBuffer) 103 | gl.Call("bufferData", glTypes.ElementArrayBuffer, indices, glTypes.StaticDraw) 104 | 105 | //// Shaders //// 106 | 107 | // Create a vertex shader object 108 | vertShader := gl.Call("createShader", glTypes.VertexShader) 109 | gl.Call("shaderSource", vertShader, vertShaderCode) 110 | gl.Call("compileShader", vertShader) 111 | 112 | // Create fragment shader object 113 | fragShader := gl.Call("createShader", glTypes.FragmentShader) 114 | gl.Call("shaderSource", fragShader, fragShaderCode) 115 | gl.Call("compileShader", fragShader) 116 | 117 | // Create a shader program object to store 118 | // the combined shader program 119 | shaderProgram := gl.Call("createProgram") 120 | gl.Call("attachShader", shaderProgram, vertShader) 121 | gl.Call("attachShader", shaderProgram, fragShader) 122 | gl.Call("linkProgram", shaderProgram) 123 | 124 | // Associate attributes to vertex shader 125 | PositionMatrix := gl.Call("getUniformLocation", shaderProgram, "Pmatrix") 126 | ViewMatrix := gl.Call("getUniformLocation", shaderProgram, "Vmatrix") 127 | ModelMatrix := gl.Call("getUniformLocation", shaderProgram, "Mmatrix") 128 | 129 | gl.Call("bindBuffer", glTypes.ArrayBuffer, vertexBuffer) 130 | position := gl.Call("getAttribLocation", shaderProgram, "position") 131 | gl.Call("vertexAttribPointer", position, 3, glTypes.Float, false, 0, 0) 132 | gl.Call("enableVertexAttribArray", position) 133 | 134 | gl.Call("bindBuffer", glTypes.ArrayBuffer, colorBuffer) 135 | color := gl.Call("getAttribLocation", shaderProgram, "color") 136 | gl.Call("vertexAttribPointer", color, 3, glTypes.Float, false, 0, 0) 137 | gl.Call("enableVertexAttribArray", color) 138 | 139 | gl.Call("useProgram", shaderProgram) 140 | 141 | // Set WeebGL properties 142 | gl.Call("clearColor", 0.5, 0.5, 0.5, 0.9) // Color the screen is cleared to 143 | gl.Call("clearDepth", 1.0) // Z value that is set to the Depth buffer every frame 144 | gl.Call("viewport", 0, 0, width, height) // Viewport size 145 | gl.Call("depthFunc", glTypes.LEqual) 146 | 147 | //// Create Matrixes //// 148 | ratio := float32(width) / float32(height) 149 | 150 | // Generate and apply projection matrix 151 | projMatrix := mgl32.Perspective(mgl32.DegToRad(45.0), ratio, 1, 100.0) 152 | var projMatrixBuffer *[16]float32 153 | projMatrixBuffer = (*[16]float32)(unsafe.Pointer(&projMatrix)) 154 | typedProjMatrixBuffer := gltypes.SliceToTypedArray([]float32((*projMatrixBuffer)[:])) 155 | gl.Call("uniformMatrix4fv", PositionMatrix, false, typedProjMatrixBuffer) 156 | 157 | // Generate and apply view matrix 158 | viewMatrix := mgl32.LookAtV(mgl32.Vec3{3.0, 3.0, 3.0}, mgl32.Vec3{0.0, 0.0, 0.0}, mgl32.Vec3{0.0, 1.0, 0.0}) 159 | var viewMatrixBuffer *[16]float32 160 | viewMatrixBuffer = (*[16]float32)(unsafe.Pointer(&viewMatrix)) 161 | typedViewMatrixBuffer := gltypes.SliceToTypedArray([]float32((*viewMatrixBuffer)[:])) 162 | gl.Call("uniformMatrix4fv", ViewMatrix, false, typedViewMatrixBuffer) 163 | 164 | //// Drawing the Cube //// 165 | movMatrix := mgl32.Ident4() 166 | var renderFrame js.Func 167 | var tmark float32 168 | var rotation = float32(0) 169 | 170 | // Bind to element array for draw function 171 | gl.Call("bindBuffer", glTypes.ElementArrayBuffer, indexBuffer) 172 | 173 | renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} { 174 | // Calculate rotation rate 175 | now := float32(args[0].Float()) 176 | tdiff := now - tmark 177 | tmark = now 178 | rotation = rotation + float32(tdiff)/500 179 | 180 | // Do new model matrix calculations 181 | movMatrix = mgl32.HomogRotate3DX(0.5 * rotation) 182 | movMatrix = movMatrix.Mul4(mgl32.HomogRotate3DY(0.3 * rotation)) 183 | movMatrix = movMatrix.Mul4(mgl32.HomogRotate3DZ(0.2 * rotation)) 184 | 185 | // Convert model matrix to a JS TypedArray 186 | var modelMatrixBuffer *[16]float32 187 | modelMatrixBuffer = (*[16]float32)(unsafe.Pointer(&movMatrix)) 188 | typedModelMatrixBuffer := gltypes.SliceToTypedArray([]float32((*modelMatrixBuffer)[:])) 189 | 190 | // Apply the model matrix 191 | gl.Call("uniformMatrix4fv", ModelMatrix, false, typedModelMatrixBuffer) 192 | 193 | // Clear the screen 194 | gl.Call("enable", glTypes.DepthTest) 195 | gl.Call("clear", glTypes.ColorBufferBit) 196 | gl.Call("clear", glTypes.DepthBufferBit) 197 | 198 | // Draw the cube 199 | gl.Call("drawElements", glTypes.Triangles, len(indicesNative), glTypes.UnsignedShort, 0) 200 | 201 | // Call next frame 202 | js.Global().Call("requestAnimationFrame", renderFrame) 203 | 204 | return nil 205 | }) 206 | defer renderFrame.Release() 207 | 208 | js.Global().Call("requestAnimationFrame", renderFrame) 209 | 210 | done := make(chan struct{}, 0) 211 | <-done 212 | } 213 | -------------------------------------------------------------------------------- /wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | (() => { 6 | // Map multiple JavaScript environments to a single common API, 7 | // preferring web standards over Node.js API. 8 | // 9 | // Environments considered: 10 | // - Browsers 11 | // - Node.js 12 | // - Electron 13 | // - Parcel 14 | 15 | if (typeof global !== "undefined") { 16 | // global already exists 17 | } else if (typeof window !== "undefined") { 18 | window.global = window; 19 | } else if (typeof self !== "undefined") { 20 | self.global = self; 21 | } else { 22 | throw new Error("cannot export Go (neither global, window nor self is defined)"); 23 | } 24 | 25 | if (!global.require && typeof require !== "undefined") { 26 | global.require = require; 27 | } 28 | 29 | if (!global.fs && global.require) { 30 | global.fs = require("fs"); 31 | } 32 | 33 | const enosys = () => { 34 | const err = new Error("not implemented"); 35 | err.code = "ENOSYS"; 36 | return err; 37 | }; 38 | 39 | if (!global.fs) { 40 | let outputBuf = ""; 41 | global.fs = { 42 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 43 | writeSync(fd, buf) { 44 | outputBuf += decoder.decode(buf); 45 | const nl = outputBuf.lastIndexOf("\n"); 46 | if (nl != -1) { 47 | console.log(outputBuf.substr(0, nl)); 48 | outputBuf = outputBuf.substr(nl + 1); 49 | } 50 | return buf.length; 51 | }, 52 | write(fd, buf, offset, length, position, callback) { 53 | if (offset !== 0 || length !== buf.length || position !== null) { 54 | callback(enosys()); 55 | return; 56 | } 57 | const n = this.writeSync(fd, buf); 58 | callback(null, n); 59 | }, 60 | chmod(path, mode, callback) { callback(enosys()); }, 61 | chown(path, uid, gid, callback) { callback(enosys()); }, 62 | close(fd, callback) { callback(enosys()); }, 63 | fchmod(fd, mode, callback) { callback(enosys()); }, 64 | fchown(fd, uid, gid, callback) { callback(enosys()); }, 65 | fstat(fd, callback) { callback(enosys()); }, 66 | fsync(fd, callback) { callback(null); }, 67 | ftruncate(fd, length, callback) { callback(enosys()); }, 68 | lchown(path, uid, gid, callback) { callback(enosys()); }, 69 | link(path, link, callback) { callback(enosys()); }, 70 | lstat(path, callback) { callback(enosys()); }, 71 | mkdir(path, perm, callback) { callback(enosys()); }, 72 | open(path, flags, mode, callback) { callback(enosys()); }, 73 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, 74 | readdir(path, callback) { callback(enosys()); }, 75 | readlink(path, callback) { callback(enosys()); }, 76 | rename(from, to, callback) { callback(enosys()); }, 77 | rmdir(path, callback) { callback(enosys()); }, 78 | stat(path, callback) { callback(enosys()); }, 79 | symlink(path, link, callback) { callback(enosys()); }, 80 | truncate(path, length, callback) { callback(enosys()); }, 81 | unlink(path, callback) { callback(enosys()); }, 82 | utimes(path, atime, mtime, callback) { callback(enosys()); }, 83 | }; 84 | } 85 | 86 | if (!global.process) { 87 | global.process = { 88 | getuid() { return -1; }, 89 | getgid() { return -1; }, 90 | geteuid() { return -1; }, 91 | getegid() { return -1; }, 92 | getgroups() { throw enosys(); }, 93 | pid: -1, 94 | ppid: -1, 95 | umask() { throw enosys(); }, 96 | cwd() { throw enosys(); }, 97 | chdir() { throw enosys(); }, 98 | } 99 | } 100 | 101 | if (!global.crypto) { 102 | const nodeCrypto = require("crypto"); 103 | global.crypto = { 104 | getRandomValues(b) { 105 | nodeCrypto.randomFillSync(b); 106 | }, 107 | }; 108 | } 109 | 110 | if (!global.performance) { 111 | global.performance = { 112 | now() { 113 | const [sec, nsec] = process.hrtime(); 114 | return sec * 1000 + nsec / 1000000; 115 | }, 116 | }; 117 | } 118 | 119 | if (!global.TextEncoder) { 120 | global.TextEncoder = require("util").TextEncoder; 121 | } 122 | 123 | if (!global.TextDecoder) { 124 | global.TextDecoder = require("util").TextDecoder; 125 | } 126 | 127 | // End of polyfills for common API. 128 | 129 | const encoder = new TextEncoder("utf-8"); 130 | const decoder = new TextDecoder("utf-8"); 131 | 132 | global.Go = class { 133 | constructor() { 134 | this.argv = ["js"]; 135 | this.env = {}; 136 | this.exit = (code) => { 137 | if (code !== 0) { 138 | console.warn("exit code:", code); 139 | } 140 | }; 141 | this._exitPromise = new Promise((resolve) => { 142 | this._resolveExitPromise = resolve; 143 | }); 144 | this._pendingEvent = null; 145 | this._scheduledTimeouts = new Map(); 146 | this._nextCallbackTimeoutID = 1; 147 | 148 | const setInt64 = (addr, v) => { 149 | this.mem.setUint32(addr + 0, v, true); 150 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); 151 | } 152 | 153 | const getInt64 = (addr) => { 154 | const low = this.mem.getUint32(addr + 0, true); 155 | const high = this.mem.getInt32(addr + 4, true); 156 | return low + high * 4294967296; 157 | } 158 | 159 | const loadValue = (addr) => { 160 | const f = this.mem.getFloat64(addr, true); 161 | if (f === 0) { 162 | return undefined; 163 | } 164 | if (!isNaN(f)) { 165 | return f; 166 | } 167 | 168 | const id = this.mem.getUint32(addr, true); 169 | return this._values[id]; 170 | } 171 | 172 | const storeValue = (addr, v) => { 173 | const nanHead = 0x7FF80000; 174 | 175 | if (typeof v === "number") { 176 | if (isNaN(v)) { 177 | this.mem.setUint32(addr + 4, nanHead, true); 178 | this.mem.setUint32(addr, 0, true); 179 | return; 180 | } 181 | if (v === 0) { 182 | this.mem.setUint32(addr + 4, nanHead, true); 183 | this.mem.setUint32(addr, 1, true); 184 | return; 185 | } 186 | this.mem.setFloat64(addr, v, true); 187 | return; 188 | } 189 | 190 | switch (v) { 191 | case undefined: 192 | this.mem.setFloat64(addr, 0, true); 193 | return; 194 | case null: 195 | this.mem.setUint32(addr + 4, nanHead, true); 196 | this.mem.setUint32(addr, 2, true); 197 | return; 198 | case true: 199 | this.mem.setUint32(addr + 4, nanHead, true); 200 | this.mem.setUint32(addr, 3, true); 201 | return; 202 | case false: 203 | this.mem.setUint32(addr + 4, nanHead, true); 204 | this.mem.setUint32(addr, 4, true); 205 | return; 206 | } 207 | 208 | let id = this._ids.get(v); 209 | if (id === undefined) { 210 | id = this._idPool.pop(); 211 | if (id === undefined) { 212 | id = this._values.length; 213 | } 214 | this._values[id] = v; 215 | this._goRefCounts[id] = 0; 216 | this._ids.set(v, id); 217 | } 218 | this._goRefCounts[id]++; 219 | let typeFlag = 1; 220 | switch (typeof v) { 221 | case "string": 222 | typeFlag = 2; 223 | break; 224 | case "symbol": 225 | typeFlag = 3; 226 | break; 227 | case "function": 228 | typeFlag = 4; 229 | break; 230 | } 231 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true); 232 | this.mem.setUint32(addr, id, true); 233 | } 234 | 235 | const loadSlice = (addr) => { 236 | const array = getInt64(addr + 0); 237 | const len = getInt64(addr + 8); 238 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 239 | } 240 | 241 | const loadSliceOfValues = (addr) => { 242 | const array = getInt64(addr + 0); 243 | const len = getInt64(addr + 8); 244 | const a = new Array(len); 245 | for (let i = 0; i < len; i++) { 246 | a[i] = loadValue(array + i * 8); 247 | } 248 | return a; 249 | } 250 | 251 | const loadString = (addr) => { 252 | const saddr = getInt64(addr + 0); 253 | const len = getInt64(addr + 8); 254 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 255 | } 256 | 257 | const timeOrigin = Date.now() - performance.now(); 258 | this.importObject = { 259 | go: { 260 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 261 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 262 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 263 | // This changes the SP, thus we have to update the SP used by the imported function. 264 | 265 | // func wasmExit(code int32) 266 | "runtime.wasmExit": (sp) => { 267 | const code = this.mem.getInt32(sp + 8, true); 268 | this.exited = true; 269 | delete this._inst; 270 | delete this._values; 271 | delete this._goRefCounts; 272 | delete this._ids; 273 | delete this._idPool; 274 | this.exit(code); 275 | }, 276 | 277 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 278 | "runtime.wasmWrite": (sp) => { 279 | const fd = getInt64(sp + 8); 280 | const p = getInt64(sp + 16); 281 | const n = this.mem.getInt32(sp + 24, true); 282 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 283 | }, 284 | 285 | // func resetMemoryDataView() 286 | "runtime.resetMemoryDataView": (sp) => { 287 | this.mem = new DataView(this._inst.exports.mem.buffer); 288 | }, 289 | 290 | // func nanotime1() int64 291 | "runtime.nanotime1": (sp) => { 292 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 293 | }, 294 | 295 | // func walltime1() (sec int64, nsec int32) 296 | "runtime.walltime1": (sp) => { 297 | const msec = (new Date).getTime(); 298 | setInt64(sp + 8, msec / 1000); 299 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 300 | }, 301 | 302 | // func scheduleTimeoutEvent(delay int64) int32 303 | "runtime.scheduleTimeoutEvent": (sp) => { 304 | const id = this._nextCallbackTimeoutID; 305 | this._nextCallbackTimeoutID++; 306 | this._scheduledTimeouts.set(id, setTimeout( 307 | () => { 308 | this._resume(); 309 | while (this._scheduledTimeouts.has(id)) { 310 | // for some reason Go failed to register the timeout event, log and try again 311 | // (temporary workaround for https://github.com/golang/go/issues/28975) 312 | console.warn("scheduleTimeoutEvent: missed timeout event"); 313 | this._resume(); 314 | } 315 | }, 316 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 317 | )); 318 | this.mem.setInt32(sp + 16, id, true); 319 | }, 320 | 321 | // func clearTimeoutEvent(id int32) 322 | "runtime.clearTimeoutEvent": (sp) => { 323 | const id = this.mem.getInt32(sp + 8, true); 324 | clearTimeout(this._scheduledTimeouts.get(id)); 325 | this._scheduledTimeouts.delete(id); 326 | }, 327 | 328 | // func getRandomData(r []byte) 329 | "runtime.getRandomData": (sp) => { 330 | crypto.getRandomValues(loadSlice(sp + 8)); 331 | }, 332 | 333 | // func finalizeRef(v ref) 334 | "syscall/js.finalizeRef": (sp) => { 335 | const id = this.mem.getUint32(sp + 8, true); 336 | this._goRefCounts[id]--; 337 | if (this._goRefCounts[id] === 0) { 338 | const v = this._values[id]; 339 | this._values[id] = null; 340 | this._ids.delete(v); 341 | this._idPool.push(id); 342 | } 343 | }, 344 | 345 | // func stringVal(value string) ref 346 | "syscall/js.stringVal": (sp) => { 347 | storeValue(sp + 24, loadString(sp + 8)); 348 | }, 349 | 350 | // func valueGet(v ref, p string) ref 351 | "syscall/js.valueGet": (sp) => { 352 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 353 | sp = this._inst.exports.getsp(); // see comment above 354 | storeValue(sp + 32, result); 355 | }, 356 | 357 | // func valueSet(v ref, p string, x ref) 358 | "syscall/js.valueSet": (sp) => { 359 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 360 | }, 361 | 362 | // func valueDelete(v ref, p string) 363 | "syscall/js.valueDelete": (sp) => { 364 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); 365 | }, 366 | 367 | // func valueIndex(v ref, i int) ref 368 | "syscall/js.valueIndex": (sp) => { 369 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 370 | }, 371 | 372 | // valueSetIndex(v ref, i int, x ref) 373 | "syscall/js.valueSetIndex": (sp) => { 374 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 375 | }, 376 | 377 | // func valueCall(v ref, m string, args []ref) (ref, bool) 378 | "syscall/js.valueCall": (sp) => { 379 | try { 380 | const v = loadValue(sp + 8); 381 | const m = Reflect.get(v, loadString(sp + 16)); 382 | const args = loadSliceOfValues(sp + 32); 383 | const result = Reflect.apply(m, v, args); 384 | sp = this._inst.exports.getsp(); // see comment above 385 | storeValue(sp + 56, result); 386 | this.mem.setUint8(sp + 64, 1); 387 | } catch (err) { 388 | storeValue(sp + 56, err); 389 | this.mem.setUint8(sp + 64, 0); 390 | } 391 | }, 392 | 393 | // func valueInvoke(v ref, args []ref) (ref, bool) 394 | "syscall/js.valueInvoke": (sp) => { 395 | try { 396 | const v = loadValue(sp + 8); 397 | const args = loadSliceOfValues(sp + 16); 398 | const result = Reflect.apply(v, undefined, args); 399 | sp = this._inst.exports.getsp(); // see comment above 400 | storeValue(sp + 40, result); 401 | this.mem.setUint8(sp + 48, 1); 402 | } catch (err) { 403 | storeValue(sp + 40, err); 404 | this.mem.setUint8(sp + 48, 0); 405 | } 406 | }, 407 | 408 | // func valueNew(v ref, args []ref) (ref, bool) 409 | "syscall/js.valueNew": (sp) => { 410 | try { 411 | const v = loadValue(sp + 8); 412 | const args = loadSliceOfValues(sp + 16); 413 | const result = Reflect.construct(v, args); 414 | sp = this._inst.exports.getsp(); // see comment above 415 | storeValue(sp + 40, result); 416 | this.mem.setUint8(sp + 48, 1); 417 | } catch (err) { 418 | storeValue(sp + 40, err); 419 | this.mem.setUint8(sp + 48, 0); 420 | } 421 | }, 422 | 423 | // func valueLength(v ref) int 424 | "syscall/js.valueLength": (sp) => { 425 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 426 | }, 427 | 428 | // valuePrepareString(v ref) (ref, int) 429 | "syscall/js.valuePrepareString": (sp) => { 430 | const str = encoder.encode(String(loadValue(sp + 8))); 431 | storeValue(sp + 16, str); 432 | setInt64(sp + 24, str.length); 433 | }, 434 | 435 | // valueLoadString(v ref, b []byte) 436 | "syscall/js.valueLoadString": (sp) => { 437 | const str = loadValue(sp + 8); 438 | loadSlice(sp + 16).set(str); 439 | }, 440 | 441 | // func valueInstanceOf(v ref, t ref) bool 442 | "syscall/js.valueInstanceOf": (sp) => { 443 | this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); 444 | }, 445 | 446 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 447 | "syscall/js.copyBytesToGo": (sp) => { 448 | const dst = loadSlice(sp + 8); 449 | const src = loadValue(sp + 32); 450 | if (!(src instanceof Uint8Array)) { 451 | this.mem.setUint8(sp + 48, 0); 452 | return; 453 | } 454 | const toCopy = src.subarray(0, dst.length); 455 | dst.set(toCopy); 456 | setInt64(sp + 40, toCopy.length); 457 | this.mem.setUint8(sp + 48, 1); 458 | }, 459 | 460 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 461 | "syscall/js.copyBytesToJS": (sp) => { 462 | const dst = loadValue(sp + 8); 463 | const src = loadSlice(sp + 16); 464 | if (!(dst instanceof Uint8Array)) { 465 | this.mem.setUint8(sp + 48, 0); 466 | return; 467 | } 468 | const toCopy = src.subarray(0, dst.length); 469 | dst.set(toCopy); 470 | setInt64(sp + 40, toCopy.length); 471 | this.mem.setUint8(sp + 48, 1); 472 | }, 473 | 474 | "debug": (value) => { 475 | console.log(value); 476 | }, 477 | } 478 | }; 479 | } 480 | 481 | async run(instance) { 482 | this._inst = instance; 483 | this.mem = new DataView(this._inst.exports.mem.buffer); 484 | this._values = [ // JS values that Go currently has references to, indexed by reference id 485 | NaN, 486 | 0, 487 | null, 488 | true, 489 | false, 490 | global, 491 | this, 492 | ]; 493 | this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id 494 | this._ids = new Map(); // mapping from JS values to reference ids 495 | this._idPool = []; // unused ids that have been garbage collected 496 | this.exited = false; // whether the Go program has exited 497 | 498 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 499 | let offset = 4096; 500 | 501 | const strPtr = (str) => { 502 | const ptr = offset; 503 | const bytes = encoder.encode(str + "\0"); 504 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); 505 | offset += bytes.length; 506 | if (offset % 8 !== 0) { 507 | offset += 8 - (offset % 8); 508 | } 509 | return ptr; 510 | }; 511 | 512 | const argc = this.argv.length; 513 | 514 | const argvPtrs = []; 515 | this.argv.forEach((arg) => { 516 | argvPtrs.push(strPtr(arg)); 517 | }); 518 | argvPtrs.push(0); 519 | 520 | const keys = Object.keys(this.env).sort(); 521 | keys.forEach((key) => { 522 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 523 | }); 524 | argvPtrs.push(0); 525 | 526 | const argv = offset; 527 | argvPtrs.forEach((ptr) => { 528 | this.mem.setUint32(offset, ptr, true); 529 | this.mem.setUint32(offset + 4, 0, true); 530 | offset += 8; 531 | }); 532 | 533 | this._inst.exports.run(argc, argv); 534 | if (this.exited) { 535 | this._resolveExitPromise(); 536 | } 537 | await this._exitPromise; 538 | } 539 | 540 | _resume() { 541 | if (this.exited) { 542 | throw new Error("Go program has already exited"); 543 | } 544 | this._inst.exports.resume(); 545 | if (this.exited) { 546 | this._resolveExitPromise(); 547 | } 548 | } 549 | 550 | _makeFuncWrapper(id) { 551 | const go = this; 552 | return function () { 553 | const event = { id: id, this: this, args: arguments }; 554 | go._pendingEvent = event; 555 | go._resume(); 556 | return event.result; 557 | }; 558 | } 559 | } 560 | 561 | if ( 562 | global.require && 563 | global.require.main === module && 564 | global.process && 565 | global.process.versions && 566 | !global.process.versions.electron 567 | ) { 568 | if (process.argv.length < 3) { 569 | console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 570 | process.exit(1); 571 | } 572 | 573 | const go = new Go(); 574 | go.argv = process.argv.slice(2); 575 | go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); 576 | go.exit = process.exit; 577 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 578 | process.on("exit", (code) => { // Node.js exits if no event handler is pending 579 | if (code === 0 && !go.exited) { 580 | // deadlock, make Go print error and stack traces 581 | go._pendingEvent = { id: 0 }; 582 | go._resume(); 583 | } 584 | }); 585 | return go.run(result.instance); 586 | }).catch((err) => { 587 | console.error(err); 588 | process.exit(1); 589 | }); 590 | } 591 | })(); 592 | --------------------------------------------------------------------------------