ファミコンエミュレーターを作ってた

今後仕事でgolangを使う可能性が少しありそうだなーと思い、素振りの題材としてファミコンエミュレータ作っていまして、最初の関門であるhello worldが表示されるようになりました。

github.com

既に色々な方々が挑戦し資料が残っているので、他の人の記事で見かけなかったものだけ書いてみます。

ネット対戦(ぽい何か)のベースを作った

せっかくならファミコンにない機能を加えたいという思いから、ネット対戦に挑戦してみることにしました。

実装の方向性としては、(クライアント側の入力に応じて)サーバー側でゲーム結果を生成し、クライアント側はそれを表示するだけにしました。 これにした理由としてはサーバー側の実装が増えたほうが課題として良いというのと、クラウドゲーミングっぽくてかっこよさそうという個人的な理由です。

細かい実装の話では、通信は速度の観点からwebrtcを、クライアント側には動画を送るようにしています(画像+音声を別々に送ると音ズレしそうなので)。

進捗としては、入力に応じて画面表示が切り替わるようにはできたので、エミュレーターの実装が進めばそれっぽく動くのではと想像してます。パフォーマンスという大きすぎる課題は残りますが...

https://j.gifs.com/jZxx2B.gif

オペコードの情報をjsonに落とせるようにした

6502のオペコードは150個程度あります。cpuを実装するには、それぞれのオペコードでCycle数やプログラムカウンタをいくつ動かすかなどの情報を保持する必要があります。

(私の観測範囲では)その情報をプログラマが扱いやすい状態に保存されているものを見つけられず、いちいちコピペする必要があったので、スクレイピングしてjsonで保存できるようにしました。
https://github.com/k-murakami0609/nesc/blob/main/tool/get_instraction.py

私はこのjsonをテンプレートエンジンに渡してgolangの構造体の形で出力してます
https://github.com/k-murakami0609/nesc/blob/main/nes/opcodes.go#L99

github actionsで自前のコンテナを使えるようにした

エミュレーターとは関係ないのですが、github actionsでGitHub Container Registryに登録されたコンテナを利用する方法にハマったので書いておきます。

最初は以下の例のようにstepの中にdocker runを書いていました

 # 略
 jobs:
   build:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
 
       - name: Login to GitHub Container Registry
         uses: docker/login-action@v1
         with:
           registry: ghcr.io
           username: ${{ github.repository_owner }}
           password: ${{ secrets.CR_PAT }}
 
       - uses: actions/cache@v2
         with:
           path: ~/go/pkg/mod
           key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
           restore-keys: |
             ${{ runner.os }}-go-
 
       - name: Pull
         run: docker pull ghcr.io/k-murakami0609/nesc/gstreamer
         
       - name: Lint
         run: docker run -v $PWD:/opt -v ~/go/pkg/mod:/root/go/pkg/mod ghcr.io/k-murakami0609/nesc/gstreamer golangci-lint run
 
       - name: Test
         run: docker run -v $PWD:/opt -v ~/go/pkg/mod:/root/go/pkg/mod  ghcr.io/k-murakami0609/nesc/gstreamer go test -v ./...
 
       - name: Fix permissions for cache (workaround)
         run: sudo chmod -R a+rwx ~/go/pkg/mod

これでも動いてはいます。が、golangci-lintの代わりにreviewdog/action-golangci-lintを使いたいと思ったときに、自前のコンテナでreviewdog動かすの???となりました。

調べたところjobs.<job_id>.containerを設定することでステップが実行される環境を指定できるそうです。
https://github.blog/changelog/2020-09-24-github-actions-private-registry-support-for-job-and-service-containers/ (余談ですが、GitHub Container Registryとgithub actionsで検索かけると、github actionsでRegistryに登録する記事ばかり出てこの情報が見つけづらかったです...)

その結果、こんな感じにシンプルになりました

 # 略
 jobs:
   ci:
     runs-on: ubuntu-latest
     container:
       image: ghcr.io/k-murakami0609/nesc/gstreamer
       credentials:
         username: ${{ github.repository_owner }}
         password: ${{ secrets.CR_PAT }}
     steps:
       - uses: actions/checkout@v2
 
       - uses: actions/cache@v2
         with:
           path: ~/go/pkg/mod
           key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
           restore-keys: |
             ${{ runner.os }}-go-
             
       - name: Get
         run: go get
 
       - name: Lint
         uses: reviewdog/action-golangci-lint@v1
         with:
           reporter: github-pr-review
 
       - name: Test
         run: go test -v ./...

もう少し描画部分を実装すればマリオくらいは動かせそうなんですが、後は作るだけなのであまりモチベーションが湧いてないです。 パフォーマンスチューニングも、webrtcや動画周りが闇が深そうで気後れしている感じです。 次何やろうかなー

よく参考にしてたサイト

https://qiita.com/bokuweb/items/1575337bef44ae82f4d3
https://github.com/fogleman/nes
http://nesdev.com/NESDoc.pdf