En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly (con Golang) en Vue 3 con Vite y Pinia. Voy a explicar cómo comunicar las funciones de WASM con JavaScript y viceversa, dejando mucho código del lado de Golang con WASM.
Toma en cuenta que es una documentación muy específica a mi modo de trabajo. No esperes un tutorial paso a paso.
Comlink es una librería que facilita el trabajo de los workers para que parezcan funciones locales. Normalmente uno se comunica con postMessage y escucha la respuesta de vuelta en el onmessage, pero es muy complejo.
Primero decidí no usar Comlink pero conforme la app fue creciendo decidí usar Comlink. A Golang compilado como WASM no le importa si usas o no Comlink, pero a Vue 3 sí.
Si tú no quieres usar Comlink entonces veamos la función que convierte una función de Golang normal y la convierte en una función JavaScript. Aquí estoy armando un mensaje de respuesta con la acción y el valor para que el invocador (en onmessage
) sepa qué acción se ha realizado y los datos que lleva esa acción.
func factoryParaWorker(laFuncion func(js.Value, []js.Value) js.Value, accion string) js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) any {
valor := laFuncion(this, args)
return armarMensajeDeRespuesta(accion, []any{valor})
})
}
Y la de armar mensaje de respuesta queda así:
func armarMensajeDeRespuesta(accion string, argumentos []any) js.Value {
objeto := js.Global().Get("Object").New()
arreglo := js.Global().Get("Array").New(argumentos...)
objeto.Set("accion", accion)
objeto.Set("datos", arreglo)
return objeto
}
Necesitamos la acción porque en el onmessage necesitamos saber cuál acción acaba de ser ejecutada. Y necesitamos el factoryParaWorker
porque las funciones tienen la siguiente firma para que puedan ser invocadas entre ellas:
func miFuncion(this js.Value, parametros []js.Value) js.Value {
return js.ValueOf("Hola")
}
Anteriormente yo invocaba a postmessage
al final de cada función pero eso no servía para cuando quería usar las funciones entre ellas, así que las expongo con el factory para worker y ya luego en mi worker hago:
const resultados = await self["parzibyte"][accion](...datos);
self.postMessage(resultados);
Y desde cualquier lugar solo hay que escuchar el onmessage del worker.
Pero ahora con Comlink es distinto. Por alguna extraña razón no puedo exponer la función directamente porque el resultado es undefined
del lado del worker. Entonces lo siguiente es incorrecto:
objetoParzibyte.Set("iniciarBaseDeDatos", IniciarBaseDeDatosConPromesa)
Porque aunque IniciarBaseDeDatosConPromesa
devuelve un js.Value
, recibo undefined
al invocarlo. Tuve que crear otra función así:
func crearFuncionDeJSDevolviendoJsValue(laFuncion func(js.Value, []js.Value) js.Value) js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) any {
valor := laFuncion(this, args)
return armarOtroMensajeDeRespuesta(valor)
})
}
Y la función que arma el mensaje:
func armarOtroMensajeDeRespuesta(argumentos any) js.Value {
objeto := js.Global().Get("Object").New()
objeto.Set("resultado", argumentos)
return objeto
}
Así es, debo crear un objeto “dummy” que guarda los resultados en su propiedad “resultado” y devolverlo. Veamos cómo iniciar la base de datos, ya que la promesa estará en resultado.resultado
:
const respuesta = await self.parzibyte.iniciarBaseDeDatos();
await respuesta.resultado;
Para otros casos que devuelven un arreglo, simplemente modifiqué la store:
const resultado = await envolturaWorkerPuenteWasm.ejecutarAccionEnWasm(accion, datos);
return resultado.resultado;
Y listo.
Comlink.expose({
iniciar: async () => {
// Código aquí
},
ejecutarAccionEnWasm: async (accion, datos) => {
return await self["parzibyte"][accion](...datos);
}
});
En iniciar cargo el wasm, lo instancio y le digo a wasm que haga su propio init.
Luego expongo la función ejecutarAccionEnWasm
que simplemente invocará a la función del objeto parzibyte
. Lo he hecho en una store:
import { defineStore } from 'pinia'
import * as Comlink from "comlink"
import ArchivoWorkerPuenteWasm from "@/worker_puente_wasm?worker"
const workerPuenteWasm = new ArchivoWorkerPuenteWasm();
const envolturaWorkerPuenteWasm = Comlink.wrap(workerPuenteWasm);
await envolturaWorkerPuenteWasm.iniciar();
export const useWasmStore = defineStore('wasm', () => {
async function ejecutarAccionEnWasm(accion:string, datos: Array<any>){
return envolturaWorkerPuenteWasm.ejecutarAccionEnWasm(accion, datos);
}
return {ejecutarAccionEnWasm }
})
Me gusta que puedo hacer un await para el iniciar,
así me aseguro de que la BD está lista. Luego expongo ejecutarAccionEnWasm
que es solo una envoltura para la misma función del worker. Entonces la comunicación es:
Quien sea que invoque a la store -> worker -> objeto parzibyte de WASM
Y finalmente en Vue dentro de la app (que es la principal) hago el init
estando seguro de que nada se va a renderizar hasta que la store termine de esperar a las promesas:
import { useWasmStore } from './stores/wasmStore';
// La cargamos sin usar para que la
// store pueda iniciar la BD esperando
// que la promesa se resuelva
const wasmStore = useWasmStore();
Luego ya puedo usarla en cualquier lugar, por ejemplo en otro componente que no es la App:
const plantillaRecienInsertada = await wasmStore.ejecutarAccionEnWasm("insertarPlantilla", [imagenCodificada, plantilla.value.nombre])
Y en este caso se me va a devolver lo que Golang devuelva con WASM. El js.Value
que devuelve la función invocada es lo que yo tendré al resolverse la promesa, todo limpio sin onmessage
. Solo promesas.
El día de hoy te mostraré cómo crear un servidor HTTP (servidor web) en Android…
En este post te voy a enseñar a designar una carpeta para imprimir todos los…
En este artículo te voy a enseñar la guía para imprimir en una impresora térmica…
Hoy te voy a mostrar un ejemplo de programación para agregar un módulo de tasa…
Los usuarios del plugin para impresoras térmicas pueden contratar licencias, y en ocasiones me han…
Hoy voy a enseñarte cómo imprimir el € en una impresora térmica. Vamos a ver…
Esta web usa cookies.