目次
- 序論
- 自己紹介
- 計画
- Debian
- ハードウェア
- systemd
- GNU Bash
- GNU core utilities
- OpenSSH
- DDNS
- 独自ドメイン
- nginx
- TLS
- XMPP
- サーバー
- クライアント
- Caddy
- Owncast
- CLIP STUDIO PAINT
- Painter
- Curl
- GnuPG
- Lynx
- HTTP
- Anvil
- Python
- 例外
- Python パッケージ
- Hatch
- SPDX
- pipx
- uv
- Magika
- Zsh
- sudo
- GNU Screen
- Ed
- gawk
- Tor
- git
- Forgejo
- Homebrew
- Vim
- tmux
- Alacritty
- HTML
- CSS
- JavaScript
- Firefox DevTools
- matrix
- サーバー
- Element
- Accessibility
- jq
- GNU Readline
- PostgreSQL
- Godot アセットパイプライン
- Synapse
- man
- Glicol
- Typeface
- Alacritty
- OAuth
- The AT Protocol SDK for Python
- Flask
- SQLite
- SQL
- スワップファイルのサイズを増やす
- Nushell
- Zellij
- Zig
- ActivityPub
- IPFS
- Ren’py
- Blender
- Godot
- Godot 3D
- Godot Command line
- GDScript
- Godot ネットっワーク
- Godot InputEvent
- Godot 入力処理
- Godot UI
- Godot Math
- Godot OS
- 環境と後処理
- ImageMgick
- G’MIC
- Uiua
- 起動
序論
この本を英語で書いた方が良いと思う。しかし、残念ながらわたしには英語を十分に扱える能力がない。
アートとは何でしょうか? わたしにはわかりません。あなたはどういう意味だと思いますか?
”アートは取り扱いが難しい”と言われることが多いんじゃないでしょうか。でも、もし”アートは取り扱いが難しいから扱わない”とするならば、それは”人間は取り扱いが難しいから扱わない”というのと似るように感じます。アートを扱う必要があるのは我々の宿命です。
他者ののサーバーを借りる場合
そのサーバーの管理者が定めたガイドラインに従う必要があります。そのガイドラインは管理者が自由に決められるので借りる側はそれに合わせる必要があります。だから時に折り合いがつかないことがあります。これはやむを得ないことです。
自分のサーバーを持つ
自分が管理者になるので他者の作ったガイドラインとは無縁になります。
わたしはこの方法を実践してみることにしました。この本はその記録です。
実際は一つのサーバーが孤立して存在するのは難しいので、そう単純ではありません。でも、ともかくやってみることにしました。
自己紹介
わたしについて少し話しておいた方が話が伝わりやすいのではないかと思います。
わたしの本名は小谷野慎也です。オンラインネームはmoliと名乗っています。
わたしは基本的にアーティストです。主に絵を描きます。絵を描いているのをストリーミングすることが多いです。音楽も作ります。プログラミングは好きですが、まともなものが作れるほどの能力はありません。
サーバー管理は今回が初めてです。
GNU/LinuxデスクトップのXubuntuなどを使っていた経験は少しあります。
わたしの主な収入源はコミッションとサポーターによるものです。
わたしのソーシャルメディアなどののリンクをここで紹介するべきなのかもしれませんが、NSFWアートが含まれるものが多いので、現段階では差し控えます。Leanpubについてもう少し理解してから考えましょう。
計画
自分でサーバをホストし、既存のサービスの代替となるソフトウェアを実行する。
- Discord
- 各種ソーシャルメディア
- ウェブサイト
- ストリーミング
- 送金
別に、わたしはこれらのサービスが嫌いなわけではありません。そのガイドラインと折り合いがつかなかったり、将来サービスが終了したりした場合の保険を用意したいだけです。
構成
デスクトップコンピュータとサーバーコンピュータの2台でシステムを構成します。
デスクトップコンピュータ
絵を描いたり、音楽を作ったり、その他日常的なことをするためには必要です。
サーバー構成要素としては、仮想端末でサーバーコンピュータにSSHで接続するために使います。
サーバーコンピュータ
サーバーとして動作させるための各種ソフトウェアはGNU/Linuxのものが多いので、それが必要です。
基本的に電源を切らずに安全に運用できる必要があるので、低電力・低発熱のものが望ましい。
ディスプレイは机の上のスペースを食うので、ヘッドレスサーバーとして使用したい。
Debian
コマンドについて知りたいことがあったらDebian Wikiを検索するのがよい。
ロケール
- [ArchLinuxのロケールの解説]https://wiki.archlinux.jp/index.php/%E3%83%AD%E3%82%B1%E3%83%BC%E3%83%AB
以下のようにして設定すると簡単なようだ。
1 sudo dpkg-reconfigure locales
ハードウェア
ルーター
WSR-6000AX8P
各種コンピュータをネットワークに接続するために使用する。他のルーターでもあまり違いはないと思われる。
デスクトップ
iMac, macOS Sequoia 15.1.1
ペンタブレット
Wacom Intuos Pro Medium
**重要な注意点として、これはそのままではiMacには接続できない。**Intuos ProはUSB-Aでコンピュータに接続する必要がある。しかし、iMacはUSB-Cのポートしかないので、変換機が必要になる。(Bluetoothで接続することもできるが、事前にIntuos Proを充電するためにUSB-AでiMacから給電する必要があるので、いずれにしてもまずUSB-AでiMacと接続するしかない)
USB-C ハブ
iMacにさまざまなものを接続するために必要になる。必要なものをリストする。
- USB-A(一つでは足りなくなるかもしれないので、2-3は必要)
- HDMI(デュアルディスプレイにするために必要だった)
- MicroSDカード(Raspberry Pi OSを書き込むために必要)
サーバー
**公式の専用のケースも一緒に購入したが、これは使わない方がいい。**Raspberry Pi Zero 2 Wの基板がケースの溝に硬く挟まってしまうので、一度ケースに入れてしまうと、取り出すのに非常に苦労する。取り外すのは不可能ではないが、ドライバーのようなものをテコにして基盤を持ち上げる必要があり、破損する危険性があると感じた。MicroSDカードを抜き差しするためには、絶対にケースから基盤を取り外す必要があるため、OSを再インストールするときに困ったことになる。
公式のケースを使うのはやめて、裸のまま使うか、適当なプラスチックの箱か何かを用意してそれに入れておけば十分だろう。
ハードウェアのセットアップ
Raspberry Piを購入してもそれだけで動作させることはできない。
何が必要なのかわからなかったので、デスクトップ環境を動作させるために必要なパーツを含めて全て用意した。今思えば必要なかった。
自分で必要なパーツを買い揃える自信がなかったので、近所の大型電気量販店に行って店員さんに全ての必要なパーツを選んでもらい購入した。
ヘッドレスサーバー
Raspberry PiはRaspberry Pi Imagerを使ってMicroSDカードにOSを書き込む時にWifi経由でSSH接続するための設定をすることができる。
なので、Raspberry Piをヘッドレスサーバーとして運用するだけなら、必要なパーツは電源だけである。
オペレーティングシステム
OSのドキュメントは全く読んでいない。
Raspberry Pi OS(64-bt)デスクトップ環境付き をインストールした。しかし、実際にはデクストップ環境は自分には必要なかった。それどころかPGPを使用する際に問題になったので(後述)、次回からはデスクトップ環境なしでインストールするべきだろう。
Paspberry Pi OSはdebianベースのOSなので、こちらのドキュメントも参考になりそうだが、全く読んでいない。
自分の場合aptを利用するだけなので、debianに精通している必要があまりない。
サーバーの総合的なセットアップ手順
セットアップには順序が重要である。
まず、ルーターを設置する。そしてインターネットに接続する設定をする。(詳細は忘れた)
次に、iMacをルーターに有線接続する。(有線の方が高速なので)
また、iMacをWifiでもルーターに接続できるようにしておく。(Raspberry Pi Imagerのため)
すると、ルーターのDHCPによってiMacがネットワークに接続できるようになる。
iMacはMicroSDカードの読み書きができないので、USB-CハブをつなぎMicroSDカードを扱えるようにする。
MacにRaspberry Pi Imagerをインストールする。
Raspberry Pi ImagerでMicroSDカードにRaspberry Pi OSを書き込む。(この時推奨の64-bit, デスクトップ環境ありを選択したが、デスクトップなしの方がよいと思う。理由は後述)
- この時iMacのWifiの設定を自動で取得してくれる。
- ホスト名を覚えておく(後でアクセスするために必要になる)
- SSHの設定をする(後でアクセスするために必要になる)
自分はこの時、ディスプレイとBluetoothのキーボード・マウスを揃えて単体でも使えるようにして起動したが、別に必要なかったと思う。(当時、Wifi経由のSSH接続だけで十分かどうか確信が持てなかった)
OSの書き込みが完了したら、Raspberry PiにMicroSDカードを差し込み、電源を繋ぐ、それだけで起動する(電源ボタンなどはない)。
ここまでの設定をしてきたのであれば、MacからsshでRaspberry Piに接続できるはずだ。
systemd
サービスの管理はsystemdが行うので、これを押さえておく必要がある。sshdやnginx、prosodyとかも皆サービスである。
独自のサービスを作成する
/etc/systemd/system
にfoo.service
という名前でUnitファイルを作る。
1 [Unit]
2 Description=Foo Service
3
4 [Service]
5 Type=simple
6 Restart=always
7 ExecStart=/home/user1/foo.sh
8
9 [Install]
10 WantedBy=multi-user.target
より短い例。
1 [Unit]
2 Description=Foo Service
3
4 [Service]
5 ExecStart=/home/moli/Desktop/foo.sh
6
7 [Install]
8 WantedBy=multi-user.target
Description=は識別子なので独自のものを。
Type=は通常simpleでよい。(デフォルトsimple)
Restart=はプロセスが終了した時に再起動するかどうかを指定する。デフォルトはno。普通はalwaysを選択することになるだろう。
ExecStartは実行される引数付きのコマンド。
WantedBy=はランレベル。multi-user.target
でいいだろう。ターゲットは/usr/lib/systemd/system/multi-user.target
のようになっている。
systemctl daemon-reload
でサービスの設定ファイルを再読み込みする
systemctl
- システムの状態を表示
-
sudo systemctl status
- 失敗したサービスを表示
-
sudo systemctl --failed
- サービスを起動
-
sudo systemctl start foo.service
- サービスを停止
-
sudo systemctl stop foo.service
- サービスの状態を表示
-
sudo systemctl status foo.service
- サービスを自動起動に設定
-
sudo systemctl enable foo.service
- サービスの自動起動を無効
-
sudo systemctl disable foo.service
- Unitファイルのバックアップファイルを表示
-
sudo systemctl cat foo.service
journalctl
ログを表示する。
- -x
-
メッセージカタログでログを拡張する
- -e
-
ページャの末尾にジャンプする
- -u unit
-
指定されたunitに一致するメッセージを表示する
- -t identifier
-
識別子でフィルタリングする(systemd-catのメッセージをフィルタするのに使える)
単にjournalctl
とすると全てのログが表示されるので、grepなりなんなりで表示を絞り込んでもいい。
systemd-cat
ジャーナルに直接メッセージを送ることができる。
- -t identifier
-
識別子を指定する。指定しないと実行ファイル名になる
1 systemd-cat -t FOO echo hello
GNU Bash
Bashとそれに関連するソフトウェアはとても多い。小さくまとめたいと思うのだけど…。
基本
ループ。
1 while true; do
2 echo hello;
3 echo world;
4 done
条件。
1 if true; then echo 'A'; fi
複合コマンド。
1 ( echo 'foo'; echo 'bar' ) | cat -n
2
3 # 中括弧を使うと現在のコンテキストで実行される
4 { echo 'foo'; echo 'bar' } | cat -n
コプロセス
1 echo foo >&${COPROC[1]}
2 echo bar >&${COPROC[1]}
3 echo baz >&${COPROC[1]}
4 cat <&${COPROC[0]}
関数。
1 f1 ()
2 {
3 # ローカル変数
4 local x='hello'
5 echo $x
6 }
7
8
9 x='world'
10 f1
11 echo $x
変数。
1 x='hello'
変数を削除。
1 unset -v x y
関数を削除。
1 unset -f f
コマンド置換。
1 echo $( ls )
2
3 # または
4
5 echo `ls`
プロセス置換。
1 cat -n <(ls -l)
シェル変数
- HOME
-
ホームディレクトリ
- PATH
-
シェルがコマンドを検索するディレクトリのリスト。コロンで区切る
.
コマンドとsource
コマンド
.
は現在のシェルコンテキストでコマンドを実行する。source
は.
の別名である。
GNU core utilities
env
コマンド名をPATHから検索してくれるので、コマンドの絶対パスがわからないときに使われることが多い。
1 #!/usr/bin/env python3
あまり使うこともないと思うが、名前らしく環境変数を設定することができる。
また、コマンドの引数を処理させるためには-S
オプションが必要である
1 #!/usr/bin/env -S foo=bar cat -n
2
3 foo
4 bar
5 baz
OpenSSH
導入
サーバーにアクセスするにはこれを使う。
Raspberry Piは初期設定でパスワード認証によってsshで接続できるようになっている。
ローカルネットワークでsshで接続するには次のようにする。
1 ssh user@host.local
sshにはいくつか認証方式がある。
- パスワード認証
- キーによる認証
キー認証の方がより安全な方式のようなので、まず、パスワード認証でログインできることを確認したら、キー認証に切り替えて、パスワード認証によるログインを無効化する。
具体的には以下のような手順になる
- クライアントの方でssh-keygenを使って公開鍵と秘密鍵のペアを生成する
- サーバーに公開鍵を登録する。手動でやるか、ssh-copy-idを使う
- sshdの設定ファイルを編集しパスワード認証を無効化する
- ssh-agentやscpなどの便利なコマンドを使う
全体を把握するためにWikibooksを読むのもいいかもしれない。
設定したのが少し前のことなので、大まかにしか書いてない、後で書き直す。
ssh
sshクライアント。
パスワード認証にせよ鍵認証にせよこれを使うことになる。
1 ssh user@host
あとは道なりに進んでゆけば良い。
ssh-keygen
鍵を生成する。
1 ssh-keygen
特にオプションなどは必要ないと思う。パスフレーズを尋ねられるので設定する。
公開鍵を登録する
サーバーの~/.ssh
がsshの設定ファイルを入れるディレクトリなのでここに鍵を入れる。鍵はテキストファイルなので端末からコピーアンドペーストすればよい。
ssh-copy-idを使うこともできる。
sshd
設定はファイルは/etc/ssh/sshd_config
である。
パスワード認証を無効化するには設定ファイルにPasswordAuthentication no
と記述する。
サービスとして実行されれいるので、設定ファイルの内容を反映するためにはsudo systemctl restart sshd.service
として再起動する。
ssh-agent
クライアントの方で使うと以後の鍵の入力を省略できるようになる。 ssh-addで秘密鍵を登録する。
1 ssh-agent bash
2 ssh-add ~/.ssh/key
複数のクライアントから同じサーバーに接続する必要がある場合は便利かもしれないが、自分の場合はあまり使っていない。
sftp & scp
クライアントからサーバーにファイルを転送するのにこれらが必要だ。sftpでもscpもどちらでもよさそうだが十分試してないので、後で調査する。
sftp
1 sftp moli@pi.local
- ヘルプを表示する
-
help
- sftpを終了する
-
bye
-
exit
- リモート ディレクトリを移動する
-
cd
- リモート ディレクトリをリスト表示する
-
ls
- ローカルにファイルを転送する
-
get foo.txt
- ローカル ディレクトリをリスト表示する
-
lls
- ローカルファイルを転送する
-
put foo.txt
DDNS
自分の場合固定IPではなかったのでDDNSに登録する必要があった。
選択肢はいくつかあったが自分はNO-IPのProプランを使うことにした。
- ルーターがNO-IPに対応していたこと(必須ではない)
- 独自のドメインを設定することができること(IndieWebに対応するために必要)
しかし、月に約15ドルもかかるので、かなり厳しい。もっとどうにかならないだろうか。
独自ドメイン
上記のサイトを参考にしつつ選んでみた。
この記事とは直接関係しないが、価格が手頃だったのでレジストリ.xyzから直接購入することにした。
しかし、自分がちゃんと設定方法を理解していないだけかもしれないが、DDNSを設定するとサブドメインを使うことができないようなので、後々NO-IPから別のドメインを設定せざるを得なくなったりと、使いにくい感がある。固定IPならば問題はなさそうなのだけど。
結果としては、特に難しいこともないのだが、初めてドメインを購入するとなるとWHOISについて悩むことになるだろう。
WHOIS
WHOISはドメインの所有者に連絡を取るための公開データベースを検索するための仕組みだ。
ここにはドメインを取得する時に記入した個人の住所、電話番号、メールアドレスが含まれる。
WHOISは公開されており誰でも検索できるので、普通にやると住所や電話番号が全世界に公開されてしまうようになっている。個人の場合には安全のために望ましくないと考えるのが常識だろう。
しかし、WHOISはその時代遅れな仕様が改められない。
対策として、レジストラがドメインの所有者にWHOIISに登録することでドメイン所有者の個人情報を隠すWHOISプライバシーなどと呼ばれる仕組みが、どのレジストラにも備わっているようだ。
ただし、WHOISプライバシーを有効にした場合、もしレジストらとドメイン所有者が紛争になった場合、ドメイン所有者に勝ち目がなくなるといった問題もあるようだ。わたしは法律に詳しくないのでなんとも言えない。
わたしは住所を公開するわけにもいかなかったので、WHOISプライバシーを有効に設定している。
考察
DDNSにせよ、ドメインにせよ、ISPにせよ、自分と異なったポリシーを持っている他者であるわけだ。これらを利用しなければならない時点で、完全に独立したサーバーとは言えない。
インターネットを利用する以上、完全に独立しているというのは無理だということが明らかになったと思う。
他者と協調しつつ、自分の権利を確保してゆくというふうに立ち回るほかない、そう思われる。
nginx
HTTPサーバーとリバースプロキシ。
aptでインストールすれば特に問題はないようだ。
同様の用途としてCaddyを使うようになってしまったので、今後使うかどうかはわからない。
ただし、使うつもりがあるなら、Certbotを設定するより前に入れておいた方がいい。Certbotはnginxの設定ファイルを書き換えてTSLが有効になるようにするからだ。
TLS
Wikipediaのページを読んでも、全く意味がわからない。
二者間の通信のセキュリティを担保するために第三者の認証局を入れるという方式のようだ。
とにかく、これを入れないとWebサーバーやXMPPサーバーが使えないので、なんとかして設定する必要がある。
認証局を選ぶ
無料で証明書を発行してくれる認証局。大抵の場合はここが使われるようだ。
証明書には有効期限(13ヶ月くらい)があるので、証明書の取得と更新を自動で行うプログラムを入れるのが一般的なようだ。
Certbot
Let’s Encryptがおすすめしているクライアントプログラムがこれなので、これを使う。
使い方は忘れたが、Webページの指示通りにやっていけば問題なかった。
XMPP
Discordの代替として見たときに、グループチャットとファイル共有は可能だが、画面共有はできない。(音声通話は可能なようだが、設定していない)
画面共有がしたいならJitsi Meetなどを使うという手もあるが、ストリーミングで代用できる部分も多いので、今は検討しないことにした。
サーバー
設定の仕方は忘れたが、ドキュメント通りにやっていけばできるはずだ。
チャット、ファイル共有、グループチャットなどには、それぞれポート開放とCertbotによる証明書の発行が必要になる。
証明書の更新
1 sudo prosodyctl --root cert import /etc/letsencrypt/live/
自動更新を設定するべきなのだろうけど、そこまで手が回っていない。
クライアント
vCardというプロフィールを設定するためのモジュールが上手く動作しなかったので他のクライアントで設定する必要があった。それ以外はあまり問題なく使えている。
Caddy
ウェブサーバーとリバースプロキシ。
いくつか特徴がある。
- 証明書を自動で取得してくれる
- 一つの実行ファイルで完結しているので、好きな場所に配置できる
- 設定ファイルをカレントディレクトリから読み込むのでディレクトリによって動作を変えられる
- ドキュメントがわかりやすい
デメリットとしては。
- サービスにしたければ手動で登録する必要がある
- nginxのような大量のアクセスに対してどの程度の性能があるのかよくわからない
今のところ大量のアクセスを処理しなければならない状況にはないので、自分にはCaddyが適していると思う。
今のCaddyfileはこんな感じ。あまり綺麗ではないので後で整理したい。
1 moli-green.xyz {
2 root * /home/moli/Public/www
3 file_server
4 }
5
6 moli-stream.zapto.org {
7 reverse_proxy :8080
8 }
9
10 :80 {
11 root * /home/moli/Public/onion
12 file_server browse
13 }
インストール
静的バイナリをインストールすることにする。
1 curl -OL https://github.com/caddyserver/caddy/releases/download/v2.9.1/caddy_2.9.1_linux_arm64.tar.gz
2 tar xzf caddy_2.9.1_linux_arm64.tar.gz
このバイナリ一つで使用するとこができる。
署名の検証方法はよくわからなかった。
Caddyをサービスにする
まあ、あとはドキュメントの通りでうまくいく。
設定ファイルを編集する。
1 sudo vim /etc/caddy/Caddyfile
2 sudo systemctl reload caddy
静的サイトの場所はどこにでも配置できる。(/var/www/html
, /srv
が例に上がっているが、標準的なディレクトリとして例示しているだけだろう)
API
caddyの管理エンドポイントはCADDY_ADMIN
環境変数で設定できる。
1 # 管理エンドポイントのために使用されてないポートを探す
2 ss -ant | grep 2222
3
4 # caddyの管理エンドポイントを設定
5 export CADDY_ADMIN=localhost:2222
6
7 # Caddyを起動(runの方がいいかもしれない)
8 ./caddy start
9
10 # 構成ファイルを作成
11 jq -n '{apps: {http: {servers: {foo: {listen: [":2015"], routes: [{handle: [{handler: "static_response", body: "hello!"}]}]}}}}}' > config.json
12
13 # 構成ファイルをロード
14 curl localhost:2222/load -H 'Content-Type: application/json' -d @config.json
15
16 # 構成を表示
17 curl localhost:2222/config/ | jq
18
19 # 構成を変更
20 curl localhost:2222/config/apps/http/servers/foo/routes/0/handle/0/body -H 'Content-Type: application/json' -d '"second."'
21
22 # IDを設定
23 curl localhost:2222/config/apps/http/servers/foo/routes/0/handle/0/@id -H 'Content-Type: application/json' -d '"msg"'
24
25 # IDを使用して構成を変更
26 curl localhost:2222/id/msg/body -H 'Content-Type: application/json' -d '"something..."'
27
28 # Caddyを停止
29 ./caddy stop
30
31 # Caddyを前回の構成で起動
32 sudo -E ./caddy run --resume &
GET/config/[path]
1 curl http://localhost:2019/config/ | jq
JSON Config Structure /apps/http/servers
1 curl -s http://localhost:2019/config/apps/http/servers | jq '.'
構成をエクスポート。
1 curl -s http://localhost:2019/config/apps/http/servers | jq '.'
Caddyfile
グローバルオプション
トップレベルの括弧はグローバルオプションを意味する
1 {
2 ...
3 }
1 email moli_green@runbox.com
HTTP3を設定するにはこうする。
1 servers :443 {
2 protocols h1 h2 h3
3 }
細かく設定すると、こんな感じになる。
1 log hoge {
2 level debug
3 format console
4 output file /home/moli/work/s/log {
5 roll_size 1mb
6 roll_keep 2
7 }
8 } roll_keep_for 1m
9
しかし、最低限の設定でも十分だと思われる。
1 log {
2 output file /home/moli/work/s/log
3 }
ディレクティブ
1 encode zstd gzip
1 header foo "bar"
HTST
httpの接続をhttpsにアップグレードする。max-age
はブラウザがそれを記憶する秒数。
preload
はGoogleのサービスを使うので、適切な資格が必要なようだ。
以下は、有効期間が5分の例(最終的に2年に設定するのが目標なようだ)
1 header Strict-Transport-Security "max-age=300; includeSubDomains"
リクエストの最大サイズを指定する。(POSTリクエストも含まれるようなので、添付できるファイルサイズの上限でもある)
1 request_body {
2 max_size 1MB
3 }
1 reverse_proxy localhost:50000 {
2 header_up Host {http.request.host}
3 }
beader_up
はバックエンドに送るヘッダーを操作する。 Header Value
の形になっている。{http.request.host}
はリクエストヘッダーに含まれるホスト名を示す。なので、この例の指定はホスト名にホスト名を設定しているだけなので、何も設定しなくても、違いはない。
1 tls {
2 protocols tls1.2 tls1.3
3 }
Owncast
ストリーミングするならPeerTubeなどの選択肢もあったが、インストールが難しそうだったので、別のソフトウェアを探していたところ、これを見つけた。
非常に簡単にインストールできる。
インストールスクリプトを実行するだけである。単体の実行ファイルとしてインストールされる。Caddyを内蔵しているのでHttpサーバーの設定なども必要ない。ホームディレクトリにインストールするならばルート権限も必要ない。
しかし、わたしのサーバーコンピュータはRaspberry Pi Zero 2 Wなので性能が足りなかった。(しばらく動かしているとダウンしてしまう)もう少し、性能の高いマシンであれば全く問題ないと思うので、サーバーマシンを買い替えたら導入したい。
CLIP STUDIO PAINT
名前が長いので以下CSPと略す。
複数ページ管理機能を使うためにはEXを購入する必要がある。
わたしはこのCSPを使い慣れているので、ペイントソフトに関してはCSPが基準になっている。
漫画を描く
CSPの使い方ではなく、総合的な話になってしまう。
3D
Painter
ワコム タブレットの設定
わたしの場合、CLIP STUDIO PAINTのペンによるパンの操作に慣れていたので少し設定を変更した。
- Wacom Centerを開く。
- ペンの設定 -> アプリケーション -> Corel Painter 2023を追加
- 下サイドスイッチを”パン/ズーム…”に変更
- ペンをタブレットから浮かせて下サイドスイッチを押した状態
-
パン ペンをタブレットにつけてしたサイドスイッチを押した状態
-
ズーム
という動作になる。
CLIP STUDIO PAINTとは少し動作が異なるが、個人的にはなんとか慣れることができそうに思う。
設定のリセット
Shiftを押しながらPainterを起動する。
ショートカット
CLIP STUDIO PAINTと違うところ、または、個人的に要注目の部分だけ取り上げる
- 1-0キー
-
ブラシの透明度を10-100%の範囲で10%刻みに変更する(例えば3なら30%の透明度)
TC# Ncat
HTTPサーバーにアクセスする。-C
オプションはLFをCRLFに変換する。
1 moli@pi:~/Public $ ncat -C localhost 8000
2 GET / HTTP/1.0
3
4 HTTP/1.0 200 OK
5 Content-Type: text/plain; charset=utf-8
6 Server: Caddy
7 Date: Wed, 15 Jan 2025 07:46:29 GMT
8 Content-Length: 3
9
10 hi.
接続を待ち受ける。デフォルトのポートは31337番。(端末を二つ開いて実行する)
1 ncat -l
先ほどのポートに接続する。
1 ncat localhost
適当にテキストを書き込むと、待ち受け先に出力される。
Curl
ネットワークからファイルを転送する。多様なプロトコルに対応している。
1 moli@pi:~/Desktop $ curl https://moli-green.xyz
2 hello www.
3 foo.
ポートを指定するならこう。
1 curl https://moli-green.xyz:443/
ファイルを保存する。
1 curl -o index.html https://moli-green.xyz
-s
-
サイレントモード
-H
-
HTTPヘッダーを追加。
@filename
でファイルを使用。@-
で標準入力を使用。
1 curl -s -H "foo: 1" -H "bar: 2" localhost:8001
GnuPG
- キーをリスト表示
-
gpg --list-keys
- 公開鍵をエクスポート
-
gpg -a --export Shinya
-
-a
,--armor
オプションはASCIIフォーマットで鍵を出力する(デフォルトはバイナリ) - 秘密鍵のエクスポート
-
gpg -a --export-secret-keys Shinya
- 公開鍵の削除
-
gpg --delete-keys Shinya
- 秘密鍵の削除(秘密鍵を先に削除する)
-
gpg --delete-secret-keys Shinya
- 公開鍵のインポート
-
gpg --import Foo.pub
- 暗号化
-
gpg -o t.gpg --encrypt -r Shinya t.txt
-
-r
,--recipient
受け取り手(IDを引数にとる。IDは名前の一部などでもよいようだ) - 復号化
-
gpg -o t.txt --decrypt t.gpg
- 署名
-
gpg -o t.sig -s -r Shinya t.txt
- 署名を検証
-
gpg --verify t.sig
- 分離署名を作成
-
gpg --detach-sig foo.txt
-
gpg --detach-sig foo.txt
iMacにGnuPGをインストールする
Mac版ビルドへのリンクが公式にあるのでそれを使う。
Lynx
テキストブラウザ。
HTTP
見てみると想像以上に複雑化している。Wikibooksの記述を見るくらいが精一杯のところだろう。
リクエストの種類についてはMethod Definitionsに記述があるが、通常GET
以外はほとんど使わないだろう。
1 moli@pi:~ $ ncat -C moli-green.xyz 80
2 GET / HTTP/1.0
Anvil
Python
標準ライブラリ
言語リファレンス
インストール
実行
インタラクティブモード
PYTHONSTARTUP
環境変数に指定したファイルが先に実行される。いつも使うモジュールはここで読み込んでおくと良い。
文字列
1 >>> 'foo bar baz'.split()
2 ['foo', 'bar', 'baz']
クラス
1 >>> class Foo:
2 ... '''this is Foo'''
3 ... x = 3
4 ... def f(self):
5 ... return self.x
6 ...
7 >>> o = Foo()
8 >>> o.f()
9 3
10 >>> help(Foo)
インスタンス作成時に初期化したいならinitメソッドを定義する。
1 >>> class Foo:
2 ... '''this is Foo'''
3 ... def __init__(self, x):
4 ... '''initialize'''
5 ... self.x = x
6 ... def f(self):
7 ... return self.x
8 ...
9 >>> o = Foo(4)
10 >>> o.f()
11 4
12 >>> help(Foo)
継承。
1 >>> class Bar(Foo):
2 ... pass
3 ...
4 >>> o = Bar(4)
5 >>> type(o)
6 <class '__main__.Bar'>
7 >>> o.f()
8 4
9 >>> help(Bar)
例外
1 >>> try:
2 ... 1 / 0
3 ... except ZeroDivisionError:
4 ... print('error!')
5 ...
6 error!
独自の例外クラスを定義するにはException
クラスから派生させる。
例外を投げるにはraise Exception()
とする。
また、最後にキャッチされた例外を取得するにはsys.exception()
とする。
1 >>> import sys
2 >>> class FooError(Exception):
3 ... pass
4 ...
5 >>> try:
6 ... raise FooError()
7 ... except Exception:
8 ... e = sys.exception()
9 ... print(type(e))
10 ...
11 <class '__main__.FooError'>
例外オブジェクトを取得するにはas
で変数を指定できる。こちらを使うべきだろう。
また、Exception
クラスはコンストラクタで任意のタプルを受け取り、e.args
のように取得できるので、データを受け渡す必要があるならこれを使うとよい。
1 >>> try:
2 ... raise Exception('foo', 'bar')
3 ... except Exception as e:
4 ... print(type(e))
5 ... print(e.args)
6 ...
7 <class 'Exception'>
8 ('foo', 'bar')
クリーンアップ処理が必要ならfinally
句を使う。
1 >>> try:
2 ... pass
3 ... finally:
4 ... print('done')
with
1 >>> class Foo:
2 ... def __enter__(self):
3 ... return 'hi'
4 ... def __exit__(self, exc_type, exc_val, exc_tb):
5 ... print(exc_type, exc_val, exc_tb)
6 ... return True
7 ...
8 >>> with Foo() as x:
9 ... print(x)
10 ... raise Exception('foo')
11 ...
12 hi
13 <class 'Exception'> foo <traceback object at 0x105745080>
ファイル
エンコーディング
Pythonはテキストモードでシステムのエンコーディングを用いる。これは特定の環境で問題を引き起こすので、utf8に固定してしまう方が望ましい。これはpythonの起動オプションか環境変数で設定できる。
Utf8モードが有効かどうかは以下のようにして確認できる。
1 >>> import sys
2 >>> sys.flags.utf8_mode
3 0
起動オプションで設定。
1 python3 -X utf8
環境変数で設定。
1 env PYTHONUTF8=1 python3
実際の実行可能スクリプトは以下のようになるだろう。
1 cat ./utf8.py
2 #!/usr/bin/env PYTHONUTF8=1 python3
3
4 import sys
5 print(sys.flags.utf8_mode)
io.TextIOBase
1 >>> with open('foo.txt', 'wt') as f:
2 ... f.write('hello')
1 >>> with open('foo.txt', 'rt') as f:
2 ... s = f.read()
3 ... print(s)
4 ...
5 hello
io.RawIOBase
1 >>> with open('foo.bin', 'wb') as f:
2 ... b = (99).to_bytes()
3 ... f.write(b)
4 ...
5 1
6 >>> with open('foo.bin', 'rb') as f:
7 ... b = f.read()
8 ... i = int.from_bytes(b)
9 ... print(i)
10 ...
11 99
実際にバイト列を読み書きするにはもう少し調べてみる必要があるだろう。
pip
Pythonパッケージマネージャ。
- インストール済みのパッケージを表示する
-
python -m pip list
- パッケージを検索する
-
WebブラウザでPyPIにアクセスして行う
- パッケージをインストールする
-
python -m pip install requests
- パッケージの情報を表示する
-
python -m pip show requests
- パッケージをアップデートする
-
python -m pip install --upgrade requests
importlib
Pythonインタプリタの中でモジュールをリロードする時に使う。
いつも使うので~/.zshrc
の中でPYTHONSTARTUP
に設定して常時読み込まれるようにしておく。
1 import aecpic
2 from importlib import reload
3 reload(aecpic)
pdb
Pythonデバッガー。
モジュールのインポートなどは必要ない。組み込み関数breakpoint()
をコードに挿入するだけで自動的にデバッガーが立ち上がる。
h, help
-
デバッガーコマンドを表示する
d, down
-
下のフレームに移動
u, up
-
上のスタックフレームに移動
a, args
-
関数の引数を表示する
s, step
-
ステップ実行
l
-
ソースコードを表示
c, continue
-
実行を継続
p
-
式の値を表示する
pp
-
p
と同様だが 表示にpprint
を使う q, quit
-
デバッガを終了する
main
1 if __name__ == '__main__':
2 print('hi')
doctest
1 cat t.py
2 def add2(x):
3 '''
4 >>> import t
5 >>> t.add2(3)
6 5
7 '''
8 return x + 2
9 python3 -m doctest t.py
pydoc
1 python3 -m pydoc t
HTML形式でドキュメントを書き出す。
1 python3 -m pydoc -w t
ローカルHTTPサーバーを起動しドキュメントを開く。
1 python3 -m pydoc -b
os
環境変数を取得する
1 >>> import os
2 >>> os.environ['HOME']
subprocess
1 import subprocess
2 r = subprocess.run(['ls', '-l'], capture_output = True)
3 r.stdout
4
5 r = subprocess.run(['curl', 'https://moli-green.xyz'], capture_output = True)
6 r.stdout
tempfile
1 import tempfile
2 f = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
3 f.name
4 # '/tmp/tmpb30bxpet.png'
5 f.close()
json
1 >>> x = {'foo': 3, 'bar': [4, 5, 6]}
2 >>> j = json.dumps(x)
3 >>> j
4 '{"foo": 3, "bar": [4, 5, 6]}'
5 >>> o = json.loads(j)
6 >>> o
7 {'foo': 3, 'bar': [4, 5, 6]}
多分、特定のインターフェースをサポートしていないオブジェクトはシリアライズできないようだけど、よくわからない。
パターンマッチと併用するのが良いだろう。
1 >>> match o:
2 ... case {'foo': x, 'bar': y}:
3 ... print(x)
4 ... for i in y:
5 ... print(i)
6 ...
組み込みクラスとマッチさせた方がセキュリティ的には良いようだが。まあ、型が合わないと後々実行時エラーが発生してわかりそうではある。
1 >>> match o:
2 ... case {'foo': str() as x, 'bar': list(y)}:
3 ... print(x)
4 ... for i in y:
5 ... print(i)
6 ...
Requests
1 import requests
GET
1 r = requests.get('https://moli-green.xyz')
ヘッダを追加するならこう。
1 h = {'foo': 'bar', 'baz': 'hoge'}
2 r = requests.get('https://moli-green.xyz', headers=h)
POST, PUT
mimetypeを取得するにはこう。
1 >>> r = requests.get('https://moli-green.xyz/img.webp')
2 >>> r.headers['content-type']
3 'image/webp'
mimetypes
拡張子からファイルタイプを判別する。
1 >>> import mimetypes
2 >>> mimetypes.guess_type('https://moli-green.xyz/img.png')
3 ('image/png', None)
4 >>> mimetypes.guess_type('https://moli-green.xyz/img.jpg')
5 ('image/jpeg', None)
6 >>> mimetypes.guess_type('https://moli-green.xyz/img.jpeg')
7 ('image/jpeg', None)
8 >>> mimetypes.guess_type('https://moli-green.xyz/img.webp')
9 ('image/webp', None)
10 >>> mimetypes.guess_type('https://moli-green.xyz/img.avif')
11 ('image/avif', None)
12 >>> mimetypes.guess_type('https://moli-green.xyz/img.j2k')
13 (None, None)
14 >>> mimetypes.guess_type('https://moli-green.xyz/img.jp2')
15 ('image/jp2', None)
16 >>> mimetypes.guess_type('https://moli-green.xyz/img.qoi')
17 (None, None)
pillow
画像処理ライブラリ。(PILのフォーク)
画像の読み込みと保存。
1 >>> from PIL import Image
2 >>> img = Image.open('img.png')
3 >>> img.save('img.webp')
pillowには他の画像形式に対応するためのプラグインが公開されていることもあるようなので、PyPIで探してみるといいだろう。
パターンマッチ
キャプチャーパターンには*
によるアンパックが使える。これはパターンマッチの機能というわけではなく、式リストの機能だと思われる。
1 >>> match input().split():
2 ... case ["foo", *objs]:
3 ... for i in objs:
4 ... print(i)
_
は何にでも一致する。
|
は or。
1 >>> match input():
2 ... case 'foo' | 'bar':
3 ... print('ok')
4 ... case _:
5 ... print('other')
マッチしたものを変数に取得したければas
を使う。
1 >>> match input().split():
2 ... case ["foo", ("bar" | "baz") as x]:
3 ... print(x)
辞書型へのパターンマッチの例
1 >>> d = dict(x=2, y=3, z=[10, 20, 30])
2 >>> match d:
3 ... case {'x': 2 as x, 'z': z, **o}:
4 ... print(x, z, o)
dataclasses
argpase
大変複雑だが、最低限次のことを覚えておけばいいだろう。
1 >>> import argparse
2 >>> p = argparse.ArgumentParser()
3 >>> p.add_argument('-f', '--foo', help='Foo!', action='store_true')
4 >>> p.add_argument('url')
5 >>> arg = p.parse_args('https://example.com'.split())
6 >>> print(arg.foo)
7 False
8 >>> print(arg.url)
9 https://example.com
Python パッケージ
仮想環境
仮想環境を作る
1 python3 -m venv tutorial-env
仮想環境をアクティベート
1 source tutorial-env/bin/activate
仮想環境のアクティベートを解除
1 deactivate
パッケージを作る
TestPyPI
Pythonパッケージのアップロードテスト用のリポジトリ
Twine
PythonパッケージをPyPIなどにアップロードするためのコマンド。
Hatch
ビルドシステム。
pipx
Hatch
設定
設定を表示
1 hatch config show
設定を変更
1 hatch config set template.name "Shinya Koyano"
設定を初期状態に戻す
1 hatch config restore
プロジェクトの初期化
注意、パッケージ名はすべて小文字にするのが無難。
1 hatch new 'foo'
既存のディレクトリを初期化するには次のようにした方がいいかもしれない。
1 hatch new moli5 .
実際的なコマンドラインアプリケーションのプロジェクトを初期化するにはこうだろう。
1 hatch new --cli foo .
ライセンス
CLI
コマンドラインインターフェースを導入することもできる。デフォルトでClickモジュールが使われるようだ。
1 hatch new --cli foo
__init__.py
と__about__.py
__init__.py
はパッケージのためのファイル。
__about__.py
はHatchがバージョン管理情報を書き込むためのファイルと思われる。
メタデータの記述方法
PyPAに仕様が書かれれいるので、これを参照する。ぱっと見ではよくわからないので、一つ一つ検討する必要があるだろう。
まあ、デフォルトのままでも特に問題はないように思われる。
依存関係
pyproject.toml
1 dependencies =[
2 "cowsay",
3 ]
環境
hatchは特定のPython仮想環境を自動的にインストールして起動するようになっている。デフォルトでは現在のPythonのバージョンが用いられる。
1 hatch shell
2 exit
特定の環境で実行するには。
1 [envs.foo.scripts]
2 show = 'echo "hello."'
そしてhatch run foo:show
バージョン(管理)
tool.hatch.version
で指定したファイルからバージョンを読み込む。
hatch veersion "0.0.2"
のように、コマンドでバージョンを更新できる(ファイルに書き込まれる)。
フォーマットと静的解析
1 hatch fmt
テスト
デフォルトでpytest
が使われるようだ。
1 # tests/test_1.py
2
3 import moli2.foo
4
5 def test_add3():
6 assert moli2.foo.add3(2) == 5
1 hatch test
ビルド
1 hatch build
src/project/
以下のファイルがwheelに含まれると思うのだが、よくわからない。
公開
まず、TestPyPIに公開するにはこうする。
APIトークンを使う場合は、ユーザー名は__token__
として、パスワードにAPIトークンを設定する。
1 hatch publish -r test
キャッシュの削除
hatchを使用すると__pycache__/
や.pytest_cache/
などのディレクトリが作られる。これらがあるとreuse lint
でもgit
でも問題になる。おそらく解決策はgit cleanで、あらかじめ削除しておくことだろう。(.gitignore
に記述しておくとなおよいだろう)。ただ、まだ試してはいない。
SPDX
ライセンスを表記するための共通形式らしい。
SPDX-FileCopyrightText
について
SPDX 3.0の仕様書には言及がないが、SPDX 2.3の仕様書には言及がある。
どうやら、REUSEという関連団体の仕様に定義があるようだ。またREUSEはプロジェクト検証用のツールを用意しているので、Hatchの場合はこれを使えばよさそうだ。
なぜSPDX 3.0でこの記述が削除されたのかわからないので、若干不安だが、まあ、多分、設定しておいても悪影響はないだろう。
なぜこのようなライセンスに関わる部分が不明瞭なのか不思議だ。ライセンスを明瞭にするのがSPDXの目的だと思うのだが。
reuse-tool
順を追って解説する。
ファイルに著作権情報とライセンスを付与する。
ライセンス識別子はSPDX License Listに記述されている。
1 pipx run reuse annotate -c 'Shinya Koyano <moli_green@runbox.com>' -l 'MIT' ./foo.py
次にライセンステキストをダウンロードする。
1 pipx run reuse download --all
ライセンスを検証する。
1 pipx run reuse lint
pipx
uv
初期化。スクリプトが存在していればヘッダが追加される。スクリプトが存在していなければ、シンプルな初期ファイルが作成される。
1 uv init --script foo.py
スクリプトにパッケージを追加する。
1 uv add --script foo.py 'cowsay'
スクリプトを実行する。
1 uv run ./foo.py
最後にreuse-toolを使ってSPDX形式のライセンスを付与する。
pipxでインストールできるパッケージの作り方。
1 uv init --lib foo
1 uv sync # 仮想環境を作成する
2 sourcd .venv/bin/activate
3 python -c 'import foo; foo.cli()' -- [something option for the cli interface]
Magika
インストール
1 python -m pip install magika==0.6.1rc1
pathlib
1 >>> from pathlib import Path
2 >>> p = Path('./img32.webp')
使い方
1 >>> from magika import Magika
2 >>> magika = Magika()
3 >>> p = Path('./img32.webp')
4 >>> r = magika.identify_path(p)
5 >>> print(r.output.label)
Zsh
設定ファイル。
1 ~/.zshrc
Zshの組み込みコマンドのManページを表示する
1 man zshbuiltins
Zshのプションを設定するにはsetopt
を使う。AUTO_CD
とAUTO_PUSHD
は設定しておいた方がZshの利便性を享受できるだろう。オプションを無効化するにはunsetopt
を使う。
1 # 変数
2 foo=3
3
4 # 配列
5
6 man zshoptions
7
8 zmodload
sudo
-E
-
既存の環境変数を保持する
GNU Screen
Ed
gawk
Tor
オニオンサービスを実行する
/etc/tor/torrc
がTorの設定ファイル。
1 HiddenServiceDir /var/lib/tor/hidden_service/
このHiddenServiceDir
のhostname
というファイルにオニオンサービスのURLが書かれている。次のようにして確認する。
1 sudo cat /var/lib/tor/hidden_service/hostname
caddyにこのURLを記述しても機能しないので80番ポートのデフォルトはオニオンサービスにしている。
git
ユーザー設定
1 git config --global user.name 'Shinya Koyano'
2 git config --global user.email 'moli_green@runbox.com'
3 git config --global user.signingkey ''
基本的な使い方
1 git init
2
3 git add .
4 git commit
5
6 git clone [paht or URI]
7
8 git status
9
10 cat .gitignore
11 *.hoge
12
13 git diff
14
15 git mv foo bar
16
17 git log
18
19 git pull
20
21 git push
MacにおけるGPG署名時のエラーについて
コミットしようとすると次のようなエラーになる。
1 error: gpg failed to sign the data fatal: failed to write commit object
Macに入っていたGitを使っていたが、それをやめ、HomebrewのGitを使うようにする。
それでもまだエラーが出るが、メッセージが変化している。
1 gpg: signing failed: Inappropriate ioctl for device
これは~/.zshrc
にexport GPG_TTY=$(tty)
とすることで解決できた。
これによってMacからでも自分のGitサーバーに署名付きでコミットできるようになった。
Forgejo
Caddyのリバースプロキシを通そうと思ったがうまくいかない。
二要素認証を有効にしているとプッシュできない。この問題の解決方法はわからない。
Homebrew
Vim
インストール
1 brew install vim
設置ファイル
$HOME/.vimrc
タブ
tabstopなどの設定が影響するようだが、いまいちよくわからない。
しかし、いちいちスペースを入力するのは、あまりに面倒なので設定してみた。しばらくこれで様子を見よう。
1 syntax enable
2 set tabstop=4
3 set expandtab
4 set autoindent
テキストオブジェクト
yab
で( ... )
の全体をコピーできる。
端末通信
まず、以下のようにして端末内でプロセスを起動する。
1 :term python
ソースファイルでyap
やyab
などとして、実行したいコードの部分をレジスタに入れる。
次のEXコマンドで端末のプロセスにコードを送信することができる。(@"
は無名レジスタを指すようだ)
1 :call term_sendkeys("python", @")
また、@@
で直前のEXコマンドを繰り返すことができる。タイプの手間が省けることもあるだろう。
実際の使用は以下のようになるだろう。
1 term python
2 yap
3 :w|call term_sendkeys("python", @")
4
5 " more edit
6 yap
7 @@
tmux
インストール
brew install tmux
操作
- セッションを開始
-
tmux new
- プレフィックスキー
-
C-b
- ヘルプを表示
-
C-b ?
- コマンドプロンプト
-
C-b :
- セッションからデタッチ
-
C-b d
- 最後のセッションにアタッチ
-
tmux attach
- 指定したセクションにアタッチ
-
tmux attach -t foo
- セッションをリスト表示
-
tmux list-session
-
tmux ls
- tmuxを完全に終了する
-
:kill-server
- ウインドウを作成する
-
C-b c
- ウインドウを切り替え
-
C-b [number]
- ウインドウを水平方向に分割
-
C-b %
- ウインドウを垂直方向に分割
-
C-b "
- ペインを移動
-
‘C-b [up, down, left, right]’
- ツリーモード
-
C-b s
-
C-b w
-
C-b choose-tree
ウインドウレイアウト
- レイアウトを順番に選ぶ
-
C-b space
- 水平に均等に並べる
-
C-b M-1
Mac標準のターミナルではメタキーが無効になっているので、設定で有効にする。
コピーモード
- コピーモード開始
-
C-b [
- 選択開始
-
C-Space
- 選択範囲をコピーし、コピーモードを終了する
-
C-w
- 貼り付け
-
C-b ]
Alacritty
HTML
CSS
calc
@media
color
@font-face
https://developer.mozilla.org/ja/docs/Web/CSS/font-feature-settings
JavaScript
テンプレートリテラル
1 {
2 let x = 3;
3 let s = `foo ${x}`;
4 console.log(x);
5 }
例外
1 try {
2 throw "hi";
3 } catch(e) {
4 console.log(e);
5 } finally {
6 console.log("bye.");
7 }
ループ
基本的にfor of
を使っておけばよさそうだ。
1 {
2 let a =["foo", "bar", "baz"];
3 a.foo = 3;
4
5 for (const i of a) {
6 console.log(i);
7 }
8 }
分割代入
角かっこはは省略できない。
1 {
2 let a, x, y;
3 a = [1, 2];
4 [x, y] = a;
5
6 console.log(`x = ${x}, y = ${y}`);
7 }
関数
関数名はつけてもつけなくてもいい。関数名をつけた場合は関数内から参照できるようになる。
1 {
2 const f = function foo (x=0) {
3 return x + 2;
4 }
5
6 console.log(f(3));
7 console.log(f());
8 }
アロー関数式
this
を持たないなど、関数とは少し違う。まあ、高階関数用の無名関数として使うにはこれの方が便利だろう。
1 {
2 let f = x => x * 2;
3 f(3);
4
5 let f2 = (x, y) => x * y;
6 f2(2, 4);
7
8 (x => x ** 2)(3);
9 }
オプショナルチェーン
.
の代わりに?.
を使う。プロパティが存在しない場合にエラーになる代わりにundefined
が返るようになる。
1 {
2 let x = { "foo": 1, "bar": 2 };
3 console.log(typeof x);
4 console.log(`${x?.foo} ${x?.baz}`);
5 }
正規表現
1 {
2 const re = /f.o/;
3 const s = "foo fox";
4 s.match(re);
5 }
map
1 {
2 a = [3, 4, 5, 6];
3 a.map(x => x + 10);
4 }
オブジェクト
1 {
2 const Foo = {
3 name: "foo"
4 };
5 const o = Object.create(Foo);
6 console.log(o?.name);
7 console.log(typeof o);
8 }
プロミス
setTimeout
1 setTimeout(() => console.log('hi'), 1000 * 5);
1 {
2 const p = new Promise(
3 (resolve, reject) => {
4 setTimeout(() => {
5 resolve("OK!");
6 }, 4000);
7 });
8
9 p.then((resolve) => {
10 console.log(resolve);
11 });
12 }
async function
プロミスの完了を待つ関数。なので、通常の関数と同じように使える。
1 {
2 function f1() {
3 return new Promise(
4 (resolve) => {
5 resolve('ok');
6 });
7 }
8 async function f2() {
9 const r = await f1();
10 console.log(r);
11 }
12
13 f2();
14 }
モジュール
querySelector, querySelectorAll
DomノードをCSSセレクタの構文で選択する。
1 {
2 const a = document.querySelectorAll("a");
3 for (i of a) {
4 console.log(i.title);
5 }
6 }
フェッチ API
クライアントサイド Web API
TypeScript
Firefox DevTools
フォントサイズを変更する
about:config
でdevtools.toolbox.zoomValue
の値を大きくする。
matrix
サーバー
Element
インストールしようかと思ったが手を広げすぎな気がする。
Accessibility
jq
GNU Readline
PostgreSQL
Godot アセットパイプライン
3Dモデル
Blenderでモデルを作成しglTF 2.0形式でエクスポートしたものを使うのが推奨されている。これでいいと思う。(Blenderから直接読み込むのも試してみたがMacのセキュリティの関係でうまくいかないようだ)
glTFにはテキスト形式*.gltf
とバイナリ形式*.glb
の二種類あるようだが、テキスト形式の方がGodotとは相性が良さそうなので、テキスト形式を使おう。
Synapse
インストール
Pyplを使ってインストールした。(デビアンパッケージでのインストールがうまくいかなかったので)
設定
起動
1 cd ~/synapse
2 source env/bin/activate
3 synctl start
4 deactivate
man
Glicol
ショートカット
- コメントアウト(トグル)
-
ctrl + /
command + /
- Rub or Update
-
command + Enter
演算子
:
-
多分オシレーターを定義
-
>>
~
-
多分モジュレーターを定義
Glicolのコンソールを開く
cmmand + shift + j
ヘルプを参照する
help("sin")
sin
サイン波のオシレーター。周波数は-1.0から1.0の範囲。
他にもオシレーターがある。
saw
-
ノコギリ波
squ
-
矩形波
tri
-
三角波
mul
入力信号に数値を掛ける。0.0から1.0の範囲。
add
入力信号に固定値を足す
seq
ノートスペースで区切って並べる。ノートは1小節を分割する。
`_``はノートをさらに分割する。
ノートの値はMidiノート番号である。(60番がC3かC4だと思われる)
sawsynth 1 2
ノコギリ波のシンセサイザー
1はアタックで、2はディケイ
choose
シーケンスから一つ選ぶ。(おそらくランダム)
1 o: seq ~a ~a ~a 50 >> sawsynth 0.1 0.5 >> mul 0.2
2 ~a: choose 40 60 0 0 0 0
sp
今はサンプルを用意するのが面倒なので飛ばす
imp
インパルス信号を生成する。1.0が一小節なのでその範囲でだろう。
1 o: imp 0.5 q >> sawsynth 0.1 0.4 >> mul 0.2
FM
特にFMシンセサイザーに特化した機能があるわけではないが、オシレーターの周波数をもジュレートすることで同じことができる。
1 o: sin ~fm >> mul 0.2
2 ~fm: sin 1.2 >> mul 300 >> add 500
noiz
ホワイトノイズ。シード値を取るが あまり変化はなさそう。
1 o: noiz 1 >> mul 0.1
lpf
ローパスフィルタ。カットオフ周波数, Q値
Typeface
Inconsolata
等幅書体。プログラミングなどに向いているようだ。
Alacritty
VI mode
1 control + shift + space
OAuth
Request-oauthlib
The AT Protocol SDK for Python
インストール
1 python -m pip install atproto
アプリパスワード
使い方
1 >>> from atproto import Client
2 >>> client = Client()
3 >>> client.login('username', 'application_password or password')
自分の識別子。
1 >>> client.me.did
ポストのリストを取得。
1 >>> def showPosts():
2 ... posts = client.app.bsky.feed.post.list(client.me.did, limit=10)
3 ... for uri, post in posts.records.items():
4 ... print(uri, post.text)
5 ...
6 >>> showPosts()
Flask
SQLite
データ型
- text
- numeric
- integer
- real
- blog
- any
SQLiteの重要な特徴として: 互換性のある型の値が挿入された場合は型変換を行った上で挿入する。しかし、データの損失なしに変換できない場合はテキストとして挿入する。
ROWID
データベースの行にはIDが必要になることもある。SQLiteでは全ての行に自動的にrowid
という一意の値が割り振られるので、これを使えばよい。
1 select rowid from foo;
SQL
データ型
データベースソフトウェアによってまちまちなようなので、各種ソフトウェアのドキュメントを参照するしかなさそうだ。
テーブルを作る
1 create table foo (x text, y int)
テーブルに挿入
1 insert into foo values('bar', 100);
テーブルを削除
1 drop table foo;
検索
1 select * from foo where x = 2;
スワップファイルのサイズを増やす
これを使う。
1 # メモリとスワップサイズのサイズを確認
2 free -h
3
4 # スワップファイルのサイズを編集
5 sudo vim /etc/dphys-swapfile
6
7 # スワップファイルの使用を停止
8 sudo dphys-swapfile swapoff
9
10 # スワップファイルを新しいサイズで作成
11 sudo dphys-swapfile swapon
12
13 # システムを再起動する(このままだと起動中のプロセスが新しいスワップファイルのサイズを認識しないため)
14 sudo shutdown -r now
Mitraのビルドでは2048MBに設定したところビルドに成功した。
Nushell
Zellij
プリセット
Unlock first
を選ぶ。そうしないとcontrol
キーをZellijが使ってしまうので、vimなどが機能しなくなる。
Zig
Raspberry Pi 用にクロスコンパイル
1 zig build-exe -target aarch64-linux-gnu main.zig
ActivityPub
IPFS
Ren’py
Blender
Godot
今はGodotで使用するモデルを作るのが目的である。だから、自ずと条件は決まってくる。
- ポリゴン数のコントロールが可能であること。
360度画像の作り方
“cycles“を選択すると、カメラのプロパティでパノラマタイプが選択できるようになる。
Geometry Nodes
Instance on Points Node
Godot
GDScript
Globals
EditorScript
とりあえず、言語を試すにはこの設定がよいか。
2D メインツールバー
q
選択モード
w
移動モード。選択モードとの違いはよくわからない
e
回転モード。
s
スケールモード。シフトで縦横比を維持できるようだ。
四角いボタン。ノードが重なっている時に選択メニューを表示する。
ピボット設定ボタン。ピボットがオブジェクトの中心位置になる。(そこからオフセットになる)
g
, space
。パン。
r
ルーラーモード。定規が表示される。
shift + s
スマートスナップモード。他のオブジェクトと整列させるために使うようだ。
shift + g
グリッドスナップモード。グリッドにスナップする。G
三つの点。スナップする対象を切り替える。
command + l
ロック。
command + g
グループ化。親ノードと子ノードをグループ化する。一つのノードを選択すると全体が選択される。
スケルトン。今のところ時期尚早。
ビュー。時期尚早だが、だいたい見ればわかる。
Node2D
canvasitem
canvasitemはすべての2Dオブジェクトの抽象基底クラスらしい。
Visible
可視不可視。
Light Mask
時期尚早。
Sprite2D
Godotのファイルシステムに画像を読み込んで、Texture
で読み込む。
Node
-Node
重要だと思うのだが、例をが見つからないのでよくわからない。
CanvasLayer
レイヤーが必要ならこれを使う。主に背景だろう。
数値が大きいほど手前にくる。常にデフォルトのCanvasLayerが存在しているようで、デフォルト値は0
。
ビューポートとキャンバス変換
Transformのことかと思うけど、よくわからない。
ライトとシャドウ
時期尚早だろう。
スプライトアニメーション
時期尚早。
パーティクル
時期尚早。
CharacterBody2D
2Dボディを動かすためのクラス。
プロジェクト -> 設定 -> インプットで入力方法を設定しておく。
以下のようなものをScript
に設定する。
1 extends CharacterBody2D
2
3 @export var speed = 300
4
5 func get_input():
6 var input_direction = Input.get_vector("ui_left", "right", "up", "down")
7 velocity = input_direction * speed
8
9 func _physics_process(delta: float) -> void:
10 get_input()
11 move_and_slide()
CollisionShape2D
多分、これが衝突オブジェクト。子にSprite2Dを持たせれば良いだろう。
衝突
内訳には4つのオブジェクトがあるようだ。
StaticBody2D
衝突可能な静的オブジェクト。床などに使うだろう。
RigidBody2D
freeze
プロパティを有効にすると物体を固定できる。そうするとStaticBody2D
と違いはなくなる。
CharacterBody2D
自分は物理の影響を受けないが、他のオブジェクトには物理の影響を与える。名前の通りキャラクター用。
velocity
が速度ベクト
CollisionShape2D
基本的に、衝突にはこれを使えばよい。
Input
入力を処理するためのメソッド(オーバーライドして使う)
シーン
どうやら、シーンはGodotにおける基本構成単位のようだ。
具体的にはファイルシステム -> 新規シーンとしてシーンを作成する。
作成したシーンは別のシーンから、シーンブラウザの「子シーンをインスタンス化」でインスタンス化することができる。(おそらくスクリプトからも可能だろうが、まだ知らない)
1 var s = preload("res://foo.tscn")
2 var i = s.instantiate()
3 add_child(i)
シーンのインスタンス化
positionは親からの相対位置になるようだ。
最後に、ノードは解放しないとリソースリークになるのだろう。
1 var s = preload("res://foo.tscn")
2
3 func genSI() -> void:
4 var i = s.instantiate()
5 var p = get_parent()
6 p.add_child(i)
7 i.position = Vector2(1000, 0)
8 print(i.position)
9 #i.queue_free()
Godot インターフェース
後で。
Godot 通知
_notification
に全ての通知が届くようだが、_physics_process
はなぜか取得できない。とりあえず、オーバーライドしておけば動くので、とりあえずこれでよしとしよう。
_init
は最初に呼び出されるようだ。
_ready
はUIで設定したプロパティを初期化した後に呼び出されるので、通常の初期化はこれを使うべきだろう。
_physics_process
はフレームごとに繰り返し処理を行う部分に使うのに最適なそうだ。
とりあえず、_ready
と_physics_process
を覚えておくだけで十分だろう。
インプット
プロジェクトの設定で設定したキーバインドを取得するには以下のようにすればよい。
1 if Input.is_action_pressed("down"):
2 print("down")
シグナル
基本的には、UIのノード -> シグナルにそのノードが発行可能なシグナルがリストアップされているので、そこから選ぶ。受信者とその受信メソッドも自動的に作成される。
スクリーンの外に出ているかどうか、シグナルで判定する
これは、UIで子ノードとして追加しする。
まだ試していない。
スプライト
テクスチャ
画像を読み込んだ場合これが生成される
Godot 3D
blender
*.glb
フォーマットで出力しておけば良さそうだ
Node3D
3D関係のクラスはここから継承されているそうだ。
StaticBody3D
デフォルトでピン留めされているようなもの
CollisionShape3D
衝突はこれだけでいいと思うのだが。どうなんだろう。
MeshInstance3D
定義済みのプリミティブシェイプ、または、独自のシェイプを読み込んで表示することができる。ただし、*.glb
形式は読み込めない。*.obj
形式なら読み込める。どの形式がベストなのかはよくわからない。
どうやら、MeshInstance3D
は*.obj
形式しか対応していないようだ。
*.glb
形式を使いたい場合は、シーンとして読み込ませる必要がある。また、そちらの方が表現力が高いので、そうするべきだと思われる。
StandardMaterial3D
CharacterBody3D
キャラクター用のボディ。基本的に物理の影響を与えるが受けないとされているようだが、正確にはそうでもなく、ある程度影響は受ける。
*.glb
ファイルの読み込み
ファイルシステムに*.glb
ファイルを読み込んでから、シーンにドラッグ&ドロップすることで挿入できる。ノードタイプはNode3D
と表示されているが、詳しいことはわからない。
移動
Basis
DirectionalLight3D
指向性のあるライト。とはいえ、画面全体を照らすので、太陽光のために使うものなようだ。
Marker3D
常に(非表示状態でも)マーカーを表示するだけ。それ以外はただのコンテナである。
Camera3D
カメラ。詳細を追求すれば膨大だが、特に何も設定しなくても問題なく使える。
Godot Command line
スクリプトを実行するには-s
オプションを使う。しかし、スクリプトとして実行可能にするためにはSceneTree
を継承する必要があるようだ。試してみたところ、Node
は継承していない。これはNode2D
やNode3D
は継承しているのだろうか。
GDScript
マニュアルから多少順番が前後している。
export
@export
系の注釈は値をインスペクターで編集可能にする
1 @export var n = 3
2 @export var s: String
3 @export_range(0, 100) var r
@onready
ノードの読み込みが終わるまで変数の初期化を遅延する(_ready)
1 @onready var child = get_node("foo")
ドキュメントコメント
シャープ二つで始める
1 ## x + 2
2 func add2(x: int) -> int:
3 ## add2
4 return x + 2
アサーション
1 assert(1 == 0, "ERROR!")
シグナル
シグナルを定義
1 signal hoge
シグナルによって呼び出されるメソッドを定義
1 func _on_hoge(args) -> void:
2 print("called signal: hoge", args)
シグナルを接続。呼び出し。
1 self.connect("hoge", _on_hoge)
2 var s: Signal = Signal(hoge)
3 s.emit([1, 2, "cat"])
呼び出し可能オブジェクト
Godotの関数は基本的にメソッドなのでself
を渡す必要があるようだ。
1 var x = Callable(self, "add2")
2 assert(x.call(3) == 5)
変数
変数はvar
で宣言する。
静的変数にはstatic
修飾子をつける。これはクラススコープになる。
1 var x = 1
2 var x: int = 1
3 static var y = 2
定数はconst
修飾子で宣言する
1 const x: int = 1
列挙型
自動的に値が割り振られる定数のようなもの。クラスのトップレベルでないと宣言できないようだ。
1 enum e {foo, bar, baz}
関数
大体、思った通りに動く。関数は常にselfを持つので指定する必要はない。
1 ## x + 2
2 func add2(x: int) -> int:
3 ## add2
4 return x + 2
5
6 func add1(x: int) -> int: return x + 1
7
8 func add3(x = 1): return x + 3
9
10 func fcf(f: Callable) -> int:
11 return f.call(1)
if
まあ普通
for
pythonとほぼ同じ
match
pythonと同じ
クラス
スクリプトファイルは無名クラスである。
ファイルの先頭で名前とアイコンを設定できる。
クラスに名前をつけるとノードの追加パネルで参照できるようになる。
1 @icon("res://icon2.png")
2 class_name MyClass
継承
1 extends Node
スーパークラスを参照するときはsuper
キーワードが使える
コンストラクタは_init
load, preload
Godotのファイルシステムからスクリプトを読み込むときには必要になるらしいが、今のところ使用しなくても動作している。あとで、検証。
ツール
シーンを実行しなくてもエディタ(2D, 3D)などから実行できる。
1 @tool
2 extends Sprite2D
3
4 func _process(delta: float) -> void:
5 if Engine.is_editor_hint():
6 self.rotation += PI * delta
7 else:
8 self.rotation -= PI * delta
9
エクスポート
ドキュメントコメント
##
二つで開始するだけ。ファイルの先頭および、各種メンバーに設定できる。
静的型付け
キャストが必要になるケースがあるようだが、まあ、それは実際に直面するまで。
## フォーマット文字列
基本的に%s
だけ覚えておけばいいと思う。
1 f = func():
2 const x: String = "foo %s bar"
3 const y: String = x % 2
4 print(y)
5 print("%s, %s, %s", [22, "foo", self])
6 f.call()
デバッグ
複数インスタンスを同時にデバッグすることもできるようだ。あとで検証したい。
ブレークポイントはテキストエディタの行番号の左側をクリックするか、breakpoint
キーワードで設定できる。
アイドルとプロセス
グループ
右ペインのグループからグループを作成できる。グループに登録されているノードをまとめて選択するための機能のようだ。
手動でグループに登録するにはシーンエディタでノードを選択してから、グループペインのチェックボックスをオンにする。
1 add_to_group("hoge")
ノードとシーンインスタンス
ノードを取得。
1 const node = self.get_node(foo)
2 node.show()
ノードをインスタンス化。クラス名をつけておく必要がある。
1 var sp = Sprite2D.new()
2 self.add_child(sp)
3 sp.queue_free()
4
5 var nd = Foo.new()
6 nd.show()
7 nd.show()
8 nd.queue_free()
シーンのインスタンス化
シーンはツールバーから作成するようだ。この部分の説明が欠けているためにわかりにくい。
インスタンス化は次のようにする。
1 const scene = preload("res://foo.tscn")
2 var instance = scene.instantiate(PackedScene.GEN_EDIT_STATE_DISABLED)
3 self.add_child(instance)
オーバーライド可能なメソッド
基本的に毎フレーム呼び出されるメソッド。それをオーバーライドして使う。
今はスクリプトに集中したいので、あとで。
一時停止
一時停止というよりはSceneTree
が重要だろう。しかし、動かせないと動作が分かりにくいので、あとで。
ファイルシステム
リソース
preload
で読み込むことができる。
シーンはパックされたリソースなので、インスタンス化する必要がある。
シングルトン
グローバル変数の代わりに自動ロードされるシングルトンクラスを使うのがよい。ということらしい。
プロジェクト設定から設定する。
SceneTree
プログラムが起動すると、ルートシーンが作られ、そこにルートビューポートが作られる。それらは以下のようにして取得できる。
1 var tree = get_tree()
2 print(tree)
3 var root = tree.root
4 print(root)
シーンの固有ノード
ボタンなどがパスが変わるとアクセスできなくなるので、パスではなく固有名でアクセスするようにする。ノードペインのコンテキストメニューから選択できる。
Godot ネットっワーク
HTTPリクエスト
1 extends Node
2
3 func _ready() -> void:
4 var hr: HTTPRequest = get_node("HTTPRequest")
5 hr.request_completed.connect(_on_request_completed)
6 hr.request("https://moli-green.xyz")
7
8
9 func _on_request_completed(result: int, response_code: int,
10 headers: PackedStringArray, body: PackedByteArray):
11 var text = body.get_string_from_utf8()
12 print("%s" % [text])
ペインを使って接続するならこんな感じ。
1 # node.gd
2 extends Node
3
4 func _ready() -> void:
5 $HTTPRequest.request('https://moli-green.xyz')
1 # http_request.gd
2 extends HTTPRequest
3
4 func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
5 match result:
6 HTTPRequest.Result.RESULT_SUCCESS:
7 for i in headers:
8 print(i)
9 print("%s" % body.get_string_from_utf8())
10 _:
11 pass
OS
サーバーとして起動されているのか、クライアントとして起動されているのか調べるのに必要になる。
専用サーバーとしてエクスポート
Godotでは基本的に、サーバーとクライアントを同じプロジェクトとして作成する。
ただ、サーバーの場合は専用サーバーとしてエクスポートする。この場合グラフィックライブラリが含まれないようになる。また不要なリソースは手動で含めないようにできる。
プロジェクト -> エクスポート -> リソース から設定する。
また、RaspberryPiにエクスポートするのでプラットフォームLinux、アーキテクチャarm64を選択する。
以下、簡単なサンプル。
1 extends Node
2
3 func _ready() -> void:
4 print("linux: %s" % OS.has_feature("linux"))
5 print("dedicated_server: %s" % OS.has_feature("dedicated_server"))
OS.has_feature("dedicated_server")
でサーバーとして起動されているかどうか調べることができるので、これでクライアントと、サーバーで処理を切り分ける。
High-level muitiplayer
-解説
一度WebSocketでテストしてみようと思ったが、クラスがなくなっていたので、テストできなかった。
いきなりこれをやるしかないが、それはまだ時期尚早だろう。シグナル関係にもっと慣れてからでないと難しい。
MultiplayerAPI
ENetMultiplayerPeer
とりあえず動いたコード。
1 extends Node
2
3 const PORT = 7000
4
5 func _ready() -> void:
6 var peer = ENetMultiplayerPeer.new()
7
8 var x = get_tree().get_multiplayer()
9
10 var y = x.is_server()
11
12 return
13
14 #if self.multiplayer.is_server():
15 #peer.create_server(PORT)
16 #print("server")
17 #else:
18 #peer.create_client("pi.local", PORT)
19 #print("client")
20
21 #peer.create_server(PORT)
22 #print("server")
23
24 #peer.create_client("pi.local", PORT)
25 #print("client")
26
27 self.multiplayer.multiplayer_peer = peer
28
29 func _process(delta: float) -> void:
30 if Input.is_action_just_pressed("ui_accept"):
31 print("test")
32 print(multiplayer.get_unique_id())
33 hello.rpc()
34
35 @rpc("any_peer", "call_remote", "unreliable", 0)
36 func hello():
37 print(multiplayer.get_unique_id())
38 print("hello")
サーバーモードで起動するか、クライアントモードで起動するか切り分ける方法が必要だ。いくつか方法は考えられるが、コマンドラインオプションを使うのが簡単だろう。そのためにはOSモジュールを使う必要がある。
公式マニュアルの解説には不足している情報がある
Godot InputEvent
Node._input(event)
はオーバーライドできるメソッドで、入力があるたびに常に呼び出される。実際に使うにはこれのGUIイベントを取得しない亜種の方が適している。
InputEvent
は入力と発行するイベントを管理するクラス。
Input
は入力を感知するシングルトン。
キーボードはこう。これで十分だろう。
1 extends Node
2
3 func _process(delta: float) -> void:
4 if Input.is_action_pressed("ui_left"):
5 print("left\n")
6
7 if Input.is_action_pressed("ui_right"):
8 print("right\n")
9
10 if Input.is_action_pressed("ui_up"):
11 print("up\n")
12
13 if Input.is_action_pressed("ui_down"):
14 print("down\n")
15
16 if Input.is_action_just_pressed("ui_left"):
17 print("just left\n")
18
19 if Input.is_action_just_pressed("ui_right"):
20 print("just right\n")
21
22 if Input.is_action_just_pressed("ui_up"):
23 print("just up\n")
24
25 if Input.is_action_just_pressed("ui_down"):
26 print("just down\n")
27
28 func _ready() -> void:
29 var x = InputMap.get_actions()
30 for i in x:
31 print(i)
マウス入力。
1 func _input(event: InputEvent) -> void:
2 var v = get_viewport();
3 if event is InputEventMouseButton:
4 print("mouse click/unclick: %s", v.get_mouse_position())
5
6 if event is InputEventMouseMotion:
Godot 入力処理
InputMap
入力を管理するシングルトン。通常はこれを使うことになるだろう。
1 extends Node
2
3 func _input(event: InputEvent) -> void:
4 var v = get_viewport();
5 if event is InputEventMouseButton:
6 print("mouse click/unclick: %s", v.get_mouse_position())
7
8 if event is InputEventMouseMotion:
9 print("mouse move: %s",v.get_mouse_position())
10
11 func _process(delta: float) -> void:
12 if Input.is_action_pressed("ui_left"):
13 print("left\n")
14
15 if Input.is_action_pressed("ui_right"):
16 print("right\n")
17
18 if Input.is_action_pressed("ui_up"):
19 print("up\n")
20
21 if Input.is_action_pressed("ui_down"):
22 print("down\n")
23
24 if Input.is_action_just_pressed("ui_left"):
25 print("just left\n")
26
27 if Input.is_action_just_pressed("ui_right"):
28 print("just right\n")
29
30 if Input.is_action_just_pressed("ui_up"):
31 print("just up\n")
32
33 if Input.is_action_just_pressed("ui_down"):
34 print("just down\n")
35
36 func _ready() -> void:
37 var x = InputMap.get_actions()
38 for i in x:
39 print(i)
40
Godot UI
サイズとアンカー
-解説
Anchors Preset
を左上に設定する。
Transform
のサイズを画面と同じサイズにする。
こうしないと画面がズレる。
コンテナとコントロール
シーンから、コントロールを選択し、コンテナーを追加し、そこにボタンなどを配置する。
コンテナの配置は Layout -> Layout Mode -> Anchors として、Anchors Presetから設定できる。(例えば左上、または中央など)
例えばボタンがどのように表示されるかは、ボタンの Lyaout -> container Sizing で設定する。
コンテナの種類はたくさんあるので、後で見てみる必要があるだろう。
スキニング
テーマを設定する方法。
Control -> Theme -> 新規 Theme とすることで、子要素に適用されるテーマを作成できる。
個別のコントロールのテーマを設定する
Theme Overrides
から設定できる。(一部のプロパティは上書きできるが、全てではないようだ。)
テーマエディター
型:
からテーマを設定したいコントロールを追加する。編集したいパラメーターの+
ボタンを押す。すると、インスペクターの方にプロパティが表示されるようになるので、そこでカスタマイズする。
Godot Math
行列
Basis
1 extends CharacterBody3D
2
3 @export var speed = 16
4 @export var fall_acceleration = 75
5
6 var target_velocity = Vector3.ZERO
7
8 func _physics_process(delta: float) -> void:
9 var direction = Vector3.ZERO
10
11 if Input.is_action_pressed("ui_left"):
12 direction.x -= 1
13
14 if Input.is_action_pressed("ui_right"):
15 direction.x += 1
16
17 if Input.is_action_pressed("ui_up"):
18 direction.z -= 1
19
20 if Input.is_action_pressed("ui_down"):
21 direction.z += 1
22
23 if self.is_on_floor() and Input.is_action_just_pressed("jump"):
24 target_velocity.y += 50
25
26 if direction != Vector3.ZERO:
27 direction = direction.normalized()
28 get_node("foo").basis = Basis.looking_at(direction)
29
30 target_velocity.x = direction.x * speed
31 target_velocity.z = direction.z * speed
32
33 if not is_on_floor():
34 target_velocity.y = target_velocity.y - (fall_acceleration * delta)
35
36 velocity = target_velocity
37 self.move_and_slide()
Godot OS
環境と後処理
blenderでskyを作ることができる。
#Erlang
tty
シェルブレイクモード
Erlangのシェルを操作するためのモードだと思われる。複数のシェルを起動したり、その切り替えをすることができる。
C-g
でシェルブレイクモードに入る。h
でヘルプ。
exit/2
他のプロセスを終了するにはexit/2を使う。
終了させられる側のプロセスがprocess_flag/2のtrap_exit
を実装していた場合、メッセージが送られる。
モニター
1 -module(t).
2 -compile(export_all).
3
4 foo() ->
5 receive
6 bye -> exit(self());
7 s ->
8 P = spawn(?MODULE, bar, []),
9 monitor(process, P),
10 foo();
11 {Tag, MonitorRef, process, Object, Info} ->
12 io:format("--- ~w ~w ~w ~w ~n", [Tag, MonitorRef, Object, Info]),
13 foo()
14 end.
15
16
17 bar() ->
18 receive
19 after
20 3000 ->
21 io:format("--- ~w.~n", [self()]),
22 exit(self())
23 end.
24
25 start() ->
26 P = spawn(?MODULE, foo, []),
27 register(p, P).
register/2
参照
gen_server
1 -module(s).
2 -behaviour(gen_server).
3 -export([start_link/0]).
4 -export([init/1, handle_cast/2, handle_call/3, handle_info/2]).
5
6 start_link() ->
7 gen_server:start_link({local, s}, s, 0, []).
8
9 init(Args) ->
10 %process_flag(trap_exit, true),
11 io:format("---- init: ~w.~n", [Args]),
12 {ok, Args}.
13
14 handle_cast(Request, State) ->
15 timer:sleep(1000 * 3),
16 X = State + Request,
17 io:format("---- handle_cast: ~w ~w.~n", [Request, State]),
18 {noreply, X}.
19
20 handle_call(Request, From, State) ->
21 X = State + Request,
22 io:format("---- handle_call: ~w ~w ~w.~n", [Request, From, State]),
23 {reply, X, X}.
24
25 handle_info(Info, State) ->
26 io:format("---- handle_info: ~w.~n", [Info]),
27 {noreply, State}.
init
は初期化。handle_cast
は非同期呼び出し(値を返すことはできない)。handle_call
は同期呼び出し(値を返すことができる)。gen_server:cast(s, foo).
のような形で呼び出す。
オプションだが、通常のメッセージ!
を受信する必要がある場合はhandle_info
を使う。
サーバーを止めるにはgen_server:stop(s).
のようにする。
手書きで書くのと比べて、状態を簡単に管理できるのが最大の特徴か。
supervisor
1 -module(sv).
2 -behaviour(supervisor).
3 -export([start_link/0]).
4 -export([init/1]).
5
6 start_link() ->
7 supervisor:start_link(sv, []).
8
9 init(_Args) ->
10 SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
11 ChildSpecs = [#{id => s,
12 start => {s, start_link, []},
13 restart => permanent,
14 shutdown => brutal_kill,
15 type => worker,
16 module => [s]}],
17 {ok, {SupFlags, ChildSpecs}}.
省略可能なオプションを全て省略すると以下のようになる。大抵の場合はこれで問題なさそうではある。
1 -module(sv).
2 -behaviour(supervisor).
3 -export([start_link/0]).
4 -export([init/1]).
5
6 start_link() ->
7 supervisor:start_link(sv, []).
8
9 init(_Args) ->
10 SupFlags = #{},
11 ChildSpecs = [#{id => s, start => {s, start_link, []}}],
12 {ok, {SupFlags, ChildSpecs}}.
スーパーバイザーはサーバー、ステートマシン、イベントを自動的に再起動するために使う。ランタイムエラーなどの場合は再起動できない。
終了処理
sys, proc_lib
sys
はシェルでのデバッグに使える。proc_lib
は標準以外のプロセスをOTP原則に準拠させるために使うようだ(あまり使うことはないだろう)。
アプリケーション
アプリケーションのモジュールadder_app.erl
を作成する。とりあえず、引数は必要ないだろう。
1 -module(adder_app).
2 -behaviour(application).
3
4 -export([start/2, stop/1]).
5
6 start(_Type, _Args) ->
7 sv:start_link().
8
9 stop(_State) ->
10 ok.
アプリケーションの設定ファイルadder_app.app
を作成する。
1 {application, adder_app,
2 [{mod, {adder_app, []}}]}.
アプリケーションを使用するにはシェルで以下のようにする。
1 1> application:start(adder_app).
2 2> application:stop(adder_app).
OTPディレクトリ構造
分散アプリケーション
コンパイルとロード
コンパイル。
1 compile:file(foo).
ロード。
1 code:load_file(foo).
タイマー
ImageMgick
画像を一括リサイズする
1 magick '*.png' -geometry 1024x o.png
フレームをつける
1 magick i.png -geometry 1024x -mattecolor green -frame 50x50+10+20 o.png
G’MIC
選択
[0]
のような表記は画像の選択を表す。先頭は[0]
、[-1]
は末尾を指す。指定がない場合は全ての画像が選択される。
run
gmicのほとんどのコマンドは[-1]
などのスプライト番号を指定できるはずだが、それをコマンドラインから直接入力するとエラーになってしまう。run
コマンドで文字列で指定するようにすればそのエラーは発生しない。
1 gmic run 'i i.png o[-1] o.jpeg'
input(i)
画像を入力する。対応しているファイルフォーマットはよくわからない。
画像を生成する。横幅、縦幅、深度、スペクトラル。
1 gmic run 'i 800,600,1,3 o o.png'
output(o)
画像を出力する。対応しているファイルフォーマットはよくわからない。
*.cimg
はgmic
のネイティブフォーマットである。
resize(r)
1 gmic run 'i i.png r 50%,50% o o.jpeg'
image(j)
画像に画像を描画する。
1 gmic run 'i i.png i j.png r[1] 50%,50% j[0] [1],100,300 o[0] o.jpeg'
name
blur
Uiua
起動
uiua init
で初期化、main.ua
というファイルが作られる。
uiua watch
、あるいは単にuiua
で起動する。動作は同じようだ。カレントディレクトリの*.ua
ファイルを監視し、スタックの表示とUnicode文字への置き換えをするようになる。
uiua
はファイルを書き換えるのでVimで再読み込みする必要がある:e
。(autoread
を使う方法もあるようだが、よくわからなかった)
-w
オプションでGUIウインドウが開く。画像の表示で使うのだろう。まだ、そん段階ではないので、後で。