summaryrefslogtreecommitdiff
path: root/website/src/docs/developers
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-08-10 08:31:20 +0900
committerSho Sakuma <me@m1sk9.dev>2026-08-10 08:31:20 +0900
commitf08d4d8f0557581d93063cd9f6e750ab3ac9cc17 (patch)
tree52c3bc1fbd6c0012edd3b1239f58d262e1a4e23d /website/src/docs/developers
parentf52d13819e431af39e5a8c9be178307261a12d19 (diff)
downloadLunaticChat-f08d4d8f0557581d93063cd9f6e750ab3ac9cc17.tar.gz
LunaticChat-f08d4d8f0557581d93063cd9f6e750ab3ac9cc17.tar.bz2
LunaticChat-f08d4d8f0557581d93063cd9f6e750ab3ac9cc17.zip
feat(paper): add /lc reload to apply message formats without a restart
Changing a message format meant taking the server down, because config.yml was read once in onEnable and every consumer held the value it was built with. Operators tune these formats far more often than anything else in the file, and a restart is a disproportionate price for a colour code. Only messageFormat is applied. The rest of config.yml settles things once, at startup - which services exist, which commands and listeners are registered, which files are opened - and none of that can be redone while the server runs: Paper registers commands only through its COMMANDS lifecycle event, Bukkit cannot unregister a listener, and the conversion cache saver keeps no task handle to cancel. Rebuilding the services would leave the old ones still wired up, so a changed setting the server cannot pick up is reported back as needing a restart rather than silently ignored. MessageFormatHolder is deliberately narrower than LunaticChatConfiguration: taking it says the value is live, taking the configuration says it was frozen at startup. A holder over the whole tree would hold a configuration matching no version of the file, since only messageFormat is ever swapped. Reload reads config.yml more strictly than startup does, because it has an option startup does not - leaving the running configuration alone. Startup falls a single unreadable setting back to its default so the server can come up; reload refuses the file, names every unreadable setting at once, and keeps what the server already had. A document holding no settings is refused for the same reason: it cannot be told apart from a config.yml caught halfway through being written, and resetting every format over that is worse than doing nothing. Closes #266 Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'website/src/docs/developers')
-rw-r--r--website/src/docs/developers/platform-paper.md5
1 files changed, 4 insertions, 1 deletions
diff --git a/website/src/docs/developers/platform-paper.md b/website/src/docs/developers/platform-paper.md
index dbba4f1..2620c85 100644
--- a/website/src/docs/developers/platform-paper.md
+++ b/website/src/docs/developers/platform-paper.md
@@ -23,7 +23,7 @@ Features are assembled via manual DI, without an external DI framework. The key
The `onEnable` flow:
-1. `saveDefaultConfig()` → build `LunaticChatConfiguration` via `ConfigManager`
+1. `saveDefaultConfig()` → build `LunaticChatConfiguration` via `ConfigManager`, then create the `MessageFormatHolder` and the `ConfigurationReloader` over it
2. Initialize `HttpClient(CIO)` and `PluginCoroutineScope`
3. `ServiceInitializer.initialize()` → receive a `ServiceContainer`
4. Move services into the public properties used by commands
@@ -146,6 +146,9 @@ Channel state itself is managed by the `chat/channel` package.
- `ConfigManager` — deserializes `config.yml` into `LunaticChatConfiguration` with **KAML**, so each default lives in exactly one place: on the data class. It replaced a hand-written dotted-key mapper that repeated every default a second time, and they had already drifted — `checkForUpdates` disagreed with both `config.yml` and the data class, and the whole `messageLogging` block was documented but never read
- Failure is handled per setting, not per file: on a `YamlException` the offending key is pruned from the document and decoding is retried, so one unreadable value costs only itself. Only a document that is not YAML at all falls back to defaults wholesale, and neither case is allowed to throw out of `onEnable`
+- `ConfigManager.loadStrictly` — the reading `/lc reload` uses. It shares the pruning loop with `loadConfiguration` but reads the outcome the other way round: any setting that had to be pruned means the whole file is refused, with every unreadable setting named at once. A document holding no settings is refused too, because a reload cannot tell a comments-only file apart from one caught mid-write, and resetting every format over that is worse than doing nothing
+- `MessageFormatHolder` — the one part of the configuration that is live. Deliberately narrower than `LunaticChatConfiguration`: taking this type says the value can change under you, taking `LunaticChatConfiguration` says it was frozen at startup. A holder over the whole configuration would produce a tree matching no version of the file, since only `messageFormat` is ever swapped
+- `ConfigurationReloader` — re-reads the file and swaps the holder. It classifies every setting into applied (leaf by leaf, from `MessageFormatConfig`) or restart-required (block by block, riding on the data class `equals`), and a test over `memberProperties` fails when `config.yml` grows a setting neither table covers. The two lists are measured against different baselines: applied against the formats currently in effect, restart-required against the configuration the server actually started on. Nothing beyond `messageFormat` can be applied, because Paper registers commands only through its `COMMANDS` lifecycle event, Bukkit cannot unregister a listener, and the cache saver keeps no task handle to cancel
- `LenientBoolean` — a `Boolean` typealias with a serializer that still accepts `yes` / `no` / `on` / `off`. Bukkit read `config.yml` as YAML 1.1, where those are booleans; kaml reads YAML 1.2, where they are plain strings, and silently resetting them would have flipped `checkForUpdates: no` to its opposite default
- Feature defaults: `quickReplies=true`, `japaneseConversion=false`, `channelChat=false`, `velocityIntegration=false`
- Under `config/key`: `FeaturesConfig` / `ChannelChatFeatureConfig` / `JapaneseConversionFeatureConfig` / `VelocityIntegrationConfig` / `QuickRepliesFeatureConfig` / `MessageFormatConfig` / `ChannelMessageLoggingConfig`