CORDEA blog

Android applications engineer

JetBrains MPS をはじめよう

この記事では、JetBrains が開発した言語ワークベンチである JetBrains MPS の使い方を紹介します。

Kyash Advent Calendar 2019 13日目の記事です。

JetBrains MPS について

JetBrains MPS (Meta Programming System) は公式にも書かれている通り、DSL を構築できるツールです。
Open Source であり、Apache-2.0 でライセンスされています。

www.jetbrains.com

日本語の紹介記事としては InfoQ が良さそうです。

www.infoq.com

今は Java code の生成に優れており、実際私も Java 以外で試したことがありませんが、C なども生成できるようです。

使い方

ここからは実際に MPS を使って手を動かしながら使い方を確認していこうと思います。
全部 Blog にまとめようかと思ったのですが、とっても長くなりそうだったので Codelab にしました。
所要時間は約 1 時間です。

mps-codelab.netlify.com

MPS に関する日本語の記事をあまり見かけたことがないので比較はできませんが、それなりに詳しく書いたつもりです。
長いですが気が向いたらやってみてください。
変えたほうがよいところを見つけたら、左下の Report a mistake から Issue を立てていただけると嬉しいです。Twitter とかでも大丈夫です。

ハマったときに色々調べつつ時間をかけて自己解決することが多いので、MPS User が 1 人でも増えて知見が集まるといいなぁと思っています。

claat で生成した codelab を netlify で CD する

先日私達が公開した Android Dagger codelab は netlify を使用して公開しており、今回はその方法を紹介します。

github.com

claat は google が公開している codelabs を生成するための command line tool です。

github.com

行っていることは非常に簡単で、deploy script を netlify で走らせているだけです。
はじめに検討したのは github.io で、実際にはじめの方は github.io で公開されていましたが、
その時は GitHub Actions も有効化されておらず、自動化を実現できなかったため netlify に移行しました。

dagger-codelabs には scripts 配下にいくつかの shell script が配置されています。

dagger-codelabs/scripts at master · outerheavenproject/dagger-codelabs · GitHub

その中でも deploy.sh を netlify の build command としており、これで deploy しています。
それぞれの shell script がやっていることを簡単に説明します。

deploy.sh

dagger-codelabs/deploy.sh at master · outerheavenproject/dagger-codelabs · GitHub

download_claat, codelab_gen を叩いているだけです。

download_claat.sh

dagger-codelabs/download_claat.sh at master · outerheavenproject/dagger-codelabs · GitHub

readonly url=$(curl https://api.github.com/repos/googlecodelabs/tools/releases/latest | \
    jq -r '.assets[] | select(.name == "claat-linux-amd64") | .url')
curl -LJ -o /tmp/claat -H 'Accept: application/octet-stream' $url
chmod u+x /tmp/claat

claat command を使用できるよう、googlecodelabs/tools から download しています。
まず latest release を見て、返ってくる json から download 用の url を取得しています。
実際に json を見てみると分かりやすいでしょう。jq は新規に install などしなくても使用できます。
次に claat command を download します。このあたりに書いてあります。

あとは実行権限を付与します。

codelab_gen.sh

dagger-codelabs/codelab_gen.sh at master · outerheavenproject/dagger-codelabs · GitHub

readonly base_dir="/tmp/dagger_codelabs"
readonly file="$base_dir/$(date +%s).md"

mkdir $base_dir

cp md/chapters/*.png "$base_dir/."

cat md/INDEX.md > $file
cat md/chapters/*.md >> $file
/tmp/claat export -o docs/ $file

rm -r $base_dir

ここでは章毎に分割された markdown をくっつけています。これは章によって担当が決まっているため、少なくとも章単位で別々の人が編集できる必要があったからです。
ついでに png もあるので、参照可能な位置に copy したあと、各章を /tmp/ にある deploy 用の md に追記していきます。
最後に claat command で markdown から html に変換します。

欠点として、最後に改行を入れないとうまく出力できないという問題があります (cat で結合しているだけなため)。
今回はメンバー間の認識を合わせるのも簡単だったため、このあたりの雑さを許容していましたが、
もっと広く Contributor が集まるような場合にはもう少しちゃんと作ったほうがいいだろうと考えています。

もっと簡単に実現できる方法もあると思います。何かあればぜひコメント、Issue、PR 等ください。

【Rust】imageproc で任意の位置に text を描画する際の注意点

imageproc に限らず、rusttype で font の layout を使用する際には概ね同じ問題に当たりそうなんですが、
そこまで調べてないので imageproc に限った話として紹介します。

今回はテキストの描画位置をちょうど真ん中にすることを考えてみます。
なお、ここで紹介している描画部分のコードは GitHub にあります。

github.com


まず、imageproc の draw_text_mut で任意の文字を描画してみます。

draw_text_mut(&mut image, Rgba([0u8, 0u8, 0u8, 255u8]), 0, 0, scale, &font, "rustpythonclojureC#");


f:id:CORDEA:20190616145632p:plain

中央にする場合、テキストの正確な width と height が必要になります。
これは font.layout で取得できるので、取得して各文字の min.x, min.y, max.x, max.y をもとに周りを囲ってみます。

f:id:CORDEA:20190616151012p:plain

見れば分かる通り、例えば max.x - min.x で文字ごとの width は取れるものの、
文字間の padding (kerning 処理?) は考慮されないため、ここを考慮して width を計算する必要があります。
また、height に関しては一文字目の max.y - min.y だけを見た場合、'r' の高さしか考慮されないため、
min.y の一番小さい文字 ('h' など) - max.y の一番大きい文字 ('p' など) を取得して高さを計算する必要があります。
高さに関してはどこを baseline と置くかで計算方法も変わってくるでしょう。

このあたりが 1 つ目の注意したほうが良さそうな点です。

以上を踏まえて、文字列全体の width と height を計算し、周りを囲ってみます。

f:id:CORDEA:20190616152046p:plain

これで正確な width, height が取得できました。
ただ、ここから中央揃えにするために (image.width / 2) - (width / 2) などとして x, y を計算して描画すると微妙にずれることに気がつくはずです。
というかそもそも画像でも margin が入っていますよね...

これが 2 つめの注意点で、描画する際に x, y に margin が入るため、描画する際にここを考慮する必要があります。

実際にこの margin がどの程度かですが
x に関しては一番はじめの文字の min.x を見れば良いです。
y に関しては、min.y が一番小さい文字の min.y を見れば良さそうです (ex. 'h')。

以上を踏まえて、x, y を margin 分左と上にずらすことで、ちょうど真ん中に描画することが出来ます。

f:id:CORDEA:20190616152910p:plain

【RoN BHS】set_object_health の挙動

Rise of Nations という Microsoft から発売された名作 RTS ゲームがあります。

このゲームには Big Huge Script という、ゲーム内でシナリオやルールを作るための言語があり、
今回はこの Script で使える関数の中から、 set_object_health という関数の挙動について説明します。

set_object_health とは

ゲーム内の Script Editor では次のように表示されます。

f:id:CORDEA:20190518122404j:plain

指定した ID の object の HP を 0-100 % の範囲で指定することができます。

tl;dr

  • 3 人 1 unit の場合、0-68% までは HP - math.floor(HP / 3)

挙動

この set_object_health は歩兵などの 3 人 1 unit の場合に不思議な挙動をします。
なお、ここでは Slingers を使用して確認しています。

どういうことかというと、単純に % 指定してそのとおりに動くとは限らないということです。

まず、ターゲットの HP を 0 に設定する場合を考えてみます。

set_object_health(who, id, 0);

これを騎兵に適用すると正常に行われます。
これは想定通りの挙動と言えます。

では、歩兵に適用してみます。

f:id:CORDEA:20190518122433j:plain

これは 3 人で 1 unit という扱いであるものの set_object_health は 1 人に対して適用される場合がある、ということの気がしています。
様々な数値で試してみると判るのですが、0-68% までは HP は 57 です。必ずしも指定する値と HP は一致しません。
なぜ 57 なのかは、1 人減っていることから

85 - (85 / 3) = 57

と考えられます。(より正確には 85.0 - math.floor(85.0 / 3.0))

さて、先程 1 人に対して適用される場合がありそうと書きましたが、では 80% など 69% 以上を設定する場合を見てみます。

set_object_health(who, id, 80);

この結果は以下のようになります。

すなわち、

85 * 0.8 = 68

で、1 人ではなく、1 unit に対して適用されていそうなことがわかります。
以上のように、歩兵などの unit に適用する場合は指定 % に対して HP が比例しないため、注意が必要です。

確認に使用した Script は以下にあります。

RoN_rules/Example at master · CORDEA/RoN_rules · GitHub

Chrome のコンテキストメニューから印刷...を消す

定期的に検索と印刷...を押し間違えて辛いので消したメモ。
もしかしたら Chrome の設定とかでできるかもしれないので、直接書き換えたくない人は探してみてください。

やり方については StackExchange に載っている通りで、Preferences の printing.enabled を false にすれば良いという話。

superuser.com

この Preferences は macOS の場合 ~/Library/Application Support/Google/Chrome/Default 配下にあります。 (Windows の場合の PATH は先のリンクに書いてありました)
printer とかがある人はすでに "printing" があると思うので、そこに "enabled" を書き加えると良いです。

Before

f:id:CORDEA:20190414121735p:plain

After

f:id:CORDEA:20190414121757p:plain

backup を用意した後 jq で format して書き換えて入れておいたのですが、起動時に勝手に minify してくれました。

おわり

Thread を toString した時の Thread[foo,0,bar]

RxJava とか使ってると稀によく見る Thread[main,5,main] みたいなやつ、どれがどれだっけってなったのでメモ

そもそも RxJava の source から読み始めたのでアレなんですが、
タイトルに書いてあるように Thread の doc か toString の実装を見ればいいです
Thread (Java Platform SE 8)
jdk8/jdk8/jdk: 687fd7c7986d src/share/classes/java/lang/Thread.java

"Thread[" + スレッド名 + "," + 優先順位 + "," + スレッドグループ + "]"

RxJava でよく見る RxCachedThreadScheduler-0 はここです

RxJava/IoScheduler.java at v2.2.8 · ReactiveX/RxJava · GitHub

後ろの番号を set しているのは

RxJava/RxThreadFactory.java at v2.2.8 · ReactiveX/RxJava · GitHub

おわり

static method 等を呼び出しているテストケースで Robolectric の Shadow を使う

あんまり知られてないような知られてるような、そんな感じがしたので Robolectric の Shadow の使い道をちょっと紹介します。

robolectric.org

紹介するのは

  • static method を呼び出ている
  • kotlin の object 宣言がされた singleton を内部で直接使用している
  • 内部で new している

などなど、色々な記事で PowerMock の出番とされていそうなケースについてです。

今回はこれを Robolectric の Shadow で解決します。
前提として、私は PowerMock を入れようと思った時、それは設計を見直しをするべき時だと考えています。
とはいえ、古いアプリや大規模なアプリなどでテストは書きたいが static method の呼び出しがネックになっている、直す工数もない。しかし PowerMock は入れたくないというケースは稀ですが存在します。
そんな時、使うべきかは置いておいて Shadow が使えます。

いくつか例を紹介します。
この例は全て GitHub にあります (難読化された感じの雑な命名はあとで直します)
github.com

1 つ目

object AUtil {
    fun a() {
        throw IllegalArgumentException()
    }

    @JvmStatic
    fun b() {
        throw IllegalArgumentException()
    }
}

class A {
    fun a() {
        AUtil.a()
    }

    fun b() {
        AUtil.b()
    }
}

1 つ目の例はこれです。
こんなの存在しないと思いますが、テストからアクセスできない何かにアクセスして throw されてる、でもその後の処理がテストしたい...!みたいな、そんな感じで考えてください。

これに対しては、以下のように Shadow を定義できます。

@Implements(AUtil::class)
class ShadowAUtil {
    companion object {
        @JvmStatic
        @Implementation
        fun b() {
        }
    }

    @Implementation
    fun a() {
    }
}

これを使用するとテストを通過させることができます。

@RunWith(AndroidJUnit4::class)
@Config(shadows = [ATest.ShadowAUtil::class])
class ATest {
    @Test
    fun a() {
        A().a()
    }

    @Test
    fun b() {
        A().b()
    }

    ...
}

2 つ目

2 つ目は内部で new していて、なんか失敗しているケースです。
Shadow は constructor の呼び出しにも対応しています。

class D(d: String) {
    init {
        throw IllegalArgumentException()
    }
}

以下のような Shadow を定義します

@Implements(D::class)
class ShadowD {
    @Implementation
    fun __constructor__(d: String) {
    }
}

これを使用すると通過します
また、これは変な使い方として、

class B(private val b: String)

このようなクラスを Java 側から使用するとして

public class C {
    public void c() {
        new B(null);
    }
}

null を入れてしまうと当たり前ですが java.lang.IllegalArgumentException: Parameter specified as non-null is null ... が発生します
が、以下のような Shadow を定義すると回避できます。

@Implements(B::class)
class ShadowB {
    @Implementation
    fun __constructor__(b: String?) {
    }
}

使い所はありません。

3 つ目

実際に呼び出されたかどうか知りたい時、引数を assert したいときなど。ここまで来たら PowerMock 使えばという話なんですが、可能です。

@Implements(AUtil::class)
class ShadowAUtil {
    var isPassed = false

    @Implementation
    fun a() {
        isPassed = true
    }
}

とりあえずこんな感じで定義しました。
Robolectric は object に対応する Shadow を受け取ることができます。

@Test
@Config(shadows = [ShadowAUtil::class])
fun a() {
    val util = Shadow.extract<ShadowAUtil>(AUtil)
    assert(!util.isPassed)
    A().a()
    assert(util.isPassed)
}

便利です。

Shadow は多くの場面で活躍しますが、一方で何をしているのかが慣れるまで分かりづらいという欠点がありますので、用法用量を守って使用すると良さそうです。