Skip to content

Commit

Permalink
Merge branch 'refs/heads/1.21.4' into 1.21.3
Browse files Browse the repository at this point in the history
# Conflicts:
#	README.md
#	build.gradle.kts
#	libs.versions.toml
  • Loading branch information
senseiwells committed Dec 21, 2024
2 parents 2c74259 + e5e0aee commit 4e276c2
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 62 deletions.
7 changes: 4 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ repositories {
maven("https://api.modrinth.com/maven")
maven("https://maven.maxhenkel.de/repository/public")
maven("https://maven.andante.dev/releases/")
maven("https://maven4.bai.lol")
mavenCentral()
}


val modVersion = "1.2.4"
val modVersion = "1.2.7"
val releaseVersion = "${modVersion}+mc${libs.versions.minecraft.get()}"
version = releaseVersion
group = "me.senseiwells"
Expand All @@ -54,9 +55,9 @@ dependencies {
modCompileOnly(libs.servux)
modCompileOnly(libs.syncmatica)
modCompileOnly(libs.voicechat)
implementation(libs.voicechat.api)
compileOnly(libs.voicechat.api)

shade(modImplementation(libs.replay.studio.get())!!)
shade(implementation(libs.replay.studio.get())!!)
includeModImplementation(libs.permissions) {
exclude(libs.fabric.api.get().group)
}
Expand Down
12 changes: 6 additions & 6 deletions libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ vmp = "0.2.0+beta.7.180+1.21.3"
c2me = "0.3.0+beta.1.0+1.21.3"
servux = "1.21.3-0.4.0-sakura.2"
syncmatica = "1.21.3-sakura.10"
replay-studio = "4e670490d1"
replay-studio = "aa5ab3a302"

# Plugins
fabric-loom = "1.9-SNAPSHOT"
mod-publish = "0.8.1"
shadow = "8.1.1"
shadow = "8.3.5"
explosion = "0.3.1"

[libraries]
Expand All @@ -44,7 +44,7 @@ servux = { module = "com.github.sakura-ryoko:servux" , version.re
syncmatica = { module = "com.github.sakura-ryoko:syncmatica" , version.ref = "syncmatica" }

[plugins]
fabric-loom = { id = "fabric-loom", version.ref = "fabric-loom" }
mod-publish = { id = "me.modmuss50.mod-publish-plugin", version.ref = "mod-publish" }
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
explosion = { id = "lol.bai.explosion", version.ref = "explosion"}
fabric-loom = { id = "fabric-loom" , version.ref = "fabric-loom" }
mod-publish = { id = "me.modmuss50.mod-publish-plugin", version.ref = "mod-publish" }
shadow = { id = "com.gradleup.shadow" , version.ref = "shadow" }
explosion = { id = "lol.bai.explosion" , version.ref = "explosion"}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import me.senseiwells.replay.config.ReplayConfig;
import me.senseiwells.replay.player.PlayerRecorder;
import me.senseiwells.replay.player.PlayerRecorders;
import me.senseiwells.replay.recorder.RecorderRecoverer;
import me.senseiwells.replay.util.processor.RecorderFixerUpper;
import me.senseiwells.replay.util.processor.RecorderRecoverer;
import net.minecraft.server.MinecraftServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
Expand Down Expand Up @@ -57,5 +58,8 @@ private void onServerStopped(CallbackInfo ci) {
for (ChunkRecorder recorder : ChunkRecorders.recorders()) {
recorder.stop();
}

RecorderRecoverer.INSTANCE.waitForRecovering();
RecorderFixerUpper.INSTANCE.waitForFixingUp();
}
}
3 changes: 3 additions & 0 deletions src/main/kotlin/me/senseiwells/replay/ServerReplay.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import me.senseiwells.replay.commands.ReplayCommand
import me.senseiwells.replay.config.ReplayConfig
import me.senseiwells.replay.http.DownloadPacksHttpInjector
import me.senseiwells.replay.http.DownloadReplaysHttpInjector
import me.senseiwells.replay.util.processor.RecorderFixerUpper
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback
import net.fabricmc.loader.api.FabricLoader
Expand Down Expand Up @@ -43,6 +44,8 @@ object ServerReplay: ModInitializer {
PackCommand.register(dispatcher)
}
}

RecorderFixerUpper.tryFixingUp()
}

fun getIp(server: MinecraftServer): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.senseiwells.replay.chunk

import me.senseiwells.replay.ServerReplay
import me.senseiwells.replay.recorder.RecorderRecoverer
import me.senseiwells.replay.util.processor.RecorderRecoverer
import net.minecraft.resources.ResourceKey
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerLevel
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/me/senseiwells/replay/config/ReplayConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ data class ReplayConfig(
@SerialName("debug")
@EncodeDefault(Mode.NEVER)
var debug: Boolean = false,
@SerialName("async_thread_pool_size")
@EncodeDefault(Mode.NEVER)
var asyncThreadPoolSize: Int? = 1,
@SerialName("world_name")
var worldName: String = "World",
@SerialName("server_name")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package me.senseiwells.replay.player

import com.mojang.authlib.GameProfile
import me.senseiwells.replay.ServerReplay
import me.senseiwells.replay.recorder.RecorderRecoverer
import me.senseiwells.replay.util.processor.RecorderRecoverer
import me.senseiwells.replay.rejoin.RejoinedReplayPlayer
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
Expand Down
10 changes: 2 additions & 8 deletions src/main/kotlin/me/senseiwells/replay/recorder/ReplayRecorder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -794,15 +794,8 @@ abstract class ReplayRecorder(
.append(", compressed to ${FileUtils.formatSize(size)}")
}

try {
val caches = this.location.parent.resolve(this.location.name + ".cache")
@OptIn(ExperimentalPathApi::class)
caches.deleteRecursively()
} catch (e: IOException) {
ServerReplay.logger.error("Failed to delete caches", e)
}

this.replay.close()
ReplayFileUtils.deleteCaches(this.location)
this.broadcastToOpsAndConsole(
Component.literal("Successfully closed replay ${this.getName()}").append(additional)
)
Expand Down Expand Up @@ -830,6 +823,7 @@ abstract class ReplayRecorder(
val registry = PacketTypeRegistry.get(version, State.LOGIN)

this.executor.execute {
// When updating before ReplayStudio ensure to write the correct meta
this.replay.writeMetaData(registry, this.meta)

this.replay.write(ENTRY_SERVER_REPLAY_META).use {
Expand Down
23 changes: 23 additions & 0 deletions src/main/kotlin/me/senseiwells/replay/util/ReplayFileUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package me.senseiwells.replay.util

import me.senseiwells.replay.ServerReplay
import java.io.IOException
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.deleteRecursively
import kotlin.io.path.exists
import kotlin.io.path.name

object ReplayFileUtils {
fun deleteCaches(location: Path) {
try {
val caches = location.parent.resolve(location.name + ".cache")
if (caches.exists()) {
@OptIn(ExperimentalPathApi::class)
caches.deleteRecursively()
}
} catch (e: IOException) {
ServerReplay.logger.error("Failed to delete caches", e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package me.senseiwells.replay.util.processor

import com.google.common.util.concurrent.ThreadFactoryBuilder
import com.replaymod.replaystudio.replay.ZipReplayFile
import com.replaymod.replaystudio.studio.ReplayStudio
import me.senseiwells.replay.ServerReplay
import me.senseiwells.replay.util.ReplayFileUtils
import java.io.IOException
import java.nio.file.FileVisitResult
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import kotlin.io.path.*

object RecorderFixerUpper {
private var future: CompletableFuture<Void>? = null

@OptIn(ExperimentalPathApi::class)
fun tryFixingUp() {
val executor = Executors.newFixedThreadPool(
ServerReplay.config.asyncThreadPoolSize ?: (Runtime.getRuntime().availableProcessors() / 3),
ThreadFactoryBuilder().setNameFormat("replay-fixer-upper-%d").build()
)
val futures = ArrayList<CompletableFuture<Void>>()
val paths = listOf(ServerReplay.config.playerRecordingPath, ServerReplay.config.chunkRecordingPath)
for (path in paths) {
if (path.isDirectory()) {
path.visitFileTree {
onVisitFile { path, _ ->
futures.add(CompletableFuture.runAsync({
tryFixUpReplayFile(path)
}, executor))
FileVisitResult.CONTINUE
}
}
}
}
this.future = CompletableFuture.allOf(*futures.toTypedArray()).thenRun {
this.future = null
}
executor.shutdown()
}

fun waitForFixingUp() {
val future = this.future ?: return
ServerReplay.logger.warn("Waiting for recordings to finish fixing up, please do NOT kill the server")
future.join()
}

private fun tryFixUpReplayFile(path: Path) {
if (!path.isReadable() || path.extension != "mcpr") {
return
}
val replay = ZipReplayFile(ReplayStudio(), path.toFile())
val meta = replay.metaData
try {
var dirty = false
// This protocol version is incorrect
if (meta.mcVersion == "1.21.4" && meta.rawProtocolVersion == 768) {
ServerReplay.logger.info("Fixing up protocol version for replay '$path'")
meta.setProtocolVersion(769)
replay.writeMetaData(null, meta)
dirty = true
}
if (dirty) {
replay.save()
ServerReplay.logger.info("Successfully fixed up replay '$path'")
}
replay.close()
ReplayFileUtils.deleteCaches(path)
} catch (e: IOException) {
ServerReplay.logger.error("Failed to fix up replay file '$path'", e)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package me.senseiwells.replay.recorder
package me.senseiwells.replay.util.processor

import com.google.common.util.concurrent.ThreadFactoryBuilder
import com.replaymod.replaystudio.lib.viaversion.api.protocol.packet.State
Expand All @@ -13,8 +13,9 @@ import kotlinx.serialization.json.encodeToStream
import me.senseiwells.replay.ServerReplay
import me.senseiwells.replay.config.ReplayConfig
import me.senseiwells.replay.config.serialization.PathSerializer
import me.senseiwells.replay.recorder.ReplayRecorder
import me.senseiwells.replay.util.ReplayFileUtils
import net.minecraft.server.MinecraftServer
import net.minecraft.util.Mth
import org.jetbrains.annotations.ApiStatus.Internal
import java.io.EOFException
import java.io.IOException
Expand All @@ -29,46 +30,57 @@ object RecorderRecoverer {

private val recordings: MutableSet<Path>

private var future: CompletableFuture<Void>? = null

init {
this.recordings = this.read()
recordings = read()
}

fun add(recorder: ReplayRecorder) {
this.recordings.add(recorder.location)
this.write()
recordings.add(recorder.location)
write()
}

fun remove(recorder: ReplayRecorder) {
this.recordings.remove(recorder.location)
this.write()
recordings.remove(recorder.location)
write()
}

@Internal
@JvmStatic
fun tryRecover(server: MinecraftServer) {
val recorders = this.recordings
val recorders = recordings
if (!ServerReplay.config.recoverUnsavedReplays || recorders.isEmpty()) {
return
}

val recordings = if (recorders.size > 1) "recordings" else "recording"
ServerReplay.logger.info("Detected unfinished replay $recordings that ended abruptly...")
val executor = Executors.newFixedThreadPool(
Mth.ceil(recorders.size / 2.0),
ServerReplay.config.asyncThreadPoolSize ?: (Runtime.getRuntime().availableProcessors() / 3),
ThreadFactoryBuilder().setNameFormat("replay-recoverer-%d").build()
)
for (recording in this.recordings) {
val futures = ArrayList<CompletableFuture<Void>>()
for (recording in RecorderRecoverer.recordings) {
ServerReplay.logger.info("Attempting to recover recording: $recording, please do not stop the server")

CompletableFuture.runAsync({ this.recover(recording) }, executor).thenRunAsync({
this.recordings.remove(recording)
this.write()
}, server)
futures.add(CompletableFuture.runAsync({ recover(recording) }, executor).thenRunAsync({
RecorderRecoverer.recordings.remove(recording)
write()
}, server))
}
this.future = CompletableFuture.allOf(*futures.toTypedArray()).thenRun {
this.future = null
}
executor.shutdown()
}

@OptIn(ExperimentalPathApi::class)
fun waitForRecovering() {
val future = this.future ?: return
ServerReplay.logger.warn("Waiting for recordings to be recovered, please do NOT kill the server")
future.join()
}

private fun recover(recording: Path) {
val temp = recording.parent.resolve(recording.name + ".tmp")
if (temp.exists()) {
Expand Down Expand Up @@ -109,41 +121,33 @@ object RecorderRecoverer {
try {
replay.saveTo(recording.parent.resolve(recording.name + ".mcpr").toFile())
replay.close()
ReplayFileUtils.deleteCaches(recording)
ServerReplay.logger.info("Successfully recovered recording $recording")
} catch (e: IOException) {
ServerReplay.logger.error("Failed to write unfinished replay $recording")
}
} else {
ServerReplay.logger.warn("Could not find unfinished replay files for $recording??")
}

val cache = recording.parent.resolve(recording.name + ".cache")
if (cache.exists()) {
try {
cache.deleteRecursively()
} catch (e: Exception) {
ServerReplay.logger.error("Failed to delete replay cache for $recording")
}
}
}

private fun write() {
try {
this.path.parent.createDirectories()
this.path.outputStream().use {
Json.encodeToStream(SetSerializer(PathSerializer), this.recordings, it)
path.parent.createDirectories()
path.outputStream().use {
Json.encodeToStream(SetSerializer(PathSerializer), recordings, it)
}
} catch (e: Exception) {
ServerReplay.logger.error("Failed to write unfinished recorders", e)
}
}

private fun read(): MutableSet<Path> {
if (!this.path.exists()) {
if (!path.exists()) {
return HashSet()
}
return try {
this.path.inputStream().use {
path.inputStream().use {
HashSet(Json.decodeFromStream(SetSerializer(PathSerializer), it))
}
} catch (e: Exception) {
Expand Down
Loading

0 comments on commit 4e276c2

Please sign in to comment.