diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-08-03 00:43:45 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-08-05 00:42:23 +0900 |
| commit | 1ee7a7fb0b24a99daace60016468774a5bd6fbf4 (patch) | |
| tree | 343191c5012dffab8e1ce227aff3ba687c893890 /platform-paper | |
| parent | 8ef90fc0a3fad0e71af6c786c4d5739ac81fea71 (diff) | |
| download | LunaticChat-1ee7a7fb0b24a99daace60016468774a5bd6fbf4.tar.gz LunaticChat-1ee7a7fb0b24a99daace60016468774a5bd6fbf4.tar.bz2 LunaticChat-1ee7a7fb0b24a99daace60016468774a5bd6fbf4.zip | |
perf: stop writing the whole settings file on every quit
PlayerQuitEvent called saveToDisk(), which KAML-serializes every player
ever recorded and writes the file inline. That runs on the tick thread,
costs time proportional to the total stored players rather than the online
ones, and so grows for the life of the server. A logout wave stacks the
writes into a visible stall. The comment above the call already said
"async save"; the storage KDoc already said the synchronous path was for
shutdown.
Quitting changes no setting, so there is nothing new to persist - the
value of saving here is flushing what an earlier toggle left pending.
queueSave() does exactly that through the existing debounce, and
saveToDisk() is now documented as the shutdown path and called from
nowhere else.
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'platform-paper')
3 files changed, 25 insertions, 2 deletions
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt index fc79ffe..6418f6f 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt @@ -95,6 +95,6 @@ class PlayerPresenceListener( channelManager?.setPlayerChannel(playerId, null) // 3. Trigger async save of player settings - playerSettingsManager.saveToDisk() + playerSettingsManager.queueSave() } } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManager.kt index 9ee8d5a..110390d 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManager.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManager.kt @@ -66,8 +66,20 @@ class PlayerSettingsManager( } /** + * Queues a debounced asynchronous save without changing any setting. + * + * Used where the caller wants what is already in memory flushed soon - a player leaving, say - + * rather than paying for a write it does not need. + */ + fun queueSave() { + storage.queueAsyncSave(::snapshot) + } + + /** * Forces an immediate synchronous save of all settings to disk. - * This should only be called during plugin shutdown. + * + * Serializes every stored player and writes the whole file inline, so this belongs on the + * shutdown path only; everywhere else should use [queueSave] or [updateSettings]. */ fun saveToDisk() { storage.saveToDisk(snapshot()) diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManagerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManagerTest.kt index b7e1385..3c34295 100644 --- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManagerTest.kt +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/settings/PlayerSettingsManagerTest.kt @@ -154,6 +154,17 @@ class PlayerSettingsManagerTest { } @Test + fun `queueSave should not write on the calling thread`() { + val (manager, storage, _) = createManager() + manager.initialize() + + manager.queueSave() + + verify(exactly = 1) { storage.queueAsyncSave(any()) } + verify(exactly = 0) { storage.saveToDisk(any()) } + } + + @Test fun `multiple players should have independent settings`() { val (manager, _, _) = createManager() manager.initialize() |
