tokuhirom's Blog

kotlin native のデバッグ用の gradle task を定義する

こんな感じ。stdin も渡した方がいいね

// ./gradlew :capjoy:runDebug -PexecArgs="displays"
tasks.register<Exec>("runDebug") {
    dependsOn("linkDebugExecutableNative")
    val arguments = project.findProperty("execArgs")?.toString()?.split(" ") ?: listOf()
    args(arguments)
    environment("CAPJOY_GRADLE_RUN_DEBUG", "true")
    executable = file("$buildDir/bin/native/debugExecutable/capjoy.kexe").absolutePath
    standardInput = System.`in`
}

Kotlin でゲーム作るなら korge が良さそう

https://korge.org/

普通に使えそうな感じがする。

kotlin native を動かしてみる

最初にkotlin native を動かした場合

e: org.jetbrains.kotlin.konan.MissingXcodeException: An error occurred during an xcrun execution. Make sure that Xcode and its command line tools are properly installed.
Failed command: /usr/bin/xcrun xcodebuild -version
Try running this command in Terminal and fix the errors by making Xcode (and its command line tools) configuration correct.

というようなエラーになることがあります。

sudo xcode-select --switch /Applications/Xcode.app

この場合はこうすればOK.

build.gradle.kts を以下のようにする。

plugins {
    kotlin("multiplatform") version "2.0.0"
}

group = "org.example"
version = "1.0-SNAPSHOT"

kotlin {
    macosArm64("native") {
        binaries {
            executable()
        }
    }

    sourceSets {

    }
}

repositories {
    mavenCentral()
}

ソースファイルを src/nativeMain/kotlin/Main.kt あたりに置いておけばOK。

Hello, world レベルのビルドが、6秒ぐらい。 コマンドの実行自体は速い。

tauri v2 では SystemTray は TrayIcon という名前に変わっている。

https://v2.tauri.app/start/migrate/from-tauri-1/#migrate-to-tray-icon-module

v2 だと SystemTray は TrayIcon に変わっている。 マイグレーションガイドの通りにやれば、動きそう。

僕が neojot で tauri v2 beta を使い始めた頃には、ドキュメントが皆無だったが、最近は migration guide が充実しているので良い。

kotlin-power-assert-demo

kotlin 公式で power-assert が出てた。これは最高っぽい。 (まだできたてホヤホヤ Experimental です)

実際動かしてみて、その結果をレポジトリに置いておいた。 https://github.com/tokuhirom/kotlin-power-assert-demo

assertj/assertK などのアサーションライブラリは便利なのだのだけど、、

そこで、power-assert ですよ。

assertK でこう書いてたコードが

    val people = listOf(Person(name = "Sue"), Person(name = "Bob"))
    assertThat(people)
        .extracting(Person::name)
        .containsExactly("Sue", "Bob")

こう書いたら

    @Test
    fun testComplex() {
        val people = listOf(Person("Sue"), Person("Bob"))
        assert(people.map { it.name } == listOf("Susie", "Bob"))
    }

こうだ!

    Assertion failed
    assert(people.map { it.name } == listOf("Susie", "Bob"))
           |      |               |  |
           |      |               |  [Susie, Bob]
           |      |               false
           |      [Sue, Bob]
           [Person(name=Sue), Person(name=Bob)]

最高!

なお、multiplatform にも対応しているみたい。 とくに、kotlin/js とかでテスト書く時にはコレ使うと便利そうねぇ。 (kotlinx.test は便利だがマッチャの種類が少なめ。そこをこれでカバーできそう。 )

tauri アプリにアイコンを設定する方法

https://tauri.app/v1/guides/features/icons/

これで解決。簡単。

npm run tauri icon ~/Documents/neojot.png

kotlinx.serialization のこと

kotlinx.serialization は、kotlin 用のシリアライゼーションライブラリである。 gradle のプラグインが提供されていて、@Serializable アノテーションがついているクラスに対するシリアライザを生成してくれる。 プラグインのソースコードは https://github.com/JetBrains/kotlin/tree/master/plugins/kotlinx-serialization にある。なにかコード生成ライブラリ使ってるのかと思ったら kotlin そのものの機能を使ってそう。

kotlinx.serialization のドキュメントは以下の場所にある。Polymorphism のような機能があって Jackson に出来ることは一通りできそう。 https://github.com/Kotlin/kotlinx.serialization/blob/4bf4113c2c6936e841caf2a8670382e701e7fcc2/docs/serialization-guide.md

たとえば以下のように使うのだが、、

package org.example

import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class Greeting(val greeting: String)

fun main() {
    println("Hello World!")
    println(Json.encodeToString(Greeting("Hello, Kotlin/JS!")))
}

生成されるコードは以下のようになっている。Greeting$serializer.serialize が生成されてるので、実行時には高速に処理ができそうだ。 (kotlin のデコンパイルを IDEA がサポートしてほしいなぁと思いつつ幾星霜)

// IntelliJ API Decompiler stub source generated from a class file
// Implementation of methods is not available

package org.example

@kotlinx.serialization.Serializable public final data class Greeting public constructor(greeting: kotlin.String) {
    public companion object {
        public final fun serializer(): kotlinx.serialization.KSerializer<org.example.Greeting> { /* compiled code */ }
    }

    internal constructor(seen0: kotlin.Int, greeting: kotlin.String?, serializationConstructorMarker: kotlinx.serialization.internal.SerializationConstructorMarker?) { /* compiled code */ }

    public final val greeting: kotlin.String /* compiled code */

    public final operator fun component1(): kotlin.String { /* compiled code */ }

    public open operator fun equals(other: kotlin.Any?): kotlin.Boolean { /* compiled code */ }

    public open fun hashCode(): kotlin.Int { /* compiled code */ }

    public open fun toString(): kotlin.String { /* compiled code */ }

    @kotlin.jvm.JvmStatic internal final fun `write$Self`(self: org.example.Greeting, output: kotlinx.serialization.encoding.CompositeEncoder, serialDesc: kotlinx.serialization.descriptors.SerialDescriptor): kotlin.Unit { /* compiled code */ }

    @kotlin.Deprecated public object `$serializer` : kotlinx.serialization.internal.GeneratedSerializer<org.example.Greeting> {
        public final val descriptor: kotlinx.serialization.descriptors.SerialDescriptor /* compiled code */

        public final fun childSerializers(): kotlin.Array<kotlinx.serialization.KSerializer<*>> { /* compiled code */ }

        public final fun deserialize(decoder: kotlinx.serialization.encoding.Decoder): org.example.Greeting { /* compiled code */ }

        public final fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: org.example.Greeting): kotlin.Unit { /* compiled code */ }
    }
}

さて、実際速度的にはどうなのかな? というのが気になりますよね。 kotlinx.serialization のレポジトリ、ちゃんとベンチマークコードが入っているのに、結果をのせてないところが奥ゆかしいですね。 https://github.com/Kotlin/kotlinx.serialization/blob/4bf4113c2c6936e841caf2a8670382e701e7fcc2/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt

./gradlew jmh

とかすれば、結果が得られるので、手元で動かして結果を見てみると良さそう。

Benchmark                                               Mode  Cnt      Score      Error   Units
JacksonComparisonBenchmark.jacksonFromString           thrpt   10    731.704 ±   17.971  ops/ms
JacksonComparisonBenchmark.jacksonSmallToString        thrpt   10   9779.674 ± 3045.866  ops/ms
JacksonComparisonBenchmark.jacksonToString             thrpt   10   1811.471 ±   55.360  ops/ms
JacksonComparisonBenchmark.jacksonToStringWithEscapes  thrpt   10   1508.877 ±  122.538  ops/ms
JacksonComparisonBenchmark.kotlinFromStream            thrpt   10   1080.475 ±   82.937  ops/ms
JacksonComparisonBenchmark.kotlinFromString            thrpt   10   1375.436 ±   89.197  ops/ms
JacksonComparisonBenchmark.kotlinSmallToOkio           thrpt   10   8141.993 ±  111.842  ops/ms
JacksonComparisonBenchmark.kotlinSmallToStream         thrpt   10  10325.260 ± 2784.316  ops/ms
JacksonComparisonBenchmark.kotlinSmallToString         thrpt   10  10832.368 ±  306.662  ops/ms
JacksonComparisonBenchmark.kotlinToOkio                thrpt   10    898.711 ±   26.997  ops/ms
JacksonComparisonBenchmark.kotlinToStream              thrpt   10   1011.233 ±   62.126  ops/ms
JacksonComparisonBenchmark.kotlinToString              thrpt   10   1912.123 ±  192.133  ops/ms
JacksonComparisonBenchmark.kotlinToStringWithEscapes   thrpt   10   1336.079 ±  303.871  ops/ms

結果はこんな感じになる。 chatgpt で markdown に整形した結果が以下。そんなにすごい差があるわけでもない。

Kotlin/JS のサンプルコード集をつくりました

https://tokuhirom.github.io/kotlin-js-samples/

Kotlin/JS 面白いなぁとおもって試してみた。 ので、サンプルコード集を作ってみました。git clone するとすぐに試せます。

なかなかの力作なのでお試しあれ。

見た目と kotlin/js

Kotlin/JS と CSS framework とか MUI のような UI library などについて考える。

kotlin/js を使うときのフレームワークは基本、どうするんだ。

kotlinx.rpc 使ってみたよ

kotlinx.rpc が楽しそうなので触ってみたのでメモ。

https://github.com/Kotlin/kotlinx-rpc/tree/main/samples/ktor-web-app ここにあるサンプルがわかりやすくてすぐ動くので、これを触って

path とかゴチャゴチャ悩まずに kotlin の interface だけ定義すれば良いのは楽かも。 とはいえ、websocket ベースだとログとか悩まないといけないという面もあり、難しいところ。

gRPC サポートはいって安定したら、サーバー間通信に使うと良いかも。

kotlin/js のチュートリアルに関するメモ

https://github.com/kotlin-hands-on/web-app-react-kotlin-js-gradle/issues/42

kotlin hands on の web-app-react-kotlin-js-gradle のコードを実行してみていて、、kotlin-wrappers の最新版に差し替えて動かしてみたら全く動かなかったんで、これ動くようにしてくれないかねえ、と言ってみた。今日現在は web-app-react-kotlin-js-gradle は kotlin-wrappers の 1.0.0-pre-430 を使っている。

kotlin-wrappers の pre-750 版だと document.getElementById("root") の結果が org.w3c.dom.Element なのに対して、createRoot の引数は web.dom.Element となっていて、、この2つは相互に変換することは出来なさそう。

どうしたらいいの?

ということで、issues で聞いてみた。

import web.dom.document instead of import kotlinx.browser.document で解決。

gradle の dependency locking っていつから使えるの?

https://docs.gradle.org/4.8/release-notes.html 4.8から使えるっぽい。歴史がすごい。

The endpoint を組み立てた

最近は keyball44 を利用しているわけだが、親指周りにキーがもっと多かったら便利なのかなぁと思い、YMGWorks さんの The endpoint を booth で購入し、試してみた。

トラックボールの位置は左手親指。トラックボールの位置の自由度が高いのは良いなぁ。一方で、ビルドガイドで「こうだったらこう。そうじゃなかったらこう」みたいな記述が多いので、注意して読まないといけないので集中力を要する。

左手親指においたらトラックボールの位置が結構遠くて、僕の手だとちょっとキツかった。 また、キーが多いとはいえ、多かったら多かったで、使うキーの種類をもっと増やしたいとはならないなぁ、と感じたので、keyball44 でちょうど良いのかも。と思った。

また、The endpoint のトラックボールモジュールは蓋がついていないため、傾けてるとすぐに外れて転がっていってしまうのがちょっと不便。トラックボールモジュールの追加部品をけぺおさんが作っているので、そういったものを使わないと、なかなか使いにくいかもしれず。

トラックボールモジュールに含まれているネジの種類が多くて、ネジの判別をするのが結構難しいので、マステの上に置いて数えながらやると良さそう。

ロータリーエンコーダーっていうのを初めて取り付けてみたが、自分的には用途がちょっと思い浮かばなかった。

などなど。

Keyball44 に横スクロール用のボタンを設置すると便利

https://github.com/tokuhirom/keyball/commit/8ae41b881609df2692aa4daa899131a171976202

こんなパッチをファームウェアに当てた。 完全に自分用なので雑。

Special Key Code の 16 を押下している間だけ、スクロールモードに入ると同時に HORIZONTAL スクロールロックをかけるという感じ。 これにより、めっちゃ快適に横スクロールできるようになって最高。

まじおすすめです。

Discussions にも投稿してみた。 https://github.com/Yowkees/keyball/discussions/561

reactor-netty の Connection prematurely closed BEFORE response

https://projectreactor.io/docs/netty/release/reference/index.html#faq.connection-closed

公式ドキュメントにトラブルシューティングの方法がまとまっている。

HTML の pre を折り返したい

HTML の pre 要素を利用して表示させているときに、通常は長い行があっても折り返されない。 折り返したい場合は以下のようにする。

.wrap {
  white-space: pre-wrap;
  word-wrap: break-word;
}

gradle の detekt と detektMain の違い

./gradlew detekt では一件も検出されないが、detektMain だと検出されまくるという時がある。

一般的な checkstyle などのプラグインではcheckstyleMain は checkstyle をメインソースセットに対してだけ実行するという意味なので、checkstyleMain と checkstyleTest を足したものにだいたい等しいのだが、detekt の場合は違う。

detektMain/detektTest の時は Type Resolution が有効になる という違いがある。

detekt の組み込みのルールの中にも、Type Resolution が有効じゃないと動かないテストも多いので注意が必要(NullableToStringCall など)。 ./gradlew detekt で CI していると./gradlew detektMain では通らないコードができあがる。

rustmigemo と surrogate pair と

rustmigemo という、migemo を rust で実現できる便利な crate がある。 これを利用しようとしたところ、入力文字列として "h" とか "s" を入れたときに、panic することがわかった。 これは、h とか s とかで始まる文字列として、surrogate pair が含まれているからだということが、panic したところに printf debug していたらわかった。

surrogate pair で検索するような文字を、migemo で探したいことはほぼないと思うので、実際にどのような文字が含まれているかを調べてみることにした。

python で以下のようなスクリプトをさらっと書いて、辞書ファイルをスキャンしてみる。

import codecs
import sys

def is_surrogate_pair(char):
    """
    Checks if a character is in the surrogate-pair range.
    """
    return 0x010000 <= ord(char) <= 0x10FFFF

infile = sys.argv[1]
with codecs.open(infile, 'r') as file:
    for line in file:
        has_surrogate_pair = False
        for char in line:
            if is_surrogate_pair(char):
                print("U+%X" % ord(char))
                line = line.replace(char, f'**{char}**')
                has_surrogate_pair = True
        if has_surrogate_pair:
            print(line)

実行したところ、以下のような文字だということがわかった。

U+20B9F
しか    仕懸    仕掛    仕替    併      兪      叱      史家    呵      四角    始華    子夏    子華    市価    市花    師家    志嘉    志賀    歯科    死花    然
        知客    確      確然    私家    糸価    紙価    紙花    緊      而      聢      芝河    詆      詞花    詞華    詩家    詩歌    賜暇    蹙      顰      飾
        **𠮟**

U+20B9F
しっ    七      卓      叱      執      失      嫉      尻      悉      櫛      湿      漆      濕      疾      確      緊      質      **𠮟**

U+20B9F
しつ    七      仕付    仕詰    叱      喞      執      失      嫉      室      志都    悉      桎      湿      漆      濕      為付    為詰    瑟      疾      膝
        蟋      貭      質      躾      隰      隲      **𠮟**

U+27631
ふき    不帰    不羈    不覊    不記    不諱    不軌    付記    吹      富喜    富貴    布岐    腐気    苳      葺      蕗      袘      附記    **𧘱**

人生で一度も使おうと思ったことがない漢字なので、これはまぁ検索出来なくてもいいだろうという気分になった。

サロゲートペアを含む文字を除外する処理を辞書作成スクリプトから除外すればいいな、と思ったが、、 yet-another-migemo-dict ではビルドツールが全部含まれてるわけじゃなくて手である程度ビルドする感じになっていたので、めんどくさいからスクリプトを書いて、再生成可能なようにした。

https://github.com/oguna/yet-another-migemo-dict/pull/3

Codemirror6 で @codemirror/lang-legacy-modes と @codemirror/lang-markdown を組み合わせる

https://discuss.codemirror.net/t/codemirror-lang-markdown-with-codemirror-legacy-modes/7925

Codemirror6 では、一部のメジャーなプログラミング言語はサポートされているが、5時代にはサポートされていたのに6ではサポートされていないプログラミング言語も多い。 そういったプログラミング言語は @codemirror/lang-legacy-modes を使うことで、利用可能だ。 @codemirror/lang-legacy-modes のドキュメントを参照すれば、Codemirror6 でどのように使うかは書いてある。

しかしながら、Codemirror6 の lang-markdown に対してどのように使うかは書いてなかった。

そこで、フォーラムで質問したところ、@marjin 氏から即座にレスがついたので大変ありがたかった。

@codemirror/language-data というパッケージを使うと、必要なプログラミング言語の文法を動的に読んでくれるらしく、lang-legacy-modes の中身もすぐに使えるようだ。

最高!

tauri 2.0.0 beta1 に移行した

tauri 2.0.0 beta1 が出た。今後は大きい breaking changes はなさそうってことなので、移行してみる。 趣味で作ってるメモアプリを tauri 2.0.0 beta1 に移行した。

趣味だから、まぁ beta でもいいかな的なやつです。

npm install @tauri-apps/cli@next
npm run tauri migrate

ってやると、一通り自動マイグレーションされるので、それでだいたいいける。 が、微妙にぶっ壊れるので、新規で skelton 作って、それとの差分を見ながら調整していくみたいな作業はやっぱり必要。

fs まわり

[dependencies]
tauri-plugin-fs = "2.0.0-beta.0"

というふうに rust 側の Cargo.toml に依存追加する。

src-tauri/src/main.rs に以下のように書いて、プラグインを有効化する必要がある。

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
}

JS の API で、renameFile が rename になったり、removeFile が remove になったりと、メソッド名もそこそこ変わっている。 各APIの dir 指定が baseDir という名前に変わってる。rename の基底ディレクトリが newPathBaseDir と oldPathBaseDir に別れたの最高っぽい。

あとは、 credentails 周りが変わってる。 migration ツールが src-tauri/capabilities/migratred.json に権限リストを移行させてくれるが、権限の名前が変わってるので、そのままだと起動しなくなる。

例えば以下のようなエラーになる。

Permission fs:allow-read-file not found, expected one of app:default, app:allow-app-hide, ...

appdata なら appdata に読み書きする権限が、個別で用意されるようになってるんで、ここのへんのフォルダだけ触る場合は以下のように書いたらよろしい。

{
  "fs:allow-appdata-write-recursive",
  "fs:allow-appdata-read-recursive"
}

メニューが便利

Menu が rust ではなく JS 側でできるっぽいがドキュメントがまだなさそう。 情報が全く見当たらない。これはしばらく待ってから対応したいところ。

SQL が使える

sqlite が組み込みになっている。便利。

まとめ

わりと簡単に移行できる。