From ac2e99052d43e8e8afdf63e71cce2bfe959a31d1 Mon Sep 17 00:00:00 2001 From: Kenneth Endfinger Date: Wed, 16 Feb 2022 23:48:51 -0500 Subject: [PATCH] Implement Chunk Export --- .../heimdall/FoundationHeimdallPlugin.kt | 4 + .../heimdall/export/ChunkExporter.kt | 84 +++++++++++++++++++ .../heimdall/export/ExportChunksCommand.kt | 17 ++++ .../heimdall/export/ExportedBlock.kt | 8 ++ .../heimdall/export/ExportedChunk.kt | 10 +++ .../heimdall/export/ExportedChunkSection.kt | 10 +++ .../src/main/resources/plugin.yml | 5 ++ .../gjallarhorn/state/BlockChangelog.kt | 5 +- 8 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ChunkExporter.kt create mode 100644 foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportChunksCommand.kt create mode 100644 foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedBlock.kt create mode 100644 foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedChunk.kt create mode 100644 foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedChunkSection.kt diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/FoundationHeimdallPlugin.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/FoundationHeimdallPlugin.kt index b222df9..b32aa8f 100644 --- a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/FoundationHeimdallPlugin.kt +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/FoundationHeimdallPlugin.kt @@ -6,6 +6,7 @@ import cloud.kubelet.foundation.heimdall.buffer.BufferFlushThread import cloud.kubelet.foundation.heimdall.buffer.EventBuffer import cloud.kubelet.foundation.heimdall.event.* import cloud.kubelet.foundation.heimdall.model.HeimdallConfig +import cloud.kubelet.foundation.heimdall.export.ExportChunksCommand import com.charleskorn.kaml.Yaml import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource @@ -38,6 +39,9 @@ class FoundationHeimdallPlugin : JavaPlugin(), Listener { private val legacyComponentSerializer = LegacyComponentSerializer.builder().build() override fun onEnable() { + val exportChunksCommand = getCommand("export_all_chunks") ?: throw Exception("Failed to get export_all_chunks command") + exportChunksCommand.setExecutor(ExportChunksCommand(this)) + val foundation = server.pluginManager.getPlugin("Foundation") as FoundationCorePlugin val configPath = Util.copyDefaultConfig( diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ChunkExporter.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ChunkExporter.kt new file mode 100644 index 0000000..6c9535b --- /dev/null +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ChunkExporter.kt @@ -0,0 +1,84 @@ +package cloud.kubelet.foundation.heimdall.export + +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToStream +import org.bukkit.Chunk +import org.bukkit.ChunkSnapshot +import org.bukkit.Server +import org.bukkit.World +import org.bukkit.plugin.Plugin +import java.io.File +import java.util.concurrent.atomic.AtomicBoolean +import java.util.zip.GZIPOutputStream + +class ChunkExporter(private val plugin: Plugin, private val server: Server, val world: World) { + private val json = Json { + ignoreUnknownKeys = true + } + + fun exportLoadedChunksAsync() { + exportChunkListAsync(world.loadedChunks.toList()) + } + + private fun exportChunkListAsync(chunks: List) { + val listOfChunks = chunks.toMutableList() + doExportChunkList(listOfChunks, AtomicBoolean(false)) + } + + private fun doExportChunkList(chunks: MutableList, check: AtomicBoolean) { + check.set(false) + val chunk = chunks.removeFirstOrNull() + if (chunk == null) { + plugin.slF4JLogger.info("Chunk Export Complete") + return + } + + val snapshot = chunk.chunkSnapshot + server.scheduler.runTaskAsynchronously(plugin) { -> + saveChunkSnapshotAndScheduleNext(snapshot, chunks, check) + } + } + + private fun saveChunkSnapshotAndScheduleNext(snapshot: ChunkSnapshot, chunks: MutableList, check: AtomicBoolean) { + exportChunkSnapshot(snapshot) + if (!check.getAndSet(true)) { + plugin.server.scheduler.runTask(plugin) { -> doExportChunkList(chunks, check) } + } + } + + private fun exportChunkSnapshot(snapshot: ChunkSnapshot) { + val sections = mutableListOf() + val yRange = world.minHeight until world.maxHeight + val chunkRange = 0..15 + for (x in chunkRange) { + for (z in chunkRange) { + sections.add(exportChunkSection(snapshot, yRange, x, z)) + } + } + + val exported = ExportedChunk(snapshot.x, snapshot.z, sections) + saveChunkSnapshot(snapshot, exported) + } + + private fun saveChunkSnapshot(snapshot: ChunkSnapshot, chunk: ExportedChunk) { + val file = File("exported_chunks/${snapshot.worldName}_chunk_${snapshot.x}_${snapshot.z}.json.gz") + if (!file.parentFile.exists()) { + file.parentFile.mkdirs() + } + + val fileOutputStream = file.outputStream() + val gzipOutputStream = GZIPOutputStream(fileOutputStream) + json.encodeToStream(ExportedChunk.serializer(), chunk, gzipOutputStream) + gzipOutputStream.close() + } + + private fun exportChunkSection(snapshot: ChunkSnapshot, yRange: IntRange, x: Int, z: Int): ExportedChunkSection { + val blocks = mutableListOf() + for (y in yRange) { + val blockData = snapshot.getBlockData(x, y, z) + val block = ExportedBlock(blockData.material.key.toString()) + blocks.add(block) + } + return ExportedChunkSection(x, z, blocks) + } +} diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportChunksCommand.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportChunksCommand.kt new file mode 100644 index 0000000..e088fb2 --- /dev/null +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportChunksCommand.kt @@ -0,0 +1,17 @@ +package cloud.kubelet.foundation.heimdall.export + +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.plugin.Plugin + +class ExportChunksCommand(private val plugin: Plugin) : CommandExecutor { + override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { + plugin.slF4JLogger.info("Exporting All Chunks") + for (world in sender.server.worlds) { + val export = ChunkExporter(plugin, sender.server, world) + export.exportLoadedChunksAsync() + } + return true + } +} diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedBlock.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedBlock.kt new file mode 100644 index 0000000..6451a16 --- /dev/null +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedBlock.kt @@ -0,0 +1,8 @@ +package cloud.kubelet.foundation.heimdall.export + +import kotlinx.serialization.Serializable + +@Serializable +data class ExportedBlock( + val type: String +) diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedChunk.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedChunk.kt new file mode 100644 index 0000000..0f803c3 --- /dev/null +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedChunk.kt @@ -0,0 +1,10 @@ +package cloud.kubelet.foundation.heimdall.export + +import kotlinx.serialization.Serializable + +@Serializable +data class ExportedChunk( + val x: Int, + val z: Int, + val sections: List +) diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedChunkSection.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedChunkSection.kt new file mode 100644 index 0000000..ce818a3 --- /dev/null +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/export/ExportedChunkSection.kt @@ -0,0 +1,10 @@ +package cloud.kubelet.foundation.heimdall.export + +import kotlinx.serialization.Serializable + +@Serializable +data class ExportedChunkSection( + val x: Int, + val z: Int, + val blocks: List +) diff --git a/foundation-heimdall/src/main/resources/plugin.yml b/foundation-heimdall/src/main/resources/plugin.yml index 39d26f5..3d5db6c 100644 --- a/foundation-heimdall/src/main/resources/plugin.yml +++ b/foundation-heimdall/src/main/resources/plugin.yml @@ -8,3 +8,8 @@ depend: - Foundation authors: - kubelet +commands: + export_all_chunks: + description: Export All Chunks + usage: /export_all_chunks + permission: foundation.heimdall.command.export_all_chunks diff --git a/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/state/BlockChangelog.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/state/BlockChangelog.kt index 1be42d7..d8e05e3 100644 --- a/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/state/BlockChangelog.kt +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/state/BlockChangelog.kt @@ -20,11 +20,12 @@ class BlockChangelog( slice.isTimeWithinSliceRange(it.time) } - val changeTimeRange: ChangelogSlice + val fullTimeSlice: ChangelogSlice get() = ChangelogSlice(changes.minOf { it.time }, changes.maxOf { it.time }) fun calculateChangelogSlices(interval: Duration, limit: Int? = null): List { - val (start, end) = changeTimeRange + val start = fullTimeSlice.rootStartTime + val end = fullTimeSlice.sliceEndTime var intervals = mutableListOf() var current = start while (!current.isAfter(end)) {