Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

Gold Sponsor として協賛したRubyKaigi 2024に行ってきました!

はいさい! 沖縄が恋しいエンジニアの小山です。

今回 Gold Sponsor として RubyKaigi 2024 に協賛させていただき、多くのメンバーと参加してきました。本記事はそのレポートです。

RubyKaigiとアカツキゲームス

RubyKaigi は、プログラミング言語 Ruby に関する国際会議です。

私たちは2013年からほぼ毎年協賛をさせていただいており、今年で12回目のスポンサーとなります。

去年の様子: 

hackerslab.aktsk.jp

当日の様子

Ruby Commiterであるパッチモンスターこと nobuさんと

BoothスタンプラリーもComplate!

印象に残った発表

参加したメンバーより、印象に残ったセッションの感想を参加レポートとして紹介します。

Unlocking Potential of Property Based Testing with Ractor / @ohbarye

speakerdeck.com

@shivashin495 です。まず、簡単にこのセッションの振り返りをします。

Ractorを知っている人は会場でも多く、9割くらいはいました。しかし、Ractorを趣味もしくは仕事で使ったことがある人となると急に3割くらいに減ってしまいます。では、なぜ使ったことがある人が少ないかというとユースケースが少ないからだと発表者は述べています。その上で、Property Based Testingこそが良いユースケースであると仮定し、その検証をしたという発表になります。

私のいるゲームプロジェクトではExample Based Testingが書かれています。これは、あるテストを書くときに任意のExample(具体的な振る舞い例)を用意してその中で正しい振る舞いをするか確認するテストです。ゲームは入力や状態が多岐にわたるため、複雑なケースでの検証やテストが用意されてます。しかし、どれほどテストを用意してもエッジケースや意図しないデグレによってバグが発見されることがしばしばあります。そこで今回のProperty Based Testingに注目しています。具体的なExampleではなくランダムな値を使うことで、潜在的なバグを発見できる可能性もあるためです。

ゲームアプリケーション以外にも、スクリプトやインフラのテストなどで使えると面白そうです。アイデアが広がる素晴らしいセッションでした。

RubyGems on ruby.wasm / @kateinoigakukun

speakerdeck.com

こんにちは、竹下です。私からは、かねてより関心のあったWASMに関するセッションがとても印象に残っているので、そちらを紹介させて頂きます。

WASMでRubyランタイムを実現し、ブラウザ上でRubyのプログラムを実行することができるruby.wasmの機能追加に関するセッションでした。

ruby.wasmではGemを使用することができますが、従来はRubyのみのGemにしか対応しておらず、C拡張を含むGemは使えませんでした。そこで、事前にパッケージをインストールする手法を選択し、Cを含むパッケージも利用できるようになったとのことでした。

今後の展望として、ruby.wasmを通常のプラットフォームの一つとすることができることを目標としているとのことで、ハンディな実行環境として有用なものになりそうだと感じました。実際、セッション内で行われたデモでは、Mastodonのサーバーをブラウザ内で起動し、ブラウザからアクセスするといった、圧巻の実演をして頂きました。Webサーバを含めて実現したいことのすべてがブラウザ内で実現する世界観が近づいてきているのだなということが感じられる内容だったと思います。

ゲームのサーバーサイドを開発するうえで、今回紹介されたデモのようにブラウザ内でハンディに動かそうということは難しいかもしれません。しかし、ゲームを開発するうえで必要なものはサーバーとクラアントだけでなく、検証ツールや諸々の管理ツールなど様々にあり、今後のツール開発の形を変える技術になるのではないかと思います。

Leveraging Falcon and Rails for Real-Time Interactivity / @ioquatix

rubykaigi.org

@nkpoidです。Railsを使ってリアルタイム性の高いインタラクティブなWeb体験を構築する方法に焦点を当てたセッションについてご紹介します。

発表者の@ioquatixさんが開発したFalconというGemは、Async Gemを基盤としたイベント駆動型アーキテクチャをRailsに導入するRack Middlewareです。デモでは、このFalconとRailsを用いて、インタラクティブな操作が必要なWebゲーム「Flappy Bird」を実装していました。Pumaの代わりにFalconをbundle installするだけで導入でき、実際にサーバサイドロジックで動くFlappy Birdを見ることができました。

テクノロジーの進化とともに、ソフトウェア開発の複雑さも増しています。しかし、RubyやRailsはその複雑さをうまく隠蔽し、「書くのが楽しい」言語であることが魅力です。今回紹介されたFalconを使うことで、インタラクティブなコードも楽しみながら学ぶことができると実感しました。ただし、パフォーマンスの観点で言えば、サーバサイドでのロジックの限界もあるため、学習用途として利用するのが現実的かもしれません。

ちなみに、私のプロジェクトでもFalconを導入しようとしてみましたが、私たちの環境ではそのままでは動作しませんでした。とはいえ、Falconの導入によるパフォーマンス改善は魅力的なので、ベンチマークを取るところまでは検証を続けたいと思います。

Embedding it(RBS Type Declaration) into Ruby code / @soutaro

speakerdeck.com

こんにちは @fumihumi です 

RBS commiterの soutaroさん のセッションでは既存のRBS定義への課題から、soutro/rbs-inline の紹介がありました。またday3のRuby Committers and the World では Matz から”rbs-inlineのようなコメントで形定義を書くスタイルについては黙認する”というコメントもありましたね。(うろ覚えなため表現が正確ではないかもしれません。) 

私のいるプロダクトでは、RBSの導入を部分的に進めており、 基本的に自動生成を用いる + 一部のみ手書きによる型定義を実施するが、チームとしてRBS定義を書くということは必須ではない、というような導入状況です。

セッション中に触れていた、型定義が別ファイルにあると更新しづらく、いわゆる手書きの型定義ファイルは適切に更新されない、されにくいという問題は事実あり、これは導入時に型定義を必ずかくという強制をせずにあくまでも書きたい(欲しい)メンバーが書くというスタイルをとったため一定仕方がないとも思っています。

そんな中でrbs-inlineによって今まで yard 相当で記載していたDocを型定義として扱えるようになる、というのは個人的にとても嬉しいものでした。

yard 自体は一定数のメンバーが書いているため、これらのメンバーに rbs-inlineのsyntaxを受け入れてさえもらえれば自然と型定義が潤っていくということになります。今までの開発フローに近い形で、型定義が潤うのは嬉しいですね。

day3では会場投票によって型定義のシンタックスについての投票があったり、セッション中でもシンタックスについては検討段階、というような形で触れていたため、まだ変わることがあるとは思いますが、さっそくプロダクトにて試してみたいと思いました。

YJIT Makes Rails 1.7x faster / @k0kubun

speakerdeck.com

@shivashin495 です。こちらはYJITに導入されている最適化に関する発表です。

day2にもBreaking the Ruby Performance Barrier / @maximecbの発表がありました。このセッションでは、YJITはRubyのバージョンを重ねるごとに高速化しており、C拡張への依存を減らして純粋なRubyで実装されたものを使用し、JIT化可能な割合を増やすことで更なる速度アップをしているという内容でした。

day3のこのセッションでは、より詳しくYJITに導入された高速化に関するテクニックの内容で、valueをstackではなくregisterを使えるようにすることで処理が高速になったなどの紹介がありました。

弊社でもRailsを使用しており、こうした高速化にワクワクしています。以前Ruby v3.1.0でYJIT有効化を試したことがありますが、当時の検証ではパフォーマンスへの改善を大きくみることができませんでした。(ref: https://hackerslab.aktsk.jp/2022/09/26/183545 )

つい最近 ruby3.3.1 へのアップデートを実施しており、セッションで聞いたような改善の恩恵を受けられるため、今回の発表の内容でどういった結果が出るのか非常に楽しみです。パフォーマンスの比較などを発表できるようにYJITの導入に挑戦してみたいと思います。

Using “modern” Ruby to build a better, faster Homebrew / @MikeMcQuaid

speakerdeck.com

村上です。私からは、Macを使っているエンジニアの多くが利用していると言っても過言ではない、Homebrewのセッションについてご紹介します(そう、HomebrewもRuby製なのです)。

発表では、まずHomebrewが利用してきたRubyバージョンの変遷の確認から始まりました。リリースからつい最近まではOSに同梱されたRuby(”System Ruby”と呼ばれることが多いようです)を使っていたようなのですが、OSの更新や年月が経過してもなかなか新しくならず、2023年になってもRuby 2.6で動作していたとのことでした。Ruby 2.6は2018年に初版リリースされたものになるので、相当古いランタイムを使い続けていたわけですね。

そんな状況に痺れを切らしてか、2023年にHomebrewは独自ビルドのRuby(Portable Ruby)に切り替えを果たします。切り替え当初はRuby 3.1でしたが、ちょうどRubyKaigiの期間中にRuby 3.3に対応したとのことでした 🎉

~  $ brew config | grep ruby
Homebrew Ruby: 3.3.1 => /opt/homebrew/Library/Homebrew/vendor/portable-ruby/3.3.1/bin/ruby

コマンドの出力を見ると、確かにRubyが更新されています。自分たちの都合に合わせてランタイムのバージョンを制御できるようになった点は、メンテナンスの面でかなり良いことなのではないでしょうか。

また、HomebrewとRubyエコシステムの関係も説明がありました。Homebrewのパッケージ定義はFormulaと呼ばれるRubyのDSLで記述されているのですが、その記法チェックやテストにRuboCopやSorbet, RSpecを利用しているそうです。これらによりPRレビューを一定自動化できているとのことで、広く認知されているツール群が活用されていることが分かりました。

発表中はHomebrew FormulaのDSLにも少し触れられていたのですが、設定ファイルと似た形で簡単かつ綺麗に記述ができる点から、Rubyとの相性の良さが伺えます。

これを見ると、アプリケーション組み込みのスクリプトでもmrubyを用いればもっと綺麗なDSLで書けるかもしれないと希望を感じました。その分野で有名な Lua(JIT) の組み込みやすさとパフォーマンスの高さはもちろん魅力的なのですが、Ruby特有の「書きやすさ」は開発効率・メンテナンス性の面でこそ輝きます。ユースケース次第とはいえ、プロダクトで使えるかを積極的に検討してみても良いのかもしれませんね。

まとめ

今回は沖縄での開催ということで、講演の挨拶も「はいさい!」と明るい雰囲気でした。海外の方も観光を含め非常に楽しんでいる様子でした。開催地を楽しめるのもRubyKaigiの素敵なところですね!

また、RubyKaigiの1週間ほど前に、弊社が公開しているOctoballというOSSにPRを送っていただいた方とOfficialPartyで偶然お会いしました。こんな出会いがあるのもRubyKaigiのいいところです!

来年度は松山で開催されるとのことで、今から非常に楽しみです!!

 

アカツキゲームスのインターンに参加しました!(サーバーサイド)

はじめに

こんにちは。2024/3/11〜2024/3/29の3週間、アカツキゲームスのインターンに参加させていただきました。今回はインターンで取り組んだ事・学んだことを紹介させていただきます!

自己紹介

東京理科大学修士2年の井出と申します。私は大学入学時からプログラミングをし始め、現在ではC++を使ったソフトウェア開発のアルバイト・pythonを使った画像処理分野の研究・Go言語を使った趣味での個人開発を行っています。

アカツキゲームスのインターンを志望した動機

私がインターンを志望した理由として、ゲーム業界でのサーバーサイドエンジニアがどういった業務をしているのかを肌で実感したかったという事が挙げられます。私が持っているモットーとして、「良いプロダクトは楽しく開発できなきゃ作れない」と考えています。その中でゲーム開発は楽しく開発するというスタンスが強いのではないかと思い、アカツキゲームスのインターンに応募しました!

取り組んだ内容

私が今回のインターンで取り組んだタスクは合計で3つでした。その中の1つをピックアップして紹介したいと思います。

Pngyu代替ツールの作成

アカツキゲームスでは、PngyuというGUIツールを使っていました

Pngyuとは?

Pngyuは、複数のPNG画像ファイルを一括で圧縮することができるユーザーフレンドリーなツールです。マウス操作だけで簡単に画像を圧縮できるため、デザイナーやゲーム開発者からの支持を受けています。

画像圧縮の必要性

ゲーム開発では、多数の画像が使用されます。タイトル画面、ガチャのお知らせバナー、ポップアップ通知など、これらの画像はゲームの魅力を伝える重要な役割を担っています。しかし、画像ファイルはテキストファイルと比べてデータサイズが大きく、そのまま使用するとゲームの読み込み速度を遅くしたり、パフォーマンスに影響を与える可能性があります。そこで画像の圧縮が重要となりますが、一枚一枚手動で処理するには時間がかかります。こうした背景から、Pngyuのような一括圧縮ツールが現場で頻繁に利用されていました。

画像圧縮の理論

画像圧縮はデジタル画像ファイルのデータ容量を削減するために用いられる技術です。ここでは、その原理について解説します。

例として、24ビットのフルカラー画像を考えてみましょう。この形式では、各ピクセルはRGB各色を8ビットで表現し、合計で3バイトのデータを使用します。これにより、約1677万色(正確には16,777,216色)を再現できます。 イメージとしては以下のような形です。

フルカラー画像のデータ容量を削減する方法の一つに、インデックスカラーへの変換があります。この方法では、使用する色の数を256色(1バイト)に限定し、各ピクセルに色のインデックスを割り当てることでデータ量を減少させます。各ピクセル情報とは別に、「カラーパレット」とよばれるデータがあり、色のインデックスごとにRGB各色の情報量を保持します。イメージとしては、以下のような形です。

このようにすることで、画像全体で1/3のデータ容量に収める事が出来ます。Pngyuではこれを内部的に行っています。

問題発生

しかし、使用していたPngyuが突然動作しなくなりました。公式のリリースノートを調べると、最後の更新は2013年11月4日であり、10年以上の歳月が経過しています。技術の進歩と共に、古いソフトウェアが互換性の問題で動かなくなるのは珍しくありません。

そこで

画像の圧縮は今後も行う必要があるため、Pngyuの代替となるツールを作成する事になりました。Pngyuでは圧縮エンジンにpngquantというライブラリを使用しており、macのターミナルでも使える圧縮ツールでもあります。幸いにもアカツキゲームスでは、会社で貸出されるPCはmacであったため、「pngquantを使って複数画像を一度に圧縮するシェルスクリプト」を作成すればよいため、これを作成しました。

pngquantのコマンド詳細

では、実際にどのようにコマンドを実行すべきなのか解説します。

pngquant 256 sample.png

上記のように実行すると、sample-fs8.pngが作成されます。 これだけの解説では寂しいので、pngquantのオプションについても一部解説します。

--outputオプション

--output <filename> とすることで、出力先のファイル名を指定できます。

--qualityオプション

前提知識として、pngquantは画像を圧縮する際に、設定された「最大品質」を満たすために必要な最少の色数を使用します。このとき、画像の品質が「最小品質」を下回ってしまう場合、その画像は保存されず、代わりに元の24ビットカラーの画像が出力されます。ここで言う「品質」とは、「画像を圧縮する際にどれだけの色情報を保持し、元の画像にどれだけ近い見た目を維持できるか」という割合です。

今回の場合、「品質に関係なく、画像が圧縮される(ビットの深さが8ビットになる)」ことが最優先であったため、<min>を0, <max>を100で指定しました。

--speedオプション

簡単に言うと、実行速度を早くしたり遅くしたりするオプションです。実行速度が速いほど圧縮後の画像が荒くなり、逆に遅いほど品質が向上します。今回、実行速度はそこまで気にする必要がなく、「複数の画像を一度に圧縮できれば良い」ため、--speed 1としました。

実際に作成したシェルスクリプト

以下が実際に作成したシェルスクリプトです。

#!/bin/sh -e

IMGS_PATH="./input/*.png"
OUT_DIR="/output"

cd "$(dirname "$0")"

# outputフォルダが作成されていなければ作成する
if [ ! -e $OUT_DIR ]; then
  mkdir $OUT_DIR
fi

# input フォルダ内の画像を圧縮
for img in `find $IMGS_PATH`
do
  #ファイル名のみを取得
  BASE_NAME= `basename $img`
  # 出力先のパスを指定
  OUT_IMG_PATH="$OUT_DIR/$BASE_NAME"
  echo "$BASE_NAME を圧縮中.."

  if [ -e $OUT_IMG_PATH 1]; then
    rm -f $OUT_IMG_PATH
  fi
  pngquant --output $OUT_IMG_PATH:--quality 0-100 --speed 1 256 $img

  echo "$OUT_IMG_PATH に圧縮した画像を保存しました\n"
done

インターンを通して学んだこと

質問することの大切さ

インターンを始める前は「些細なことでも分からない事を質問をする」のが恥ずかしいと思っていました。しかし、現場独自のコードの書き方であったり、社内専用のデバッグシステムの使い方があるため、質問しなきゃ分からない事がとても多かったです。タスクをこなしていく中で「分からない事をそのままにしている」事の方がよっぽど良くない事に気づきました。

コードに対する責任感

エンジニアにとって「責任感のある仕事」ってなんだろう?と以前からずっと思っていましたが、その答えが出た気がしています。インターンのタスク中に「自分の書いたコード・書き直そうと思っているコード」それぞれのメリット・デメリットをまとめ、説明する機会がありました。 今まで「こうした方が良いのではないか?」という感覚的にしかコードを書いてこなかったため、自分の中だけで完結していました。しかし、今回メリットデメリットをまとめ、説明する中でエンジニアにとって「責任感のある仕事」とは、自分の書いたコード1つ1つに対し「なぜその書き方にしたのか」「どういう意図を持って書いたのか」を他人に説明できる事であると感じました。

コードレビューの大切さ

自分の実装した機能をコードレビューしてもらう機会を何度もしていただきました。その中でElixirのコードの書き方やテストコードの書き方であったり、(これは恥ずかしいですが)自分で気付かなかったようなスペルミスをレビューしてもらいました。コードレビューをする事でよりコードの保守性が向上すると実感しました。

感じたこと・感想

楽しかった!

本当にこれに尽きると思います。開発を進める中で自分がマスタデータやAPIの設計を行うタスクがあり、サーバーエンジニアの方以外にも、プランナーさんやクライアントさんと話し合う機会がありましたが、話した人全員「ゲームをよりよくしていこう!」という雰囲気が感じられました。また、そんな方たちと一緒に開発ができていると実感でき、とても楽しく感じられました!

つよつよエンジニアがたくさん!

今回のインターンでは、サーバーエンジニアの業務に潜ってタスクをしていました。自身のタスクをこなしつつチームメンバーと1on1面談を沢山やっている人や、レビューを依頼すると1,2時間後には完璧にコードレビューをまとめてくれる人、1人で多くのタスクを抱えながらてきぱき捌けている人など尊敬できる人が多く、とても刺激のあるインターンでした!

最後に

初日は「やり切れるのか」という不安もあり、緊張していましたが、タスクをこなしていく中で少しずつ自信が付き、3週間があっという間に経っていました。3週間を通して、楽しいだけじゃなく、自分が今まで知らなかった知識や自分の弱点を知れる、学びの多いインターンだったと感じています。メンターの方、インターンシップに参加する機会を提供してくださった人事の方など、すべての方に感謝しています。3週間ありがとうございました!

あらゆる人間関係の衝突は、謙虚・尊敬・信頼・カレーの欠如によるものだ

エンジニアリングオフィス(以下EO)の島村です。

先日、アカツキゲームスに所属しているエンジニアを対象にディナー(交流会)を開催したのでレポートをさせていただきます。

開催のきっかけ

もともと、エンジニア組織としては隔週火曜日17~18時に「エンジニア定例MTG」という完全オンラインの全体ミーティングを開催しています。

そこではエンジニア全体への連絡( イベントの告知など)はもちろんですが、外部カンファレンス参加のレポートや、有志により技術共有発表があったりと、エンジニア主導で様々な情報が交換されています。

そこにEOのメンバーより、最近は出社して作業している人も増えてきており、せっかくの集まる機会だからエンジニア定例のあとの時間に横の繋がりを作るエンジニアディナーを開催しようという提案があり、実施することになりました。*1

*1:元々、エンジニアディナーという企画自体は以前より不定期で開催していたのですが、開催するきっかけがなく、半年に1回程度の開催となっていました。

続きを読む

which-keyはいいぞ

こんにちは!エンジニアの宮川です。株式会社アカツキゲームスでクライアントエンジニアをやっています。

このエントリーは Akatsuki Games Advent Calendar 2023 の14日目の記事です。
昨日の記事は「最先端のライフゲーム(Particle Lenia)を作ったので眺める」でした。

qiita.com

コンピュータープログラムから生み出される有機的な挙動にはロマンを感じますね!この手のシミュレーションは永遠に見ていられます。

14日目のこの記事では、私が愛用しているwhich-keyという仕組みを紹介します!

 

時に皆さん、ショートカットキーは使っていますか?既存のショートカットキーをマスターしたり、新しいショートカットキーを登録したりして日々QoLを爆上げしているかと思います。

ですが、ショートカットキーの登録をするにあたってこんな悩みに直面したことはありませんか?

  • 新しくショートカットキーを登録したいのに、既存のショートカットキーやmacosのショートカットキーとバッティングしてしまう
  • ショートカットキーを登録し過ぎて新たに割り当てるキーが枯渇している
  • 一生懸命ショートカットキーを設定しても、後でどのキーに割り当てたかどうか忘れてしまう
  • 同時押しが複雑過ぎて指が混線する

which-keyはそんなあなたの悩みにそっと寄り添ってくれる、新しいショートカットキーの仕組みかもしれません。

which-keyとは

which-keyとは、特定のキー(Spaceキーなど)を基点とした任意長のキーシーケンスにコマンドを割り当てることができる仕組みです。Vimmer向けに言えば、リアルタイムガイド付きLeader-コマンドとも言えます。

イメージとしては、ウィンドウ上部のメニューバーの階層構造をキーボード操作のみで「たどる」ような感じです。

which-keyは、プラグインを入れることでEmacs, Vim/NeoVim, VSCode, Intellij IDEAといったJetbrains製IDEなどで使うことができます。

デモ

言葉だけで説明するのがなかなか難しい概念ですので、早速以下に簡単なデモを見せます。

(埋め込みで画質が荒い場合はYouTubeに移動して見て下さい)

このデモではSpaceキーがwhich-keyのトリガーとなっていて、Spaceを押すと画面下部にメニューが出現します。

メニューに書かれたキーを押す事で、登録されたコマンドを実行するか、もしくは別のメニューに移動することができます。

このデモではwキーに「Window...」メニューが登録されている他、fキーには「Find...」メニュー、gキーには「Goto...」メニュー等が登録されています。

「Window...」メニューには色々なコマンドが登録されていて、例えばvでウィンドウ縦分割、sでウィンドウ横分割、cで分割ウィンドウ削除ができます。また、トップメニューのqにはエディタの終了が登録されていて、これはデモの最後で使用されています。

このように、which-keyではコマンドをメニューの階層構造にして登録することができるのです。

これを「キーシーケンスとコマンドの対応関係」として見てみると次のようになります:

  • <Space>wv → ウィンドウ縦分割
  • <Space>wv → ウィンドウ横分割
  • <Space>wc → 分割ウィンドウ削除
  • <Space>q → エディタ終了

which-keyの特徴

which-keyがどういうものなのかデモをお見せした所で、次はwhich-keyの良いところを特徴ごとに説明していきます:

which-keyの仕組み自体がチートシートの役割を果たしている

同時押しによって発動する一般的なショートカットキーとは違い、which-keyではリアルタイムでメニューウィンドウが表示されます。

これはショートカットキーのチートシートの役割も果たしていて、登録したキーシーケンスを覚えていなくてもその場で登録されているコマンドを確認することができます。

階層構造でコマンドの割当ができるので、好きなだけ登録できる

ワンストロークでキーボードの組み合わせを定義するショートカットキーとは異なり、コマンドを階層構造で登録することができるので、何個でもコマンドを登録しておくことができます。

「which-keyを起動するキーボードショートカット」さえ確保できていれば、既存のキーボードショートカットと衝突することもありません。

キーシーケンスに意味をもたせやすくて覚えやすい

さらに、メニューが階層構造で登録できることの恩恵として、キーシーケンスが比較的覚えやすいという事も挙げられます。

キーボードショートカットの空きが枯渇している場合とかだと「Ctrl + Bは使われてるから隣のGを使うか...」「Ctrl + BもCtrl + Shift + Bも使われてるからAlt + Bにするか...」といったケースがあると思います(私はあります)。そして、そうやって適当に無理やり作ったショートカットキーは割りとすぐに忘れてしまいます。

例えばデモで挙げたようなコマンドは <Space>wvにウィンドウ縦分割、<Space>wc分割ウィンドウ削除が登録されていますが、それぞれ

  • <Space>wv → Window Vertical split
  • <Space>wc → Window Close

といった意味とセットで覚えられます。(こういうのはVimmerには馴染み深いと思います。)

まっさらな状態から作り込める

ショートカットキーには慣例的に定義されているもの(Ctrl-C, Ctrl-Vなど)もあれば、ツールに最初から定義されているものもあり、それらを避けながら自分好みのショートカットキーを構築するのは面倒だったりします。

which-keyにはそういったしがらみは無く、基本的に自分でゼロから作り込むことができます。たいていのwhich-keyプラグインにはデフォルトのシーケンスも用意されているのでそれを使って見るのも良いですが、ゼロから作り込むのも楽しいものです。

which-keyが使える環境

which-keyは元はEmacsのプラグインとして開発されたものなのですが、これにインスパイアされた様々なエディタ向けのwhich-keyプラグインが開発されています。

冒頭で軽く触れましたが、私が知る限りだとEmacsの他にVim/NeoVim, VSCode, Intellij IDEA向けのwhich-keyプラグインが公開されています。せっかくなのでいくつかリンクを貼っておきます:

Emacs向けプラグイン - https://github.com/justbur/emacs-which-key

NeoVim向けプラグイン - https://github.com/folke/which-key.nvim

VSCode向けプラグイン - https://vspacecode.github.io

Jetbrains製IDE向けプラグイン - https://github.com/TheBlob42/idea-which-key

また、NeoVimにはゴリゴリにプラグインを入れまくってパッケージ化されたものがあったりするのですが(LunarVim など)、こういったものにはwhich-keyが標準搭載されていたりします。

shell環境では使えないんですか!?

...と思ったそこのあなた、安心して下さい。

私が作っておきました。

github.com

結構前に作ったもので作りも雑なのですが、一応zsh環境で動くはずです。私は日頃愛用しています。例えば、

git log --graph --decorate --abbrev-commit -5 --all --pretty=short

といったコマンドをいつも <Ctrl-G>gl で実行しています。

実装小話

which-key特有のメニューを実現するための要件として

  • Returnキーを押さなくても即座に入力を受け取れるようにしたい(一般的なCLIアプリケーションとは異なる)
  • できればプロンプトを「荒らす」ようなことはしたくない(which-keyモードを抜けた時に何も残らないで欲しい)

の2つがあったので、Cursesという仕組みを使ってレンダリングを行いました。

CursesはPythonから使うことができ、which-key-shellでもこれを使っています。

docs.python.org

ただshellという環境上、Spaceキーをトリガーにするのは難しく、そこが難点です。

おわりに

Akatsuki Games Advent Calendar 2023 14日目のこの記事では、私が日頃愛用しているwhich-keyという仕組みについて解説を行ったり自作ツールを自慢したりしました。

この記事を通して、マイナーながら強力なwhich-keyというシステムが少しでも広まれば良いなと思います。目指せ脱マウス!!

明日、15日目は竹下さん(通称: ばじくん)の「RubyでTOTPのクライアントを自前実装してみた」です!日頃からお世話になっているTOTPの仕組みがどうなっているのか、とても気になる内容です!

最後に、アカツキゲームスでは一緒に働くエンジニアを募集しています。
カジュアル面談もやっていますので、気軽にご応募ください。
応募はここから!👉 https://herp.careers/v1/aktskgames/requisition-groups/47f46396-e08a-4b2f-8f9b-b2fc79e63b83

DoxygenでC++クラスを分析し、GitHub ActionsでPRコメントする仕組みを作ってみました

こんにちは!

株式会社アカツキゲームスに所属クライアントエンジニアのスーです。 今年の The Game Awards もいっぱい情報が発表されましたね。モンハンの新作、FF7 Rebirthすごく楽しみしています。

この記事は Akatsuki Games Advent Calendar 2023 の12日目の記事です。

この記事では、私が所属するチームで Doxygen を利用して特定のクラスが変更された際に、GitHub Actionsで自動的にPRにコメントする仕組みを作ったの知見を共有できればと思います。

Advent Calendar の11日目は渡辺さんの DiscordのStage Channelに読み上げbotを導入する でした。 自分もDiscordのベビーユーザーなので、サクッとbotの作成する方法について大変勉強になりました。 自分で何かを作って、ツールをより便利にする姿勢はとてもいいですね!👍

では、本編を始めたいと思います!!

なぜこの仕組みを作るか

自分が今所属しているチームでは、日本版だけではなく、他の言語に翻訳して全世界のユーザーにプレイしています。 どの文字列が翻訳されるべきか、今まではエンジニアが機能実装する時に、「手動」で追加しています。 人のてでやるのはやはり限界があります。翻訳文字を網羅できなくて国際版制作の工数が増えたことがありました。

もし翻訳文字の追加作業が自動化できだらいいなーと思って、 コードの差分から翻訳文字を抽出して、PR出す時に自動的にその文字を下にコメントする仕組みを作ってみました! この仕組みをどうやって作るのはをここで紹介できればなと思います!

DoxygenでC++コードを分析する

対象コード

前提として、今回の対象コードは下記のようにレイアウトを示すC++ヘッダーです。 ui::Textui::FormatTextは翻訳対象クラスとして抽出したいです。 クラスの中に他のレイアウトクラスを参照する場合があります。参照されたクラスに翻訳文字が存在した場合、その参照先の文字も抽出したいです。 再帰的にヘッダーを分析する必要があります。

#include "LayoutBase.h"

class LayoutAAA : public LayoutBase
{
public:
    bool init() override;

    ui::Image* getImg();
    ui::Text* getText();
    ui::Marquee* getMarquee();
    LayoutBBB* getBBB();
};

class LayoutBBB : public LayoutBase
{
public:
    bool init() override;

    ui::Text* getText();
};

Doxyfile を作成

Doxygenは、C++、C、Java、Objective-C、Python、IDL (Corba、Microsoft 風)、Fortran、VHDL、PHP、C# 向けのドキュメンテーション・システムです。 コードを分析し、HTML、XMLなどの出力フォーマットにドキュメントを出力するプログラムです。 下記のようなよくみるドキュメントはDoxygenで作成しています。

Cocos2dxのドキュメントもDoxygenで生成されました

今回はDoxygenで特定ディレクトリーに入ってるヘッダーファイルをXMLフォーマットに出力します。Doxyfileというファイルで出力内容を設定します。 下記のコマンドで今のディレクトリーにDoxyfileを生成します。

doxygen -g

デフォルト設定を変更した項目だけ紹介します。詳しくはこちらをご覧ください。

GENERATE_XML = YES               # XMLフォーマットを出力
INPUT = path/to/code             # 対象コードのディレクトリー
OUTPUT_DIRECTORY = doxygen/xml   # 出力ディレクトリー

下記のコマンドでXMLを出力します。

doxygen ./Doxyfile

Git diffで取得した差分ファイルを分析する

Git diffで分析対象コードの差分を抽出します。変更されたファイル名を取得し、ファイルごとに分析します。 今回はRubyでスクリプトで書きました。

Doxygen XMLを読み込むするため、doxyparser という gem を使いました。

まずgit diffでコードの差分を取得します。差分があるファイルのXMLを読み込んで、再帰的に分析します。その中に翻訳対象があれば記録して最後にMarkdown形式で出力します。

▼クリックでコードを表示する

出力はこんな感じです:

## LayoutClassesの翻訳差分
### Added Strings
- LayoutAAA
  - [ ] ui::Text* getText
  - [ ] ui::Marquee* getMarquee
  - LayoutBBB
    - [ ] ui::Text* getText

### Removed Strings
- LayoutCCC
  - [ ] ui::Marquee* getMarquee

あとはその結果をPRにコメントすれば...!

GitHub Actionsで翻訳差分をPRにコメントする

対象ディレクトリーが変更があった場合、上記のスクリプトを実行し、その結果をPRにコメントするGitHub Actions workflowで実装しました。

ファイル変更は tj-actions/changed-files で取得します。 前回のプッシュからの差分だけ取得する場合、checkoutのfetch_depth を 0 に設定(超大事)して、since_last_remote_commit: true にすればOKです。

steps:
  - name Checkout Repo
     uses: actions/checkout@v4
     with:
       fetch-depth: 0

  - name: Get changed files
     id: changed-files
     uses: tj-actions/changed-files@v40
     with:
       since_last_remote_commit: true

GitHub CLIを利用してPRにコメントします。下記の処理を実行する前にGitHub CLIのインストールは必要です。 gh auth login しないとPRにコメントする権限がないので必須処理です。

  - name: Comment on pull request
     if: steps.changed-files.outputs.any_modified == 'true'
     env:
       OWNER: ${{ github.repository_owner }}
       REPO: ${{ github.event.repository.name }}
      run: |
        echo '${{ secrets.GITHUB_TOKEN }}' | gh auth login --with-token
        gh pr comment ${{ github.event.number }} --repo ${OWNER}/${REPO} -F ./output.txt

▼クリックでフルコードを表示する gist.github.com

結果

対象ディレクトリーに入ってるファイルを適切に変更して commit & push すると、下記のようなコメントが届きます! 🥳🥳🥳

github-actions君が翻訳差分をPRにコメントしてくれた!ありがとう!

まとめ

Doxygenを使用してC++コードを分析して、GitHub ActionsでPRにコメントする実装の知見とシェアしました。

はじめてRubyのカスタマイズクラスの Set を実装しました。Github Actionsの書き方やGitHub CLIの使い方もとても勉強になりました。Doxygenの他の活用法も考えたいですね〜

皆さんも、もしCIでの特定な差分内容を出力してコメントしたいなら、この記事を読んで、少しでも力になれたのなら嬉しいです。

明日の記事はEito Shimomuraさんの記事です。お楽しみに〜

最後まで読んでいただきありがとうございました!!!