diff options
Diffstat (limited to 'website/src/ja/docs/developers')
| -rw-r--r-- | website/src/ja/docs/developers/architecture.md | 8 | ||||
| -rw-r--r-- | website/src/ja/docs/developers/engine.md | 12 | ||||
| -rw-r--r-- | website/src/ja/docs/developers/platform-paper.md | 24 |
3 files changed, 20 insertions, 24 deletions
diff --git a/website/src/ja/docs/developers/architecture.md b/website/src/ja/docs/developers/architecture.md index cd3965a..5e3c513 100644 --- a/website/src/ja/docs/developers/architecture.md +++ b/website/src/ja/docs/developers/architecture.md @@ -36,13 +36,11 @@ Paper と Velocity が同一定義でないと壊れるもの. #### (b) プラットフォーム非依存の純ロジック -どこに置いてもよいが,純粋な再利用可能のロジックを中立コアに寄せたもの. - -- `converter` — ローマ字変換の純アルゴリズム (Trie) +外部 API クライアント +engine の中身はすべて (a) に該当します.「どこに置いてもよい純ロジック」であることは,それだけでは engine に置く理由になりません.ローマ字変換はかつて「プラットフォーム非依存の純ロジック」としてここにありましたが,唯一の呼び出し元である `platform-paper` へ移したことで engine は Ktor 依存を落とせました.Velocity 側のビルドはそれまで,呼ばない HTTP クライアントの分だけ JAR サイズを払っていたためです. (a) を engine に一元化する最大の狙いは,**ワイヤ契約の「単一の真実源」を作ること**です.Paper と Velocity は別々にビルド・デプロイ・バージョニングされる 2 つの成果物であり,protocol を両モジュールに複製すれば必ず drift します.engine に 1 つだけ置けば,契約の不一致が「本番での実行時ミスマッチ」ではなく「コンパイルエラー / スナップショットテスト失敗」として早期に顕在化します. -engine は Bukkit / Velocity API に依存せず,Adventure / Brigadier も「型・値の意味」だけを借りて本体依存を避けています (`compileOnly` の Adventure,Brigadier に依存せず `Int` を返す `toBrigadierResult()`).これにより engine は Minecraft サーバーを立てずに pure-JVM でテストでき,プラットフォーム都合 (Folia のスケジューラ等) は platform 側に隔離されます. +engine は Bukkit / Velocity / Adventure / Brigadier のいずれの API にも依存せず,唯一の依存は `kotlinx-serialization-json` です.描画は platform 側へ押し出されており (`CommandResult` はメッセージを文字列で運び,`toBrigadierResult()` は Brigadier に依存せず `Int` を返す),プラットフォームのランタイムから何も借りていません.これにより engine は Minecraft サーバーを立てずに pure-JVM でテストでき,プラットフォーム都合 (Folia のスケジューラ,HTTP,Adventure コンポーネント等) は platform 側に隔離されます. ## プロトコルバージョンによる互換管理 @@ -77,7 +75,7 @@ Paper–Velocity 間の互換性は,プラグインのバージョンではな 4. **アノテーション駆動コマンド** — `@Command` / `@Permission` / `@PlayerOnly` を Kotlin リフレクションで読み Brigadier ツリーへマッピングします.コマンドの定義とメタデータ (権限・エイリアス) が同じ場所に宣言的に並びます. 5. **Folia 互換性** — 非同期処理は `asyncScheduler` と `PluginCoroutineScope` (SupervisorJob) で行い,Bukkit API 呼び出しは `scheduler.runTask` でメインスレッドへ戻します.リージョンスレッド化された Folia でも壊れないよう,スレッド境界を明示的に扱います. 6. **永続化の使い分け** — 言語/プレイヤー設定=KAML(YAML),チャンネル/変換キャッシュ=kotlinx.serialization JSON,チャンネルログ=NDJSON.いずれも「メモリキャッシュ+非同期保存 (デバウンス/キュー)+shutdown 同期保存」の共通パターンに従います. -7. **DM/チャンネル=ローカル,グローバル=プロキシ経由** — チャットの種類でルーティングが分かれ,グローバルチャットだけが Velocity を経由します.中継は「送信元サーバー除外」+「messageId による重複排除」の二段でループを防ぎます. +7. **チャンネル=ローカル,グローバルと DM=任意でプロキシ経由** — チャットの種類でルーティングが分かれます.チャンネルチャットは常にサーバーローカルで,グローバルチャットは `crossServerGlobalChat` が有効なとき,ダイレクトメッセージは `crossServerDirectMessage` が有効なときにプロキシを経由します.中継は「送信元サーバー除外」+「messageId による重複排除」の二段でループを防ぎます. ## 各モジュールの詳細についてはこちら diff --git a/website/src/ja/docs/developers/engine.md b/website/src/ja/docs/developers/engine.md index fb9ee9e..f798335 100644 --- a/website/src/ja/docs/developers/engine.md +++ b/website/src/ja/docs/developers/engine.md @@ -48,13 +48,11 @@ Paper–Velocity の互換性は,プラグインバージョンではなく `P この設計の帰結として Paper と Velocity を独立にリリースできます.詳しくは [ビルド・リリース・バージョニング](/ja/docs/developers/resource#独立バージョニング) を参照してください. -## converter — ローマ字→日本語変換 +## ローマ字変換はここにはない -converter は Paper↔Velocity の契約ではなく (Velocity はローマ字変換をしない),**プラットフォームに依存しない純ロジックだから** engine に置かれています.3 段構成です. +ローマ字変換は「プラットフォームに依存しない純ロジックだから」という理由で engine に置かれていましたが,現在は唯一の呼び出し元である [platform-paper](/ja/docs/developers/platform-paper) にあります. -- `KanaConverter` (`object`) — **Trie** でローマ字→ひらがなに変換.`sealed class TrieNode { Leaf, Branch }` の不変構造で,4 文字 (`xtsu`→っ) 〜1 文字 (`a`→あ) を網羅.`isValidRomaji()` で変換前検証,`toHiragana()` は最長一致+促音処理を行う純アルゴリズム -- `GoogleIMEClient` — Ktor `HttpClient` を DI で受け取り,Google IME (`langpair=ja-Hira|ja`) でひらがな→漢字仮名交じりに変換.レスポンスの各セグメント第 1 候補を連結する -- `CacheData` (`@Serializable`) — 変換結果 (`version` + `entries: Map`) の永続化スキーマ.コストの高い IME 変換をキャッシュするための器で,キャッシュ本体のロジックは paper 側にある +プラットフォーム非依存であることは,engine に置く理由としては不十分でした.ここに置くと engine が Ktor に依存し,両プラットフォームが engine に依存する構図上,**Velocity** の成果物が呼ばれることのない HTTP クライアントを同梱してしまうためです.これを外したことで engine の依存を `kotlinx-serialization-json` だけに絞れました. ## chat/channel — チャンネルのドメインモデル @@ -81,14 +79,14 @@ UUID シリアライザが 2 つあるのは用途が違うためです. ## exception — 共通の例外語彙 -ドメインエラーを Paper / Velocity 双方で同じ型として扱えるよう,例外を engine に集約しています.共通の封印基底は持たず,`Exception` を直接継承するフラット構造 (23 種) です.存在/参照系・状態系・制限系・権限/BAN・KICK 系に分類でき,多くが `playerId` / `channelId` / `limit` をコンストラクタで受けてメッセージを自前生成します.基底を持たないため,呼び出し側は個別に catch する前提です. +ドメインエラーを Paper / Velocity 双方で同じ型として扱えるよう,例外を engine に集約しています.共通の封印基底は持たず,`Exception` を直接継承するフラット構造です.存在/参照系・状態系・制限系・権限/BAN・KICK 系に分類でき,多くが `playerId` / `channelId` / `limit` をコンストラクタで受けてメッセージを自前生成します.基底を持たないため,呼び出し側は個別に catch する前提です. ## permission / command — 中立抽象 Bukkit / Velocity どちらの API にも渡せる中立表現として,権限とコマンド結果を engine に置いています. - `LunaticChatPermissionNode` — `sealed class` + `object` サブクラスで権限を型安全に列挙.文字列ノードは両プラットフォームの permission API に渡せ,`when` で網羅性チェックも効く -- `CommandResult` — `sealed class` (`Success` / `SuccessWithMessage` / `Failure` / `InvalidUsage`).メッセージは Adventure `Component`,`toBrigadierResult()` は Brigadier 本体に依存せず成功=1/失敗=0 という「戻り値の意味」だけを表現する +- `CommandResult` — `sealed class` (`Success` / `SuccessWithMessage` / `Failure` / `InvalidUsage`).メッセージは平文の `String` として運ばれ,engine に Adventure の型は入らない (装飾は platform 側の責務).`toBrigadierResult()` は Brigadier 本体に依存せず成功=1/失敗=0 という「戻り値の意味」だけを表現する ## 関連 diff --git a/website/src/ja/docs/developers/platform-paper.md b/website/src/ja/docs/developers/platform-paper.md index bbbeb0c..26a9ad7 100644 --- a/website/src/ja/docs/developers/platform-paper.md +++ b/website/src/ja/docs/developers/platform-paper.md @@ -121,7 +121,7 @@ config フラグ ### ダイレクトメッセージ (DirectMessageHandler) -`/tell`・`/reply` の状態を管理します.`lastMessager` / `lastRecipient` の 2 つの `ConcurrentHashMap` で返信先を追跡し,`getReplyTarget()` は「自分に送ってきた人 → 自分が送った人」の優先順でオンラインのプレイヤーを返します. +`/tell`・`/reply` の状態を管理します.`lastMessager` / `lastRecipient` の 2 つの `ConcurrentHashMap` が返信先を `sealed interface ReplyTarget` (`Local` = UUID / `Remote` = プレイヤー名+サーバー名) として追跡し,`getReplyTarget()` は「自分に送ってきた人 → 自分が送った人」の優先順で解決しながら検証します.`Local` はオンラインであること,`Remote` は `RemotePlayerRegistry` がそのサーバーに在席を報告していることが条件です. `sendDirectMessage()` は,送信者設定に応じたローマ字変換 → spy プレイヤーへの hover 付き配信 (送受信者は除外) → 送受信者への整形メッセージ送信+通知音 (設定依存) を行います.メッセージには `/tell <sender>` を補完する `ClickEvent.suggestCommand` が付きます. @@ -144,27 +144,27 @@ config フラグ ## config -- `ConfigManager` — メイン `config.yml` を **Bukkit の `FileConfiguration`** からドット記法で読み,`LunaticChatConfiguration` を手組みする (この経路は KAML ではない点に注意) +- `ConfigManager` — `config.yml` を **KAML** で `LunaticChatConfiguration` にデシリアライズする.デフォルト値の定義箇所をデータクラス 1 箇所に限定するためで,以前のドット記法の手組みマッパーは同じデフォルトを二重に持っており,実際に乖離していた (`checkForUpdates` が `config.yml` とデータクラスの双方と食い違い,`messageLogging` ブロックはドキュメント化されていながら一度も読まれていなかった) +- 失敗はファイル単位ではなく設定単位で処理する.`YamlException` が出た場合は該当キーをドキュメントから取り除いてデコードを再試行するため,読めない値 1 つの影響はその値だけに留まる.全体をデフォルトに落とすのは「YAML として成立していない」場合だけで,いずれのケースも `onEnable` の外へ例外を投げない +- `LenientBoolean` — `yes` / `no` / `on` / `off` も受け付けるシリアライザ付きの `Boolean` typealias.Bukkit は `config.yml` を YAML 1.1 として読んでいたためこれらは真偽値だったが,kaml が読む YAML 1.2 では単なる文字列であり,黙ってリセットすると `checkForUpdates: no` がデフォルトの逆の値に反転してしまう - 機能デフォルト: `quickReplies=true`, `japaneseConversion=false`, `channelChat=false`, `velocityIntegration=false` - `config/key` 以下に `FeaturesConfig` / `ChannelChatFeatureConfig` / `JapaneseConversionFeatureConfig` / `VelocityIntegrationConfig` / `QuickRepliesFeatureConfig` / `MessageFormatConfig` / `ChannelMessageLoggingConfig` -::: warning 実装ノート -`ChannelChatFeatureConfig.messageLogging` は `ConfigManager` でロードされず,デフォルト値 (enabled=true, retention=30, 100MB) 固定になっています.意図的な仕様か要確認 — 修正するか,仕様として明記するかを決める必要があります. -::: - ## i18n - `Language` (enum) — `EN` / `JA`.未知コードは EN にフォールバック - `LanguageManager` — 起動時に `resources/languages/` を KAML でロードし,ネストした YAML をドット記法 (`toggle.on` 等) にフラット化する.`getMessage(key, placeholders)` は 選択言語 → EN フォールバック で解決し `{placeholder}` を置換,未発見はキー自身を返す.EN が無ければ致命エラー - `MessageFormatter` (`object`) — `[LC]` プレフィックス付きの Adventure `Component` を生成し,`{braces}` プレースホルダを正規表現で検出して色分けする -## converter (paper 側) — engine 連携 +## converter — ローマ字→日本語変換 -paper 側は「キャッシュ管理・タイムアウト・Bukkit スケジューリング」というプラットフォーム都合を担い,変換アルゴリズムと API 通信は engine に委譲します. +ローマ字変換はアルゴリズム・API クライアント・キャッシュ・プラットフォーム都合 (タイムアウト,スケジューリング) のすべてがここにあります.かつては「プラットフォーム非依存の純ロジック」として engine にありましたが,呼び出し元は `platform-paper` だけであり,engine に置いたままでは Velocity の成果物が使わない Ktor を同梱することになるため移されました. -- `RomanjiConverter` — 2 段変換のオーケストレータ.単語ごとに キャッシュ確認 → engine `KanaConverter` でローマ字→ひらがな → engine `GoogleIMEClient` でひらがな→漢字.API 失敗時はひらがなにフォールバック -- `ConversionCache` — engine `CacheData` を JSON 永続化.メモリキャッシュ+デバウンス保存 (`maxEntries` 超過時の退避は ConcurrentHashMap の順不同により実質ランダム,との FIXME あり) -- `RomajiConversionHelper` — `convertWithRomaji()`.`runBlocking` + `withTimeoutOrNull` (既定 1000ms) で同期呼び出しし,成功時 `"元文 §e(変換)"`,失敗/タイムアウト時は原文を返す +- `KanaConverter` (`object`) — **Trie** でローマ字→ひらがなに変換.`sealed class TrieNode { Leaf, Branch }` の不変構造で,4 文字 (`xtsu`→っ) 〜1 文字 (`a`→あ) を網羅.`isValidRomaji()` で変換前検証,`toHiragana()` は最長一致+促音処理を行う純アルゴリズム +- `GoogleIMEClient` — Ktor `HttpClient` を DI で受け取り,Google IME (`langpair=ja-Hira|ja`) でひらがな→漢字仮名交じりに変換.レスポンスの各セグメント第 1 候補を連結する +- `RomanjiConverter` — 2 段変換のオーケストレータ.単語ごとに キャッシュ確認 → `KanaConverter` → `GoogleIMEClient`.単語は並行して変換され,API 失敗時はメッセージ全体を失敗させずひらがなにフォールバックする +- `ConversionCache` — `CacheData` を JSON 永続化.メモリキャッシュ+デバウンス保存 (`maxEntries` 超過時に削除されるのは古い順ではなく任意の 10%,ConcurrentHashMap が順不同であるため,との FIXME あり) +- `RomajiConversionHelper` — `convertWithRomaji()` は `suspend` 関数で `withTimeoutOrNull` (既定 1000ms) により上限が設けられ,成功時 `"元文 §e(変換)"`,失敗/タイムアウト時は原文を返す.`convertWithRomajiBlocking()` はこれを `runBlocking` で包んだもので,イベントをキャンセルするか否かを return 前に決めなければならない `AsyncChatEvent` だけが使う.コマンドハンドラは tick スレッドで動くため suspend 版を使う必要がある ## velocity 連携 (Paper 側視点) @@ -177,7 +177,7 @@ engine の protocol を使い,Bukkit の Plugin Messaging Channel (`lunaticcha ## settings / common - `PlayerSettingsManager` — 3 種のブール設定を `ConcurrentHashMap` で管理.engine の DTO を使い,未設定はデフォルト true -- `YamlPlayerSettingsStorage` — KAML で `player-settings.yaml` を read/write.読み込み失敗時はバックアップから復旧,5 秒デバウンス保存 +- `YamlPlayerSettingsStorage` — KAML で `player-settings.yaml` を read/write.5 秒デバウンス保存.バックアップファイルは存在せず,読み込み失敗時はログを出して**空の設定**にフォールバックするため,全プレイヤーの設定が黙ってデフォルトに戻る - `UpdateChecker` — GitHub Releases API を Ktor で叩き semver 比較.結果は sealed `UpdateCheckResult` - `SoundCollector` — 通知音の Adventure `Sound` 定数と Player 拡張関数 - `PermissionCollector` — `@PermissionDsl` + `+LunaticChatPermissionNode` 演算子で権限を集める DSL.`requirePermission` は engine の `RequirePermissionException` を投げる |
