Slide 1

Slide 1 text

TinyGo 開発サイクルを高速化する Goで作るエミュレータ入門 Go Conference 2026 株式会社ZOZO 倉澤 大樹 Copyright © ZOZO, Inc. 1

Slide 2

Slide 2 text

自己紹介 株式会社ZOZO 検索基盤部 ZOZOAD開発ブロック 倉澤 大樹 X: @kurasawah ● バックエンドエンジニア ● ZOZOTOWNの検索連動型広告システムの開発をしています ● ZOZOではバックエンドの開発言語にGoを採用しています 余談 ● © ZOZO, Inc. 生まれも育ちも中野なので今日話せることに縁を感じています 2

Slide 3

Slide 3 text

このテーマを話そうと思ったきっかけ ● Go Conference mini in Sendai をきっかけにTinyGoに入門してみました ● まずはマイコン(Wio Terminal)を購入 ● いざコードを書いて実機に書き込んでみると、色々詰まった ● もっと手軽に動作確認できる方法が欲しくなった © ZOZO, Inc. 3

Slide 4

Slide 4 text

この発表でお伝えしたいこと TinyGo x Wio Terminalで デスクトップ上で動作確認できる環境(エミュレータ)を作った話と、 その過程で得た TinyGoの開発をより快適にするための知見 画像引用元:秋月電子通商 https://akizukidenshi.com/catalog/g/g115275/ © ZOZO, Inc. 4

Slide 5

Slide 5 text

このような方へ ● TinyGoに興味はあるけど、まだ踏み出せていない ● 実機(Wio Terminal)を買う前にまずは試してみたい ● TinyGoの開発サイクルを改善したい ● エミュレータの仕組み・設計に興味がある © ZOZO, Inc. 5

Slide 6

Slide 6 text

目次 1. TinyGo とは 2. 実機開発の課題 3. 今回作成したエミュレータの紹介 4. 最初の設計とその限界 5. 設計の刷新: build tag x net/rpc x Ebitengine 6. 設計から得た学び 7. まとめ © ZOZO, Inc. 6

Slide 7

Slide 7 text

TinyGoとは Goを組み込みシステムで動かすためのコンパイラ 開発の背景 ● 標準のGoはメモリ資源が限られたマイコンでは動かない ● TinyGoはそのギャップを埋めるために生まれた 幅広いデバイスをサポート ● 150以上のマイコンに対応 ● 今日登場するWio Terminalもそのひとつ TinyGo logo by The TinyGo Authors — based on the Go gopher © Renée French, licensed under CC BY 4.0 © ZOZO, Inc. 7

Slide 8

Slide 8 text

TinyGoとGoの違い TinyGo Go 書き方 Goの文法と同じ ー 動く場所 マイコン・小型デバイス PC・サーバ 標準ライブラリ 一部使えないものがある ー tinygo build go build ビルド © ZOZO, Inc. 8

Slide 9

Slide 9 text

TinyGoの特徴 : 豊富なライブラリ machineパッケージ ● ボタン・LEDなどのハードウェアをGoから直接操作するための標準パッケージ tinygo.org/x/drivers ● machineを使って特定デバイスを操作するドライバ群 ● ディスプレイ(ili9341)、各種センサーなど © ZOZO, Inc. 9

Slide 10

Slide 10 text

Wio Terminalとは Wio Terminalの特徴 ● カラーディスプレイ搭載のマイコンボード ● Wi-Fi/Bluetooth内蔵 ● 5方向スイッチ・3ボタン搭載 ● 内蔵センサー・拡張ポートが豊富 ● TinyGoが公式に対応しているボード TinyGoでいろいろ作ってみたい方に、おすすめなマイコンです 画像引用元:秋月電子通商 https://akizukidenshi.com/catalog/g/g115275/ © ZOZO, Inc. 10

Slide 11

Slide 11 text

TinyGoの開発フロー 01 コード編集  02 書き込みモード(ブートローダーモード)に切り替え  03 tinygo flash -target wioterminal  04 © ZOZO, Inc. 実機確認 11

Slide 12

Slide 12 text

実機開発の課題① トラブルシューティングが難しい ● ビルドは通っているのに実機に反映されない ● (私の環境では)tinygo flashがセキュリティ制限(read-only mount)で使 えない ● エラーメッセージがTinyGo固有で検索してもヒットしない コードが悪いのか、フラッシュが失敗しているのか、 わからない状態になる © ZOZO, Inc. 12

Slide 13

Slide 13 text

実機開発の課題② 開発サイクルが遅い 01 コード編集  02 ビルド  03 フラッシュ  04 実機確認   _ デスクトップで確認できたら、どれだけ楽になるか 実機への書き込み(フラッシュ)をスキップし、開発サイクルを劇的に高速化 m c © ZOZO, Inc. 13

Slide 14

Slide 14 text

今回作成したエミュレータ 1. Wio Terminalの画面を デスクトップ上に再現 2. TinyGoのコードを ほぼそのままで動かせる 3. © ZOZO, Inc. The Go gopher was designed by Renée French. Illustrations by avocadoneko. go run .するだけで反映 14

Slide 15

Slide 15 text

今回作成したエミュレータの全体像 ● クライアント(ユーザー側のコード)とサーバー(エミュレータ)の2プロセス構成 ● ユーザー側のコードは実機向けのTinyGoコードとほぼ同じ ● クライアント・サーバー間はGo標準のnet/rpcでつなぐ ● エミュレータの画面描画はEbitengine ● サーバーは常駐したまま、ユーザー側のコードだけ再実行できる © ZOZO, Inc. 15

Slide 16

Slide 16 text

今回作成したエミュレータ https://github.com/kurakura967/wiodisplay © ZOZO, Inc. 16

Slide 17

Slide 17 text

エミュレータの使い方 1. エミュレーターの起動 $ wio-emu 2. ユーザーコード側からラッパーパッケージをimportするように書き換える import "github.com/kurakura967/wiodisplay/machine" 3. アプリケーションを実行 #エミュレータで確認 $ go run ./your_app/ #wio terminalへ反映させたい場合もそのままビルド可能 $tinygo flash -target wioterminal ./your_app/ © ZOZO, Inc. 17

Slide 18

Slide 18 text

今回作成したエミュレータは 設計を見直して作り直した © ZOZO, Inc. 18

Slide 19

Slide 19 text

最初のアイデア 目標:TinyGoコードをそのままデスクトップで動かせる環境を作る ● ユーザー側のコードを一切変えなくていい ● CLI コマンド1つでエミュレータを起動できる ● エミュレータ側がすべてを引き受ける # インストール $ go install github.com/kurakura967/wiodisplay/cmd/wio-emu@latest # エミュレータ起動 $ wio-emu main.go © ZOZO, Inc. 19

Slide 20

Slide 20 text

最初のアイデア: ユーザーは何も変えなくていい PC向けのスタブに差し替え ユーザのTinyGoコード import ( $ wio-emu main.go import ( "machine" "machine/stub" "ili9341" "ili9341/stub" ASTによる書き換え ) func main() { コードを構文木として importを差し替え ) func userMain() { // 画面描画など } © ZOZO, Inc. // 同じロジック } 20

Slide 21

Slide 21 text

なぜimportを差し替えるのか machine・ili9341 は実機のハードウェアを操作するパッケージ PCにはこれらのハードウェアは存在しなので、そのままではPC上でビルドできない import ( "machine" "tinygo.org/x/drivers/ili9341" ) // ボタンの状態を読む pressed := machine.WIO_KEY_A.Get() © ZOZO, Inc.

Slide 22

Slide 22 text

ASTによるimportの自動書き換え // 差し替え前(TinyGo向け) // 差し替え後(PC向け) import ( "machine" "tinygo.org/x/drivers/ili9341" ) import ( // PCでボタン入力を再現 "machine_stub" // PCでディスプレイ描画を再現 "ili9341_stub" ) これをASTによって自動で行うのが最初の設計 © ZOZO, Inc.

Slide 23

Slide 23 text

差し替えた先では何が起きるのか? © ZOZO, Inc. 23

Slide 24

Slide 24 text

差し替え先:スタブパッケージが実機の代わりを担う 1. 実機のmachine・ili9341の代わりに「スタブ」が動く 2. スタブはボタン入力・描画命令をPC上で再現する 3. 内部ではEbitengineがウィンドウを描画する © ZOZO, Inc. 24

Slide 25

Slide 25 text

EbitengineでWio Terminal をPC上に再現する Wio Terminal はディスプレイとボタンを持つデバイス ● PC上で再現するには「描画」と「入力処理」の仕組みが必要 Ebitengine(Go製ゲームエンジン)はこの両方を持つ 毎フレーム Update() → Draw()の順で処理が走る ● Update() : キーボード・マウスの入力を読み取る ● Draw() : 画面に描画する ● Layout() : ウィンドウサイズを定義する © ZOZO, Inc. 25

Slide 26

Slide 26 text

差し替え先の実装: 描画はどう画面に反映されるのか スタブ > ピクセルバッファ > EbitengineがWio Terminalの液晶部分に描画 1. ili9341スタブパッケージ(自作)がピクセルバッファに書き込む 2. ピクセルバッファ(320x240の共有バッファ)に一時的に保持される 3. EbitengineのDraw()が毎フレーム、ピクセルバッファの内容を LCDエリアにスケールして描画する テキスト © ZOZO, Inc.

Slide 27

Slide 27 text

限界が積み上がった ● 対応パッケージがハードコード ○ 未対応のパッケージを使うと即ビルドエラー ○ ユーザ側には手の打ちようがない ● 起動のたびにgo mod tidyが走る ○ 通信コストで起動が遅い ● ホットリロードでウィンドウが毎回閉じる ○ 再起動のたびに画面がリセットされる © ZOZO, Inc.

Slide 28

Slide 28 text

課題①: 対応パッケージがハードコード 差し替えるパッケージが限定されている var importReplacements = map[string]string{ "machine": "github.com/kurakura967/wiodisplay/machine", "tinygo.org/x/drivers/ili9341": "github.com/kurakura967/wiodisplay/driver/ili9341", } 対応パッケージを追加するにはエミュレータ自体を変更するしかない import "tinygo.org/x/drivers/ws2812" // LED ドライバ // → リストにない → PC 上でビルドエラー © ZOZO, Inc.

Slide 29

Slide 29 text

課題②: 起動のたびにgo mod tidyが走る 起動のたびに一時プロジェクトを生成して実行する 01 02 wio-emu main.go 一時フォルダ go.mod生成 03 go mod tidy 毎回ネット通信 (ボトルネック) 04 05 go build 実行 cコードを変更するたびに、この重いサイクルが繰り返される © ZOZO, Inc.

Slide 30

Slide 30 text

課題③: ホットリロードでウィンドウが毎回閉じる 01 02 wio-emu main.go (再実行) 新しいプロセスで Ebitengine ウィンドウ起動 03 04 前のウィンドウが 閉じる 新しいウィンドウ が開く 開発中の画面状態が毎回リセットされ、開発のリズムが途切れる © ZOZO, Inc.

Slide 31

Slide 31 text

“隠しすぎた”ことが問題だった 当初の設計:自動化と隠蔽 生じた弊害:自由度の喪失 「ユーザーは何も変えなくていい」 「何も変えなくていい」=「何もできない」 ● importの差し替えを全て自動化 ● 未対応パッケージの利用時に手が打てない ● ユーザーはコードに一切触れない ● ユーザーの自由度がゼロだった buユーザーに道具を渡す設計へ © ZOZO, Inc.

Slide 32

Slide 32 text

設計を根本から見直した ● ver.1の反省:仕組みを隠しすぎてユーザーの自由度がゼロだった ● ver.2の方針:Goの標準の仕組みをユーザーに使ってもらう ○ 自動化を隠すのではなく、明示的に選択できるようにする ○ ユーザー自身がコントロールできる余地を残す © ZOZO, Inc.

Slide 33

Slide 33 text

ver.2のエミュレータの全体設計 クライアントプロセス $ go run . ● ユーザーコードを実行する ● ボタン入力・描画命令などをnet/rpcでサーバーへ送る サーバープロセス $ wio-emu . ● EbitengineでWio Terminalの画面を常時表示 ● ボタン状態を返し、描画命令を受け取って画面に反映する © ZOZO, Inc.

Slide 34

Slide 34 text

ユーザーがやること:importの差し替えだけ 1. ユーザーコードは、実機向けのmachineパッケージと同じ書き方で呼び出す import "github.com/kurakura967/wiodisplay/machine" btnA := machine.WIO_KEY_A.Get() 2. 内部のwiodisplay/machine(自作パッケージ)が、その呼び出しを net/rpcでサーバー(エミュレータ)に転送する 3. サーバー側で処理された結果が、戻り値としてユーザーコードに返る © ZOZO, Inc.

Slide 35

Slide 35 text

build tagで実装を切り替える(エミュレータ側) ユーザーがimportする wiodisplay/machine は、内部で実装が分かれている tinygo flashもgo run.も同じmain.goで動く tinygo flash の時 実機の machine パッケージを 再エクスポート go run . の時 net/rpc を介してPC上のサーバー へ問い合わせる //go:build tinygo import "machine" //go:build !tinygo func (p Pin) Get() bool { type Pin = machine.Pin client.Conn.Call(....) } © ZOZO, Inc.

Slide 36

Slide 36 text

net/rpcによるプロセス間通信 別プロセスで動かすことでwio-emuを起動したままユーザーコードを何度でも実行できる クライアントプロセス ユーザーのコード net/rpc サーバープロセス エミュレータ(wio-emu) $ go run . $ wio-emu . ➔ コード変更のたびに再実行 ➔ 起動したまま(常駐) 別プロセス化(net/rpc)による絶大なメリット ● ● ● ウィンドウが閉じない — エミュレータ画面が維持されるため、実行の度に開き直すストレスがありません。 go mod tidy が毎回走らない — 依存関係の再検証プロセスをスキップでき、起動が極めて高速になります。 ver.1の課題が全て解決する — ユーザーの自由度と、エミュレータ開発の高速なフィードバックループを両立。 © ZOZO, Inc.

Slide 37

Slide 37 text

Ebitengineを選んだ理由:koebitenとの対称性 ● koebiten =TinyGo向けのゲームエンジン ● Ebitengine にインスパイアされたAPIを持つ Koebitenで書いたTinyGoのコードが ほぼそのままPCでも Ebitengineとして動く この対称性があるから 「実機用コードをそのままデスクトップで確認する」体験が成立する © ZOZO, Inc.

Slide 38

Slide 38 text

例えば、キー入力のAPIはほぼ同じ // koebiten (TinyGo) // Ebitengine (デスクトップ) koebiten.IsKeyPressed(koebiten.Key0) ebiten.IsKeyPressed(koebiten.Key0) ● koebiten を ebitenに変えるだけ この対称性があるから 「実機用のコードをそのままデスクトップで確認する」体験が成立する © ZOZO, Inc.

Slide 39

Slide 39 text

ver.2で解決した3つの課題 ver.1の課題 ① 対応パッケージがハードコード ver.2での解決アプローチ importを差し替えるだけ ユーザーによる自由な拡張を可能に設計変更 wio-emuは常時起動のまま維持 ② 起動のたびに go mod tidy が走る ③ ホットリロードの度にウィンドウが閉じる © ZOZO, Inc. 不要な依存解決ステップをスキップし、起動を極め て高速化 別プロセス化によりウィンドウは開いたまま 実行の度に画面を開き直すストレスを完全に解消

Slide 40

Slide 40 text

普通のGoと同じ開発スピード 開発の基本サイクル 開発速度を向上する工夫 1 wio-emu 起動 (最初の1回だけ) 2 go run . ウィンドウが閉じない 別プロセス化により、ホットリロード時も エミュレータ画面が開いたまま維持され、 開発が中断されません。 3 エミュレータ上で即確認 4 コード変更 5 即確認 © ZOZO, Inc. go mod tidy が毎回走らない 起動のたびに実行されていた不要な依存 解決ステップをスキップ。通常のGo開発 と同等の瞬時の起動を実現します。

Slide 41

Slide 41 text

設計から得た学び: パッケージ境界で止める ver.1: 内側に深く入り込む ver.2: パッケージ境界で止まる エミュレータがソースコードの中まで入り 込む設計 エミュレータは完全に境界の外で構える 設計 ● AST(抽象構文木)でユーザーコードを走 査する複雑な処理 ● 純粋なパッケージを提供するだけのシン プルな依存関係 ● 直接import文を書き換えるトリッキーな 挙動 ● ユーザーコードには一切手を触れない安 全な実行環境 ユーザーの自由度がゼロ © ZOZO, Inc. ユーザーが自由に拡張できる

Slide 42

Slide 42 text

まとめ①: TinyGoでの開発 ● エミュレータがあれば、実機なしで開発サイクルを回せる ● コード変更 -> 確認 -> tinygo flashするだけ ● TinyGoは今日から始められる © ZOZO, Inc.

Slide 43

Slide 43 text

まとめ②: エミュレータの設計 ● エミュレータの役割・境界をどこに引くのかが設計の革新 ● ASTでユーザーコードに踏み込む設計 -> 自由度が下がる ● パッケージ境界で止まる設計 -> 拡張性が高まる © ZOZO, Inc.

Slide 44

Slide 44 text

今日から始めるには $ go install github.com/kurakura967/wiodisplay/cmd/wio-emu@latest © ZOZO, Inc.

Slide 45

Slide 45 text

No content