As TypedArray no longer exists as of Go 1.13, there is no way to convert slices other than []byte from/to TypedArray objects other than Uint8Array. It is technically possible to do this by using encoding/binary, but I'm worried this conversion is not efficient.
As there are Web APIs that take such TypedArray objects (e.g., AudioBuffer.copyToChannel takes a Float32Array [1]), it would be very useful if syscall/js had such conversion functions.
CC @neelance
[1] https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer/copyToChannel
A possible workaround, for now, is to cast slices with unsafe and reflect.SliceHeader.
@dennwc Thanks. Now I've succeeded to do what I wanted:
func sliceToByteSlice(s interface{}) []byte {
switch s := s.(type) {
case []int8:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
return *(*[]byte)(unsafe.Pointer(h))
case []int16:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Len *= 2
h.Cap *= 2
return *(*[]byte)(unsafe.Pointer(h))
case []int32:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Len *= 4
h.Cap *= 4
return *(*[]byte)(unsafe.Pointer(h))
case []int64:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Len *= 8
h.Cap *= 8
return *(*[]byte)(unsafe.Pointer(h))
case []uint8:
return s
case []uint16:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Len *= 2
h.Cap *= 2
return *(*[]byte)(unsafe.Pointer(h))
case []uint32:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Len *= 4
h.Cap *= 4
return *(*[]byte)(unsafe.Pointer(h))
case []uint64:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Len *= 8
h.Cap *= 8
return *(*[]byte)(unsafe.Pointer(h))
case []float32:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Len *= 4
h.Cap *= 4
return *(*[]byte)(unsafe.Pointer(h))
case []float64:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Len *= 8
h.Cap *= 8
return *(*[]byte)(unsafe.Pointer(h))
default:
panic(fmt.Sprintf("jsutil: unexpected value at sliceToBytesSlice: %T", s))
}
}
func SliceToTypedArray(s interface{}) js.Value {
switch s := s.(type) {
case []int8:
a := js.Global().Get("Uint8Array").New(len(s))
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Int8Array").New(buf, a.Get("byteOffset"), a.Get("byteLength"))
case []int16:
a := js.Global().Get("Uint8Array").New(len(s) * 2)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Int16Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/2)
case []int32:
a := js.Global().Get("Uint8Array").New(len(s) * 4)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Int32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4)
case []uint8:
a := js.Global().Get("Uint8Array").New(len(s))
js.CopyBytesToJS(a, s)
runtime.KeepAlive(s)
return a
case []uint16:
a := js.Global().Get("Uint8Array").New(len(s) * 2)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Uint16Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/2)
case []uint32:
a := js.Global().Get("Uint8Array").New(len(s) * 4)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Uint32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4)
case []float32:
a := js.Global().Get("Uint8Array").New(len(s) * 4)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Float32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4)
case []float64:
a := js.Global().Get("Uint8Array").New(len(s) * 8)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Float64Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/8)
default:
panic(fmt.Sprintf("jsutil: unexpected value at SliceToTypedArray: %T", s))
}
}
@hajimehoshi Is this available as a library somewhere?
No so far. Ebiten has an internal package though: https://github.com/hajimehoshi/ebiten/tree/master/internal/jsutil
I resorted to patching wasm_exec.js with:
const loadFloatSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Float32Array(this._inst.exports.mem.buffer, array, len);
}
...
"syscall/js.copyBytesToJS": (sp) => {
const dst = loadValue(sp + 8);
let src;
if (dst instanceof Uint8Array || dst instanceof Uint8ClampedArray) {
src = loadSlice(sp + 16);
} else if (dst instanceof Float32Array) {
src = loadFloatSlice(sp + 16)
} else {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
These methods get called quite often in my WebGL game and I can't afford indirection or extra syscalls used here:
https://github.com/hajimehoshi/ebiten/blob/982a68e5a21f3b2dff494132b34639084fd3e9ac/internal/jsutil/go113_js.go#L104
Looking for something similar to be included in the standard syscall/js library (supporting all other typed arrays too, not just Float32 which I am currently restricting myself to). An API like CopyFloat32sToJS would be perfect!
@finnbear For performance critical code you probably want to reuse a single buffer anyways. So first create a Uint8Array and Float32Array that share the same buffer and then use copyBytesToJS with sliceToByteSlice (see above) multiple times. This should have no additional overhead.
@hajimehoshi @finnbear Are you both happy with what is already possible? Shall we close this issue?
@neelance I'm fine with CopyBytesToJS as-is, mostly because the option to use multiple typed arrays referencing the same buffer is available. I highly suggest you document that, although I'm still using the hack I showed above because my application commonly copies float32s and bytes. I'm fine with this issue being closed 馃憤
I'm fine with closing this issue.
Most helpful comment
@dennwc Thanks. Now I've succeeded to do what I wanted: