Ruby on Browser 1.0 リリース

窓の杜で取り上げていただいた後も実装は少しずつ続けていて、ひとまずブラウザ上で最新のRubyを試すのに必要な機能は一通り実装できたんじゃないかと思う。リファレンスマニュアルへのリンクを貼ってシンタックスハイライトを入れたりCtrl+Enterで実行できるようにした。(自分が書いたサンプルコードはScrapboxにあるのでコピペして試せます)

https://rubyonbrowser.ongaeshi.me/

f:id:tuto0621:20220417001111p:plain

モバイルでも簡単なコードだったら書けるように色々工夫したのでちょっとしたコードを書きたいときにぜひ試してみてほしい。(Select Allボタンは結構こだわった)

f:id:tuto0621:20220417001229p:plain

他のブラウザ言語処理系と大きく違うこととして「ファイルを読み書きするAPIも使える」ということがある。元々WASIがWASMにファイルIOや通信を持せたることを目的にしたものなのでRuby WASM/WASI自体がファイルIOを持っており、そこにWasmerFSというブラウザ上に仮想ファイルシステムを持たせることができるライブラリと組み合わせて実現している。

おかげで以下のようなサンプルコードをブラウザ上で実行できる!PC上で実行するよりもはるかに安全に色々試せるのはとてもよい。(ファイルはブラウザリロードで消える)

https://docs.ruby-lang.org/ja/latest/class/File.html#I_TRUNCATE

f:id:tuto0621:20220417001133g:plain

まだまだ伸びしろを感じているのでもうしばらくRuby WASIで遊ぶ予定。

Ruby On BrowserとRuby WASM/WASIの雑感

Ruby WASM/WASI の発表にえらくテンションが上がったので、勢いで作ったものが窓の杜で紹介されてびっくりしました。(それだけ注目されているということですね)

Ruby On Browserは51行しかないHTMLでまだまだ荒削りなのでもっとちゃんとしたものを試したい方は是非TryRuby playgroundのCRuby 3.2.0dev をお試しください。

Ruby On Browser自体もまだまだ発展させていくつもりですが、現状Ruby WASM/WASIを触ってみていいなあと思ったことです。

1. 簡単に自分好みのブラウザRubyが作れる

Try Rubyのようにブラウザ上でプログラミング言語が試せること自体は現在はそこまで珍しくないですが、クライアントサイドだけで(しかもとても短いコードで)動かせるのは大変魅力的です。個人のPCやイントラネット上に好みのカスタマイズを加えたブラウザRubyを簡単に構築することができます。

2. wasmファイルの入れ替えが簡単

Webアプリ自体はそのままにバイナリファイルの ruby.wasm を入れ替えるだけで別バージョンのRubyを試すことができます。

3. 最新版がすでにNightlyビルドされている

すでにCRubyのレポジトリに取り込まれているため常に最新の.wasmが動くのも大きいです。Nightlyビルド を使えば一番簡単に最新版を試せる環境となりそうです。(2022-03-25現在のruby.wasmは9.8MiBなので1週間分保管しておいても70MiB程度)

f:id:tuto0621:20220326231418p:plain

4. ファイルAPIが使える

内部で使っている @wasmer/wasmfs はメモリ上に仮想ファイルシステムを構築できるため、File.read, File.write のようなファイルAPIもブラウザ上で動かすことができます。(今実験中です)

WASI自体はファイルシステム以外にソケット通信などもサポートしているため、将来的には require "net/socket"require "net/http"もできるようになるかもしれません。夢が広がりますね。

gifアニメにお絵描きできるGifDrawerをリリースしました

リモートワークでスクショやgifアニメに手書きコメントを付けて共有する機会が増えたのですが、 手書きコメントにもアニメーションがつけられたら便利なんじゃないかと思い作りました。

左クリックで書くf:id:tuto0621:20220308011626g:plainf:id:tuto0621:20220308011651g:plain
f:id:tuto0621:20220308012355g:plainf:id:tuto0621:20220308011726g:plain
f:id:tuto0621:20220308011740g:plainf:id:tuto0621:20220308011752g:plain

インストール

https://github.com/ongaeshi/GifDrawertagsから最新版をダウンロードして適当なところへ展開してexeをダブルクリックすれば動きます。gitレポジトリを直接cloneしてもよいです。

お絵描きしながらタイムラインを動かすことでアニメーションがつけられます。コマ送りを使うと書きやすいです。ペンタブで書きたい人はWindows InkをOFFにしてください。

gifや画像をドラッグ&ドロップするとそれを背景にしてお絵描きできます。

ソフトウェア構成

ClipScriptというタイムラインに連動したアニメーションをスクリプトで記述できるアプリケーションの上で作っています。 ClipScriptはOpenSiv3DのAPIをmrubyにバインドして動いています。

大部分がスクリプトで動いているためmain.rbのパラメータを変更すると色セットやペンの太さを変更することができます(本当はもっと色々できます)。

# 調整用パラメータ

# ペンの色
# "navy", "blue", "aqua", "teal", "olive", "green", "lime", "yellow", "orange"
# "red", "fuchsia", "purple", "maroon", "white", "silver", "gray", "black"
PEN_COLORS = ["red", "blue", "green", "black"]

# ペンの太さ
PEN_THICKNESSES = [1, 2, 4, 8]

# 消しゴムの太さ
ERASER_THICKNESS = 32

# gifアニメが未設定のときの終了時間
DEFAULT_END_TIME = 3

# コマ送りの再生レート(1が60fps、3で20fps)
FRAME_ADVANCE_RATE = 3

.
.

おわりに

感想や質問など #gifdrawer ハッシュタグを付けたりしてつぶやいてもらえたら嬉しいです。

残りゲーム体力が少なくてもブラウザゲームをRubyで簡単に書きたい

crisp-game-libというゲーム制作体力が残り10%でもゲームが書けるJavaScriptのライブラリがあるのだが、それをOpal に移植してRubyで書けるようにした。(画面クリックで遊べます)

https://ongaeshi.github.io/crisp-opal-test/fall/

2色のりんご🍎が落ちてくるので、同じ色を取ると得点、違う色は避ける、画面タップでプレイヤーの色が変わるので状況によって使い分けよう。モバイルの場合は両手プレイで移動と色替えの操作の手を分けると遊びやすいかも。

コード

https://github.com/ongaeshi/crisp-opal-test/blob/master/fall/main.rb

setup(
  title: "FALL",
  description: <<~EOS,
    [SWIPE] Move
    [TAP] Color
  EOS
  characters: [
    <<~EOS,
      llllll
      ll l l
      ll l l
      llllll
       l  l
       l  l
    EOS
    <<~EOS,
       pp
       pp
     rrrrrr
     rrrrrr
     rrrrrr
     rrrrrr
    EOS
    <<~EOS,
       yy
       yy
     llllll
     llllll
     llllll
     llllll
    EOS
  ],
  options: {
    isPlayingBgm: true,
    isReplayEnabled: true,
    seed: 200
  }
)
 
class Falling
  def initialize(string, x, y)
    @string = string
    @pos = vec(x, y)
  end

  def update(player_color)
    @pos.y += 1
  
    if char(@string, @pos).is_colliding.char.a
      if player_color == "red" && @string == "b" ||
        player_color == "black" && @string == "c"
        add_score(1, @pos)
        return true
      else
        end_game
      end
    end

    @pos.y > 102
  end
end

@player_color = nil
@fallings = nil

def update
  if ticks == 0
    @player_color = "red"
    @fallings = []
  end

  if input.is_just_pressed
    @player_color = @player_color == "red" ? "black" : "red"
  end

  color(@player_color)
  char("a", input.pos.x, 95)

  color("black")
  @fallings.push(Falling.new(rndi(2) == 1 ? "b" : "c", rnd(100), 0)) if ticks % 5 == 0
  @fallings.delete_if do |e|
    e.update(@player_color)
  end
end

crisp-game-libはupdateという関数の中に処理を書いてゲームが作れる。初期化処理はif ticks==0の中に書く。

crisp-game-libの特徴として描画処理と当たり判定が一体化しているところがある。対象と重なっているかは描画したときの戻り値を使って判定する(まだ描画されていない対象との当たり判定は当然取得できないので描画順に気をつける必要がある)。これは極力短く書くための仕組みで、ゲームは通常ロジックと描画が分かれて記述することが多いが確かにこれならコード量が半分になり余計なことを考えずに済むと思った。テクスチャも貼れない(ドットは配列作れば打てる)、サウンドは自動生成されたものを番号指定するだけ、効果音組み込みで数種類の中から選択、とライブラリ全体としてゲームを高速に作るために特化していて書いていると心地よい。(このゲームも書き始めてから30分位で書けたと思う) 後リソースを一切要求しないのでコードだけで配布できるのもよい。

もっとたくさんのゲームを触ってみたい方は、crisp-game-lib作者のABAさんがすでに大量の面白いゲームを作られているので是非遊んでみて欲しい。ほぼ全てのAPIがOpalに移植できていると思うのでブラウザゲームRubyで記述したい、という趣味の方がいましたら是非お使いください。

Humankindを読んでニュースの読み方を変えた

話題になっている「Humankind」を読み終わった。

読む前は人間は基本いい人だから世の中は考え方1つで変わるよ、みたいな楽観的なことが書いてあるのかと思っていたがそれは前置きで、むしろ大半の人間は本来善良なのに

  1. 民衆が愚かで悪人が多いと認識されている方が都合が良い
  2. 事実と異なるデータや一面的な思想を伝えて他の人を思い通りの方向に誘導したい
  3. 自分の地位や資産を増やせるならどんな手段を取っても構わない

といった目的を持つ少数の人間によって「冷笑的な社会だと思い込まされている」ことに警鐘を鳴らしているのだと理解した。すごく面白かった。これから少しずつ自分の中の認識も見直していきたいと思う。

ニュースを避ける

少しの間、想像してみよう。新しい薬が市場に出る。その薬は中毒性が高く、誰もがすぐ夢中になる。科学者は調査して、その薬は「リスクの誤認、不安、気分の低下、無力感、他者に対する軽蔑と敵意、感情の麻痺を引き起こす」という結論を下す。
この薬をわたしたちは使おうとするだろうか。子どもたちが摂取するのを許すだろうか。政府は認可するだろうか。答えはすべてイエスだ。なぜなら、その薬はすでに、現代の最大の依存症の一つを引き起こしているからだ。わたしたちが毎日摂取し、多額の助成金を受けていて、子どもたちに大量に配られている薬。その薬とは、ニュースである。

書籍内には沢山の興味深いトピックがあるがその中でも「ニュースを避ける」はすぐに自分の中に取り入れられそうで色々試している(元々自分のスマホのアクティビティの中でニュースサイトの閲覧率が高めで、また調子が悪いときほどニュースばかり見る傾向があると感じていた)。

平日はニュースを見ない

平日にニュースを見るのをやめて、暇なときは漫画読んだりゲームやったり好きなことをするようになった。

世代的に日々新聞を読まないと社会人としてよくない、という思想を受けてきたので何となく心理的な束縛があったがHumankindのおかげで開放された。

手持ち無沙汰になったときに無意識的にニュースサイトにアクセスしてしまうので、体操など体を動かすことも含めて、暇なときにやることをもう少し揃えておくのがよさそう。

テレビもプライムタイムに付けると大体ニュース番組がやってる確率が高いのでその場合はチャンネルを切り替えるか、録画した別の番組を見るようにしている。

日曜版の新聞を読む

本当にニュースを何も見ないとさすがに世間から切り離されてしまうので、最小回数で効率的に情報を取得したい。

新聞の土曜版や日曜版は平日よりも比較的ゆったりしており、インパクトのある見出しでセンセーショナルに読者をゆさぶる度合いも少なめでよい。また今週あった主なニュースをまとめた記事があったりするので、ここだけ見ておけば重要なニュースを取りこぼすこともなさそう。

一部180円位なのでこれなら月1000円以内で済む。毎週違う新聞を買ったりもできる。

特定のニュースを深掘りしたくなったら後でインターネットで検索する。(これは能動的な調べものなのでニュースを読むとは違うという認識)

まとめ

今はこんな感じの作戦でやっている。まだまだ調整が必要そうだが自由時間が増えたような感触があるのが嬉しい。(この記事を書く時間も捻出できた)

まずは毎日それなりにあったニュースを見ていた時間を、それ以外のことに割り当て直していこうと思う。

Rubyの開発環境を2021年ぽくする for Windows

2021年なのでこれくらいは欲しい。

  • Ruby 2.7.4
  • VSCode
  • バイナリgemを確実にビルドできる
  • コードフォーマッタ
  • Lint
  • デバッガ
  • コード補完

それぞれは独立した機能なので全部入れなくてもいいと思います。(個人的には上から順に必須度が高い)

Ruby 2.7.4

Ruby3自体は安定しているがgem周りの挙動が若干安定していなかったのでこちらを採用した。 (RubyInstallerも2.7系をまだおすすめしていた。)

https://rubyinstaller.org/downloads/

rubyinstaller-devkit-2.7.4-1-x64.exeをダウンロードしてインストール。

Rubyのインストール終了後にmsysなどもインストールしてくるか聞いてくるので基本的には全てインストール。

バイナリgemのインストール

スタートメニューに「Start Command Prompt with Ruby」が作られるのでそこからgem installする。

f:id:tuto0621:20210807102945p:plain

(ビルドにかなり色々必要な) popplerもちゃんとインストールできて感動した。

コードフォーマッタ

コードフォーマッタにはrufoを使うことにした。 rubocopと比べてかなり高速に動作するのがよい。 VSCodeで編集中は定期的にShift+Alt+Fする派なのですぐに整形できる方がありがたい。prettierと違ってnodeを必要としないのもよい。

> gem install -N rufo

f:id:tuto0621:20210807103030g:plain

※ 整形したコードは有名なprettierのあれ

Lint

Toolboxで調べるとかなり勢いのあるstandardを採用。 面倒な設定無しですぐに使えるのがありがたい。

> gem install -N standard

適当なRubyプロジェクトの下に移動して、以下のコマンドを実行すればいい感じでコードを修正してくれる。

$ cd /path/to/project
$ standardrb --fix 

手持ちのmrubyプロジェクトのコードも問題なくコード修正できた。

ifのtrue節とfalse節で同じ変数に代入する場合に式の戻り値で代入するように変更してくれてなかなかすごい。(というかRubyのifが文じゃなくて式なことも知らなかった。こういうのも勝手に教えてくれるのがLintのいいところだと思う)

f:id:tuto0621:20210807103441p:plain

設定すればVSCodeからも問題のあるコードを教えてくれる。

f:id:tuto0621:20210807145448p:plain

デバッガ

これは色々なところでおすすめされていたgemをそのまま使った。

> gem install -N debase ruby-debug-ide

f:id:tuto0621:20210807145604g:plain

デバッガをF5で起動するには.vscode/launch.jsonの設定が必要。(VSCodeGUI上で自動生成してくれるし、手で書いてもよい)

f:id:tuto0621:20210807145718p:plain

VSCodeの設定

ここまでインストールしたgemをVSCodeでも使えるようにする。

いわゆるRubyプラグインをインストールして、以下の設定を加える。

https://github.com/testdouble/standard/wiki/IDE:-vscode

  • Language Serverを有効にする
    • ブロックのfold, unfoldなどが有効になる
  • インテリセンスにrubyLocate
    • F12で定義ジャンプができるようになる
  • Linterはstandard
  • Formatterはrufo
  • Debuggerは特に設定不要

settings.jsonは以下のような感じ。

{
    "ruby.lint": {
        "standard": true,
    },
    "ruby.format": "rufo",
    "ruby.useLanguageServer": true,
    "[ruby]": {
        "editor.tabSize": 2
    },
    "ruby.languageServer": {
        "logLevel": "info"
    },
    "ruby.intellisense": "rubyLocate",
}

コード補完

ここまででも十分に便利だが、さらにコード補完にも対応するならsolargraph gemを入れるとよい。

> gem install -N solargraph 

solargraphには専用のVSCodeプラグインが用意されている。"Ruby Solargraph"をインストール。

https://github.com/castwide/vscode-solargraph

補完だけじゃなくメソッドのドキュメントがその場で見れるのも便利。

f:id:tuto0621:20210807150126p:plain

まとめ

コード整形、Lint、デバッガ、コード補完とコーディングに必要な機能が一通り使えるようになり大分便利になった。

この辺りのコーディング支援機能がgolangやRustのような後発の言語と違って標準で用意されていない(もしくは選択肢が多すぎてどれを使ってよいか分からない)ため、どのgemを入れればよいかを検証するのにそこそこ時間がかかった(探せば何かしらあるであろうことは分かっていたのだが)。

irbも最近はコード補完が強力になってかなり使いやすくなったため、例えばコード整形機能だけでも標準で用意されているとすごく印象がすごくよくなりそうだなぁと思った。

Rubyは成熟した言語なのでこの辺りの周辺機能の充実がより重要なのかもしれない(https://github.com/ruby/debug などの開発が進んでいるのはとても楽しみです)。

Rustのライフタイムについて調べている

The Rust Programming Languageを読んでいる。GC無しでどうやって参照を安全に管理しているのかを知りたかったのだが、10章ライフタイムまで来てようやくぼんやりと分かってきた。

Rustの全ての参照には寿命が設定されている。書かなくていい場合もあるがそれは暗黙で設定されている。参照は当然何らかの実体を指しており(nullは無い)、実体の寿命より参照の寿命が長い場合はエラーになる。C++でこれをやった場合はいわゆるダングリングポインタになる。

ここまでは何となく分かっていたのだが、問題は関数の引数に渡した参照を戻り値で返すような場合だ。関数コールはいくらでも深くできるので正確にどの実体を指しているか把握しようとすると難しい。

例えば fn longest(x: str&, y: str&) -> str& のような、文字列参照を2つ受け取って長い方を返す関数を考えてみる。寿命にしても文字列長にしてもx, yどちらが長いかは呼び出し箇所によって異なる。どのように参照安全を確保すればよいのだろうか?Rustではこの問題を解決するためにライフタイムを明示させる。(実際のところ前述の書き方ではRustはコンパイルエラーになる)

fn longest<'a>(x: str&'a, y: str&'a) -> str&'a

'aがライフタイムで、この場合はx, yと同じ寿命の文字列参照を返す、という意味になる。

呼び出し側はx, yどちらの参照が返ってくるか分からないので「longest()の結果の参照は引数の短い方の寿命だと仮定してその後のコードを処理する」ことになる(ここが賢いところ)。確かによく考えたらコンパイラが実際の寿命よりも仮定すればコードは安全になる。

メンバ変数の参照にもライフタイムが設定されており、自分よりも早く実体が消えるものの参照はメンバ変数に代入できない。基本的な考え方は同じということだ。

まとめると

  • 寿命が自分よりも短い実体の参照は保持できない
  • 関数呼び出しで複数の候補から参照を返すような場合は、その中で最も短いものが返って来ると仮定して静的に解析する

となる。寿命のワーストケースを仮定しながら解決していけば静的に参照安全を保証できるのか。