runtime: support syscall/js
This commit is contained in:
106
runtime/internal/lib/syscall/js/func.go
Normal file
106
runtime/internal/lib/syscall/js/func.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
funcsMu sync.Mutex
|
||||
funcs = make(map[uint32]func(Value, []Value) any)
|
||||
nextFuncID uint32 = 1
|
||||
)
|
||||
|
||||
// Func is a wrapped Go function to be called by JavaScript.
|
||||
type Func struct {
|
||||
Value // the JavaScript function that invokes the Go function
|
||||
id uint32
|
||||
}
|
||||
|
||||
// FuncOf returns a function to be used by JavaScript.
|
||||
//
|
||||
// The Go function fn is called with the value of JavaScript's "this" keyword and the
|
||||
// arguments of the invocation. The return value of the invocation is
|
||||
// the result of the Go function mapped back to JavaScript according to ValueOf.
|
||||
//
|
||||
// Invoking the wrapped Go function from JavaScript will
|
||||
// pause the event loop and spawn a new goroutine.
|
||||
// Other wrapped functions which are triggered during a call from Go to JavaScript
|
||||
// get executed on the same goroutine.
|
||||
//
|
||||
// As a consequence, if one wrapped function blocks, JavaScript's event loop
|
||||
// is blocked until that function returns. Hence, calling any async JavaScript
|
||||
// API, which requires the event loop, like fetch (http.Client), will cause an
|
||||
// immediate deadlock. Therefore a blocking function should explicitly start a
|
||||
// new goroutine.
|
||||
//
|
||||
// Func.Release must be called to free up resources when the function will not be invoked any more.
|
||||
func FuncOf(fn func(this Value, args []Value) any) Func {
|
||||
funcsMu.Lock()
|
||||
id := nextFuncID
|
||||
nextFuncID++
|
||||
funcs[id] = fn
|
||||
funcsMu.Unlock()
|
||||
var buf [20]byte
|
||||
sid := string(itoa(buf[:], uint64(id)))
|
||||
wrap := functionConstructor.New(ValueOf(`
|
||||
const event = { id:` + sid + `, this: this, args: arguments };
|
||||
Module._llgo_invoke(event);
|
||||
return event.result;
|
||||
`))
|
||||
return Func{
|
||||
id: id,
|
||||
Value: wrap,
|
||||
}
|
||||
}
|
||||
|
||||
func itoa(buf []byte, val uint64) []byte {
|
||||
i := len(buf) - 1
|
||||
for val >= 10 {
|
||||
buf[i] = byte(val%10 + '0')
|
||||
i--
|
||||
val /= 10
|
||||
}
|
||||
buf[i] = byte(val + '0')
|
||||
return buf[i:]
|
||||
}
|
||||
|
||||
// Release frees up resources allocated for the function.
|
||||
// The function must not be invoked after calling Release.
|
||||
// It is allowed to call Release while the function is still running.
|
||||
func (c Func) Release() {
|
||||
funcsMu.Lock()
|
||||
delete(funcs, c.id)
|
||||
funcsMu.Unlock()
|
||||
}
|
||||
|
||||
//export llgo_export_invoke
|
||||
func llgo_export_invoke(cb Value) bool {
|
||||
id := uint32(cb.Get("id").Int())
|
||||
funcsMu.Lock()
|
||||
f, ok := funcs[id]
|
||||
funcsMu.Unlock()
|
||||
if !ok {
|
||||
Global().Get("console").Call("error", "call to released function")
|
||||
return true
|
||||
}
|
||||
|
||||
// Call the js.Func with arguments
|
||||
this := cb.Get("this")
|
||||
argsObj := cb.Get("args")
|
||||
args := make([]Value, argsObj.Length())
|
||||
for i := range args {
|
||||
args[i] = argsObj.Index(i)
|
||||
}
|
||||
result := f(this, args)
|
||||
|
||||
// Return the result to js
|
||||
cb.Set("result", result)
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user