台式电脑

怎么样在电脑上制作路标(这可能是世界上最简单的用Go来写WebAssembly的教程)

这可能是世界上最简单的用Go来写WebAssembly的教程

你认为WebAssembly(WASM)只用于图像处理、复杂的数学计算或者Web上的小小应用吗?你是否经常将WASM与WebWorkers和ServiceWorkers的概念混淆?你对WASM不感兴趣,是因为你认为现在的Web应用程序在未来10年里依旧是JavaScript主导?你是否想过用JS以外的语言做Web前端开发?

如果你不想细读,你可以看下我做的demo页面或者直接看下go-wasm-cat-game-on-canvas-with-docker这个项目,我会讲的简洁一些,尽量不浪费你的时间。以下是我这个项目的一些关键的代码解析。

故事开始了

我们的目标是给猫做一个简单的小游戏:做一个小红点在手机上不停的移动,整个过程还有HiFi音乐还有震动。整个项目我们会用Golang(Go)这门语言来实现,包括DOM操作、逻辑还有相关的状态。

而而而而而且,由于猫咪不会使用鼠标,我们还需要给猫爪做一些点击触摸的交互。

说一下我的理解!

把WASM想象成一个通用虚拟机(UVM,UniversalVirtualMachine)或者一个沙箱,你只需编写一次任何代码,它便可以在任何地方运行。

WASM是一个编译目标,而不是一种语言。就像你要同时针对Windows,MacOS和Linux进行编译一样!

我不认为WASM会废弃JS,你可以有其他选择而不用付出任何代价。

怎么样在电脑上制作路标(这可能是世界上最简单的用Go来写WebAssembly的教程)

想象一下使用Go,Swift,Rust,Ruby,C++,OCaml或者其他语言的开发人员。现在,他们可以使用自己喜欢的语言来创建交互式,联网,快速,具有脱机功能的网站和Web应用。

你是否曾经参与过类似「一个项目是用一个代码仓库管理还是多个代码仓库管理?」问题的讨论?

好吧,不管你有没有,你现在也要想一下现在这个项目打算用一门语言实现还是多门语言实现了。

当大家可以使用相同的技术栈时,一切都会变得更加容易,尤其是团队之间的沟通。

你可以依旧使用React或者Vue,但你现在开始也可以不用使用JS来开发了。

WASM跟ServiceWorkers还有WebWorkers有什么区别?

ServiceWorkers还有WebWorkers允许应用在后台运行,也可以做到离线运行和缓存。它们模仿线程,无法访问DOM,并且不能共享数据(仅能通过消息传递),只能在单独的上下文中运行。咦,其实我们甚至可以在其中运行WASM而不是JS。对我来说,它们只提供一些具有特殊特权的抽象层,没有人说这些层必须执行JS。

ServiceWorkers还有WebWorkers是浏览器上的功能,不是JS的专有功能。

设置开发环境

我们将使用WASM,Go,JS和Docker(这个是可选的)来进行开发。

如果您不了解Go,但了解JS,请点击这里学习Go,然后再回来继续阅读。让我们从GoWASMWiki开始。

你可以使用安装在电脑本地的go版本,在这里我使用Docker的golang:1.12-rc镜像。只需在此处为go编译器设置两个WASM标志。在main.go中创建一个简单的helloworld进行测试。

$GOOS=jsGOARCH=wasmgobuild-ogame.wasmmain.gobuild_go:dockerrun--rm\-v`pwd`/src:/game\--envGOOS=js--envGOARCH=wasm\golang:1.12-rc\/bin/bash-c"gobuild-o/game/game.wasm/game/main.go;cp/usr/local/go/misc/wasm/wasm_exec.js/game/wasm_exec.js"

现在,让我们利用好Go团队提供的wasm_exec.js代码。代码里的全局变量Go对WASM进行了初始化操作,我们不必自己从头开始做好任何DOM的实现。等我们编译好wasm文件后,它会获取.wasm文件并运行我们的游戏。

总而言之,它应该看起来像这样:

[xss_clean][xss_clean][xss_clean]asyncfunctionrun(fileUrl){try{constfile=awaitfetch(fileUrl);constbuffer=awaitfile.arrayBuffer();constgo=newGo();const{instance}=awaitWebAssembly.instantiate(buffer,go.importObject);go.run(instance);}catch(err){console.error(err);}}setTimeout(()=>run("./game.wasm"));[xss_clean]

放码过来!(当然是Go的码)

要渲染我们的这个小游戏,这个标签应该足够了。我们可以直接从Go代码创建DOM结构和元素!这个syscall/js文件(包含在标准Go库中)为我们处理了与DOM交互的方法。

main()方法

我敢打赌,你很久没见过main()方法了。

packagemainimport(//https://github.com/golang/go/tree/master/src/syscall/js"syscall/js")var(//js.Value可以是任意的JS对象、类型或者构造函数window,doc,body,canvas,laserCtx,beepjs.ValuewindowSizestruct{w,hfloat64})funcmain(){setup()}funcsetup(){window=js.Global()doc=window.Get("document")body=doc.Get("body")windowSize.h=window.Get("innerHeight").Float()windowSize.w=window.Get("innerWidth").Float()canvas=doc.Call("createElement","canvas")canvas.Set("height",windowSize.h)canvas.Set("width",windowSize.w)body.Call("appendChild",canvas)//这个是小红点Canvas对象laserCtx=canvas.Call("getContext","2d")laserCtx.Set("fillStyle","red")//http://www.iandevlin.com/blog/2012/09/html5/html5-media-and-data-uri/beep=window.Get("Audio").New(_"data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjI1LjEwMQAAAAAAAAAAAAAA/+NAwAAAAAAAAAAAAFhpbmcAAAAPAAAAAwAAA3YAlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaW8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw////////////////////////////////////////////AAAAAExhdmYAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAN2UrY2LgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/jYMQAEvgiwl9DAAAAO1ALSi19XgYG7wIAAAJOD5R0HygIAmD5+sEHLB94gBAEP8vKAgGP/BwMf+D4Pgh/DAPg+D5//y4f///8QBhMQBgEAfB8HwfAgIAgAHAGCFAj1fYUCZyIbThYFExkefOCo8Y7JxiQ0mGVaHKwwGCtGCUkY9OCugoFQwDKqmHQiUCxRAKOh4MjJFAnTkq6QqFGavRpYUCmMxpZnGXJa0xiJcTGZb1gJjwOJDJgoUJG5QQuDAsypiumkp5TUjrOobR2liwoGBf/X1nChmipnKVtSmMNQDGitG1fT/JhR+gYdCvy36lTrxCVV8Paaz1otLndT2fZuOMp3VpatmVR3LePP/8bSQpmhQZECqWsFeJxoepX9dbfHS13/////aysppUblm//8t7p2Ez7xKD/42DE4E5z9pr/nNkRw6bhdiCAZVVSktxunhxhH//4xF+bn4//6//3jEvylMM2K9XmWSn3ah1L2MqVIjmNlJtpQux1n3ajA0ZnFSu5EpX////uGatn///////1r/pYabq0mKT//TRyTEFNRTMuOTkuNaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+MQxNIAAANIAcAAAKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg==")}

看起来是不是很像JS代码?

是的,这就是与DOM交互所需的全部内容!现在只需要几个get方法还有调用函数即可。

在这一点上,我问自己:在某种程度上,我仍然在写JS…这怎么算是升级?因为我们还不能直接访问DOM,所以我们必须(通过JS)调用DOM来做任何事情。想象一下如何用JSX/React来抽象化它。

实际上,已经可以做到了,请期待我的下篇文章。

「渲染」还有事件处理

直接使用syscall/js库,这个写法看起来有点像ES5的回调。但我们能够监听DOM事件,而且那些静态类型看起来很干净!

funcmain(){setup()//在编译时声明渲染器varrendererjs.Func//没有错,看起来很像JS的回调renderer=js.FuncOf(func(thisjs.Value,args[]js.Value)interface{}{updateGame()//实现60FPS的动画window.Call("requestAnimationFrame",renderer)returnnil})window.Call("requestAnimationFrame",renderer)//让我们处理下鼠标/手势点击事件varmouseEventHandlerjs.Func=js.FuncOf(func(thisjs.Value,args[]js.Value)interface{}{updatePlayer(args[0])returnnil})window.Call("addEventListener","pointerdown",mouseEventHandler)}funcupdatePlayer(eventjs.Value){}funcupdateGame(){}

日志记录、音频播放以及「异步」执行

在Go中,有一个惯例是把所有的函数都写成同步的方式,由调用者决定函数的执行是否是异步的。异步运行函数非常简单,只要在前面加上go就行了!它使用自己的上下文创建一个线程,你仍然可以将父级上下文绑定给它,不要担心哈。

funcupdatePlayer(eventjs.Value){mouseX:=event.Get("clientX").Float()mouseY:=event.Get("clientY").Float()//`go`关键字是主要用来实现线程、异步、并行的功能//TODO与WebWorkers的区别//TODO与ServiceWorkers的区别//https://gobyexample.com/goroutinesgolog("mouseEvent","x",mouseX,"y",mouseY)//下一个关键点ifisLaserCaught(mouseX,mouseY,gs.laserX,gs.laserY){goplaySound()}}//不要以为我用了什么黑魔法,这里直接使用了HTML5的API//https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement#Basic_usagefuncplaySound(){beep.Call("play")window.Get("navigator").Call("vibrate",300)}//这里主要用了JS的解构赋值语法//这里的`...interface{}`有点像TS的`any`语法//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters#Descriptionfunclog(args...interface{}){window.Get("console").Call("log",args...)}

让游戏一直跑下去!

该代码创建一个非缓冲通道,并尝试从该通道接收数据。因为没有人向它发送任何东西,它本质上是一个永久的阻塞操作,允许我们永远运行我们的程序。

funcmain(){//https://stackoverflow.com/a/47262117//创建空通道runGameForever:=make(chanbool)setup()//尝试从空通道接收//由于没有人向它发送任何数据,它本质上是一个永久阻塞操作//我们有一个daeomon/service/background程序//在WASM里,我们的游戏会一直运行<-runGameForever}

更新游戏状态并移动小红点

这里没有状态管理,只有一个简单的声明类型的结构体,它不允许在内部传递任何不正确的值。

import("math")typegameStatestruct{laserX,laserY,directionX,directionY,laserSizefloat64}var(//gs处于最高范围,小于这个范围小红点都能都能访问gs=gameState{laserSize:35,directionX:3.7,directionY:-3.7,laserX:40,laserY:40})funcupdateGame(){//边界判断ifgs.laserX+gs.directionX>windowSize.w-gs.laserSize||gs.laserX+gs.directionX< gs.laserSize { gs.directionX = -gs.directionX } if gs.laserY+gs.directionY >windowSize.h-gs.laserSize||gs.laserY+gs.directionY< gs.laserSize { gs.directionY = -gs.directionY } // 移动小红点 gs.laserX += gs.directionX gs.laserY += gs.directionYr/>//清除画布laserCtx.Call("clearRect",0,0,windowSize.w,windowSize.h)r/>//画一个小红点laserCtx.Call("beginPath")laserCtx.Call("arc",gs.laserX,gs.laserY,gs.laserSize,0,math.Pi*2,false)laserCtx.Call("fill")laserCtx.Call("closePath")}r/>//判断点击的点是不是在小红点内部funcisLaserCaught(mouseX,mouseY,laserX,laserYfloat64)bool{r/>//直接这样返回是不行的r/>//returnlaserCtx.Call("isPointInPath",mouseX,mouseY).Bool()>//所以这里我通过勾股定理来实现r/>//同时我给laserSize属性的值加上15,让猫爪更容易点击return(math.Pow(mouseX-laserX,2)+math.Pow(mouseY-laserY,2))< math.Pow(gs.laserSize+15, 2)}

总结

事实上,WASM仍然被认为是一个[MVP](https://hacks.mozilla.org/2018/10/webassembly-post-MVP-future/)(MAP),你可以不用编写一行JS,就能创建一个像这样的游戏。惊不惊讶!CanIUse上WASM的支持已经是一片绿色了,没有人可以阻止你去创建基于WASM的网站和应用。

你可以组合所有你想要的语言,像是把JS转成WASM。最后,它们都将编译成WASM字节码。如果你需要在他们之间分享任何东西,也没问题,因为它们可以共享原始内存。

我担心的是,在最近的新闻中,我们关注到微软正在开发Chromium浏览器还有Firefox市场份额低于9%。这使谷歌在WASM上有了致命的切换能力。如果他们不愿意配合,大众可能永远不会知道有这个特性。

现在都有谁在用WASM?

你必须得承认,我的项目已经在用了。这个项目仅仅是画了一个全屏的画布,这里有一些更高级的例子,它们关注于语义Webawesome-wasm#web-frameworks-libraries

同时,也有相当多的项目已经上了WASM的车了。我对Spotify、Twitch和FigmaEWASM更感兴趣。

Web3时代的WASM

现在,如果你想在手机上使用以太坊钱包(Ethereumwallet),你必须从应用商店下载一个类似于http://Status.im的移动端钱包App,并且信任所有商家。

如果有一个先进的WebApp,可以运行geth(GoEthereum客户端),并且能在WebRTC上光速同步,这会怎么样?它可以使用ServiceWorker来更新它的WASM代码并在后台运行,可以托管在IPFS/Dat上。

一些有用的关于WASM的文章、资源还有学习资料

WebAssemblyismorethanthewebWebAssemblyandGo:Alookatthefuture还有HNcommentsMozillaHacksHackerNews发布的文章WebAssemblyarchitectureforGoawesome-wasm,awesome-wasm-langs,gowasm-experiments,WasmWeekly,WasmRocks,SPAwithC++,betterDOMbindingsforGo

感谢twifkak在AndroidChrome上对Go的优化!

原标题:Theworld’seasiestintroductiontoWebAssembly

原文链接:Theworld’seasiestintroductiontoWebAssembly-freeCodeCamp.org-Medium

作者:MartinOlsansky(olso)

相关新闻

返回顶部