summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-08-03 00:48:56 +0900
committerSho Sakuma <me@m1sk9.dev>2026-08-05 00:42:24 +0900
commitc7e23c73b4c5863242646d61c1d7d3296bdfb4ba (patch)
treef45398fd6d1da767ab86b1466c26980a93b05d48
parent8529d652372c5c85e9624e23c4fc1a67bebb00b2 (diff)
downloadLunaticChat-c7e23c73b4c5863242646d61c1d7d3296bdfb4ba.tar.gz
LunaticChat-c7e23c73b4c5863242646d61c1d7d3296bdfb4ba.tar.bz2
LunaticChat-c7e23c73b4c5863242646d61c1d7d3296bdfb4ba.zip
perf: coalesce channel writes instead of rewriting the file per change
Every channel mutation built a full snapshot - copying the channel map, every member list, and stringifying a UUID per active player - and then queued a task that pretty-printed and rewrote all of channels.json. There was no debounce, unlike the settings storage, so a fifty-player join wave meant fifty snapshots and fifty whole-file writes. Joins and quits both go through it via setPlayerChannel, and so does the self-healing branch of getPlayerChannelContext, which sits on the channel-chat message path. ChannelStorage now debounces like the other two storages, and takes the snapshot as a supplier so it is built once when the write runs rather than once per queued change. saveToStorage and saveToDisk had the same six-line snapshot construction; that is now one private function. The trade-off is the same one the settings storage already makes: a crash within the debounce window loses the last few seconds of channel state. Shutdown still writes synchronously. Co-Authored-By: Claude <noreply@anthropic.com>
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt2
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt27
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorage.kt20
3 files changed, 24 insertions, 25 deletions
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
index fdfb9fa..9729f55 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt
@@ -221,7 +221,7 @@ class ServiceInitializer(
val storage =
ChannelStorage(
channelsFile = channelsFile,
- plugin = plugin,
+ saver = DebouncedSaver(plugin),
logger = logger,
)
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt
index 07b5662..7b41f75 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt
@@ -434,28 +434,27 @@ class ChannelManager(
* Saves the current state of channels and members to storage asynchronously.
*/
private fun saveToStorage() {
- val data =
- ChannelData(
- channels = channelsCache.toMap(),
- members = membersCache.mapValues { it.value.toList() },
- activeChannels = activeChannels.mapKeys { it.key.toString() },
- )
- storage.queueAsyncSave(data)
+ storage.queueAsyncSave(::snapshot)
logger.fine("${channelsCache.size} channels queued for saving to storage.")
}
/**
+ * A point-in-time copy of everything persisted. Built inside the write rather than at each
+ * call site, so a burst of changes copies the caches once.
+ */
+ private fun snapshot(): ChannelData =
+ ChannelData(
+ channels = channelsCache.toMap(),
+ members = membersCache.mapValues { it.value.toList() },
+ activeChannels = activeChannels.mapKeys { it.key.toString() },
+ )
+
+ /**
* Saves the current state of channels and members to storage synchronously.
* Should only br called during server shutdown.
*/
fun saveToDisk() {
- val data =
- ChannelData(
- channels = channelsCache.toMap(),
- members = membersCache.mapValues { it.value.toList() },
- activeChannels = activeChannels.mapKeys { it.key.toString() },
- )
- storage.saveToDisk(data)
+ storage.saveToDisk(snapshot())
}
/**
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorage.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorage.kt
index 7a0c3c8..832f8f3 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorage.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelStorage.kt
@@ -3,8 +3,8 @@ package dev.m1sk9.lunaticChat.paper.chat.channel
import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelData
import dev.m1sk9.lunaticChat.engine.exception.ChannelStorageLoadException
import dev.m1sk9.lunaticChat.engine.exception.ChannelStorageSaveException
+import dev.m1sk9.lunaticChat.paper.DebouncedSaver
import kotlinx.serialization.json.Json
-import org.bukkit.plugin.java.JavaPlugin
import java.nio.file.Path
import java.util.logging.Logger
import kotlin.io.path.bufferedReader
@@ -15,12 +15,12 @@ import kotlin.io.path.writeText
* Manages the storage of channel data on disk.
*
* @property channelsFile The path to the file where channel data is stored.
- * @property plugin The JavaPlugin instance for accessing plugin resources.
+ * @property saver Coalesces bursts of save requests into one asynchronous write.
* @property logger The logger for logging messages.
*/
class ChannelStorage(
private val channelsFile: Path,
- private val plugin: JavaPlugin,
+ private val saver: DebouncedSaver,
private val logger: Logger,
) {
private val json =
@@ -78,18 +78,18 @@ class ChannelStorage(
}
/**
- * Queues an asynchronous save of channel data to disk.
+ * Queues a debounced asynchronous save of channel data to disk.
*
- * @param data The ChannelData to save.
- * @throws ChannelStorageSaveException if there is an error saving the data.
+ * @param data Supplies the channel data to write. It is called when the write runs rather
+ * than when it is queued, so a burst of channel changes costs one snapshot and one file
+ * write instead of one of each per change.
*/
- fun queueAsyncSave(data: ChannelData) {
- plugin.server.asyncScheduler.runNow(plugin) {
+ fun queueAsyncSave(data: () -> ChannelData) {
+ saver.request {
try {
- saveToDisk(data)
+ saveToDisk(data())
} catch (e: ChannelStorageSaveException) {
logger.severe("Error saving channel data asynchronously: ${e.message}")
- e.printStackTrace()
}
}
}