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

发表于 4年以前  | 总阅读数:1185 次

一个与猫咪交互的 Canvas 手机游戏,这个项目完全由 Golang 编写。图里这只小猫正在体验我编写的小游戏

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

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

故事开始了

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

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

说一下我的理解!

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

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

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

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

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

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

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

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

WASM 跟 Service Workers 还有 Web Workers 有什么区别?

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

Service Workers 还有 Web Workers 是浏览器上的功能,不是 JS 的专有功能。

设置开发环境

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

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

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

$ GOOS=js GOARCH=wasm go build -o game.wasm main.go
build_go:
 docker run --rm \
 -v `pwd`/src:/game \
 --env GOOS=js --env GOARCH=wasm \
 golang:1.12-rc \
 /bin/bash -c "go build -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 文件并运行我们的游戏。

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

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <style>body{height:100%;width:100%;padding:0;margin:0;background-color:#000000;color:#FFFFFF;font-family:Arial,Helvetica,sans-serif}</style>
    <script type="text/javascript" src="./wasm_exec.js"></script>
    <script type="text/javascript">
      async function run(fileUrl) {
        try {
          const file = await fetch(fileUrl);
          const buffer = await file.arrayBuffer();
          const go = new Go();
          const { instance } = await WebAssembly.instantiate(buffer, go.importObject);
          go.run(instance);
        } catch (err) {
          console.error(err);
        }
      }
      setTimeout(() => run("./game.wasm"));
    </script>
  </head>
  <body></body>
</html>

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

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

main() 方法

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

package main

import (
 // https://github.com/golang/go/tree/master/src/syscall/js
 "syscall/js"
)

var (
 // js.Value 可以是任意的 JS 对象、类型或者构造函数
 window, doc, body, canvas, laserCtx, beep js.Value
 windowSize struct{ w, h float64 }
)

func main() {
 setup()
}

func setup() {
 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 事件,而且那些静态类型看起来很干净!

func main() {
 setup()

  // 在编译时声明渲染器
 var renderer js.Func
 // 没有错,看起来很像 JS 的回调 
 renderer = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  updateGame()
  // 实现 60FPS 的动画
  window.Call("requestAnimationFrame", renderer)
  return nil
 })
 window.Call("requestAnimationFrame", renderer)

 // 让我们处理下 鼠标/手势 点击事件
 var mouseEventHandler js.Func = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  updatePlayer(args[0])
  return nil
 })
 window.Call("addEventListener", "pointerdown", mouseEventHandler)
}

func updatePlayer(event js.Value) {}
func updateGame() {}

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

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

func updatePlayer(event js.Value) {
 mouseX := event.Get("clientX").Float()
 mouseY := event.Get("clientY").Float()

  // `go` 关键字是主要用来实现线程、异步、并行的功能
 // TODO 与 Web Workers 的区别
 // TODO 与 Service Workers 的区别
 // https://gobyexample.com/goroutines
 go log("mouseEvent", "x", mouseX, "y", mouseY)

 // 下一个关键点
 if isLaserCaught(mouseX, mouseY, gs.laserX, gs.laserY) {
  go playSound()
 }
}

// 不要以为我用了什么黑魔法,这里直接使用了 HTML5 的 API
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement#Basic_usage
func playSound() {
 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#Description
func log(args ...interface{}) {
 window.Get("console").Call("log", args...)
}

让游戏一直跑下去!

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

func main() {
 // https://stackoverflow.com/a/47262117
 // 创建空通道
 runGameForever := make(chan bool)

 setup()

 // 尝试从空通道接收
 // 由于没有人向它发送任何数据,它本质上是一个永久阻塞操作
  // 我们有一个 daeomon / service / background 程序
  // 在 WASM 里,我们的游戏会一直运行 
 <-runGameForever
}

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

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

import (
 "math"
)

type gameState struct{ laserX, laserY, directionX, directionY, laserSize float64 }

var (
 // gs 处于最高范围,小于这个范围小红点  都能都能访问
 gs = gameState{laserSize: 35, directionX: 3.7, directionY: -3.7, laserX: 40, laserY: 40}
)

func updateGame() {
 // 边界判断
 if gs.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.directionY

 // 清除画布
 laserCtx.Call("clearRect", 0, 0, windowSize.w, windowSize.h)

 //画一个小红点 
 laserCtx.Call("beginPath")
 laserCtx.Call("arc", gs.laserX, gs.laserY, gs.laserSize, 0, math.Pi*2, false)
 laserCtx.Call("fill")
 laserCtx.Call("closePath")
}

// 判断点击的点是不是在小红点  内部
func isLaserCaught(mouseX, mouseY, laserX, laserY float64) bool {
 // 直接这样返回是不行的
 // return laserCtx.Call("isPointInPath", mouseX, mouseY).Bool()

 // 所以这里我通过勾股定理  来实现
 // 同时我给 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?

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

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

Web3 时代的 WASM

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

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

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

本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/I0grz0IfFZo98sfaCNh-Vw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237267次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8105次阅读
 目录