Blog

Rust の FFI で C の callback に closure を渡したい

https://adventures.michaelfbryan.com/posts/rust-closures-in-ffi/

↑の記事で丁寧に解説されているので、読むと良い。

ポイントとしては、rust は extern "C" された静的な関数しか FFI 対象に渡すことができないということ。そして、closure を void* に直接キャストすることもできないので、トランポリン使ってやりましょうね。ということです。

rust で `*mut u8` から `&[u8]` を求める

FFI などで *mut u8 な pointer と i32 な size が得られたときに、そこから &[u8] というスライスにしないと rust 側では扱いづらい。 そういう場合には std::slice::from_raw_parts(image, size as usize); を利用する。

https://stackoverflow.com/questions/50941427/casting-mut-u8-to-u8-without-std

jumpapp を i3 に適用して一発でアプリにフォーカスをあてる on Linux

jumpapp というコマンドがある。これを使うと該当のウィンドウが起動していなければ起動し、起動していればフォアグラウンドにもってくることができる。

僕の場合は i3wm を使っているので、.config/i3/config に以下のように設定する。

bindsym $mod+Ctrl+Shift+o exec "jumpapp obsidian"
bindsym $mod+Ctrl+Shift+c exec "jumpapp -c google-chrome google-chrome-stable"
bindsym $mod+Ctrl+Shift+t exec "jumpapp moderndeck"
bindsym $mod+Ctrl+Shift+w exec "jumpapp wezterm"
bindsym $mod+Ctrl+Shift+f exec "jumpapp -c franz franz"

Ultimate hacking keyboard のバンパーを Ctrl+Shift+Super に割り当てて、2つのキーを押すだけでアプリを起動できるようになった。便利。

linux desktop で slack app がうまくログインできないときは frantz をためそう

linux desktop で slack app を使おうとしてもログイン後に browser から戻ってくるところでなんかうまくいかなくてログインできないことがある。 そういう場合は、Frantz を使うといいかも。

frantz は slack や gitter などの web chat をまとめて一つの画面で表示してくれるという便利アプリ。 コレ自体便利なので昔は使っていたが最近つかってなかった。

slack のアプリと違って frantz はログインフローを frantz アプリ内の埋め込みブラウザで処理するので、slack app のような問題はおきない。

本質的な解決策ではないが、個人マシンである desktop linux でログインするのは雑談チャットとかOSS関連のチャットだけなので、まぁ frantz でいいかなぁ、といったところ。

ライフゲエムを実装した

ライフゲエム、たぶん過去に何回か作ろうと思ったことがある気がするんだが、自分が実装したものが github に見当たらなかったのでさっとつくった。もしかしたら過去にも作ったかもしれないが記憶にございません。

時間計測してないけど2時間ぐらいでできた。

https://tokuhirom.github.io/lifegame/index.html https://github.com/tokuhirom/lifegame

react とか使ってもいいけど、こんなもんは pure javascript で書いたほうがいいだろうということで、pure javascript です。

↓このサイトを参考に実装しました(JavaScript コードは見てない)。 http://math.shinshu-u.ac.jp/~hanaki/lifegame/

arch linux で使うのに最適な twitter クライアントを探す

最近色々ある twitter ですが、なんだかんだ twitter クライアントを起動しておきたいなと思っている今日この頃なので、、

最初は CawBird を使っていたが、vim keybinding が使えなかったりしてちょっと不満。

なので、気合で自分にあうものを探してみようかなと。yay -Ss twitter として twitter 関連のものを探し、その中から GUI クライアントを絞り込むものとする。このキーワード検索では、どうしてもライブラリや CLI ツールなどが混ざってくるので、目で確認していく。

aur/birdie-git 1.1.r12.g20b3c83-1 (+0 0.00) (Orphaned)
    Twitter client for Linux
aur/v2cmod-z 2.19.1-8 (+0 0.00)
    A 2ch/5ch and Twitter browser
aur/moderndeck-bin 9.4.5-1 (+1 0.24)
    A beautiful, powerful Twitter client for desktop.
aur/mikutter-git 3.4.2.r0.gdf0a70c-1 (+2 0.00)
    a moest twitter client (Upstream version)
aur/alice-git latest-1 (+2 0.00)
    A minimal Twitter client based on GTK+3.
aur/nocturn 1.8.4-2 (+6 0.00)
    Desktop twitter client
aur/cawbird-git 1.5.r0.g0a60a0bc38-1 (+7 0.01)
    A fork of the Corebird GTK Twitter client that continues to work with Twitter
aur/choqok-git v1.6.0.r106.gd8a53169-1 (+35 0.00)
    Microblogging client for KDE with support for Pump.io, GNU social and Twitter.com - git version
aur/mikutter 5.0.4-1 (+39 0.00)
    a moest twitter client
community/cawbird 1.5-2 (644.4 KiB 2.0 MiB) (Installed)
    Native GTK Twitter client

cawbird

cawbird が微妙なのは、hjkl 移動できないという点とフォントがなんか汚い。 フォントは設定すればなんとかなりそうだが。。

cawbird で issue たっているが、あんまメンテナが乗り気じゃなさそうだった>ショートカット

birdie

インストール不能

nocturn

すでに試したのだが起動しなかった。

[1022523:1222/153244.185725:FATAL:gpu_data_manager_impl_private.cc(439)] GPU process isn't usable. Goodbye.
zsh: trace trap (core dumped)  nocturn

と言われてしまう。

これは nocturn --no-sandbox すれば起動はできる。

が、結局なんかしらんけどログインができない。

mikutter

twitter client ではなく mastdon クライアントになっていた

v2cmod-z

2ch クライアントがメインっぽい。。

alice-git

なんか github レポジトリが消えてる

choqok

動く。動くのがすごい、みたいになってるのがちょっとあれだけど。。 choqok は一番まともかな。ただ、twitter 特化じゃないのでなんか変。 fav するためのショートカットがなかったり、vim like keybindings がなかったりする。

これだったら cawbird でいいかな。

moderndeck

これが、基本的には twitterdeck なので体験が良い。

ということで、moderndeck を使うことにした。 Use os native title bar を appearance 設定で利用することで、ちゃんと i3 で枠がつくようになる。

というわけで、しばらくは moderndeck を使おうかなと思う。

Obsidian を linux で使うとウィンドウに枠がつかないとき(i3 などの場合)

linux の window manager はウィンドウに対して枠を付与するものが多いのだが、Obsidian はデフォルトでは Window manager のウィンドウの枠を無効化する設定になっている。 しかしそのままではアクティブなウィンドウがどれかわかりにくいので、枠を付与したい。

調べてみると、Appearance の設定のなかに Window frame type というのがあるので、これを Native frame に変更すれば良い。

i3wm でスクショを取る設定

https://dev.to/dianjuar/i3wm-screenshot-shortcuts-3n7b

を参考に設定。

PrtScn で選択した領域をスクショできるだけでまぁいいかなという気がしたので以下の設定だけ入れました。

bindsym Print exec --no-startup-id maim --select | xclip -selection clipboard -t image/png↲

xremap を使い始めた

前回 linux desktop 環境を構築したときには、xkeysnail を linux 用のキーリマッパとして利用していたのだが、いつのまにか xremap が rust で書き直されていたので xremap に移行することにした。 yaml で設定できるのも良い。

基本方針として、Mac のような 2 つのモディファイアをを駆使したキーマッピングにしたい。 つまり、ctrl+n, ctrl+f などの emacs binding と、Cmd+w, Cmd+f などのメニュー操作の両立ができるという Mac みたいな入力スタイルを実現したい。 Mac でこれになれてしまったので、このスタイルが楽なのである。

幸い、Ultimate hacking keyboard を使っているのでかなり自由にキーマッピングを変更可能なので、Ultimate hacking keyboard 側のマッピングをいじりつつ xremap でもマッピングしていい感じに変更していく。

とはいえやっていることはシンプルで、Mac でいう cmd の位置を alt にして、a のとなりは ctrl とするというだけ。

あとは、terminal 上以外では Emacs like なキーバインディングを設定するというのと、chrome は win/linux と mac でキーバインディングに差異があるので、mac のキーバインディングで操作できるように調整する。最後に Alt+〇〇 を Ctrl+〇〇にリマップすれば完成。割とシンプルな構成で実現できた。xremap 便利。

# Mac でいう Cmd の位置に alt key をアサインしている。
# ctrl キーは小指の位置。
keymap:
  - name: Emacs
    application:
      not: wezterm
    remap:
      C-b: { with_mark: left }
      C-f: { with_mark: right }
      C-p: { with_mark: up }
      C-n: { with_mark: down }

      C-a: { with_mark: home }
      C-e: { with_mark: end }

      C-h: { with_mark: backspace }

      C-m: enter
      C-j: enter

      C-d: [delete, { set_mark: false }]

      C-k: [Shift-end, C-x, { set_mark: false }]
      C-leftbrace: esc


  # Chrome tab switching
  - name: Chrome
    application:
      only: Google-chrome
    remap:
      Shift-Alt-rightbrace: C-tab
      Shift-Alt-leftbrace: Shift-C-tab
      Alt-leftbrace: Alt-Left

  # remap alt-key to control
  - name: Cmd key mapping
    remap:
      Alt-a: C-a
      Alt-b: C-b
      Alt-c: C-c
      Alt-d: C-d
      Alt-e: C-e
      Alt-f: C-f
      Alt-g: C-g
      Alt-h: C-h
      Alt-i: C-i
      Alt-j: C-j
      Alt-k: C-k
      Alt-l: C-l
      Alt-m: C-m
      Alt-n: C-n
      Alt-o: C-o
      Alt-p: C-p
      Alt-q: C-q
      Alt-r: C-r
      Alt-s: C-s
      Alt-t: C-t
      Alt-u: C-u
      Alt-v: C-v
      Alt-w: C-w
      Alt-x: C-x
      Alt-y: C-y
      Alt-z: C-z

  # find the app name by `wmctrl -x -l` on X

archlinux で fcitx5 で特定のアプリ(chromeなど)で日本語入力できないとき

pacman -S fcitx5-im

とする。fcitx5-gtk や fcitx5-qt などがインストールされて問題が解決する。

arch linux を入れる 2022年版

久々に arch linux を使おうかな、と思いまして。なんかもうプライベートで使うマシン、mac じゃなくていいなーって気分が高まりすぎてる。 archlinux の利点は、pacman で管理できて、だいたいのものが AUR 経由でインストールできるので便利という感じなのと、ubuntu と違って、必要なものしか入らない感じにできるってことかな。 (その結果として、必要なものが入ってなくて変な状況になることもあるけど)

昔は頑張って windows のディスクをパーティションきって設定したりしてたけど、めんどくさいので SSD を USB で増設してやる。最近は USB 接続でも十分な速度が出るので良い時代だ。

https://wiki.archlinux.jp/index.php/USB_%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2 rufus で usb memory に書いた。

あとは、公式のガイド通りに設定していけばいい。fdisk はちょっとだるいので、TUI でできる cfdisk でやった。 https://wiki.archlinux.org/title/installation_guide

細かいところでよくわかんなかったところは https://zenn.dev/ytjvdcm/articles/0efb9112468de3 を参考にしたのだが、何を参考にしたのか忘れた。たぶん EFI 周り。

そして、i3wm を startx するような構成にした。 具体的に、入れてるのは以下のようなものです。

#!/bin/bash

set -ex

# install: pacman -S pkgname
# search:  pacman -Ss keyword

# update list
sudo pacman -Syu

# core
sudo pacman -S --needed --noconfirm openssh vim git tmux sudo git-lfs gcc base-devel go the_silver_searcher zsh w3m curl

# nvidia driver(if needed)
sudo pacman -S --needed --noconfirm nvidia

# manage mirror site
sudo pacman -S reflector
sudo reflector --country 'Japan' --age 24 --protocol https --sort rate --save /etc/pacman.d/mirrorlist

# network
# network-manager-applet=nm-applet
sudo pacman -S --needed --noconfirm networkmanager network-manager-applet
sudo systemctl enable NetworkManager

# yay
# yay is the wrapper for AUR and pacman.
if [[ -e /usr/bin/yay ]]; then
    echo "yay exists"
else
    git clone https://aur.archlinux.org/yay.git /tmp/yay && cd /tmp/yay && makepkg -si
fi


# X
sudo pacman -S --needed --noconfirm wezterm firefox i3lock xdg-utils
yay -S --needed --noconfirm google-chrome
yay -S --needed --noconfirm obsidian
# startx
yay -S --needed --noconfirm xorg-xinit

# i3
sudo pacman -S --needed --noconfirm rofi i3-wm polybar xss-lock dunst

# Japanese
sudo pacman -S --needed --noconfirm adobe-source-han-sans-jp-fonts adobe-source-han-serif-jp-fonts otf-ipafont noto-fonts noto-fonts-cjk noto-fonts-emoji
yay -S --needed --noconfirm ttf-mona ttf-monapo ttf-ipa-mona ttf-vlgothic ttf-mplus ttf-koruri ttf-mplus ttf-sazanami ttf-hanazono
sudo pacman -S --needed --noconfirm fcitx5-mozc fcitx5-configtool fitx5-gtk fcitx5-qt fcitx5-im

# JetBrains
yay -S --noconfirm intellij-idea-ultimate-edition intellij-idea-ultimate-edition-jre
yay -S --noconfirm jetbrains-fleet

# Ultimate hacking keyboard
yay -S --noconfirm uhk-agent-appimage

./setup-vimplug.sh

# I need to enable multilib for installing steam app
yay -S --noconfirm steam

# Chatting
yay -S --noconfirm slack-electron

Mount & Blade Ⅱ Bannerlord のキャンペーンをクリアした

シミュレーションゲーム。アクション要素もあるが、途中からめんどくさくてほぼアクションパート飛ばしていた。

序盤は盗賊を狩って、あとは鍛冶で金を稼いで、という感じ。 序盤に鍛冶が得意な人と医学が得意な英雄を集めて攻略。

帝国を滅ぼすところまでキャンペーンでやって満足。プレイ時間は128時間。

難易度は「こそ泥」。「戦士」にすると急に難しくなるなぁという印象。

今年やったゲームの中だと一番おもしろかったかもしれない。

Ultimate hacking keyboard を買った。これは Ultimate かも?

分離型のキーボードとして MD550 を使い始めた。分離型は方が自然な位置になるのでなかなか気に入りまして、Kinesis の freestyle2 などいくつか試しました(Kinesis は微妙に手になじまずすぐに諦めてしまった)。 そんな中、Ultimate hacking keyboard を知って購入したのが数ヶ月前。長らく立って、やっと届いたのが先週ぐらい、という感じ。

特徴としては

もともと値段が高いデバイスであり、さらに円安もあるのとハンガリーという遠い場所で作られてるということもあり、まぁ高い。 ポインティングデバイスがトラックボール、トラックポイント、タッチパッドから選べるのでそれを全種類うっかり頼んだのでそれもあってまぁ高くなった。

買ったもの

サイレントの茶軸。サイレントとはいえまぁそこそこ音はします。打鍵感は最高に良いです。

キーマッピング

キーマッピングはさすがに Ultimate というだけあって、複数をキーボード側に記憶させることができる。 なので、PC用とMac用で完全にワケたキーマッピングを設定している。

Windows 用のキーマッピング

Windows を操作するときに問題になるのが、Mac では Cmd と Ctrl という2つのモディファイアを使うことによって、編集系の操作とメニュー系の操作を行うことができるわけだが、これを行う機能が Windows にはないということだ。これは非常に面倒で、擬似的に Mac 風な操作を AutoHotKey で実現していた。 しかし、AutoHotKey の設定ファイルは記号まみれでいじるのがしんどい感じのファイルなので、あまりメンテナンスする気が起きないものなのである。

Ultimate hacking keyboard なら、そういった悩みはなく、GUIでポチポチ設定すればOK。簡単だった。 マクロも設定できるんで、案外複雑なマッピングもできる。

ベースマップ

ベースのキーマップはそこまでいじらず。

Mod マップ

デフォルトだとカーソルの移動が独特なので、HJKLスタイルに変更。音量の切り替えもここに入れた。音量の切り替え、どうしてもゲームやってるときとかによく使うので、割と触りやすい位置においておきたかった。

Mouse マップ

マウスキー機能がある。が、実際にはアタッチメントをつけてるのであんまいらないかな。と思いつつ一応設定。 HJKL 風にしている。

Fn マップ

基本的にはキーマップの切り替え機能だけを設定している。

Fn2 マップ(Cmd相当)

Cmd キーを押したときをエミュレートしている。

Fn3 マップ(Ctrl 相当)

Ctrl+A を押したときに Home キーを出す、みたいなのを設定している。 Ctrl+K で kill する部分は、マクロで設定している。

Mac 用のキーマップ

基本的には Windows 用と一緒。 ただし、いくつかのカスタマイズを入れている。

進歩するハードウェア

Ultimate hacking keyboard の良いところは、V2 になったことでかなり進化していて、かつファームウェア的にも進歩しているようだ。 初期の頃に Ultimate hacking keyboard を買った人のブログ記事を一通り読んだのだが、初期の頃に買った人が書いている改善点がかなり改善されてきていて、かなり良くなっているように思う(ありがとう先人の人たち)

特に大きいのがスマートマクロ機能で、起動時に設定を変えるとかそういうのができるようになっている。 個人的にはOSごとに違うキーマップを設定しているので、OSの切り替え時に自動的にキーマップを変えられる機能がほしい。今は UHK Agent を自前でビルドすればできるぽいけどまだ試していない。

まとめ

結論、とても満足している。ポインティングデバイスが一体化していることのメリットがとにかくでかいし、キーマッピングをめっちゃこまかく設定できるんで快適。 今までだったらできなかったような設定ができてる(多分似たようなことができるものは存在しているのかもしれないが、僕は他に知らない。) ファームウェアのアップデートなどでできることが増えているようだし、今後使い続けていくのも楽しみでならない。

kotlin 1.7.21 にアップグレードしようとしたら謎エラーになるケースの対応方法

kotlin 1.7.10 から 1.7.21 にアップグレードしようとしたら以下の謎エラーが出る怪奇現象が発生していた。

e: java.lang.NoSuchMethodError: 'void kotlin.script.experimental.api.KotlinType.<init>(kotlin.reflect.KClass, boolean, int, kotlin.jvm.internal.DefaultConstructorMarker)'
        at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition$1.invoke(ScriptCompilationConfigurationFromDefinition.kt:32)
        at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition$1.invoke(ScriptCompilationConfigurationFromDefinition.kt:28)
        at kotlin.script.experimental.api.ScriptCompilationConfiguration.<init>(scriptCompilation.kt:23)
        at kotlin.script.experimental.api.ScriptCompilationConfiguration.<init>(scriptCompilation.kt:25)
        at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition.<init>(ScriptCompilationConfigurationFromDefinition.kt:27)
        at org.jetbrains.kotlin.scripting.definitions.ScriptDefinition$Companion.getDefault(ScriptDefinition.kt:221)
        at org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationExtension.updateConfiguration(ScriptingCompilerConfigurationExtension.kt:67)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment$Companion.configureProjectEnvironment(KotlinCoreEnvironment.kt:578)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:199)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:108)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment$Companion.createForProduction(KotlinCoreEnvironment.kt:445)
        at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.createCoreEnvironment(K2JVMCompiler.kt:192)
        at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:143)
        at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
        at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:99)
        at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:47)
        at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
        at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:475)
        at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:125)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:373)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally$default(IncrementalCompilerRunner.kt:318)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.rebuild(IncrementalCompilerRunner.kt:114)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:207)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:79)
        at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:625)
        at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:101)
        at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1746)
        at jdk.internal.reflect.GeneratedMethodAccessor26.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
        at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
        at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
        at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base/java.lang.Thread.run(Thread.java:833)

色々試した結果、spring boot bom の中で kotlin のバージョンを固定しているのが問題なようだった。以下のように dependency management plugin でバージョン指定しているため、spring boot が 2.7.5 だったので、org.jetbrains.kotlin:* のバージョンが 1.6.21 になってしまって、結果として API が齟齬ったようだ。

    dependencyManagement {
        imports {
            mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
        }
	}

というわけで、以下のようにして解決。

    dependencyManagement {
        imports {
            mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
            mavenBom("org.jetbrains.kotlin:kotlin-bom:1.7.21")
        }
	}

20221129 追記

    ext["kotlin.version"] = Versions.KOTLIN

とかするのがスマートかも

Boot 3 というか micrometer の新しいやつが Exemplar 使えてめっちゃ良さそう。

https://ik.am/entries/715

tracing(zipkin とか) を使っていても、トラブルシューティングのタイミングで開くのって億劫で、なかなか開くことがなかった。 が、micrometer Observaiton を使うと、brave でやっていたような tracing をすると、同時に metrics も出すことができる。めっちゃ便利。

https://micrometer.io/docs/observation

しかも、Exemplars を使うと、prometheus の metrics に対して、その metrics に関連した trace id を一個付与することができる。 これを利用して、prom 側に tracing id を入れることができる。

https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#exemplars

grafana では Exemplars に対応しているので、metrics をグラフにレンダリングするときに Exemplars に指定した spanid もレンダリングされるようになって、そこから tracing にサッいけるようになるので、tracing がより有効活用できるようになると思う。そして、troubleshooting の際に、たとえば HTTP Server の latency が悪化した場合に、どこで悪化しているのかをスッと探せるようになるんじゃないかなぁ、と思っています。

https://grafana.com/docs/grafana/latest/fundamentals/exemplars/

なので、boot 3 へのアップグレードがなされれば、是非活用したいなーと思ってます(手元のプロダクトの Java 17 へのアップグレードから)。

kweb の router では KVar を意識しよう

kweb の router においては、

path("/entry/{id}") {params ->
    val id = params["id"]!!.value
    val entry = entryRepository.findById(id)
    div().text(entry.title)
}

のように書きたくなりがちだが、同一パスで path variable のみが変化した場合は KVar の変化しかおきなくて、path の callback のみが呼ばれるわけではない。

なので、以下の様に書く必要がある。

path("/entry/{id}") {params ->
    render(params["id"]!!) { id ->
        val entry = entryRepository.findById(id)
        div().text(entry.title)
    }
}

冒険ダンジョン村2

Android でプレイしました。

2週間ぐらいかけて75年目ぐらいまでやって、全村星5、周辺地域住民コンプ、街ガイド検定コンプまでやった。 ちまちま動くのが可愛いというカイロソフトの安定感があるゲームでした。

街をせっかくつくっても周辺地域住民とか街ガイドを効率的に埋めようとすると結局町並みを壊すことになるので、あんまきれいに並べようっていうモチベが薄いのと、街ごとにそこまで変わらないのでわりと後半飽きるなーという感じではありつつ。

kweb でファイルアップロードをハンドリングしたい

kweb を使ってインタラクティブなサイトを作っているときに、ファイルをアップロードする機能が欲しくなることがある。 そういう場合は KwebPlugin を継承して実装すればよい。appServerConfigurator を実装すると、ktor のコードを自由に書ける。ので、ここで簡単に設定できる。

class FileUploadPlugin : KwebPlugin() {
    override fun appServerConfigurator(routeHandler: Routing) {
        routeHandler.post(path) {
               // ここの中は ktor のコードとして書ける
        }
    }
}

ということは call.receiveStream() とかで InputStream を取れば好きに input できる。

JS 側では以下のようにする。window.fetch 使うとめっちゃ簡単にアップロードできて便利。

const response = await fetch("/upload_attachments", {
    method: "POST",
    body: item.getAsFile()
})

Rogue with dead というゲームをクリアした

rogue like だが、周回前提でジェムを集めるタイプ。アーティファクトという、死んでも持ち越せる強化アイテムを集めつつ、周回でもらえるジェムを集めて回していく。 パソコンはさわれないし集中して何かをできるわけでもないがスマホをなんとなく触れるという期間があったので、それを利用してプレイ。

放置ゲーとしてもプレイできるのだが、かなりアクティブめに周回回して4日ぐらいでクリアした。

課金が1500円で30日間広告なしで広告引いた時のリワードを受け取れるので、さっくり課金してプレイと言う感じ。無期限で広告アンロックするやつもあるのだが、30日を超えてプレイする感じはしなかったので30日の方で。課金したほうが圧倒的に楽に進められる。

この手の放置ゲー、Galaxy Z fold 4 で画面分割して半分でプレイしつつ、もう半分で twitter 眺めたり、もう片方で kindle 読んだりすると快適すぎるのでおすすめ。

トータルで 36時間ぐらいプレイしたっぽい。放置してるので実際にはそんなに長いこと触ってないけど。

Kotlin でかんたんにインタラクティブなウェブアプリを書ける kweb とは結局なんなのか?

一般的なウェブアプリというのは、クライアントサイドを JS で書いて、サーバーサイドを kotlin 等の別の言語で書く、といったアプローチが一般的です。 このアプローチになるのは、基本的にクライアントサイドでは JavaScript で書く必要があるからです。近年では JavaScript に transpile したりwasm で動かしたりといろんなアプローチが生まれています。 kotlin 界隈でも kotlin/js を react で動かすなど、様々なアプローチが行われています。

一般的なこの方式では、クライアントサイドとサーバーサイドでのコードの共有が難しかったり、接続部分のコーディングが面倒だったりします。

一方 kweb では、クライアントサイドで動くコードを書くから大変なんだ!クライアントサイドで動くコードを書くのが大変なら全部サーバーサイドで動かせばいいじゃない!というアプローチをとっています。 ブラウザ上でのイベント発生時にサーバー側に通信を行って、サーバー側で処理します。そして、サーバー側で DOM を操作すれば、それがクライアントサイドに伝搬する感じになっています。 これらの通信を都度都度行うと高コストなので、websocket で通信を張りっぱなしにしています。 これはなんというか、まぁ通信コスト等も高いですし、富豪的プログラミング だなぁ、という感じですね! 富豪的ではありつつも、めちゃくちゃ簡単にインタラクティブなウェブページを宣言的に書くことができて楽しいです。僕のこのブログの編集画面も kweb で書かれていますが、以前の spring webmvc で書かれていたバージョンに比べると圧倒的にいじりやすくなっていますし、SPA をめちゃくちゃ簡単に実装できるようになっています。

たとえば、簡単な web chat システムを書く場合、以下のように書くだけ。client side と server side の通信プロセスは隠蔽されています。ObservableList に入れたものは、それを watch しているページを表示しているクライアントがあれば、それらすべてに websocket で描画の更新内容が伝搬されます。 実際にどういうふうに通信されてるかは chrome の inspector などで見るのが良いです。

import kweb.ButtonType
import kweb.InputElement
import kweb.InputType
import kweb.Kweb
import kweb.button
import kweb.form
import kweb.input
import kweb.li
import kweb.new
import kweb.state.KVar
import kweb.state.ObservableList
import kweb.state.renderEach
import kweb.ul

val list = ObservableList<String>()

fun main() {
    Kweb(port = 16097) {
        doc.body.new {
            lateinit var inputElement: InputElement
            lateinit var input: KVar<String>

            form {
                inputElement = input(type = InputType.text)
                input = inputElement.value

                button(type = ButtonType.submit)
                    .text("Tweet")
            }.on(preventDefault = true).submit {
                list.add(0, input.value)
                inputElement.setValue("")
            }

            ul {
                renderEach(list) { msg ->
                    li().text(msg)
                }
            }
        }
    }
}

一方で、サーバーサイドで state を持つことによる弊害もあります。 現行バージョンではサーバー側が再起動した場合の挙動がまだちょっと怪しいかもです。あと、Load balancer で sticky session にしないといけないですね。同じインスタンスに常にいかないといけないので。 あとドキュメントがちょっと荒削りだったりとか、まだまだ伸びしろがあるフレームワークだなーという感じ。

業務で使うにはまだちょっと早いかな、という感じはしつつ、考え方は面白いかなーと思うので最近これで遊んでいるというワケ。 コードベースも非常に小さいんで気軽にイジれるし。